test: preserve historian baseline oracles under SDK 2.0
This commit is contained in:
@@ -47,9 +47,9 @@ public sealed class StatusCodeParityTests
|
||||
/// <summary>Every <c>Opc.Ua.StatusCodes</c> member, by name — the oracle this test checks against.</summary>
|
||||
private static readonly IReadOnlyDictionary<string, uint> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Every status-shaped <c>const uint</c> declared anywhere in the deployed driver assemblies, as
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Drives the pinned OPC UA SDK's own Part 13 aggregate calculators (<c>Opc.Ua.Server</c> — the classes the SDK's
|
||||
/// <c>AggregateManager</c> 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
|
||||
/// <see cref="AggregateConfiguration"/> (TreatUncertainAsBad = false, PercentDataBad = PercentDataGood = 100,
|
||||
/// UseSlopedExtrapolation = false), sloped interpolation, source timestamps.
|
||||
@@ -17,6 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Conformance;
|
||||
/// </remarks>
|
||||
public static class SdkAggregateOracle
|
||||
{
|
||||
private static readonly ITelemetryContext Telemetry = DefaultTelemetry.Create(_ => { });
|
||||
/// <summary>One processed value: value (double / int / null), full status code, timestamp.</summary>
|
||||
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<Result>();
|
||||
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
|
||||
{
|
||||
|
||||
+2
@@ -23,6 +23,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Test the same preserved aggregate algorithms registered by the application server. -->
|
||||
<ProjectReference Include="..\..\..\src\Server\ZB.MOM.WW.OtOpcUa.OpcUaServer\ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb\ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common\ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
+4
-4
@@ -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
|
||||
/// <summary>
|
||||
/// 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 — <c>SetAggregateBits</c> replaces, it does not OR.
|
||||
/// agrees — <c>WithAggregateBits</c> replaces, it does not OR.
|
||||
/// </summary>
|
||||
[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() =>
|
||||
|
||||
+2
@@ -28,6 +28,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Test the same preserved aggregate algorithms registered by the application server. -->
|
||||
<ProjectReference Include="..\..\..\src\Server\ZB.MOM.WW.OtOpcUa.OpcUaServer\ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common\ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Test the same preserved aggregate algorithms registered by the application server. -->
|
||||
<ProjectReference Include="..\..\..\src\Server\ZB.MOM.WW.OtOpcUa.OpcUaServer\ZB.MOM.WW.OtOpcUa.OpcUaServer.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer\ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.csproj"/>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common\ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user