ff4e210a27
N3 (critical): the node manager carries only NATIVE conditions' state over a full rebuild; scripted conditions come back fresh and rely on the engine's re-assert, which since the off-thread plan is handled in the window and lands on the discarded instance. On a rebuild the condition replay scope is now AddedConditions plus every enabled scripted condition in the new composition (native kept conditions stay carried-over, not replayed). The test sink again models the real rebuild: it clears non-native condition state only. N4: a surgical in-place shape edit (DataType / IsArray / ArrayLength) resets the raw node and its UNS mirror to BadWaitingForInitialData without ensuring them, so the window's value was not replayed and the two NodeIds disagreed. The applier records re-shaped nodes (MaterialisationRecording.Reshaped); their deferred value replays only once fitted to the new shape (VariableShape.TryFit: as is, or converted invariantly; never an old-typed value - WriteValues assigns the value verbatim with no type check), otherwise it is dropped at Debug and the node keeps waiting. Status-only entries are not replayed onto a re-shaped node. Minors: m1/m2 documented on _replayScope (degraded-path only); m3 corrected the false carry-over premise in the scope doc (the ScriptedAlarmHostActor ordering remark is outside this lane - F8); m4 the replay Debug log reports alarm-state and alarm-quality counts again. Refs #732 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
65 lines
2.6 KiB
C#
65 lines
2.6 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
|
|
|
/// <summary>
|
|
/// Lane B review N4 — <see cref="VariableShape.TryFit"/> decides whether a value deferred during a plan window may
|
|
/// be replayed onto a node an in-place edit re-shaped: never an old-typed value the new DataType contradicts.
|
|
/// </summary>
|
|
public sealed class VariableShapeTests
|
|
{
|
|
[Fact]
|
|
public void A_value_of_the_new_type_fits_unchanged()
|
|
{
|
|
new VariableShape("Int32", false, null).TryFit(7, out var fitted).ShouldBeTrue();
|
|
fitted.ShouldBe(7);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_convertible_old_typed_value_is_converted_to_the_new_type()
|
|
{
|
|
new VariableShape("Int32", false, null).TryFit(5.0f, out var fitted).ShouldBeTrue();
|
|
fitted.ShouldBeOfType<int>().ShouldBe(5);
|
|
new VariableShape("Double", false, null).TryFit((short)3, out var d).ShouldBeTrue();
|
|
d.ShouldBeOfType<double>().ShouldBe(3d);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Int32", "fast")] // not a number
|
|
[InlineData("Byte", 300)] // overflow
|
|
[InlineData("DateTime", 12)] // no conversion
|
|
public void A_value_the_new_type_cannot_hold_does_not_fit(string dataType, object value)
|
|
{
|
|
new VariableShape(dataType, false, null).TryFit(value, out var fitted).ShouldBeFalse();
|
|
fitted.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Scalar_and_array_never_cross()
|
|
{
|
|
new VariableShape("Int32", false, null).TryFit(new[] { 1, 2 }, out _).ShouldBeFalse();
|
|
new VariableShape("Int32", true, 2).TryFit(1, out _).ShouldBeFalse();
|
|
new VariableShape("String", true, null).TryFit("ab", out _).ShouldBeFalse("a string is not an array of strings");
|
|
}
|
|
|
|
[Fact]
|
|
public void An_array_is_converted_element_wise_and_its_fixed_length_enforced()
|
|
{
|
|
new VariableShape("Int16", true, 3).TryFit(new[] { 1, 2, 3 }, out var fitted).ShouldBeTrue();
|
|
fitted.ShouldBeOfType<short[]>().ShouldBe(new short[] { 1, 2, 3 });
|
|
new VariableShape("Int16", true, 4).TryFit(new[] { 1, 2, 3 }, out _).ShouldBeFalse("wrong length");
|
|
new VariableShape("Int16", true, null).TryFit(new[] { 1, 2, 3 }, out _).ShouldBeTrue("no fixed length");
|
|
new VariableShape("Byte", true, null).TryFit(new[] { 1, 999 }, out _).ShouldBeFalse("an element overflows");
|
|
}
|
|
|
|
[Fact]
|
|
public void Null_and_unknown_types_always_fit()
|
|
{
|
|
new VariableShape("Int32", false, null).TryFit(null, out var n).ShouldBeTrue();
|
|
n.ShouldBeNull();
|
|
new VariableShape("Structure", false, null).TryFit("anything", out var any).ShouldBeTrue();
|
|
any.ShouldBe("anything");
|
|
}
|
|
}
|