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 @@
+
+