b211911f98
v2-ci / unit-tests (push) Failing after 13m22s
Akka.TestKit.Xunit2 -> Akka.TestKit.Xunit 1.5.62 (xunit.v3 TestKit; needs xunit.v3 3.2.2, set via VersionOverride on the four projects). Removes the xunit 2.9.3 pin. IAsyncLifetime returns ValueTask, SqlServerFactAttribute gains the source-info ctor, and xUnit1051 call sites pass TestContext.Current.CancellationToken. Test counts unchanged. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
88 lines
4.5 KiB
C#
88 lines
4.5 KiB
C#
using Akka.Actor;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.OpcUa;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
|
|
|
/// <summary>
|
|
/// Gitea #662 — a native-alarm feed must survive a deploy that does NOT respawn its driver, end to end
|
|
/// through the real <see cref="Runtime.Drivers.DriverHostActor"/> and its real
|
|
/// <see cref="Runtime.Drivers.DriverInstanceActor"/> child.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Found live on the docker-dev rig: a deploy that only disabled ANOTHER driver re-pushed the desired set to
|
|
/// an unchanged OpcUaClient driver, whose data re-subscribe tore down the alarm event handler too. The
|
|
/// condition then froze while upstream kept emitting, and only a respawn brought it back. Every existing
|
|
/// end-to-end alarm test drives the driver directly (the host's driver is <c>Enabled=false</c>), so none of
|
|
/// them could see the instance actor drop the feed.
|
|
/// </remarks>
|
|
public sealed class NativeAlarmFeedSurvivesDeployTests : RuntimeActorTestBase
|
|
{
|
|
private static readonly RevisionHash RevA = RevisionHash.Parse(new string('a', 64));
|
|
private static readonly RevisionHash RevB = RevisionHash.Parse(new string('b', 64));
|
|
private static readonly NodeId TestNode = NodeId.Parse("node-1:4053");
|
|
|
|
private const string DriverId = "DI-1";
|
|
private const string AlarmXPath = "Plant/Modbus/dev1/alarmX";
|
|
private const string AlarmConfig = """{"alarm":{"alarmType":"AlarmCondition","severity":500}}""";
|
|
|
|
private static readonly (string Driver, string DriverName, string Device, string Tag, string DataType, bool Writable)[]
|
|
RawTags =
|
|
[
|
|
(DriverId, "Modbus", "dev1", "speed", "Float", false),
|
|
(DriverId, "Modbus", "dev1", "alarmX", "Float", false),
|
|
];
|
|
|
|
private static readonly Dictionary<string, string> TagConfigs = new() { ["alarmX"] = AlarmConfig };
|
|
|
|
[Fact]
|
|
public void An_upstream_alarm_still_reaches_its_condition_after_a_non_respawning_deploy()
|
|
{
|
|
var db = NewInMemoryDbFactory();
|
|
var factory = new RecordingSubscribableDriverFactory("Modbus");
|
|
var revA = V3ArtifactSeeder.SeedV3Deployment(
|
|
db, RevA, RawTags, Array.Empty<(string, string, string, string, string?)>(),
|
|
enabled: true, tagConfigByTag: TagConfigs);
|
|
var (host, publish, _) = V3ArtifactSeeder.SpawnHostAndApply(this, db, TestNode, revA, RevA, factory);
|
|
|
|
RecordingSubscribableDriver driver = null!;
|
|
AwaitAssert(() =>
|
|
{
|
|
factory.Created.Count.ShouldBe(1);
|
|
driver = factory.Created[0];
|
|
driver.SubscribeCount.ShouldBeGreaterThanOrEqualTo(1);
|
|
driver.LastSubscribedAlarmRefs.ShouldBe(new[] { AlarmXPath });
|
|
driver.AlarmSubscriberCount.ShouldBe(1);
|
|
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
|
var dataSubscribesBefore = driver.SubscribeCount;
|
|
|
|
// Revision B only ADDS another driver: DI-1's merged DriverConfig is byte-identical, so it is kept,
|
|
// and the bulk push re-sends its (unchanged) desired set — which re-subscribes its data.
|
|
var revB = V3ArtifactSeeder.SeedV3Deployment(
|
|
db, RevB, RawTags, Array.Empty<(string, string, string, string, string?)>(),
|
|
enabled: true, tagConfigByTag: TagConfigs, taglessDrivers: ["DI-2"]);
|
|
host.Tell(new DispatchDeployment(revB, RevB, CorrelationId.NewId()));
|
|
|
|
AwaitAssert(() =>
|
|
{
|
|
driver.SubscribeCount.ShouldBeGreaterThan(dataSubscribesBefore,
|
|
"precondition: the deploy must re-subscribe DI-1's data, or this test exercises nothing");
|
|
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
|
driver.InitializeCount.ShouldBe(1, "precondition: DI-1 must NOT have been respawned or re-initialised");
|
|
driver.ShutdownCount.ShouldBe(0);
|
|
factory.Created[0].ShouldBeSameAs(driver);
|
|
|
|
driver.AlarmSubscriberCount.ShouldBe(1, "the alarm event handler must survive the data re-subscribe");
|
|
driver.FireAlarm(AlarmXPath, AlarmTransitionKind.Raise);
|
|
|
|
var update = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(
|
|
m => m.AlarmNodeId == AlarmXPath && m.State.Active, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
|
update.State.Acknowledged.ShouldBeFalse();
|
|
}
|
|
}
|