diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests/StatusCodeParityTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests/StatusCodeParityTests.cs index 02122ad1b..d617384be 100644 --- a/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests/StatusCodeParityTests.cs +++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests/StatusCodeParityTests.cs @@ -47,9 +47,9 @@ public sealed class StatusCodeParityTests /// Every Opc.Ua.StatusCodes member, by name — the oracle this test checks against. private static readonly IReadOnlyDictionary SdkStatusCodes = typeof(Opc.Ua.StatusCodes) - .GetFields(BindingFlags.Public | BindingFlags.Static) - .Where(f => f.IsLiteral && f.FieldType == typeof(uint)) - .ToDictionary(f => f.Name, f => (uint)f.GetRawConstantValue()!, StringComparer.Ordinal); + .GetProperties(BindingFlags.Public | BindingFlags.Static) + .Where(p => p.PropertyType == typeof(Opc.Ua.StatusCode)) + .ToDictionary(p => p.Name, p => ((Opc.Ua.StatusCode)p.GetValue(null)!).Code, StringComparer.Ordinal); /// /// Every status-shaped const uint declared anywhere in the deployed driver assemblies, as diff --git a/tests/Drivers/Historian.Conformance/AtTimeConformance.cs b/tests/Drivers/Historian.Conformance/AtTimeConformance.cs index 0379f484e..4061ce032 100644 --- a/tests/Drivers/Historian.Conformance/AtTimeConformance.cs +++ b/tests/Drivers/Historian.Conformance/AtTimeConformance.cs @@ -24,7 +24,7 @@ public abstract class AtTimeConformance : ConformanceBase var code = new Opc.Ua.StatusCode(GoodInterpolated); code.AggregateBits.ShouldBe(Opc.Ua.AggregateBits.Interpolated); Opc.Ua.StatusCode.IsGood(code).ShouldBeTrue(); - new Opc.Ua.StatusCode(UncertainInterpolated).CodeBits.ShouldBe(Opc.Ua.StatusCodes.UncertainDataSubNormal); + new Opc.Ua.StatusCode(UncertainInterpolated).CodeBits.ShouldBe(Opc.Ua.StatusCodes.UncertainDataSubNormal.Code); await Task.CompletedTask; } diff --git a/tests/Drivers/Historian.Conformance/ProcessedConformance.cs b/tests/Drivers/Historian.Conformance/ProcessedConformance.cs index 5f51434a9..71858e26c 100644 --- a/tests/Drivers/Historian.Conformance/ProcessedConformance.cs +++ b/tests/Drivers/Historian.Conformance/ProcessedConformance.cs @@ -31,10 +31,10 @@ public abstract class ProcessedConformance : ConformanceBase { new Opc.Ua.StatusCode(GoodCalc).AggregateBits.ShouldBe(Opc.Ua.AggregateBits.Calculated); new Opc.Ua.StatusCode(UncertainCalc).AggregateBits.ShouldBe(Opc.Ua.AggregateBits.Calculated); - new Opc.Ua.StatusCode(UncertainCalc).CodeBits.ShouldBe(Opc.Ua.StatusCodes.UncertainDataSubNormal); - new Opc.Ua.StatusCode(NoDataCalc).CodeBits.ShouldBe(Opc.Ua.StatusCodes.BadNoData); + new Opc.Ua.StatusCode(UncertainCalc).CodeBits.ShouldBe(Opc.Ua.StatusCodes.UncertainDataSubNormal.Code); + new Opc.Ua.StatusCode(NoDataCalc).CodeBits.ShouldBe(Opc.Ua.StatusCodes.BadNoData.Code); new Opc.Ua.StatusCode(NoDataCalc).AggregateBits.ShouldBe(Opc.Ua.AggregateBits.Calculated); - new Opc.Ua.StatusCode(InvalidInputsCalc).CodeBits.ShouldBe(Opc.Ua.StatusCodes.BadAggregateInvalidInputs); + new Opc.Ua.StatusCode(InvalidInputsCalc).CodeBits.ShouldBe(Opc.Ua.StatusCodes.BadAggregateInvalidInputs.Code); } [Theory] diff --git a/tests/Drivers/Historian.Conformance/SdkAggregateOracle.cs b/tests/Drivers/Historian.Conformance/SdkAggregateOracle.cs index b7dd2f781..0be20dfd0 100644 --- a/tests/Drivers/Historian.Conformance/SdkAggregateOracle.cs +++ b/tests/Drivers/Historian.Conformance/SdkAggregateOracle.cs @@ -1,13 +1,14 @@ using Opc.Ua; using Opc.Ua.Server; +using ZB.MOM.WW.OtOpcUa.OpcUaServer.Compatibility.Aggregates; using Shouldly; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Conformance; /// -/// Drives the pinned OPC UA SDK's own Part 13 aggregate calculators (Opc.Ua.Server — the classes the SDK's -/// AggregateManager registers) in-process over a sample set, so our processed reads can be compared with the +/// Drives the application's versioned 1.5.378.182 Part 13 compatibility calculators — the same factories +/// the upgraded server registers for live aggregates in-process over a sample set, so our processed reads can be compared with the /// reference implementation bucket by bucket (#677). The calculators run under the server's default /// (TreatUncertainAsBad = false, PercentDataBad = PercentDataGood = 100, /// UseSlopedExtrapolation = false), sloped interpolation, source timestamps. @@ -17,6 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Conformance; /// public static class SdkAggregateOracle { + private static readonly ITelemetryContext Telemetry = DefaultTelemetry.Create(_ => { }); /// One processed value: value (double / int / null), full status code, timestamp. public readonly record struct Result(object? Value, uint StatusCode, DateTime Timestamp); @@ -43,22 +45,7 @@ public static class SdkAggregateOracle var id = AggregateId(aggregate); var ms = interval.TotalMilliseconds; var cfg = DefaultConfiguration; - IAggregateCalculator calc = aggregate switch - { - HistoryAggregateType.Average or HistoryAggregateType.TimeAverage or HistoryAggregateType.TimeAverage2 - or HistoryAggregateType.Total or HistoryAggregateType.Total2 => - new AverageAggregateCalculator(id, start, end, ms, false, cfg, null), - HistoryAggregateType.Minimum or HistoryAggregateType.Maximum or HistoryAggregateType.Range - or HistoryAggregateType.MinimumActualTime or HistoryAggregateType.MaximumActualTime => - new MinMaxAggregateCalculator(id, start, end, ms, false, cfg, null), - HistoryAggregateType.Count => new CountAggregateCalculator(id, start, end, ms, false, cfg, null), - HistoryAggregateType.PercentGood or HistoryAggregateType.PercentBad => - new StatusAggregateCalculator(id, start, end, ms, false, cfg, null), - HistoryAggregateType.Start or HistoryAggregateType.End or HistoryAggregateType.Delta => - new StartEndAggregateCalculator(id, start, end, ms, false, cfg, null), - HistoryAggregateType.Interpolative => new AggregateCalculator(id, start, end, ms, false, cfg, null), - _ => throw new ArgumentOutOfRangeException(nameof(aggregate), aggregate, "no SDK calculator mapped"), - }; + IAggregateCalculator calc = CompatibilityAggregateFactory.Create(id, start, end, ms, false, cfg, Telemetry); // The SDK expects values in the direction of the read: ascending forward, descending reverse. var ordered = samples.OrderBy(s => s.Time).ToList(); @@ -68,9 +55,8 @@ public static class SdkAggregateOracle var results = new List(); for (var guard = 0; guard < 100_000; guard++) { - var dv = calc.GetProcessedValue(true); - if (dv is null) break; - results.Add(new Result(dv.Value, dv.StatusCode.Code, DateTime.SpecifyKind(dv.SourceTimestamp, DateTimeKind.Utc))); + if (!calc.TryGetProcessedValue(true, out var dv)) break; + results.Add(new Result(dv.WrappedValue.AsBoxedObject(Variant.BoxingBehavior.Legacy), dv.StatusCode.Code, (DateTime)dv.SourceTimestamp)); } return results; } @@ -123,20 +109,16 @@ public static class SdkAggregateOracle problems.ShouldBeEmpty($"{label} differs from the SDK:\n" + string.Join("\n", problems)); } - private static DataValue ToDataValue(DateTime t, HistorianValue v, uint status) => new() - { - WrappedValue = v.Kind switch + private static DataValue ToDataValue(DateTime t, HistorianValue v, uint status) => new( + v.Kind switch { HistorianValueKind.Empty => Variant.Null, - HistorianValueKind.Int64 => new Variant(v.Integer), - HistorianValueKind.Boolean => new Variant(v.Number != 0), - HistorianValueKind.String => new Variant(v.Text), - _ => new Variant(v.Number), + HistorianValueKind.Int64 => Variant.From(v.Integer), + HistorianValueKind.Boolean => Variant.From(v.Number != 0), + HistorianValueKind.String => v.Text is { } text ? Variant.From(text) : Variant.Null, + _ => Variant.From(v.Number), }, - StatusCode = status, - SourceTimestamp = t, - ServerTimestamp = t, - }; + status, t, t); private static NodeId AggregateId(HistoryAggregateType a) => a switch { diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests.csproj b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests.csproj index de0bc2753..e814277ae 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests.csproj +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests.csproj @@ -23,6 +23,8 @@ + + diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/StatusCodeOracleTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/StatusCodeOracleTests.cs index a672a6211..c5638b19e 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/StatusCodeOracleTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/StatusCodeOracleTests.cs @@ -9,7 +9,7 @@ public sealed class StatusCodeOracleTests [Fact] public void Calculated_bits_match_the_SDK() { - var sdk = new Opc.Ua.StatusCode(Opc.Ua.StatusCodes.Good).SetAggregateBits(Opc.Ua.AggregateBits.Calculated); + var sdk = Opc.Ua.StatusCodes.Good.WithAggregateBits(Opc.Ua.AggregateBits.Calculated); sdk.Code.ShouldBe(HistorianInfoBits.WithCalculated(HistorianStatusCodes.Good)); ((uint)Opc.Ua.AggregateBits.Calculated).ShouldBe(HistorianInfoBits.HistorianCalculated); } @@ -17,7 +17,7 @@ public sealed class StatusCodeOracleTests [Fact] public void Interpolated_bits_match_the_SDK() { - var sdk = new Opc.Ua.StatusCode(Opc.Ua.StatusCodes.Good).SetAggregateBits(Opc.Ua.AggregateBits.Interpolated); + var sdk = Opc.Ua.StatusCodes.Good.WithAggregateBits(Opc.Ua.AggregateBits.Interpolated); sdk.Code.ShouldBe(HistorianInfoBits.WithInterpolated(HistorianStatusCodes.Good)); ((uint)Opc.Ua.AggregateBits.Interpolated).ShouldBe(HistorianInfoBits.HistorianInterpolated); } @@ -25,7 +25,7 @@ public sealed class StatusCodeOracleTests /// /// A stored status that already carries a historian bit (a sample written via HistoryUpdate with the Calculated /// bit, say) must come back with ONE historian bit, never both: the existing pair is masked off first. The SDK - /// agrees — SetAggregateBits replaces, it does not OR. + /// agrees — WithAggregateBits replaces, it does not OR. /// [Fact] public void Setting_one_historian_bit_clears_the_other() @@ -45,7 +45,7 @@ public sealed class StatusCodeOracleTests [Fact] public void Generic_bad_matches_the_SDK() => - HistorianStatusCodes.Bad.ShouldBe(Opc.Ua.StatusCodes.Bad); + HistorianStatusCodes.Bad.ShouldBe(Opc.Ua.StatusCodes.Bad.Code); [Fact] public void Info_type_is_data_value() => diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests.csproj b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests.csproj index f52434f39..959a08849 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests.csproj +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests.csproj @@ -28,6 +28,8 @@ + + diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests.csproj b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests.csproj index 732de3bb5..8f3d3b1d0 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests.csproj +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests.csproj @@ -22,6 +22,8 @@ + +