From b211911f98086f553d2669c6ef73ccaaf0bdf722 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 10 Nov 2026 08:57:33 -0500 Subject: [PATCH] test: migrate AdminUI/ControlPlane/Runtime/Host tests to xunit.v3 (#756) 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 --- Directory.Packages.props | 11 +- .../Acls/AclScopeServiceTests.cs | 62 +++---- .../AdminUiSqlServerFixture.cs | 13 +- .../AlarmsHistorianReaderTests.cs | 4 +- .../AlertsAckPathTests.cs | 6 +- .../Api/DeploymentStatusEndpointTests.cs | 6 +- .../ApiKeyGalaxyAckIdentityServiceTests.cs | 14 +- .../Audit/ActorAuditWriterTests.cs | 6 +- .../Deployments/DeploymentDriftProbeTests.cs | 2 +- .../FleetDiagnosticsLinksTests.cs | 6 +- .../Grpc/CentralCommandGrpcServiceTests.cs | 14 +- .../Hosts/DeployedDriverSetTests.cs | 22 +-- .../Hubs/TelemetryModeWiringTests.cs | 12 +- .../Raw/RawBrowseRebirthRefreshTests.cs | 2 +- .../Raw/RawCsvRenderingTests.cs | 4 +- .../AnalysisCompilationReuseTests.cs | 6 +- .../ScriptAnalysis/CtxCompletionGuardTests.cs | 10 +- .../ScriptAnalysis/EquipTokenEditorTests.cs | 8 +- .../ScriptAnalysis/HoverSignatureTests.cs | 16 +- .../ScriptAnalysis/MarkerPayloadBoundTests.cs | 6 +- .../ScriptAnalysis/ScriptTagCatalogTests.cs | 56 +++---- .../ScriptList/ScriptListQueriesTests.cs | 6 +- .../Telemetry/TelemetryDialDeadlineTests.cs | 8 +- .../Telemetry/TelemetryDialSupervisorTests.cs | 28 ++-- .../Uns/EffectiveNameGuardTests.cs | 24 +-- .../Uns/RawTagConfigGateTests.cs | 10 +- .../Uns/RawTreeServiceRenameWarningTests.cs | 12 +- .../RawTreeServiceSecretRefRoundTripTests.cs | 12 +- .../Uns/RawTreeServiceTests.cs | 80 ++++----- .../Uns/RawTreeServiceWaveBTests.cs | 60 +++---- .../Uns/ScriptSourceServiceTests.cs | 16 +- .../Uns/UnsSegmentGuardTests.cs | 30 ++-- .../Uns/UnsTreeServiceAreaLineTests.cs | 42 ++--- .../Uns/UnsTreeServiceCreateScriptTests.cs | 8 +- .../Uns/UnsTreeServiceDeleteClusterTests.cs | 16 +- .../UnsTreeServiceEquipmentChildRowsTests.cs | 2 +- ...UnsTreeServiceEquipmentPickContextTests.cs | 6 +- .../Uns/UnsTreeServiceEquipmentTests.cs | 28 ++-- .../Uns/UnsTreeServiceImportTests.cs | 8 +- .../Uns/UnsTreeServiceLoadEditTests.cs | 24 +-- .../Uns/UnsTreeServiceReferenceTests.cs | 50 +++--- .../Uns/UnsTreeServiceScriptedAlarmTests.cs | 36 ++-- .../Uns/UnsTreeServiceStructureTests.cs | 6 +- .../Uns/UnsTreeServiceTagDriversTests.cs | 2 +- .../Uns/UnsTreeServiceTagTests.cs | 14 +- .../Uns/UnsTreeServiceVirtualTagTests.cs | 38 ++--- .../VirtualTagEquipTokenValidationTests.cs | 18 +- .../ZB.MOM.WW.OtOpcUa.AdminUI.Tests.csproj | 7 +- .../AdminOperationsActorMailboxTests.cs | 28 ++-- ...nOperationsActorServerFaultMessageTests.cs | 4 +- ...AdminOperationsActorSingleSnapshotTests.cs | 6 +- .../AdminOperationsActorTagConfigGateTests.cs | 16 +- .../AdminOperationsActorTests.cs | 56 +++---- .../AuditWriterActorRejectedRowTests.cs | 8 +- .../AuditWriterActorTests.cs | 16 +- .../ClusterNodeAddressReconcilerActorTests.cs | 12 +- .../CentralCommunicationActorTests.cs | 108 ++++++------ .../ControlPlaneStarterOrderTests.cs | 8 +- .../MeshPskProviderRefreshTests.cs | 2 +- .../Communication/MeshPskProviderTests.cs | 2 +- .../Communication/MeshRouterDispatchTests.cs | 14 +- ...nfigComposerSqlServerHashStabilityTests.cs | 6 +- .../ConfigComposerTests.cs | 42 ++--- ...figPublishCoordinatorDbSourcedAcksTests.cs | 20 +-- .../ConfigPublishCoordinatorRecoveryTests.cs | 44 ++--- .../ConfigPublishCoordinatorTests.cs | 8 +- .../ConfigPublishCoordinatorTimeoutTests.cs | 10 +- .../DataPlaneAuthorityArtifactTests.cs | 36 ++-- .../DeploymentRetentionTests.cs | 46 +++--- .../GossipedRoleCaseInsensitivityTests.cs | 2 +- .../Harness/ControlPlaneTestHarness.cs | 4 +- .../ManualFailoverServiceTests.cs | 8 +- .../RedundancyPrimaryElectionTests.cs | 4 +- .../RedundancyStateActorTests.cs | 10 +- ...B.MOM.WW.OtOpcUa.ControlPlane.Tests.csproj | 5 +- ...erResilienceStatusPublisherServiceTests.cs | 2 +- .../SecretsReplicationRegistrationTests.cs | 2 +- .../VirtualTagToVirtualTagFlowTests.cs | 4 +- .../Grpc/NodeCommandGrpcServiceTests.cs | 14 +- .../Health/ActiveTierRegistrationTests.cs | 2 +- .../OpcUa/ApiKeyAuthenticationTests.cs | 28 ++-- .../OpcUa/ArtifactApiKeyStoreTests.cs | 14 +- .../OpcUa/BoundedApiKeyStoreKnownIdTests.cs | 34 ++-- .../BoundedApiKeyStorePerEndpointTests.cs | 20 +-- .../OpcUa/BoundedApiKeyStoreTests.cs | 56 +++---- .../OpcUa/ConfigDbApiKeyStoreTests.cs | 12 +- ...cUaServerHostedServiceStartFailureTests.cs | 2 +- .../Startup/FailStartExitTests.cs | 4 +- .../ZB.MOM.WW.OtOpcUa.Host.Tests.csproj | 4 +- .../EventStreamCommandDeliveryTests.cs | 12 +- .../Communication/GrpcApplyAckSenderTests.cs | 22 +-- .../NodeCommunicationActorTests.cs | 20 +-- .../LocalDbDeploymentArtifactCacheTests.cs | 74 ++++----- .../AbCipAlmaNativeAlarmEndToEndTests.cs | 4 +- .../Drivers/ActorNodeWriteGatewayTests.cs | 4 +- .../Drivers/CalculationDependencyFlowTests.cs | 8 +- .../DriverHostActorApplyingStashTests.cs | 30 ++-- .../DriverHostActorArtifactCacheTests.cs | 20 +-- .../DriverHostActorAsyncCacheWriteTests.cs | 12 +- .../DriverHostActorBatchFanOutTests.cs | 18 +- .../DriverHostActorBootFromCacheTests.cs | 6 +- .../Drivers/DriverHostActorCommsLossTests.cs | 8 +- ...DriverHostActorCommsLossValueNodesTests.cs | 26 +-- .../Drivers/DriverHostActorDbLessTests.cs | 4 +- .../DriverHostActorDegradedAckTests.cs | 40 ++--- ...iverHostActorDirectApplyOffMailboxTests.cs | 10 +- ...verHostActorDirectApplyReadFailureTests.cs | 10 +- ...verHostActorFetchAndCacheBootstrapTests.cs | 8 +- .../DriverHostActorFetchAndCacheTests.cs | 12 +- ...erHostActorFetchAndCacheUnreadableTests.cs | 14 +- ...iverHostActorHistorizationRoleViewTests.cs | 54 +++--- ...iverHostActorHistoryUpdateRoleViewTests.cs | 14 +- .../DriverHostActorHistoryWriterTests.cs | 2 +- .../Drivers/DriverHostActorLiveValueTests.cs | 22 +-- ...iverHostActorNativeAlarmAckRoutingTests.cs | 12 +- .../DriverHostActorNativeAlarmTests.cs | 58 +++---- .../DriverHostActorPrimaryGateTests.cs | 24 +-- .../DriverHostActorPrimaryRoleSourceTests.cs | 14 +- .../DriverHostActorProbeResultDropTests.cs | 6 +- .../Drivers/DriverHostActorReconcileTests.cs | 34 ++-- .../DriverHostActorReprovisionStampTests.cs | 12 +- .../Drivers/DriverHostActorRestartTests.cs | 46 +++--- .../Drivers/DriverHostActorRoleViewTests.cs | 14 +- ...riverHostActorScriptedAlarmRestartTests.cs | 10 +- .../DriverHostActorSourceRemovedTests.cs | 10 +- .../Drivers/DriverHostActorStaleDropTests.cs | 8 +- .../Drivers/DriverHostActorTests.cs | 20 +-- .../DriverHostActorUnreadableArtifactTests.cs | 50 +++--- .../Drivers/DriverHostActorVirtualTagTests.cs | 10 +- .../Drivers/DriverHostActorWriteAskTests.cs | 10 +- .../DriverHostActorWriteRoutingTests.cs | 16 +- .../DriverHostCommsLossRecoveryTests.cs | 14 +- .../DriverInstanceActorBatchForwardTests.cs | 66 ++++---- ...iverInstanceActorCommsLossWatchdogTests.cs | 40 ++--- ...DriverInstanceActorConcurrentWriteTests.cs | 50 +++--- ...rInstanceActorConnectionLossSignalTests.cs | 24 +-- .../DriverInstanceActorDiagnosticsTests.cs | 20 +-- .../DriverInstanceActorDisposeTests.cs | 28 ++-- .../DriverInstanceActorHostStatusTests.cs | 24 +-- .../DriverInstanceActorNativeAlarmTests.cs | 80 ++++----- ...InstanceActorReconnectSingleFlightTests.cs | 12 +- ...iverInstanceActorRediscoverySignalTests.cs | 32 ++-- ...riverInstanceActorResilienceWiringTests.cs | 14 +- .../DriverInstanceActorSkippedTagTests.cs | 24 +-- ...InstanceActorSubscriptionReconcileTests.cs | 10 +- .../Drivers/DriverInstanceActorTests.cs | 82 ++++----- ...iverInstanceActorWriteAndSubscribeTests.cs | 24 +-- .../Drivers/FocasNativeAlarmEndToEndTests.cs | 18 +- .../NativeAlarmFeedSurvivesDeployTests.cs | 6 +- .../NativeAlarmRestatementEndToEndTests.cs | 72 ++++---- .../Drivers/S7AccessDeniedWatchdogTests.cs | 4 +- .../Harness/RuntimeActorTestBase.cs | 2 +- .../Health/DbHealthProbeOffThreadTests.cs | 6 +- .../Health/HealthProbeActorTests.cs | 12 +- .../Health/PeerProbeSupervisorTests.cs | 26 +-- .../AddHistorianProvisioningTests.cs | 2 +- ...istorizationRecorderAppendPipelineTests.cs | 30 ++-- ...ntinuousHistorizationRecorderGaugeTests.cs | 10 +- ...uousHistorizationRecorderPassFlushTests.cs | 10 +- .../ContinuousHistorizationRecorderTests.cs | 146 ++++++++-------- ...ousHistorizationRecorderTypedValueTests.cs | 2 +- .../Historian/HistorianAdapterActorTests.cs | 32 ++-- .../OtOpcUaTelemetryHookTests.cs | 4 +- .../OpcUaPublishActorApplyOutcomeTests.cs | 24 +-- .../OpcUa/OpcUaPublishActorBatchTests.cs | 2 +- .../OpcUaPublishActorDeferredWriteTests.cs | 106 ++++++------ .../OpcUaPublishActorEndpointStateTests.cs | 4 +- .../OpcUa/OpcUaPublishActorFreshnessTests.cs | 56 +++---- .../OpcUaPublishActorOffThreadPlanTests.cs | 8 +- .../OpcUa/OpcUaPublishActorRebuildTests.cs | 68 ++++---- .../OpcUaPublishActorStatusBatchTests.cs | 2 +- .../OpcUa/OpcUaPublishActorTests.cs | 58 +++---- .../OpcUaEndpointStepDownActorTests.cs | 12 +- .../DependencyMuxTagUpstreamSourceTests.cs | 4 +- .../ScriptedAlarmHostActorRestartTests.cs | 6 +- .../ScriptedAlarmHostActorTests.cs | 156 +++++++++--------- .../Scripting/DpsScriptLogPublisherTests.cs | 4 +- .../ServiceCollectionExtensionsTests.cs | 18 +- .../Telemetry/PublishSeamEmitsToHubTests.cs | 14 +- .../DependencyConsumerMuxAdapterTests.cs | 4 +- .../VirtualTags/DependencyMuxActorTests.cs | 42 ++--- .../VirtualTags/DependencyMuxReplayTests.cs | 36 ++-- .../VirtualTagActorAsyncEvaluationTests.cs | 50 +++--- ...VirtualTagActorFailureLogRateLimitTests.cs | 10 +- .../VirtualTagActorInputQualityTests.cs | 58 +++---- .../VirtualTagActorRunnerUnavailableTests.cs | 22 +-- .../VirtualTags/VirtualTagActorTests.cs | 68 ++++---- .../VirtualTags/VirtualTagActorTimerTests.cs | 30 ++-- .../VirtualTags/VirtualTagHostActorTests.cs | 90 +++++----- .../VirtualTagOutputRoutingTests.cs | 36 ++-- .../ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj | 5 +- 191 files changed, 2161 insertions(+), 2166 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b8205137..272995401 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,7 @@ - + @@ -212,13 +212,10 @@ Avalonia.X11 (Linux desktop). Pin to the patched 0.21.3 in the same 0.x line. Applied via a direct PackageReference in the consuming desktop project(s). --> - - + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorMailboxTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorMailboxTests.cs index 95a2b0fc7..2d14b2c09 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorMailboxTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorMailboxTests.cs @@ -33,16 +33,16 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase var connectClient = CreateTestProbe("connect-client"); connectClient.Send(actor, new TestDriverConnect("Blocking", "{}", TimeoutSeconds: 60, Guid.NewGuid())); - probe.Started.Wait(PresenceBudget).ShouldBeTrue("the probe must be in flight before the ack is sent"); + probe.Started.Wait(PresenceBudget, TestContext.Current.CancellationToken).ShouldBeTrue("the probe must be in flight before the ack is sent"); actor.Tell(new AcknowledgeAlarmCommand("alarm-1", "op", null, CorrelationId.NewId())); - var ack = ExpectMsg(AckBudget); + var ack = ExpectMsg(AckBudget, cancellationToken: TestContext.Current.CancellationToken); ack.Ok.ShouldBeTrue(); // Positive control: the probe reply still lands, with the right sender, once the probe returns. - connectClient.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + connectClient.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); probe.Release(new DriverProbeResult(true, null, TimeSpan.FromMilliseconds(5))); - var result = connectClient.ExpectMsg(PresenceBudget); + var result = connectClient.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); result.Ok.ShouldBeTrue(); result.LatencyMs.ShouldNotBeNull(); } @@ -58,7 +58,7 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase actor.Tell(new TestDriverConnect("Blocking", "{}", TimeoutSeconds: 1, Guid.NewGuid())); - var result = ExpectMsg(PresenceBudget); + var result = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); result.Ok.ShouldBeFalse(); result.Message.ShouldNotBeNull(); result.Message.ShouldContain("timed out"); @@ -79,16 +79,16 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase gated.Hold(); deployer.Send(actor, new StartDeployment("joe", CorrelationId.NewId())); - gated.Entered.Wait(PresenceBudget).ShouldBeTrue("the compose must be parked before the ack is sent"); + gated.Entered.Wait(PresenceBudget, TestContext.Current.CancellationToken).ShouldBeTrue("the compose must be parked before the ack is sent"); actor.Tell(new AcknowledgeAlarmCommand("alarm-2", "op", null, CorrelationId.NewId())); - ExpectMsg(AckBudget).Ok.ShouldBeTrue(); + ExpectMsg(AckBudget, cancellationToken: TestContext.Current.CancellationToken).Ok.ShouldBeTrue(); // Positive control: the deploy completes once released. - deployer.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + deployer.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); gated.Release(); - deployer.ExpectMsg(PresenceBudget).Outcome.ShouldBe(StartDeploymentOutcome.Accepted); - coordinator.ExpectMsg(PresenceBudget); + deployer.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.Accepted); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -106,14 +106,14 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase gated.Hold(); first.Send(actor, new StartDeployment("joe", CorrelationId.NewId())); - gated.Entered.Wait(PresenceBudget).ShouldBeTrue(); + gated.Entered.Wait(PresenceBudget, TestContext.Current.CancellationToken).ShouldBeTrue(); second.Send(actor, new StartDeployment("kim", CorrelationId.NewId())); - second.ExpectMsg(AckBudget).Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight); + second.ExpectMsg(AckBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight); gated.Release(); - first.ExpectMsg(PresenceBudget).Outcome.ShouldBe(StartDeploymentOutcome.Accepted); - coordinator.ExpectMsg(PresenceBudget); + first.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.Accepted); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } private sealed class BlockingProbe : IDriverProbe diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorServerFaultMessageTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorServerFaultMessageTests.cs index f4e6fd228..cfa249103 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorServerFaultMessageTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorServerFaultMessageTests.cs @@ -29,7 +29,7 @@ public sealed class AdminOperationsActorServerFaultMessageTests : ControlPlaneAc actor.Tell(new StartDeployment("joe", correlationId)); - var reply = ExpectMsg(PresenceBudget); + var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); reply.Message.ShouldNotBeNull(); reply.Message.ShouldNotContain("hunter2"); @@ -46,7 +46,7 @@ public sealed class AdminOperationsActorServerFaultMessageTests : ControlPlaneAc actor.Tell(new TestDriverConnect("Throwing", "{}", 5, correlationId)); - var reply = ExpectMsg(PresenceBudget); + var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reply.Ok.ShouldBeFalse(); reply.Message.ShouldNotBeNull(); reply.Message.ShouldNotContain("hunter2"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorSingleSnapshotTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorSingleSnapshotTests.cs index aa5ded290..d1c1bda06 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorSingleSnapshotTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorSingleSnapshotTests.cs @@ -67,9 +67,9 @@ public sealed class AdminOperationsActorSingleSnapshotTests : ControlPlaneActorT actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var reply = ExpectMsg(PresenceBudget); + var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted, reply.Message); - coordinator.ExpectMsg(PresenceBudget); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); using (var verify = plain.CreateDbContext()) { @@ -85,7 +85,7 @@ public sealed class AdminOperationsActorSingleSnapshotTests : ControlPlaneActorT // Positive control: now that the row is visible to a fresh read, the gate rejects it. actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var second = ExpectMsg(PresenceBudget); + var second = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); second.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); second.Message.ShouldNotBeNull(); second.Message.ShouldContain("EquipmentIdNotDerived"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTagConfigGateTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTagConfigGateTests.cs index 8c078542b..fb62a6262 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTagConfigGateTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTagConfigGateTests.cs @@ -61,8 +61,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget); - var reply = ExpectMsg(PresenceBudget); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("Intt16"); @@ -80,8 +80,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); - var reply = ExpectMsg(PresenceBudget); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); + var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("TagConfigInvalid"); @@ -102,8 +102,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget); - var reply = ExpectMsg(PresenceBudget); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); (reply.Message is null || !reply.Message.Contains("Intt16")).ShouldBeTrue(); } @@ -121,8 +121,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget); - ExpectMsg(PresenceBudget).Outcome.ShouldBe(StartDeploymentOutcome.Accepted); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.Accepted); } // lmxopcua#781 item 3 — through the actor, in Error mode too: the writable mismatch is a warning, never a reject. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTests.cs index 819d5618b..237f68dd2 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AdminOperationsActorTests.cs @@ -46,14 +46,14 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase var correlationId = CorrelationId.NewId(); actor.Tell(new AcknowledgeAlarmCommand("alarm-42", "operator-jo", "looking into it", correlationId)); - var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3)); + var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); published.AlarmId.ShouldBe("alarm-42"); published.Operation.ShouldBe("Acknowledge"); published.User.ShouldBe("operator-jo"); published.Comment.ShouldBe("looking into it"); published.UnshelveAtUtc.ShouldBeNull(); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Ok.ShouldBeTrue(); reply.Message.ShouldBeNull(); reply.CorrelationId.ShouldBe(correlationId); @@ -72,13 +72,13 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase var correlationId = CorrelationId.NewId(); actor.Tell(new ShelveAlarmCommand("alarm-7", "op-kim", ShelveKind.OneShot, UnshelveAtUtc: null, Comment: null, correlationId)); - var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3)); + var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); published.AlarmId.ShouldBe("alarm-7"); published.Operation.ShouldBe("OneShotShelve"); published.User.ShouldBe("op-kim"); published.UnshelveAtUtc.ShouldBeNull(); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Ok.ShouldBeTrue(); reply.CorrelationId.ShouldBe(correlationId); } @@ -96,13 +96,13 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase var unshelveAt = DateTime.UtcNow.AddMinutes(15); actor.Tell(new ShelveAlarmCommand("alarm-9", "op-lee", ShelveKind.Timed, unshelveAt, Comment: "maint window", CorrelationId.NewId())); - var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3)); + var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); published.AlarmId.ShouldBe("alarm-9"); published.Operation.ShouldBe("TimedShelve"); published.UnshelveAtUtc.ShouldBe(unshelveAt); published.Comment.ShouldBe("maint window"); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Ok.ShouldBeTrue(); } @@ -118,11 +118,11 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new ShelveAlarmCommand("alarm-3", "op-sam", ShelveKind.Unshelve, UnshelveAtUtc: null, Comment: null, CorrelationId.NewId())); - var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3)); + var published = topicProbe.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); published.Operation.ShouldBe("Unshelve"); published.UnshelveAtUtc.ShouldBeNull(); - ExpectMsg(TimeSpan.FromSeconds(3)).Ok.ShouldBeTrue(); + ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).Ok.ShouldBeTrue(); } /// Verifies a shelve with a null UnshelveAtUtc is rejected @@ -137,13 +137,13 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new ShelveAlarmCommand("alarm-1", "op-zoe", ShelveKind.Timed, UnshelveAtUtc: null, Comment: null, CorrelationId.NewId())); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Ok.ShouldBeFalse(); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("UnshelveAtUtc"); // No command should have been published. - topicProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + topicProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Verifies that starting a deployment inserts a row and dispatches to the coordinator. @@ -156,11 +156,11 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var dispatch = coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + var dispatch = coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); dispatch.DeploymentId.Value.ShouldNotBe(Guid.Empty); dispatch.RevisionHash.Value.Length.ShouldBe(64); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); reply.DeploymentId.ShouldBe(dispatch.DeploymentId); reply.RevisionHash.ShouldBe(dispatch.RevisionHash); @@ -237,8 +237,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("UnsEffectiveNameCollision"); // the v3 rule's error code @@ -275,8 +275,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("EquipmentIdNotDerived"); @@ -321,9 +321,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); // Still dispatches — the advisory is non-blocking. - coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("will compile"); @@ -360,9 +360,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); // No compiled scripts → no advisory emitted. (reply.Message is null || !reply.Message.Contains("will compile")).ShouldBeTrue(); @@ -396,9 +396,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("will compile"); @@ -454,8 +454,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); reply.Message.ShouldNotBeNull(); reply.Message.ShouldContain("ClusterEnabledNodeCountMismatch"); @@ -511,9 +511,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); (reply.Message is null || !reply.Message.Contains("ClusterEnabledNodeCountMismatch")).ShouldBeTrue(); (reply.Message is null || !reply.Message.Contains("ClusterRedundancyModeInvalid")).ShouldBeTrue(); @@ -544,8 +544,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase actor.Tell(new StartDeployment("joe", CorrelationId.NewId())); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); - var reply = ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); + var reply = ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); reply.Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight); reply.DeploymentId.ShouldNotBeNull(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorRejectedRowTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorRejectedRowTests.cs index aba376480..86855b2c7 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorRejectedRowTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorRejectedRowTests.cs @@ -35,7 +35,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase Watch(actor); actor.Tell(PoisonPill.Instance); // PostStop flushes - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = rejecting.CreateDbContext(); var persisted = db.ConfigAuditLogs.Select(r => r.EventId).ToList(); @@ -71,7 +71,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase foreach (var id in ids) actor.Tell(NewEvent(id)); Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = factory.CreateDbContext(); var persisted = db.ConfigAuditLogs.Select(r => r.EventId).ToList(); @@ -99,7 +99,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase foreach (var id in ids) actor.Tell(NewEvent(id)); Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor, PresenceBudget); + ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); using var db = sql.NewContext(); var persisted = db.ConfigAuditLogs.Select(r => r.EventId).ToList(); @@ -120,7 +120,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase for (var i = 0; i < 5; i++) actor.Tell(NewEvent(Guid.NewGuid())); Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); // terminates ⇒ the retry loop did not spin + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); // terminates ⇒ the retry loop did not spin using var db = rejecting.CreateDbContext(); db.ConfigAuditLogs.Count().ShouldBe(0); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorTests.cs index 7d0c5522b..3089b159b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorTests.cs @@ -39,7 +39,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase { using var db = dbFactory.CreateDbContext(); db.ConfigAuditLogs.Count().ShouldBe(AuditWriterActor.FlushBatchSize); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that duplicate event IDs within a batch are deduplicated in the buffer. @@ -57,7 +57,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase // Force a flush — send PoisonPill, which triggers PostStop → FlushBuffer. Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); db.ConfigAuditLogs.Count().ShouldBe(100, "in-buffer dedup should collapse duplicate EventIds"); @@ -76,7 +76,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); db.ConfigAuditLogs.Count().ShouldBe(10); @@ -94,7 +94,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); var row = db.ConfigAuditLogs.Single(); @@ -127,7 +127,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); var row = db.ConfigAuditLogs.Single(); @@ -146,7 +146,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase var testRef = ActorOfAsTestActorRef(AuditWriterActor.Props(dbFactory)); IAuditWriter writer = testRef.UnderlyingActor; - var task = writer.WriteAsync(NewEvent(Guid.NewGuid(), action: "Published")); + var task = writer.WriteAsync(NewEvent(Guid.NewGuid(), action: "Published"), TestContext.Current.CancellationToken); task.IsCompletedSuccessfully.ShouldBeTrue("WriteAsync must be best-effort and complete synchronously"); await Should.NotThrowAsync(async () => await task); } @@ -165,7 +165,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); var row = db.ConfigAuditLogs.Single(); @@ -187,7 +187,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); var row = db.ConfigAuditLogs.Single(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ClusterNodeAddressReconcilerActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ClusterNodeAddressReconcilerActorTests.cs index 27299c63a..c65228c59 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ClusterNodeAddressReconcilerActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ClusterNodeAddressReconcilerActorTests.cs @@ -28,7 +28,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe SeedNode(dbFactory, "site-a-1:4053", enabled: true); EventFilter.Warning(contains: "site-a-1:4053").ExpectOne(TimeSpan.FromSeconds(10), () => - Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory))); + Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory)), TestContext.Current.CancellationToken); } /// @@ -49,7 +49,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe dbFactory, sweepInterval: TimeSpan.FromMilliseconds(400))); // Hold the filter open past several sweeps rather than returning immediately. ExpectNoMsg(TimeSpan.FromSeconds(2.5)); - }); + }, TestContext.Current.CancellationToken); } /// @@ -67,7 +67,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props( dbFactory, sweepInterval: TimeSpan.FromMilliseconds(400))); ExpectNoMsg(TimeSpan.FromSeconds(1.5)); - }); + }, TestContext.Current.CancellationToken); } /// @@ -88,7 +88,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props( dbFactory, ownClusterId: "MAIN", sweepInterval: TimeSpan.FromMilliseconds(400))); ExpectNoMsg(TimeSpan.FromSeconds(1.5)); - }); + }, TestContext.Current.CancellationToken); } /// @@ -103,7 +103,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe SeedNode(dbFactory, "main-1:4053", enabled: true, clusterId: "MAIN"); EventFilter.Warning(contains: "main-1:4053").ExpectOne(TimeSpan.FromSeconds(10), () => - Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: "MAIN"))); + Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: "MAIN")), TestContext.Current.CancellationToken); } /// @@ -118,7 +118,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe SeedNode(dbFactory, "site-a-1:4053", enabled: true, clusterId: "SITE-A"); EventFilter.Warning(contains: "site-a-1:4053").ExpectOne(TimeSpan.FromSeconds(10), () => - Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: null))); + Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: null)), TestContext.Current.CancellationToken); } private static void SeedNode( diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/CentralCommunicationActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/CentralCommunicationActorTests.cs index 51dcd0466..39c1747c1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/CentralCommunicationActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/CentralCommunicationActorTests.cs @@ -3,7 +3,7 @@ using Akka.Actor; using Akka.Cluster.Tools.PublishSubscribe; using Akka.Event; using Akka.TestKit; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Microsoft.EntityFrameworkCore; using Shouldly; using Xunit; @@ -86,7 +86,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // without an explicit sender would ack to TestActor and this wait would hang. DistributedPubSub.Get(Sys).Mediator.Tell( new Subscribe(DeploymentsTopic, mediatorProbe.Ref), mediatorProbe.Ref); - mediatorProbe.ExpectMsg(TimeSpan.FromSeconds(5)); + mediatorProbe.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); var actor = Spawn(factory, new MeshTransportOptions()); var dispatch = new DispatchDeployment( @@ -94,7 +94,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase actor.Tell(new MeshCommand(DeploymentsTopic, dispatch)); - mediatorProbe.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(dispatch); + mediatorProbe.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch); } [Fact] @@ -114,7 +114,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase actor.Tell(ack, node.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(ack); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(ack); // Forward, not Tell: the coordinator must see the node, not this relay. coordinator.LastSender.ShouldBe(node.Ref); } @@ -133,7 +133,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase new CorrelationId(Guid.NewGuid())); EventFilter.Warning(contains: "not resolvable").ExpectOne(TimeSpan.FromSeconds(5), () => - actor.Tell(ack)); + actor.Tell(ack), TestContext.Current.CancellationToken); } [Fact] @@ -147,11 +147,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // fail with two matches instead of one. EventFilter.Warning(contains: "contact set was NOT refreshed").ExpectOne( TimeSpan.FromSeconds(5), - () => actor.Tell(new Status.Failure(new InvalidOperationException("db down")))); + () => actor.Tell(new Status.Failure(new InvalidOperationException("db down"))), TestContext.Current.CancellationToken); // Still serving: a transient DB outage must not take the command boundary down with it. actor.Tell(new MeshCommand(DeploymentsTopic, "anything")); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } // ------------------------------------------------------------------ Mode = Grpc @@ -177,7 +177,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 3, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 3, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.Calls.Select(c => c.Endpoint).ShouldBe( new[] { "http://host-a:4056", "http://host-b:4056", "http://host-c:4056" }, ignoreOrder: true); @@ -198,15 +198,15 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase var mediatorProbe = CreateTestProbe(); DistributedPubSub.Get(Sys).Mediator.Tell( new Subscribe(DeploymentsTopic, mediatorProbe.Ref), mediatorProbe.Ref); - mediatorProbe.ExpectMsg(TimeSpan.FromSeconds(5)); + mediatorProbe.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); var rig = SpawnGrpcRig(factory); AwaitContactsLoaded(rig); rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5)); - mediatorProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); + mediatorProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } [Fact] @@ -226,11 +226,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.Calls[0].Endpoint.ShouldContain("host-a"); // And no late arrival from the two excluded rows. - ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); rig.Dialer.Calls.Count.ShouldBe(1); } @@ -248,13 +248,13 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase GrpcRig? rig = null; EventFilter.Warning(contains: "has no GrpcPort").ExpectOne( - TimeSpan.FromSeconds(5), () => rig = SpawnGrpcRig(factory)); + TimeSpan.FromSeconds(5), () => rig = SpawnGrpcRig(factory), TestContext.Current.CancellationToken); rig.ShouldNotBeNull(); AwaitContactsLoaded(rig); rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.Calls[0].Endpoint.ShouldBe("http://host-b:4056"); } @@ -273,11 +273,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase SetGrpcPort(factory, "host-a:4053", 4056); rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts()); AwaitCondition( - () => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5)); + () => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); SetGrpcPort(factory, "host-a:4053", null); EventFilter.Warning(contains: "has no GrpcPort").ExpectOne(TimeSpan.FromSeconds(5), () => - rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts())); + rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts()), TestContext.Current.CancellationToken); } [Fact] @@ -302,14 +302,14 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // The node id, not the endpoint: an operator reading the log is looking at ClusterNode rows. EventFilter.Warning(contains: "host-a:4053").ExpectOne(TimeSpan.FromSeconds(5), () => - rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()))); + rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken); // Both were attempted; the healthy one is unaffected by its neighbour's failure. - AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); // Still serving after a per-node failure — no supervision restart, no state loss. rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 4, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 4, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); } [Fact] @@ -330,9 +330,9 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase AwaitContactsLoaded(rig); EventFilter.Warning(contains: "Unauthenticated").ExpectOne(TimeSpan.FromSeconds(5), () => - rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()))); + rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken); - AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); } [Fact] @@ -343,7 +343,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase AwaitContactsLoaded(rig); EventFilter.Warning(contains: "dropping").ExpectOne(TimeSpan.FromSeconds(5), () => - rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()))); + rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken); rig.Dialer.Calls.ShouldBeEmpty(); } @@ -359,13 +359,13 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase AwaitContactsLoaded(rig); EventFilter.Error(contains: "Cannot encode").ExpectOne(TimeSpan.FromSeconds(5), () => - rig.Actor.Tell(new MeshCommand(DeploymentsTopic, "not a command record"))); + rig.Actor.Tell(new MeshCommand(DeploymentsTopic, "not a command record")), TestContext.Current.CancellationToken); rig.Dialer.Calls.ShouldBeEmpty(); // Still serving. rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); } [Fact] @@ -384,7 +384,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase rig.Actor.Tell(new MeshCommand(DeploymentsTopic, new DispatchDeployment( new DeploymentId(Guid.NewGuid()), new RevisionHash("abc"), correlationId))); - AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.Calls.ShouldAllBe(c => c.Envelope.CorrelationId == correlationId.ToString()); // One envelope object, encoded once and fanned — not re-encoded per node. rig.Dialer.Calls.Select(c => c.Envelope).Distinct().Count().ShouldBe(1); @@ -408,7 +408,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase rig.Actor.Tell(new MeshCommand( "alarms", new AlarmCommand("alarm-1", "Acknowledge", "operator", null, null))); - AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.Calls.Select(c => c.Envelope.CorrelationId).Distinct().Count().ShouldBe(1); rig.Dialer.Calls[0].Envelope.CorrelationId.ShouldNotBeNullOrWhiteSpace(); rig.Dialer.Calls[0].Envelope.AlarmCommand.AlarmId.ShouldBe("alarm-1"); @@ -461,7 +461,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase new CorrelationId(Guid.NewGuid())); rig.Actor.Tell(ack); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(ack); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(ack); } finally { @@ -502,11 +502,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts()); AwaitCondition( - () => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5)); + () => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.Calls.Select(c => c.Endpoint).ShouldBe( new[] { "http://host-a:4056", "http://host-b:4056" }, ignoreOrder: true); @@ -540,7 +540,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase () => psk)); AwaitCondition( - () => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(5)); + () => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); psk.KnownClusterPublications[0].ShouldBe(new[] { "C1" }); psk.InvalidateAllCount.ShouldBeGreaterThanOrEqualTo(1); } @@ -565,8 +565,8 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5)); - ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); rig.Dialer.Calls.Count.ShouldBe(1); rig.Dialer.Calls[0].ClusterId.ShouldBe("C1"); } @@ -589,7 +589,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // Targets-before-gate, observed the only way it can be from outside the actor: the very next // message finds the dispatch set already populated. rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); } [Fact] @@ -614,7 +614,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // same absence at two severities. EventFilter.Error(contains: "no command dialer is wired").ExpectOne( TimeSpan.FromSeconds(5), - () => actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()))); + () => actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken); } // ------------------------------------------------------------------ PreStart resolve-race logging @@ -673,7 +673,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase actor!.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); return dialer.Calls.Count >= 1; }, TimeSpan.FromSeconds(5)); - }); + }, TestContext.Current.CancellationToken); // Both retries actually happened — this fact is not vacuously true because the factories // never got a second chance to run. @@ -731,7 +731,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase Sys.ActorOf(CentralCommunicationActor.Props( factory, ModeOptions(mode), () => null, () => new RecordingDialer(), PskFactory)); AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(10)); - }); + }, TestContext.Current.CancellationToken); } [Fact] @@ -744,9 +744,9 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase EventFilter.Warning().Expect(2, TimeSpan.FromSeconds(10), () => Sys.ActorOf(CentralCommunicationActor.Props( - factory, GrpcMode(), () => null, () => new RecordingDialer(), () => psk))); + factory, GrpcMode(), () => null, () => new RecordingDialer(), () => psk)), TestContext.Current.CancellationToken); - AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(10)); + AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken); } [Fact] @@ -778,25 +778,25 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // A mailbox barrier also waits for PreStart to register the zero-delay periodic tick. var marker = new object(); actor.Tell(marker); - barrier.ExpectMsg(TimeSpan.FromSeconds(5)).Message.ShouldBeSameAs(marker); + barrier.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Message.ShouldBeSameAs(marker); scheduler.Advance(TimeSpan.Zero); - loads.ExpectMsg(1, TimeSpan.FromSeconds(5)); - local.AwaitCondition(() => Volatile.Read(ref calls) == 2, TimeSpan.FromSeconds(5)); + loads.ExpectMsg(1, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); + local.AwaitCondition(() => Volatile.Read(ref calls) == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); // This mailbox marker is handled after the failed ContactsLoaded handler returns. // Unlike observing the provider call alone, it proves retry registration is complete. actor.Tell(marker); - barrier.ExpectMsg(TimeSpan.FromSeconds(5)).Message.ShouldBeSameAs(marker); + barrier.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Message.ShouldBeSameAs(marker); local.EventFilter.Info(contains: "Known-cluster gate published").ExpectOne( - TimeSpan.FromSeconds(5), () => actor.Tell(new CentralCommunicationActor.RefreshContacts())); - loads.ExpectMsg(2, TimeSpan.FromSeconds(5)); + TimeSpan.FromSeconds(5), () => actor.Tell(new CentralCommunicationActor.RefreshContacts()), TestContext.Current.CancellationToken); + loads.ExpectMsg(2, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // Cross the pending retry deadline, leaving the 60-second periodic tick untouched. // LoadContactsFromDb uses Task.Run, so observe its boundary for the original absence // budget rather than asserting before a mistakenly retained retry can start its read. scheduler.Advance(TimeSpan.FromMilliseconds(2500)); - loads.ExpectNoMsg(TimeSpan.FromMilliseconds(2500)); + loads.ExpectNoMsg(TimeSpan.FromMilliseconds(2500), TestContext.Current.CancellationToken); factory.Calls.ShouldBe(2); psk.KnownClusterPublications.Count.ShouldBe(1); } @@ -810,7 +810,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase factory, GrpcMode(), () => null, () => new RecordingDialer(), () => new RecordingPskProvider(), bootstrapRetryInterval: TimeSpan.FromMilliseconds(20))); - EventFilter.Warning().Expect(32, TimeSpan.FromSeconds(10), () => { }); + EventFilter.Warning().Expect(32, TimeSpan.FromSeconds(10), () => { }, TestContext.Current.CancellationToken); Thread.Sleep(1500); // 1 initial + 15 retries; the 60 s periodic tick has not fired again. factory.Calls.ShouldBe(16); @@ -854,7 +854,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // ran and PublishKnownClusters's property access performed the first-use retry — not // merely that the actor was constructed. AwaitCondition(() => pskCalls >= 2, TimeSpan.FromSeconds(5)); - }); + }, TestContext.Current.CancellationToken); // The bootstrap retry is BOUNDED: with a 50 ms interval and a budget of 15 it has long // finished, so the call count plateaus instead of growing with elapsed time. @@ -873,7 +873,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase { actor.Tell(new CentralCommunicationActor.RefreshContacts()); AwaitCondition(() => pskCalls > pskCallsAfterFirstLoad, TimeSpan.FromSeconds(5)); - }); + }, TestContext.Current.CancellationToken); } [Fact] @@ -904,15 +904,15 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase // Proof PreStart's resolve attempt and the first contact load it schedules have // actually run, so this block's silence is not vacuous. AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(5)); - }); + }, TestContext.Current.CancellationToken); EventFilter.Error(contains: "no command dialer is wired").ExpectOne(TimeSpan.FromSeconds(5), () => - actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()))); + actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken); // A SECOND dispatch must ALSO log it: this signal is per-dispatch and unguarded, the // opposite shape from the PSK provider's once-per-incarnation guard above. EventFilter.Error(contains: "no command dialer is wired").ExpectOne(TimeSpan.FromSeconds(5), () => - actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()))); + actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken); } private static void SetGrpcPort( @@ -943,7 +943,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase AwaitContactsLoaded(rig); // The dialer is resolved lazily on first use; a dispatch forces that, and the NEXT refresh prunes. rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())); - AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5)); + AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); using (var db = factory.CreateDbContext()) { @@ -954,7 +954,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase AwaitCondition( () => rig.Dialer.RetainCalls.Any(set => set.Count == 1 && set.Single().Contains("host-a")), - TimeSpan.FromSeconds(5)); + TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); rig.Dialer.RetainCalls.Last().ShouldBe(new[] { "http://host-a:4056" }); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/ControlPlaneStarterOrderTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/ControlPlaneStarterOrderTests.cs index 618f9e788..4ed4f5683 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/ControlPlaneStarterOrderTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/ControlPlaneStarterOrderTests.cs @@ -75,7 +75,7 @@ public sealed class ControlPlaneStarterOrderTests } finally { - await host.StopAsync(); + await host.StopAsync(TestContext.Current.CancellationToken); } } @@ -101,16 +101,16 @@ public sealed class ControlPlaneStarterOrderTests // StartAsync at which a synchronous assertion is correct. using var host = BuildAdminHost(mode, useSignallingProvider: true); - await host.StartAsync(); + await host.StartAsync(TestContext.Current.CancellationToken); try { var signal = (SignallingPskProvider)host.Services.GetRequiredService(); - await signal.Published.WaitAsync(TimeSpan.FromSeconds(20)); + await signal.Published.WaitAsync(TimeSpan.FromSeconds(20), TestContext.Current.CancellationToken); } finally { - await host.StopAsync(); + await host.StopAsync(TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderRefreshTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderRefreshTests.cs index 259fa42f4..02aafc1b6 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderRefreshTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderRefreshTests.cs @@ -51,7 +51,7 @@ public sealed class MeshPskProviderRefreshTests resolver.Value = "new"; resolver.Hold(); var refresh = provider.RefreshAsync(new[] { "SITE-A" }, CancellationToken.None); - resolver.Entered.Wait(TimeSpan.FromSeconds(5)).ShouldBeTrue(); + resolver.Entered.Wait(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken).ShouldBeTrue(); // Mid-refresh: the old key, from memory — the store is NOT re-read on this path. var callsMidRefresh = resolver.Calls; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderTests.cs index 6ccafb65c..356bd97a8 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshPskProviderTests.cs @@ -307,7 +307,7 @@ public sealed class MeshPskProviderTests return psk; }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray(); - arrived.Wait(TimeSpan.FromSeconds(10)).ShouldBeTrue("all callers should reach the start gate"); + arrived.Wait(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken).ShouldBeTrue("all callers should reach the start gate"); start.Set(); var results = await Task.WhenAll(callers); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshRouterDispatchTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshRouterDispatchTests.cs index 5d4095b74..d5a1fcaaf 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshRouterDispatchTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Communication/MeshRouterDispatchTests.cs @@ -38,7 +38,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase coordinator.Tell(new Commons.Messages.Deploy.DispatchDeployment( deploymentId, new RevisionHash("rev-1"), CorrelationId.NewId())); - var cmd = router.ExpectMsg(PresenceBudget); + var cmd = router.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cmd.Topic.ShouldBe(DeploymentsTopic); cmd.Message.ShouldBeOfType(); } @@ -49,7 +49,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase var subscriber = CreateTestProbe(); DistributedPubSub.Get(Sys).Mediator.Tell( new Subscribe(DeploymentsTopic, subscriber.Ref), subscriber.Ref); - subscriber.ExpectMsg(PresenceBudget); + subscriber.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var coordinator = Sys.ActorOf(ConfigPublishCoordinator.Props(NewInMemoryDbFactory())); @@ -59,7 +59,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase // Presence budget, not 5 s: the coordinator's first ConfigDb (EF InMemory) read sits in front // of the dispatch and is a cold multi-second cost on a loaded CI runner (#606). - subscriber.ExpectMsg(PresenceBudget); + subscriber.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -75,7 +75,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase admin.Tell(new RestartDriver("C1", "driver-1", "alice", Guid.NewGuid())); - var cmd = router.ExpectMsg(PresenceBudget); + var cmd = router.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cmd.Topic.ShouldBe(DriverControlTopic.Name); cmd.Message.ShouldBeOfType(); } @@ -93,7 +93,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase admin.Tell(new ReconnectDriver("C1", "driver-1", "alice", Guid.NewGuid())); - var cmd = router.ExpectMsg(PresenceBudget); + var cmd = router.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cmd.Topic.ShouldBe(DriverControlTopic.Name); cmd.Message.ShouldBeOfType(); } @@ -111,7 +111,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase admin.Tell(new AcknowledgeAlarmCommand("alarm-1", "alice", "c", CorrelationId.NewId())); - var cmd = router.ExpectMsg(PresenceBudget); + var cmd = router.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cmd.Topic.ShouldBe(AlarmCommandsTopic.Name); cmd.Message.ShouldBeOfType(); } @@ -129,7 +129,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase admin.Tell(new ShelveAlarmCommand("alarm-1", "alice", ShelveKind.OneShot, null, "c", CorrelationId.NewId())); - var cmd = router.ExpectMsg(PresenceBudget); + var cmd = router.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cmd.Topic.ShouldBe(AlarmCommandsTopic.Name); cmd.Message.ShouldBeOfType(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerSqlServerHashStabilityTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerSqlServerHashStabilityTests.cs index 67ac26bd6..afcc07037 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerSqlServerHashStabilityTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerSqlServerHashStabilityTests.cs @@ -32,20 +32,20 @@ public sealed class ConfigComposerSqlServerHashStabilityTests // Collation order a-2, B-1, c-3; ordinal order B-1, a-2, c-3. foreach (var id in new[] { "c-3", "B-1", "a-2" }) seed.Scripts.Add(new Script { ScriptId = id, Name = id, SourceCode = "return 1;", SourceHash = id }); - await seed.SaveChangesAsync(); + await seed.SaveChangesAsync(TestContext.Current.CancellationToken); } await using var legacyDb = sql.NewContext(); var legacy = await LegacySnapshotAndFlattenAsync(legacyDb); await using var viaDb = sql.NewContext(); - var current = await ConfigComposer.SnapshotAndFlattenAsync(viaDb); + var current = await ConfigComposer.SnapshotAndFlattenAsync(viaDb, TestContext.Current.CancellationToken); current.Blob.ShouldBe(legacy.Blob); current.RevisionHash.ShouldBe(legacy.RevisionHash); // The deploy path's form (validate + compose from ONE snapshot) must agree too. await using var deployDb = sql.NewContext(); - var viaSnapshot = ConfigComposer.Compose(await DraftSnapshotFactory.FromConfigDbAsync(deployDb)); + var viaSnapshot = ConfigComposer.Compose(await DraftSnapshotFactory.FromConfigDbAsync(deployDb, TestContext.Current.CancellationToken)); viaSnapshot.RevisionHash.ShouldBe(legacy.RevisionHash); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerTests.cs index 674ececd6..6f740dfd9 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigComposerTests.cs @@ -20,10 +20,10 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase var f = NewInMemoryDbFactory(); await using var db1 = f.CreateDbContext(); - var a1 = await ConfigComposer.SnapshotAndFlattenAsync(db1); + var a1 = await ConfigComposer.SnapshotAndFlattenAsync(db1, TestContext.Current.CancellationToken); await using var db2 = f.CreateDbContext(); - var a2 = await ConfigComposer.SnapshotAndFlattenAsync(db2); + var a2 = await ConfigComposer.SnapshotAndFlattenAsync(db2, TestContext.Current.CancellationToken); a1.RevisionHash.ShouldBe(a2.RevisionHash); a1.Blob.ShouldBe(a2.Blob); @@ -40,9 +40,9 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase { db.ServerClusters.Add(NewCluster("cluster-a")); db.ServerClusters.Add(NewCluster("cluster-b")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash; + var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash; // Fresh DB, same rows in reverse insertion order. var f2 = NewInMemoryDbFactory(); @@ -50,9 +50,9 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase { db.ServerClusters.Add(NewCluster("cluster-b")); db.ServerClusters.Add(NewCluster("cluster-a")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var hashBA = (await ConfigComposer.SnapshotAndFlattenAsync(f2.CreateDbContext())).RevisionHash; + var hashBA = (await ConfigComposer.SnapshotAndFlattenAsync(f2.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash; hashAB.ShouldBe(hashBA); } @@ -65,16 +65,16 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase await using (var db = f.CreateDbContext()) { db.ServerClusters.Add(NewCluster("cluster-a")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var hashA = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash; + var hashA = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash; await using (var db = f.CreateDbContext()) { db.ServerClusters.Add(NewCluster("cluster-b")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash; + var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash; hashAB.ShouldNotBe(hashA); } @@ -84,7 +84,7 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase public async Task Hash_is_64_lowercase_hex_chars() { var f = NewInMemoryDbFactory(); - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); artifact.RevisionHash.Length.ShouldBe(64); artifact.RevisionHash.ShouldMatch("^[0-9a-f]{64}$"); } @@ -102,10 +102,10 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase await using (var db = f.CreateDbContext()) { db.ServerClusters.Add(NewCluster("c1")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); artifact.Blob[0].ShouldBe((byte)0x1F); artifact.Blob[1].ShouldBe((byte)0x8B); @@ -118,7 +118,7 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase using var inflated = new MemoryStream(); await using (var gz = new GZipStream(new MemoryStream(artifact.Blob), CompressionMode.Decompress)) { - await gz.CopyToAsync(inflated); + await gz.CopyToAsync(inflated, TestContext.Current.CancellationToken); } var json = inflated.ToArray(); Encoding.UTF8.GetString(json).ShouldStartWith("{"); @@ -155,11 +155,11 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase DeviceId = "dev-1", DriverInstanceId = "drv-1", Name = "dev-1", DeviceConfig = "{\"HostAddress\":\"10.9.9.9:8193\"}", }); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } await using var readDb = f.CreateDbContext(); - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb, TestContext.Current.CancellationToken); var specs = DeploymentArtifact.ParseDriverInstances(artifact.Blob); var spec = specs.ShouldHaveSingleItem(); @@ -208,11 +208,11 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase DriverInstanceId = "drv-2", ClusterId = "c1", Name = "Modbus2", DriverType = "Modbus", DriverConfig = "{}", }); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } await using var readDb = f.CreateDbContext(); - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb, TestContext.Current.CancellationToken); var specs = DeploymentArtifact.ParseDriverInstances(artifact.Blob); var withOverride = specs.Single(s => s.DriverInstanceId == "drv-1"); @@ -241,11 +241,11 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase ApiKeyId = "k1", Name = "scada", SecretHash = "h", Principal = "scadabridge", CreatedUtc = FixedTimestamp, }); db.Scripts.Add(new Script { ScriptId = "s-1", Name = "s", SourceCode = "return 1;", SourceHash = "x" }); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var viaDb = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); - var snapshot = await Configuration.Validation.DraftSnapshotFactory.FromConfigDbAsync(f.CreateDbContext()); + var viaDb = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); + var snapshot = await Configuration.Validation.DraftSnapshotFactory.FromConfigDbAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var viaSnapshot = ConfigComposer.Compose(snapshot); viaSnapshot.RevisionHash.ShouldBe(viaDb.RevisionHash); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorDbSourcedAcksTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorDbSourcedAcksTests.cs index 95d8a4ac3..467630bf6 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorDbSourcedAcksTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorDbSourcedAcksTests.cs @@ -60,7 +60,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct using var db = dbFactory.CreateDbContext(); db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" }); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); @@ -69,7 +69,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -94,7 +94,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" }); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // ...and the enabled node's ack alone seals it. If the disabled row were expected this would // sit at AwaitingApplyAcks until the deadline. @@ -105,7 +105,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -126,13 +126,13 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // A node nobody registered acks. One expected node, one ack — if it were counted, the // coordinator would seal here. actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("ghost-9:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); - ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); using (var db = dbFactory.CreateDbContext()) { @@ -147,7 +147,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -170,7 +170,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); db.NodeDeploymentStates.ShouldBeEmpty(); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -202,7 +202,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" }); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); @@ -211,7 +211,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } private static void SeedNodes( diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorRecoveryTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorRecoveryTests.cs index 8c93fb1ce..bd73313ad 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorRecoveryTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorRecoveryTests.cs @@ -46,7 +46,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes using var db = dbFactory.CreateDbContext(); db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" }); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); @@ -55,7 +55,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -79,7 +79,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes row.Status.ShouldBe(DeploymentStatus.TimedOut); row.FailureReason.ShouldNotBeNull(); row.FailureReason.ShouldContain("lost"); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Positive control: the coordinator is not wedged — a fresh deployment runs end to end. var nextId = SeedDeployment(dbFactory, DeploymentStatus.Dispatching, DateTime.UtcNow); @@ -88,7 +88,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = dbFactory.CreateDbContext(); db.Deployments.Single(d => d.DeploymentId == nextId.Value).Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new ApplyAck(nextId, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); @@ -96,7 +96,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = dbFactory.CreateDbContext(); db.Deployments.Single(d => d.DeploymentId == nextId.Value).Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -116,7 +116,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); db.NodeDeploymentStates.ShouldBeEmpty(); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -139,10 +139,10 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes // Arm the fault AFTER PreStart has run (it would otherwise fault the recovery read itself and // the actor would be stopped, not restarted). Identify is processed after creation, so a // resolved selection proves PreStart completed. - await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget); + await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget, TestContext.Current.CancellationToken); faulting.FailNext(); admin.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var first = ExpectMsg(PresenceBudget); + var first = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); first.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); first.DeploymentId.ShouldNotBeNull(); @@ -153,7 +153,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes using var db = adminFactory.CreateDbContext(); db.NodeDeploymentStates.Count(s => s.DeploymentId == first.DeploymentId.Value.Value).ShouldBe(1); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); coordinator.Tell(new ApplyAck(first.DeploymentId.Value, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); @@ -161,11 +161,11 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = adminFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Positive control: nothing is wedged — the next deploy is Accepted and seals too. admin.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var second = ExpectMsg(PresenceBudget); + var second = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); second.Outcome.ShouldBe(StartDeploymentOutcome.Accepted); coordinator.Tell(new ApplyAck(second.DeploymentId!.Value, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); @@ -173,7 +173,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = adminFactory.CreateDbContext(); db.Deployments.Count(d => d.Status == DeploymentStatus.Sealed).ShouldBe(2); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -189,14 +189,14 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes var dbFactory = NewInMemoryDbFactory(); SeedNodes(dbFactory, "site-a-1:4053"); var coordinator = Sys.ActorOf(ConfigPublishCoordinator.Props(dbFactory, NoTimeout)); - await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget); // PreStart saw nothing + await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget, TestContext.Current.CancellationToken); // PreStart saw nothing var stale = SeedDeployment(dbFactory, DeploymentStatus.Dispatching, DateTime.UtcNow.AddMinutes(-10)); var admin = Sys.ActorOf(AdminOperationsActor.Props( dbFactory, coordinator, Enumerable.Empty(), applyDeadline: NoTimeout)); admin.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var result = ExpectMsg(PresenceBudget); + var result = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); result.Outcome.ShouldBe(StartDeploymentOutcome.Accepted, result.Message); using (var db = dbFactory.CreateDbContext()) @@ -214,7 +214,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes using var db = dbFactory.CreateDbContext(); db.Deployments.Single(d => d.DeploymentId == result.DeploymentId.Value.Value).Status .ShouldBe(DeploymentStatus.Sealed); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Control for the closure: the same row INSIDE the deadline is still in flight and still refuses. @@ -228,7 +228,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes dbFactory, coordinator.Ref, Enumerable.Empty(), applyDeadline: NoTimeout)); admin.Tell(new StartDeployment("joe", CorrelationId.NewId())); - var result = ExpectMsg(PresenceBudget); + var result = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); result.Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight); result.DeploymentId.ShouldBe(pending); @@ -261,7 +261,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = dbFactory.CreateDbContext(); db.Deployments.Single(d => d.DeploymentId == live.Value).Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); using var verify = dbFactory.CreateDbContext(); verify.Deployments.Single(d => d.DeploymentId == done.Value).Status.ShouldBe(terminal); @@ -298,7 +298,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes { using var db = sql.NewContext(); db.Deployments.AsNoTracking().Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: PresenceBudget); // PreStart re-drives the un-dispatched row + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // PreStart re-drives the un-dispatched row coordinator.Tell(new ApplyAck(id, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); AwaitAssert(() => @@ -308,7 +308,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes row.Status.ShouldBe(DeploymentStatus.Sealed); row.SealedAtUtc.ShouldNotBeNull(); row.ArtifactBlob.ShouldBe(blob); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -327,7 +327,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes SeedNodes(inner, "site-a-1:4053"); var interposer = new InterposingDbFactory(inner); var coordinator = Sys.ActorOf(ConfigPublishCoordinator.Props(interposer, NoTimeout)); - await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget); // PreStart ran over an empty table + await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget, TestContext.Current.CancellationToken); // PreStart ran over an empty table // The race proper: a new Dispatching row whose dispatch is interleaved with an AdminOps-style closure. var victim = SeedDeployment(inner, DeploymentStatus.Dispatching, DateTime.UtcNow); @@ -339,7 +339,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes (int)DeploymentStatus.TimedOut, victim.Value); }); coordinator.Tell(new DispatchDeployment(victim, TestRevision, CorrelationId.NewId())); - AwaitAssert(() => interposer.Fired.ShouldBeTrue(), duration: PresenceBudget); + AwaitAssert(() => interposer.Fired.ShouldBeTrue(), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Positive control + ordering: a live row dispatched after it IS flipped, so the victim's dispatch was processed. var live = SeedDeployment(inner, DeploymentStatus.Dispatching, DateTime.UtcNow); @@ -349,7 +349,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes using var db = sql.NewContext(); db.Deployments.AsNoTracking().Single(d => d.DeploymentId == live.Value).Status .ShouldBe(DeploymentStatus.AwaitingApplyAcks); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); using var verify = sql.NewContext(); verify.Deployments.AsNoTracking().Single(d => d.DeploymentId == victim.Value).Status diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTests.cs index fc16f0b55..a691e9579 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTests.cs @@ -30,7 +30,7 @@ public sealed class ConfigPublishCoordinatorTests : ControlPlaneActorTestBase using var db = dbFactory.CreateDbContext(); var status = db.Deployments.Single().Status; status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that stale apply acknowledgments arriving after seal are ignored. @@ -48,14 +48,14 @@ public sealed class ConfigPublishCoordinatorTests : ControlPlaneActorTestBase { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Now send a late ApplyAck for the just-sealed deployment. Should be a no-op — neither // crash the actor nor modify the row. We give it a beat and re-check the status. actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("ghost-node"), ApplyAckOutcome.Applied, null, CorrelationId.NewId())); - ExpectNoMsg(TimeSpan.FromMilliseconds(250)); + ExpectNoMsg(TimeSpan.FromMilliseconds(250), TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); } @@ -106,7 +106,7 @@ public sealed class ConfigPublishCoordinatorTests : ControlPlaneActorTestBase ApplyAckOutcome.Applied, null, CorrelationId.NewId())); // Give it time to process and potentially seal if buggy. - ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); using var db2 = dbFactory.CreateDbContext(); var status = db2.Deployments.Single().Status; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTimeoutTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTimeoutTests.cs index 5639996f4..917be29e5 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTimeoutTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ConfigPublishCoordinatorTimeoutTests.cs @@ -53,7 +53,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.TimedOut); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that stale DeadlineElapsed for other deployments is ignored. @@ -68,7 +68,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest actor.Tell(new ConfigPublishCoordinator.DeadlineElapsed(DeploymentId.NewId())); // The seeded one should remain in its starting state (no transition triggered). - ExpectNoMsg(TimeSpan.FromMilliseconds(250)); + ExpectNoMsg(TimeSpan.FromMilliseconds(250), TestContext.Current.CancellationToken); using var db = dbFactory.CreateDbContext(); var status = db.Deployments.Single().Status; status.ShouldBeOneOf(DeploymentStatus.Dispatching, DeploymentStatus.AwaitingApplyAcks, DeploymentStatus.Sealed); @@ -108,7 +108,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest { using var db = dbFactory.CreateDbContext(); db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -135,7 +135,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest row.FailureReason.ShouldContain("No ack from: site-b-1:4053."); row.FailureReason.ShouldContain("(1/2 landed)"); row.FailureReason.ShouldNotContain("site-a-1"); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// lmxopcua#784 — the timeout reason stays bounded on a large fleet: it names 20 nodes and counts the rest. @@ -159,7 +159,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest reason.ShouldNotContain("node-20-"); reason.ShouldContain("(and 5 more)"); reason.Length.ShouldBeLessThanOrEqualTo(2048); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } private static void SeedNodeRows( diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DataPlaneAuthorityArtifactTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DataPlaneAuthorityArtifactTests.cs index 857590c31..b098f85d8 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DataPlaneAuthorityArtifactTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DataPlaneAuthorityArtifactTests.cs @@ -33,10 +33,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase { db.ServerClusters.Add(NewCluster("cluster-a")); db.ApiKeys.Add(NewKey("abc123", "scadabridge-dcl")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA); var key = parsed.Keys.ShouldHaveSingleItem(); @@ -58,10 +58,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase db.ServerClusters.Add(NewCluster("cluster-a")); db.NodeAcls.Add(NewAcl("acl-1", "cluster-a", "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.Read | NodePermissions.WriteOperate)); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA); var grant = parsed.Grants.ShouldHaveSingleItem(); @@ -82,10 +82,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase var acl = NewAcl("acl-1", "cluster-a", "p", NodeAclScopeKind.Equipment, NodePermissions.Read); acl.ScopeId = "EQ-1"; db.NodeAcls.Add(acl); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA); var grant = parsed.Grants.ShouldHaveSingleItem(); @@ -110,10 +110,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase var key = NewKey("abc123", "p"); key.ExpiresUtc = expires; db.ApiKeys.Add(key); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA); var parsedKey = parsed.Keys.ShouldHaveSingleItem(); @@ -139,10 +139,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase db.ApiKeys.Add(NewKey("key-b", "principal-b")); db.NodeAcls.Add(NewAcl("acl-a", "cluster-a", "principal-a", NodeAclScopeKind.Cluster, NodePermissions.Read)); db.NodeAcls.Add(NewAcl("acl-b", "cluster-b", "principal-b", NodeAclScopeKind.Cluster, NodePermissions.Read)); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA); parsed.ClusterId.ShouldBe("cluster-a"); @@ -162,10 +162,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase db.ServerClusters.Add(NewCluster("cluster-b")); db.ClusterNodes.Add(NewNode(NodeB, "cluster-b")); db.NodeAcls.Add(NewAcl("acl-b", "cluster-b", "principal-b", NodeAclScopeKind.Cluster, NodePermissions.Read)); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, "node-nowhere:4840"); parsed.Grants.ShouldBeEmpty(); @@ -184,10 +184,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase var key = NewKey("abc123", "p"); key.Enabled = false; db.ApiKeys.Add(key); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext()); + var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken); var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA); parsed.Keys.ShouldHaveSingleItem().Enabled.ShouldBeFalse(); @@ -211,16 +211,16 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase await using (var db = f.CreateDbContext()) { db.ServerClusters.Add(NewCluster("cluster-a")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var before = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash; + var before = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash; await using (var db = f.CreateDbContext()) { db.ApiKeys.Add(NewKey("abc123", "p")); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } - var after = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash; + var after = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash; after.ShouldNotBe(before); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DeploymentRetentionTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DeploymentRetentionTests.cs index bb607aafe..3cd0cd13a 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DeploymentRetentionTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/DeploymentRetentionTests.cs @@ -29,20 +29,20 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase int pruned; await using (var db = f.CreateDbContext()) - pruned = await DeploymentRetentionActor.SweepAsync(db, options, SweepNow); + pruned = await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken); pruned.ShouldBe(7); await using var verify = f.CreateDbContext(); foreach (var id in ids.Take(5)) - (await verify.Deployments.SingleAsync(d => d.DeploymentId == id)).ArtifactBlob.Length.ShouldBeGreaterThan(0, "newest 5 keep their blob"); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == id, TestContext.Current.CancellationToken)).ArtifactBlob.Length.ShouldBeGreaterThan(0, "newest 5 keep their blob"); foreach (var id in ids.Skip(5)) - (await verify.Deployments.SingleAsync(d => d.DeploymentId == id)).ArtifactBlob.ShouldBeEmpty("older rows are emptied"); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == id, TestContext.Current.CancellationToken)).ArtifactBlob.ShouldBeEmpty("older rows are emptied"); // Rows survive: the audit record is intact. - (await verify.Deployments.CountAsync()).ShouldBe(12); + (await verify.Deployments.CountAsync(TestContext.Current.CancellationToken)).ShouldBe(12); // Idempotent: a second sweep finds nothing. await using var again = f.CreateDbContext(); - (await DeploymentRetentionActor.SweepAsync(again, options, SweepNow)).ShouldBe(0); + (await DeploymentRetentionActor.SweepAsync(again, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(0); } [Fact] @@ -76,19 +76,19 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase DeploymentId = inFlight, RevisionHash = new string('f', 64), Status = DeploymentStatus.AwaitingApplyAcks, CreatedBy = "test", CreatedAtUtc = SweepNow.AddDays(-400), ArtifactBlob = new byte[] { 1, 2, 3 }, }); - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } var options = new DeploymentRetentionOptions { KeepSealed = 1, ReferencedDays = 30 }; await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(2); // ids[1], ids[2] + (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(2); // ids[1], ids[2] await using var verify = f.CreateDbContext(); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0])).ArtifactBlob.ShouldNotBeEmpty("newest"); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[1])).ArtifactBlob.ShouldBeEmpty(); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[2])).ArtifactBlob.ShouldBeEmpty("applied 90 days ago"); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[3])).ArtifactBlob.ShouldNotBeEmpty("applied yesterday"); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == inFlight)).ArtifactBlob.ShouldNotBeEmpty("in flight"); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty("newest"); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[1], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldBeEmpty(); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[2], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldBeEmpty("applied 90 days ago"); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[3], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty("applied yesterday"); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == inFlight, TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty("in flight"); } /// @@ -121,17 +121,17 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase CreatedBy = "test", CreatedAtUtc = SweepNow.AddHours(-i), ArtifactBlob = new byte[] { 9 }, }); } - await db.SaveChangesAsync(); + await db.SaveChangesAsync(TestContext.Current.CancellationToken); } var options = new DeploymentRetentionOptions { KeepSealed = 50, ReferencedDays = 30, MaxRowsPerSweep = 200 }; await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(60); + (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(60); await using var verify = f.CreateDbContext(); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == sealedId)).ArtifactBlob + (await verify.Deployments.SingleAsync(d => d.DeploymentId == sealedId, TestContext.Current.CancellationToken)).ArtifactBlob .ShouldNotBeEmpty("the newest Sealed artifact is never pruned"); - (await verify.Deployments.CountAsync(d => timedOut.Contains(d.DeploymentId) && d.ArtifactBlob.Length == 0)) + (await verify.Deployments.CountAsync(d => timedOut.Contains(d.DeploymentId) && d.ArtifactBlob.Length == 0, TestContext.Current.CancellationToken)) .ShouldBe(60); } @@ -143,11 +143,11 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase var ids = Seed(f, sealedCount: 3, mixInTimedOut: false); await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, new DeploymentRetentionOptions { KeepSealed = 0 }, SweepNow)) + (await DeploymentRetentionActor.SweepAsync(db, new DeploymentRetentionOptions { KeepSealed = 0 }, SweepNow, TestContext.Current.CancellationToken)) .ShouldBe(2); await using var verify = f.CreateDbContext(); - (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0])).ArtifactBlob.ShouldNotBeEmpty(); + (await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty(); } [Fact] @@ -158,13 +158,13 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase var options = new DeploymentRetentionOptions { KeepSealed = 2, MaxRowsPerSweep = 3 }; await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(3); + (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(3); await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(3); + (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(3); await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(2); + (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(2); await using (var db = f.CreateDbContext()) - (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(0); + (await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(0); } /// The actor runs the same rule off its thread and reports back. @@ -180,7 +180,7 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase actor.Tell(DeploymentRetentionActor.Sweep.Instance); - ExpectMsg(PresenceBudget).Pruned.ShouldBe(2); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Pruned.ShouldBe(2); using var verify = f.CreateDbContext(); verify.Deployments.Count(d => d.ArtifactBlob.Length == 0).ShouldBe(2); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/GossipedRoleCaseInsensitivityTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/GossipedRoleCaseInsensitivityTests.cs index 3cc6c3c92..47eee29c6 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/GossipedRoleCaseInsensitivityTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/GossipedRoleCaseInsensitivityTests.cs @@ -149,7 +149,7 @@ public sealed class GossipedRoleCaseInsensitivityTests }), $"redundancy-{Guid.NewGuid():N}"); - var snapshot = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(15)); + var snapshot = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(15), TestContext.Current.CancellationToken); var node = snapshot.Nodes.ShouldHaveSingleItem(); node.Role.ShouldBe( diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Harness/ControlPlaneTestHarness.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Harness/ControlPlaneTestHarness.cs index d98828a01..c073e5803 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Harness/ControlPlaneTestHarness.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/Harness/ControlPlaneTestHarness.cs @@ -1,6 +1,6 @@ using Akka.Cluster; using Akka.TestKit; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; @@ -8,7 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness; /// /// Akka TestKit fixture for ControlPlane actor tests. Provides: -/// - A test ActorSystem (xunit2 TestKit) wired with PubSub/Cluster extensions. +/// - A test ActorSystem (xunit.v3 TestKit) wired with PubSub/Cluster extensions. /// - A fresh in-memory per harness instance. /// - An the actors can hold. /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ManualFailoverServiceTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ManualFailoverServiceTests.cs index c2f088529..e016bd323 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ManualFailoverServiceTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ManualFailoverServiceTests.cs @@ -58,7 +58,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime } """); - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { // Order matters: the seed forms the cluster and is therefore the oldest member. _oldest = ActorSystem.Create("manual-failover", NodeConfig(OldestPort, "manual-failover", OldestPort)); @@ -69,7 +69,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime await WaitForUpAsync(_youngest, expectedMembers: 2); } - public async Task DisposeAsync() + public async ValueTask DisposeAsync() { if (_youngest is not null) await _youngest.Terminate(); if (_oldest is not null) await _oldest.Terminate(); @@ -128,7 +128,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime // The leaving node runs CoordinatedShutdown and terminates — that is what makes the process // exit and be restarted as the youngest member. - await _oldest!.WhenTerminated.WaitAsync(TimeSpan.FromSeconds(30)); + await _oldest!.WhenTerminated.WaitAsync(TimeSpan.FromSeconds(30), TestContext.Current.CancellationToken); // ... and the survivor is now what RedundancyStateActor names Primary. var snapshot = await CaptureSnapshotAsync(_youngest!); @@ -192,7 +192,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime snapshotPrimary.ShouldBe(elected.ToString(), "the UI panel must name the same node the button acts on"); // The dry run really was dry. - await Task.Delay(500); + await Task.Delay(500, TestContext.Current.CancellationToken); Akka.Cluster.Cluster.Get(_oldest!).State.Members .Count(m => m.Status == MemberStatus.Up) .ShouldBe(2, "a dry run must not remove anyone"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyPrimaryElectionTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyPrimaryElectionTests.cs index 73be49736..30b172f4a 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyPrimaryElectionTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyPrimaryElectionTests.cs @@ -74,7 +74,7 @@ public sealed class RedundancyPrimaryElectionTests : IAsyncLifetime } """); - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { // Order matters: the seed forms the cluster and is therefore the oldest member. _oldest = ActorSystem.Create("redundancy-election", NodeConfig(OldestPort)); @@ -85,7 +85,7 @@ public sealed class RedundancyPrimaryElectionTests : IAsyncLifetime await WaitForUpAsync(_youngest, expectedMembers: 2); } - public async Task DisposeAsync() + public async ValueTask DisposeAsync() { if (_youngest is not null) await _youngest.Terminate(); if (_oldest is not null) await _oldest.Terminate(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyStateActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyStateActorTests.cs index f48b7a5d9..e8c70f602 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyStateActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/RedundancyStateActorTests.cs @@ -23,7 +23,7 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase Sys.ActorOf(RedundancyStateActor.Props(broadcast: msg => probe.Ref.Tell(msg)), "redundancy-actor"); - var msg = probe.ExpectMsg(TimeSpan.FromSeconds(3)); + var msg = probe.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); msg.Nodes.ShouldNotBeNull(); msg.CorrelationId.Value.ShouldNotBe(Guid.Empty); } @@ -37,10 +37,10 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase "redundancy-debounce"); // First publish should arrive within the debounce window. - probe.ExpectMsg(TimeSpan.FromSeconds(3)); + probe.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // After debounce settles, no more events are fired by a quiescent cluster. - probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// @@ -60,10 +60,10 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase "redundancy-heartbeat"); // First publish: the change-driven self-join snapshot. - probe.ExpectMsg(TimeSpan.FromSeconds(2)); + probe.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); // Second publish: the periodic heartbeat re-publish, with NO new cluster event sent. - probe.ExpectMsg(TimeSpan.FromSeconds(2)); + probe.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); } /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.csproj b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.csproj index 1e6c1d352..2b051af8e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.csproj +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.csproj @@ -7,11 +7,10 @@ - - + - + all diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/DriverResilienceStatusPublisherServiceTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/DriverResilienceStatusPublisherServiceTests.cs index f9e195ace..94834d955 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/DriverResilienceStatusPublisherServiceTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/DriverResilienceStatusPublisherServiceTests.cs @@ -68,7 +68,7 @@ public sealed class DriverResilienceStatusPublisherServiceTests public async Task ExecuteAsync_emits_Resilience_item_per_published_snapshot() { // Plain self-joined single-node cluster (mirrors SecretsReplicationRegistrationTests — the - // xunit.v3 project cannot use Akka.TestKit.Xunit2). public-hostname is load-bearing for the bind. + // xunit.v3 project cannot use Akka.TestKit.Xunit). public-hostname is load-bearing for the bind. var hocon = ConfigurationFactory.ParseString(""" akka { loglevel = WARNING diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/SecretsReplicationRegistrationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/SecretsReplicationRegistrationTests.cs index fcbe25376..5111174fe 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/SecretsReplicationRegistrationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/SecretsReplicationRegistrationTests.cs @@ -128,7 +128,7 @@ public sealed class SecretsReplicationRegistrationTests // the hook, and prove the replication actor exists. On 0.2.0 this fails because the actor // is never spawned (inert replicator); on 0.2.1 it deadlocks in the resolve (scadaproj#1). // - // Akka.TestKit.Xunit2 is xunit-v2-only and this project is on xunit.v3 (CS0433), so this + // Akka.TestKit.Xunit is xunit-v2-only and this project is on xunit.v3 (CS0433), so this // uses a plain self-joined single-node cluster — which is also all the actor needs: its // constructor gets the DistributedPubSub mediator, no peers required. ActorSystem system = ActorSystem.Create( diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Engines/VirtualTagToVirtualTagFlowTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Engines/VirtualTagToVirtualTagFlowTests.cs index 71f8748be..70674ab35 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Engines/VirtualTagToVirtualTagFlowTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Engines/VirtualTagToVirtualTagFlowTests.cs @@ -1,5 +1,5 @@ using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Microsoft.Extensions.Logging.Abstractions; using Serilog; using Shouldly; @@ -62,7 +62,7 @@ public sealed class VirtualTagToVirtualTagFlowTests : TestKit mux.Tell(Driver(5)); publish.FishForMessage( m => m.NodeId == AKey && Equals(m.Value, 10d), TimeSpan.FromMilliseconds(500)); - }, Budget, TimeSpan.FromMilliseconds(50)); + }, Budget, TimeSpan.FromMilliseconds(50), TestContext.Current.CancellationToken); NextFor(publish, BKey, m => Equals(m.Value, 11d) && m.StatusCode == OpcUaStatus.Good); // A's input changes: B re-evaluates against A's NEW value (ctx.GetTag returns A's current value). diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Grpc/NodeCommandGrpcServiceTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Grpc/NodeCommandGrpcServiceTests.cs index a6a15050f..301128686 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Grpc/NodeCommandGrpcServiceTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Grpc/NodeCommandGrpcServiceTests.cs @@ -1,5 +1,5 @@ using Akka.Hosting; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Grpc.Core; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; @@ -54,16 +54,16 @@ public sealed class NodeCommandGrpcServiceTests : TestKit var alarm = new AlarmCommand("alarm-7", "Acknowledge", "carol", "looked at it", null); (await service.Execute(MeshTransportDtoMapper.ToEnvelope(dispatch), Ctx())).ShouldNotBeNull(); - probe.ExpectMsg().ShouldBe(dispatch); + probe.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch); await service.Execute(MeshTransportDtoMapper.ToEnvelope(restart), Ctx()); - probe.ExpectMsg().ShouldBe(restart); + probe.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(restart); await service.Execute(MeshTransportDtoMapper.ToEnvelope(reconnect), Ctx()); - probe.ExpectMsg().ShouldBe(reconnect); + probe.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(reconnect); await service.Execute(MeshTransportDtoMapper.ToEnvelope(alarm, AlarmCorrelation), Ctx()); - probe.ExpectMsg().ShouldBe(alarm); + probe.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(alarm); } [Fact] @@ -94,7 +94,7 @@ public sealed class NodeCommandGrpcServiceTests : TestKit new CommandEnvelope { CorrelationId = Guid.NewGuid().ToString("N") }, Ctx())); ex.StatusCode.ShouldBe(StatusCode.InvalidArgument); - probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } [Theory] @@ -109,7 +109,7 @@ public sealed class NodeCommandGrpcServiceTests : TestKit var ex = await Should.ThrowAsync(() => service.Execute(envelope, Ctx())); ex.StatusCode.ShouldBe(StatusCode.InvalidArgument); - probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } public static TheoryData UndecodableEnvelopes() => diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs index bca0ea885..39e978c6d 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Health/ActiveTierRegistrationTests.cs @@ -1,6 +1,6 @@ using Akka.Actor; using Akka.Configuration; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Options; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ApiKeyAuthenticationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ApiKeyAuthenticationTests.cs index 54e709f0e..b5905c34c 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ApiKeyAuthenticationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ApiKeyAuthenticationTests.cs @@ -106,7 +106,7 @@ public sealed class ApiKeyAuthenticationTests { var (presented, store) = SetUp(NodePermissions.WriteOperate, NodeAclScopeKind.Cluster); - var result = await Auth(store).AuthenticateUserNameAsync("apikey", presented, default); + var result = await Auth(store).AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken); result.Success.ShouldBeTrue(); result.DisplayName.ShouldBe(Principal); @@ -117,7 +117,7 @@ public sealed class ApiKeyAuthenticationTests public async Task The_wrong_username_is_denied() { var (presented, store) = SetUp(); - var result = await Auth(store).AuthenticateUserNameAsync("alice", presented, default); + var result = await Auth(store).AuthenticateUserNameAsync("alice", presented, TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); } @@ -127,10 +127,10 @@ public sealed class ApiKeyAuthenticationTests var (presented, store) = SetUp(); var auth = Auth(store); - var wrongSecret = await auth.AuthenticateUserNameAsync("apikey", $"otua_{store.KeyId}_wrong", default); - var noSuchKey = await auth.AuthenticateUserNameAsync("apikey", "otua_ffffffff_whatever", default); - var malformed = await auth.AuthenticateUserNameAsync("apikey", "not-a-key", default); - var empty = await auth.AuthenticateUserNameAsync("apikey", "", default); + var wrongSecret = await auth.AuthenticateUserNameAsync("apikey", $"otua_{store.KeyId}_wrong", TestContext.Current.CancellationToken); + var noSuchKey = await auth.AuthenticateUserNameAsync("apikey", "otua_ffffffff_whatever", TestContext.Current.CancellationToken); + var malformed = await auth.AuthenticateUserNameAsync("apikey", "not-a-key", TestContext.Current.CancellationToken); + var empty = await auth.AuthenticateUserNameAsync("apikey", "", TestContext.Current.CancellationToken); foreach (var r in new[] { wrongSecret, noSuchKey, malformed, empty }) { @@ -141,7 +141,7 @@ public sealed class ApiKeyAuthenticationTests } // ...and the valid one still works, so the above is not passing because everything is denied. - (await auth.AuthenticateUserNameAsync("apikey", presented, default)).Success.ShouldBeTrue(); + (await auth.AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken)).Success.ShouldBeTrue(); } [Fact] @@ -150,7 +150,7 @@ public sealed class ApiKeyAuthenticationTests // The interface contract forbids throwing — an exception escapes into the SDK session-activation // path and surfaces as BadInternalError instead of a clean rejection. var store = new ThrowingApiKeyStore(); - var result = await Auth(store).AuthenticateUserNameAsync("apikey", "otua_aabbccdd_secret", default); + var result = await Auth(store).AuthenticateUserNameAsync("apikey", "otua_aabbccdd_secret", TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Error.ShouldBe(ApiKeyOpcUaUserAuthenticator.DenyMessage); @@ -165,7 +165,7 @@ public sealed class ApiKeyAuthenticationTests Options.Create(new ApiKeyOptions { Username = "apikey", Pepper = "" }), NullLogger.Instance); - (await auth.AuthenticateUserNameAsync("apikey", presented, default)).Success.ShouldBeFalse(); + (await auth.AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken)).Success.ShouldBeFalse(); } // ---------- anonymous as a normal principal (D3) ---------- @@ -183,7 +183,7 @@ public sealed class ApiKeyAuthenticationTests PermissionFlags = NodePermissions.WriteOperate, }); - var result = await Auth(store).AuthenticateAnonymousAsync(default); + var result = await Auth(store).AuthenticateAnonymousAsync(TestContext.Current.CancellationToken); result.Success.ShouldBeTrue(); result.DisplayName.ShouldBe(OpcUaPrincipals.Anonymous); @@ -197,7 +197,7 @@ public sealed class ApiKeyAuthenticationTests // Today's behaviour preserved: it connects, and every role-gated operation denies. var (_, store) = SetUp(); - var result = await Auth(store).AuthenticateAnonymousAsync(default); + var result = await Auth(store).AuthenticateAnonymousAsync(TestContext.Current.CancellationToken); result.Success.ShouldBeTrue(); result.Roles.ShouldBeEmpty(); @@ -208,7 +208,7 @@ public sealed class ApiKeyAuthenticationTests { // Deliberately NOT admit-with-no-roles. That is the pre-#520 behaviour, and defaulting to it // exactly when we cannot read what anonymous may do stops being harmless once reads are gated. - var result = await Auth(new ThrowingApiKeyStore()).AuthenticateAnonymousAsync(default); + var result = await Auth(new ThrowingApiKeyStore()).AuthenticateAnonymousAsync(TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); } @@ -227,7 +227,7 @@ public sealed class ApiKeyAuthenticationTests CreatedUtc = DateTime.UtcNow, }; - var result = await Auth(new FakeApiKeyStore(key, [])).AuthenticateUserNameAsync("apikey", presented, default); + var result = await Auth(new FakeApiKeyStore(key, [])).AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken); // Otherwise a credentialled client silently inherits whatever anonymous was granted. result.Success.ShouldBeFalse(); @@ -241,7 +241,7 @@ public sealed class ApiKeyAuthenticationTests // Interface-typed on purpose: a default interface implementation is only reachable through the // interface, which is how OpcUaApplicationHost holds it. IOpcUaUserAuthenticator sut = NullOpcUaUserAuthenticator.Instance; - var result = await sut.AuthenticateAnonymousAsync(default); + var result = await sut.AuthenticateAnonymousAsync(TestContext.Current.CancellationToken); result.Success.ShouldBeTrue(); result.Roles.ShouldBeEmpty(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ArtifactApiKeyStoreTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ArtifactApiKeyStoreTests.cs index 9cffddb05..275abe34b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ArtifactApiKeyStoreTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ArtifactApiKeyStoreTests.cs @@ -26,7 +26,7 @@ public sealed class ArtifactApiKeyStoreTests { var sut = new ArtifactApiKeyStore(new DataPlaneAuthorityState()); - (await sut.FindAsync("abc", Now, default)).ShouldBeNull(); + (await sut.FindAsync("abc", Now, TestContext.Current.CancellationToken)).ShouldBeNull(); } [Fact] @@ -37,7 +37,7 @@ public sealed class ArtifactApiKeyStoreTests [Key("abc", "scadabridge-dcl")], [Acl("acl-1", "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate)], NoScopePaths); - var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, default); + var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, TestContext.Current.CancellationToken); found.ShouldNotBeNull(); found!.Key.Principal.ShouldBe("scadabridge-dcl"); @@ -67,7 +67,7 @@ public sealed class ArtifactApiKeyStoreTests Acl("acl-equipment", "p", NodeAclScopeKind.Equipment, NodePermissions.WriteOperate), ], NoScopePaths); - var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, default); + var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, TestContext.Current.CancellationToken); found!.Grants.Select(g => g.NodeAclId).ShouldBe(["acl-cluster", "acl-equipment"], ignoreOrder: true); } @@ -91,7 +91,7 @@ public sealed class ArtifactApiKeyStoreTests [Acl("acl-equipment", "p", NodeAclScopeKind.Equipment, NodePermissions.WriteOperate)], NoScopePaths); - var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, default); + var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, TestContext.Current.CancellationToken); found!.Grants.ShouldHaveSingleItem(); ApiKeyInterimRoleMapper.Map(found.Grants, "p").ShouldBeEmpty(); @@ -106,13 +106,13 @@ public sealed class ArtifactApiKeyStoreTests var sut = new ArtifactApiKeyStore(state); state.Apply("cluster-a", "rev-1", [Key("abc", "p")], [], NoScopePaths); - (await sut.FindAsync("abc", Now, default)).ShouldNotBeNull(); + (await sut.FindAsync("abc", Now, TestContext.Current.CancellationToken)).ShouldNotBeNull(); var revoked = Key("abc", "p"); revoked.Enabled = false; state.Apply("cluster-a", "rev-2", [revoked], [], NoScopePaths); - (await sut.FindAsync("abc", Now, default)).ShouldBeNull(); + (await sut.FindAsync("abc", Now, TestContext.Current.CancellationToken)).ShouldBeNull(); } [Fact] @@ -124,7 +124,7 @@ public sealed class ArtifactApiKeyStoreTests [Acl("acl-anon", OpcUaPrincipals.Anonymous, NodeAclScopeKind.Cluster, NodePermissions.Read)], NoScopePaths); - var grants = await new ArtifactApiKeyStore(state).FindGrantsAsync(OpcUaPrincipals.Anonymous, default); + var grants = await new ArtifactApiKeyStore(state).FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken); grants.ShouldHaveSingleItem().PermissionFlags.ShouldBe(NodePermissions.Read); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreKnownIdTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreKnownIdTests.cs index 8d08df5ee..6eb109028 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreKnownIdTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreKnownIdTests.cs @@ -25,13 +25,13 @@ public sealed class BoundedApiKeyStoreKnownIdTests var clock = new FakeClock(T0); var sut = Bounded(inner, clock, cacheSeconds: 5); - (await sut.FindAsync("k-guess-1", T0.UtcDateTime, default)).ShouldBeNull(); - (await sut.FindAsync("k-guess-2", T0.UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess-1", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); + (await sut.FindAsync("k-guess-2", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(0, "unknown ids never reach the store"); inner.ListCalls.ShouldBe(1, "one set load serves every unknown id inside the TTL"); // Positive control: the real id reaches the store and is found. - (await sut.FindAsync("k-real", T0.UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync("k-real", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); inner.FindCalls.ShouldBe(1); } @@ -42,15 +42,15 @@ public sealed class BoundedApiKeyStoreKnownIdTests var clock = new FakeClock(T0); var sut = Bounded(inner, clock, cacheSeconds: 5); - (await sut.FindAsync("k-new", T0.UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-new", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.Mint("k-new"); // Still inside the TTL: the stale set refuses it (the documented window). clock.Advance(TimeSpan.FromSeconds(3)); - (await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(0); clock.Advance(TimeSpan.FromSeconds(3)); - (await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); inner.ListCalls.ShouldBe(2); } @@ -62,20 +62,20 @@ public sealed class BoundedApiKeyStoreKnownIdTests var sut = Bounded(inner, clock, cacheSeconds: 5); // No set yet and the load fails: the per-id path runs (old behaviour), nothing is wedged. - (await sut.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(1); sut.KnownKeyIds.ShouldBeNull(); inner.ListThrows = false; clock.Advance(TimeSpan.FromSeconds(6)); - (await sut.FindAsync("k-guess-2", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess-2", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); sut.KnownKeyIds.ShouldNotBeNull(); inner.FindCalls.ShouldBe(1, "once the set loaded, the guess was refused in memory"); // A later failed refresh keeps the previous set in force. inner.ListThrows = true; clock.Advance(TimeSpan.FromSeconds(6)); - (await sut.FindAsync("k-guess-3", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess-3", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(1); } @@ -88,7 +88,7 @@ public sealed class BoundedApiKeyStoreKnownIdTests var sut = Bounded(inner, clock, cacheSeconds: 0, missBurst: 3, refillPerSecond: 1); for (var i = 0; i < 3; i++) - (await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(3); // Budget exhausted: refused without a store read, as a throw the authenticator denies on. @@ -97,14 +97,14 @@ public sealed class BoundedApiKeyStoreKnownIdTests // Refill: one second restores one token. clock.Advance(TimeSpan.FromSeconds(1)); - (await sut.FindAsync("k-guess-10", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess-10", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(4); // A hit is not a miss: it reaches the store even with the bucket empty? No — it needs a token to // be attempted, but the token is refunded, so the next attempt still has it. Prove the refund. clock.Advance(TimeSpan.FromSeconds(1)); - (await sut.FindAsync("k-real", clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); - (await sut.FindAsync("k-guess-11", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-real", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); + (await sut.FindAsync("k-guess-11", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(6); } @@ -114,8 +114,8 @@ public sealed class BoundedApiKeyStoreKnownIdTests var inner = new ListingStore("k-real"); var sut = Bounded(inner, new FakeClock(T0), cacheSeconds: 0, missBurst: 0); - (await sut.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull(); - (await sut.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); + (await sut.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(2); inner.ListCalls.ShouldBe(0); } @@ -139,13 +139,13 @@ public sealed class BoundedApiKeyStoreKnownIdTests var unlisting = new ListingStore("k-real") { SupportsListing = false }; var first = new BoundedApiKeyStore(unlisting, options, NullLogger.Instance, clock, guard); - (await first.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull(); + (await first.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); unlisting.FindCalls.ShouldBe(1, "no set: the per-id path ran"); guard.KnownIdSet.ShouldBeTrue("the shared options object is never written"); var listing = new ListingStore("k-real"); var second = new BoundedApiKeyStore(listing, options, NullLogger.Instance, clock, guard); - (await second.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull(); + (await second.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); listing.FindCalls.ShouldBe(0, "the second decorator still refuses unknown ids in memory"); listing.ListCalls.ShouldBe(1); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStorePerEndpointTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStorePerEndpointTests.cs index 2a43369d3..d506a0c68 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStorePerEndpointTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStorePerEndpointTests.cs @@ -31,7 +31,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests var sut = Bounded(inner, new FakeClock(T0), missBurst: 100, perEndpointBurst: 3); for (var i = 0; i < 3; i++) - (await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, default)).ShouldBeNull(); + (await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(3); var refused = await Should.ThrowAsync( @@ -40,7 +40,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests inner.FindCalls.ShouldBe(3, "the exhausted client is refused without a store read"); // Positive control: another client still reaches the store. - (await sut.FindAsync("k-guess-q", T0.UtcDateTime, Quiet, default)).ShouldBeNull(); + (await sut.FindAsync("k-guess-q", T0.UtcDateTime, Quiet, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(4); } @@ -51,8 +51,8 @@ public sealed class BoundedApiKeyStorePerEndpointTests var inner = new UnlistingStore("k-real"); var sut = Bounded(inner, new FakeClock(T0), missBurst: 2, perEndpointBurst: 10); - (await sut.FindAsync("k-a", T0.UtcDateTime, Noisy, default)).ShouldBeNull(); - (await sut.FindAsync("k-b", T0.UtcDateTime, remoteAddress: null, default)).ShouldBeNull(); + (await sut.FindAsync("k-a", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull(); + (await sut.FindAsync("k-b", T0.UtcDateTime, remoteAddress: null, TestContext.Current.CancellationToken)).ShouldBeNull(); var refused = await Should.ThrowAsync( () => sut.FindAsync("k-c", T0.UtcDateTime, Quiet, default)); @@ -70,14 +70,14 @@ public sealed class BoundedApiKeyStorePerEndpointTests var sut = Bounded(inner, clock, missBurst: 100, perEndpointBurst: 2, perEndpointRefill: 1); for (var i = 0; i < 5; i++) - (await sut.FindAsync("k-real", T0.UtcDateTime, Noisy, default)).ShouldNotBeNull(); + (await sut.FindAsync("k-real", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldNotBeNull(); - (await sut.FindAsync("k-g1", T0.UtcDateTime, Noisy, default)).ShouldBeNull(); - (await sut.FindAsync("k-g2", T0.UtcDateTime, Noisy, default)).ShouldBeNull(); + (await sut.FindAsync("k-g1", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull(); + (await sut.FindAsync("k-g2", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull(); await Should.ThrowAsync(() => sut.FindAsync("k-g3", T0.UtcDateTime, Noisy, default)); clock.Advance(TimeSpan.FromSeconds(1)); - (await sut.FindAsync("k-g4", clock.GetUtcNow().UtcDateTime, Noisy, default)).ShouldBeNull(); + (await sut.FindAsync("k-g4", clock.GetUtcNow().UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(8); } @@ -89,7 +89,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests var sut = Bounded(inner, new FakeClock(T0), missBurst: 100, perEndpointBurst: 0); for (var i = 0; i < 20; i++) - (await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, default)).ShouldBeNull(); + (await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.FindCalls.ShouldBe(20); } @@ -107,7 +107,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests NullLogger.Instance); var result = await authenticator.AuthenticateUserNameAsync( - "apikey", "otua_k-guess_secret", new IPEndPoint(Noisy, 50123), default); + "apikey", "otua_k-guess_secret", new IPEndPoint(Noisy, 50123), TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); store.LastAddress.ShouldBe(Noisy); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreTests.cs index 652019056..704e5a291 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/BoundedApiKeyStoreTests.cs @@ -54,7 +54,7 @@ public sealed class BoundedApiKeyStoreTests var auth = Auth(Bounded(inner, timeoutMs: 150, cacheSeconds: 0)); var result = await auth.AuthenticateUserNameAsync("apikey", presented, CancellationToken.None) - .WaitAsync(TimeSpan.FromSeconds(5)); + .WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Error.ShouldBe(ApiKeyOpcUaUserAuthenticator.DenyMessage); @@ -95,10 +95,10 @@ public sealed class BoundedApiKeyStoreTests var pending = auth.AuthenticateUserNameAsync("apikey", presented, CancellationToken.None); // Let the deadline pass, THEN hand the inner store its (valid) answer. - await Task.Delay(400); + await Task.Delay(400, TestContext.Current.CancellationToken); gate.SetResult(); - var result = await pending.WaitAsync(TimeSpan.FromSeconds(5)); + var result = await pending.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Error.ShouldBe(ApiKeyOpcUaUserAuthenticator.DenyMessage); } @@ -167,10 +167,10 @@ public sealed class BoundedApiKeyStoreTests // The abandoned inner task now completes, with a perfectly valid key, into nobody's hands. inner.Release(); - await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2)); + await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Well inside the 60 s TTL: a cached late answer would make this a hit. - (await sut.FindAsync(inner.KeyId, DateTime.UtcNow, default).WaitAsync(TimeSpan.FromSeconds(2))) + (await sut.FindAsync(inner.KeyId, DateTime.UtcNow, TestContext.Current.CancellationToken).WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken)) .ShouldNotBeNull("the store answers immediately once released"); inner.Calls.ShouldBe(2, "the abandoned lookup must not have populated the cache"); } @@ -197,12 +197,12 @@ public sealed class BoundedApiKeyStoreTests .WaitAsync(TimeSpan.FromSeconds(2))); inner.ReleaseWithFault(new InvalidOperationException("SqlClient gave up")); - await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2)); + await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // The continuation is a separate task from the one just awaited; give it a moment to run. var deadline = Stopwatch.StartNew(); while (log.Lines.Count == 0 && deadline.Elapsed < TimeSpan.FromSeconds(2)) - await Task.Delay(10); + await Task.Delay(10, TestContext.Current.CancellationToken); log.Lines.ShouldContain(line => line.Contains("abandoned", StringComparison.OrdinalIgnoreCase)); } @@ -222,11 +222,11 @@ public sealed class BoundedApiKeyStoreTests var pending = auth.AuthenticateUserNameAsync("apikey", presented, CancellationToken.None); // Far longer than any deadline this class configures; an active bound would have denied by now. - await Task.Delay(300); + await Task.Delay(300, TestContext.Current.CancellationToken); pending.IsCompleted.ShouldBeFalse("a disabled deadline must not cancel a slow lookup"); gate.SetResult(); - (await pending.WaitAsync(TimeSpan.FromSeconds(5))).Success.ShouldBeTrue(); + (await pending.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken)).Success.ShouldBeTrue(); } /// The anonymous activation path gets the same deadline, and the same denial. @@ -242,7 +242,7 @@ public sealed class BoundedApiKeyStoreTests var auth = Auth(Bounded(inner, timeoutMs: 150, cacheSeconds: 0)); var result = await auth.AuthenticateAnonymousAsync(CancellationToken.None) - .WaitAsync(TimeSpan.FromSeconds(5)); + .WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); } @@ -257,9 +257,9 @@ public sealed class BoundedApiKeyStoreTests var clock = new FakeClock(new DateTimeOffset(2026, 8, 20, 12, 0, 0, TimeSpan.Zero)); var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); clock.Advance(TimeSpan.FromSeconds(2)); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); inner.Calls.ShouldBe(1); } @@ -280,18 +280,18 @@ public sealed class BoundedApiKeyStoreTests var clock = new FakeClock(new DateTimeOffset(2026, 8, 20, 12, 0, 0, TimeSpan.Zero)); var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)) + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)) .ShouldNotBeNull("non-vacuity: the key really was usable before the revocation"); inner.Revoked = true; // Still inside the TTL — the documented, accepted staleness window. clock.Advance(TimeSpan.FromSeconds(3)); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); // Past it — the store is re-read and the revocation lands. clock.Advance(TimeSpan.FromSeconds(3)); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.Calls.ShouldBe(2); } @@ -304,7 +304,7 @@ public sealed class BoundedApiKeyStoreTests var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 0, clock); for (var i = 0; i < 3; i++) - await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default); + await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken); inner.Calls.ShouldBe(3); } @@ -328,7 +328,7 @@ public sealed class BoundedApiKeyStoreTests () => sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)); // Same instant — a cached fault would still be "fresh" here, so the clock cannot rescue this. - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); inner.Calls.ShouldBe(2); } @@ -340,12 +340,12 @@ public sealed class BoundedApiKeyStoreTests var clock = new FakeClock(new DateTimeOffset(2026, 8, 20, 12, 0, 0, TimeSpan.Zero)); var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock); - await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, default); - await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, default); + await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken); + await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken); inner.Calls.ShouldBe(1); clock.Advance(TimeSpan.FromSeconds(6)); - await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, default); + await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken); inner.Calls.ShouldBe(2); } @@ -369,15 +369,15 @@ public sealed class BoundedApiKeyStoreTests guard: new ApiKeyStoreGuardOptions { MissBurst = 0 }); // Seed the real key, then flood past the ceiling with ids that do not exist. - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); for (var i = 0; i < BoundedApiKeyStore.MaxCacheEntries + 1; i++) - await sut.FindAsync($"ghost-{i}", clock.GetUtcNow().UtcDateTime, default); + await sut.FindAsync($"ghost-{i}", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken); var callsAfterFlood = inner.Calls; // Still correct after the clear — and the real key was evicted, so it is re-read rather than // served from a cache the flood was supposed to have emptied. - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull(); inner.Calls.ShouldBe(callsAfterFlood + 1); } @@ -406,7 +406,7 @@ public sealed class BoundedApiKeyStoreTests var clock = new FakeClock(start); var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 60, clock); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)) + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)) .ShouldNotBeNull("non-vacuity: the key was usable and IS now cached"); inner.Revoked = true; @@ -414,7 +414,7 @@ public sealed class BoundedApiKeyStoreTests // Past ExpiresUtc (+2 s) but far inside the 60 s TTL — the entry is still "fresh" to the cache. clock.Advance(TimeSpan.FromSeconds(5)); - (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull(); + (await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull(); inner.Calls.ShouldBe(2, "the expired entry must be re-read, not served"); } @@ -449,10 +449,10 @@ public sealed class BoundedApiKeyStoreTests using var meter = new MeterRecorder(); var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock); - await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default); + await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken); meter.WithOutcome("cache_hit").ShouldBe(0, "the first call is a miss"); - await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default); + await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken); meter.WithOutcome("cache_hit").ShouldBe(1); } @@ -468,7 +468,7 @@ public sealed class BoundedApiKeyStoreTests using var meter = new MeterRecorder(); var sut = Bounded(inner, timeoutMs: 2000, cacheSeconds: 0); - await sut.FindAsync(inner.KeyId, DateTime.UtcNow, default); + await sut.FindAsync(inner.KeyId, DateTime.UtcNow, TestContext.Current.CancellationToken); meter.Total.ShouldBe(0); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ConfigDbApiKeyStoreTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ConfigDbApiKeyStoreTests.cs index 506284eee..f6f511645 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ConfigDbApiKeyStoreTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/ConfigDbApiKeyStoreTests.cs @@ -52,7 +52,7 @@ public sealed class ConfigDbApiKeyStoreTests keys: [Key("abc", "scadabridge-dcl")], acls: [Acl("acl-other", OtherCluster, "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate)]); - var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, default); + var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, TestContext.Current.CancellationToken); // The KEY still resolves — keys are fleet-wide credentials (ApiKey carries no ClusterId); it is // the grants that are cluster-bound. Asserting a null lookup here would pass for the wrong reason. @@ -76,7 +76,7 @@ public sealed class ConfigDbApiKeyStoreTests Acl("acl-other", OtherCluster, "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate), ]); - var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, default); + var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, TestContext.Current.CancellationToken); found!.Grants.ShouldHaveSingleItem().NodeAclId.ShouldBe("acl-mine"); } @@ -98,7 +98,7 @@ public sealed class ConfigDbApiKeyStoreTests Acl("acl-b", OtherCluster, "p", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate), ]); - var found = await new ConfigDbApiKeyStore(factory, clusterId: null).FindAsync("abc", Now, default); + var found = await new ConfigDbApiKeyStore(factory, clusterId: null).FindAsync("abc", Now, TestContext.Current.CancellationToken); found!.Grants.Select(g => g.NodeAclId).ShouldBe(["acl-a", "acl-b"], ignoreOrder: true); } @@ -119,7 +119,7 @@ public sealed class ConfigDbApiKeyStoreTests keys: [Key("abc", "p")], acls: [Acl("acl-mine", "SITE-A", "p", NodeAclScopeKind.Cluster, NodePermissions.Read)]); - var found = await new ConfigDbApiKeyStore(factory, "site-a").FindAsync("abc", Now, default); + var found = await new ConfigDbApiKeyStore(factory, "site-a").FindAsync("abc", Now, TestContext.Current.CancellationToken); found!.Grants.ShouldHaveSingleItem().NodeAclId.ShouldBe("acl-mine"); } @@ -141,7 +141,7 @@ public sealed class ConfigDbApiKeyStoreTests ]); var grants = await new ConfigDbApiKeyStore(factory, ThisCluster) - .FindGrantsAsync(OpcUaPrincipals.Anonymous, default); + .FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken); grants.ShouldHaveSingleItem().NodeAclId.ShouldBe("acl-anon-mine"); } @@ -168,7 +168,7 @@ public sealed class ConfigDbApiKeyStoreTests Acl("acl-equipment", ThisCluster, "p", NodeAclScopeKind.Equipment, NodePermissions.WriteOperate), ]); - var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, default); + var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, TestContext.Current.CancellationToken); found!.Grants.Select(g => g.NodeAclId).ShouldBe(["acl-cluster", "acl-equipment"], ignoreOrder: true); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/OtOpcUaServerHostedServiceStartFailureTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/OtOpcUaServerHostedServiceStartFailureTests.cs index 341595d75..b705f82e2 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/OtOpcUaServerHostedServiceStartFailureTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/OpcUa/OtOpcUaServerHostedServiceStartFailureTests.cs @@ -54,7 +54,7 @@ public sealed class OtOpcUaServerHostedServiceStartFailureTests state.Status.ShouldBe(OpcUaEndpointStatus.Failed); state.FailureReason.ShouldNotBeNullOrWhiteSpace(); - var probe = await new OpcUaEndpointHealthCheck(state).CheckHealthAsync(new HealthCheckContext()); + var probe = await new OpcUaEndpointHealthCheck(state).CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken); probe.Status.ShouldBe(HealthStatus.Unhealthy); probe.Description.ShouldNotBeNull(); probe.Description.ShouldContain("failed to start"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Startup/FailStartExitTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Startup/FailStartExitTests.cs index e1ea58a3a..060af9894 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Startup/FailStartExitTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/Startup/FailStartExitTests.cs @@ -29,8 +29,8 @@ public sealed class FailStartExitTests psi.Environment["Cluster__SeedNodes__0"] = "not-a-valid-address"; using var proc = Process.Start(psi)!; - var stdout = proc.StandardOutput.ReadToEndAsync(); - var stderr = proc.StandardError.ReadToEndAsync(); + var stdout = proc.StandardOutput.ReadToEndAsync(TestContext.Current.CancellationToken); + var stderr = proc.StandardError.ReadToEndAsync(TestContext.Current.CancellationToken); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); try { await proc.WaitForExitAsync(cts.Token); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/ZB.MOM.WW.OtOpcUa.Host.Tests.csproj b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/ZB.MOM.WW.OtOpcUa.Host.Tests.csproj index 4d254b399..6b68aa551 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/ZB.MOM.WW.OtOpcUa.Host.Tests.csproj +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.Tests/ZB.MOM.WW.OtOpcUa.Host.Tests.csproj @@ -7,11 +7,11 @@ - + - + diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs index aaa06b3c6..793e65252 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs @@ -1,5 +1,5 @@ using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy; @@ -56,7 +56,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase // EventStream subscription. Without this the relay below races the subscription. var warmup = NewDispatch(); driverHost.Tell(warmup); - ackRouter.ExpectMsg(TimeSpan.FromSeconds(15)).DeploymentId.ShouldBe(warmup.DeploymentId); + ackRouter.ExpectMsg(TimeSpan.FromSeconds(15), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(warmup.DeploymentId); // Now the real assertion — relayed exactly as central would send it. The comm actor only // publishes node-locally, so the host must be subscribed for this to arrive at all. @@ -66,7 +66,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase // The deployment id does not exist, so the host answers Failed. That is the stronger // assertion, not a weaker one: an ack at all proves the command was received and handled, // and only PreStart's EventStream subscription can have delivered it. - ackRouter.ExpectMsg(TimeSpan.FromSeconds(15)).DeploymentId.ShouldBe(relayed.DeploymentId); + ackRouter.ExpectMsg(TimeSpan.FromSeconds(15), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(relayed.DeploymentId); } [Fact] @@ -83,7 +83,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase var dispatch = NewDispatch(); driverHost.Tell(dispatch); - ackRouter.ExpectMsg(TimeSpan.FromSeconds(15)).DeploymentId.ShouldBe(dispatch.DeploymentId); + ackRouter.ExpectMsg(TimeSpan.FromSeconds(15), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(dispatch.DeploymentId); } } @@ -150,12 +150,12 @@ akka { // Tell INSIDE the assertion window alongside the relay, which made the test vacuous — the // direct Tell alone produced the log the assertion was waiting for. EventFilter.Debug(contains: "ignoring AlarmCommand") - .ExpectOne(TimeSpan.FromSeconds(15), () => host.Tell(cmd)); + .ExpectOne(TimeSpan.FromSeconds(15), () => host.Tell(cmd), TestContext.Current.CancellationToken); // The real assertion: relayed ONLY through the comm actor, which publishes node-locally. // The host owns no alarms, so it logs and stops — still a positive delivery signal, because // that line is only reached if the message arrived. EventFilter.Debug(contains: "ignoring AlarmCommand") - .ExpectOne(TimeSpan.FromSeconds(15), () => comm.Tell(cmd)); + .ExpectOne(TimeSpan.FromSeconds(15), () => comm.Tell(cmd), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/GrpcApplyAckSenderTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/GrpcApplyAckSenderTests.cs index a8820cf11..d9c25aa09 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/GrpcApplyAckSenderTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/GrpcApplyAckSenderTests.cs @@ -73,7 +73,7 @@ public sealed class GrpcApplyAckSenderTests return (CentralCommandService.CentralCommandServiceClient)new RecordingClient(calls); }; - await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack()); + await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken); consulted.ShouldBe(["http://central-1:4055"]); calls.Count.ShouldBe(1); @@ -90,7 +90,7 @@ public sealed class GrpcApplyAckSenderTests "disk full", new CorrelationId(Guid.NewGuid())); - await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(ack); + await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(ack, TestContext.Current.CancellationToken); var sent = calls.Single().Request; sent.DeploymentId.ShouldBe(ack.DeploymentId.ToString()); @@ -109,7 +109,7 @@ public sealed class GrpcApplyAckSenderTests var calls = new List<(ApplyAckMessage Request, CallOptions Options)>(); var before = DateTime.UtcNow; - await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(Ack()); + await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(Ack(), TestContext.Current.CancellationToken); var options = calls.Single().Options; options.Headers.ShouldNotBeNull(); @@ -138,7 +138,7 @@ public sealed class GrpcApplyAckSenderTests ? (CentralCommandService.CentralCommandServiceClient)new SlowThrowingClient(calls, TimeSpan.FromMilliseconds(50)) : new RecordingClient(calls); - await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack()); + await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken); calls.Count.ShouldBe(2); calls[1].Options.Deadline!.Value.ShouldBeGreaterThan(calls[0].Options.Deadline!.Value); @@ -158,7 +158,7 @@ public sealed class GrpcApplyAckSenderTests : new RecordingClient(calls); }; - await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack()); + await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken); consulted.ShouldBe(["http://central-1:4055", "http://central-2:4055"]); calls.Count.ShouldBe(1); @@ -185,7 +185,7 @@ public sealed class GrpcApplyAckSenderTests }; var ack = Ack(); - await Sender(["http://central-1:4055", "http://central-2:4055"], factory, log).SendAsync(ack); + await Sender(["http://central-1:4055", "http://central-2:4055"], factory, log).SendAsync(ack, TestContext.Current.CancellationToken); attempts.ShouldBe(2); var warning = log.Entries.Single(e => e.Level == LogLevel.Warning); @@ -213,7 +213,7 @@ public sealed class GrpcApplyAckSenderTests : new RecordingClient(calls); }; - await Sender(["http://c1:4055", "http://c2:4055", "http://c3:4055"], factory).SendAsync(Ack()); + await Sender(["http://c1:4055", "http://c2:4055", "http://c3:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken); // c3 is never dialled: the pass stops at the first success, and it does not go back to c1. consulted.ShouldBe(["http://c1:4055", "http://c2:4055"]); @@ -230,7 +230,7 @@ public sealed class GrpcApplyAckSenderTests var factory = (string _) => (CentralCommandService.CentralCommandServiceClient) new ThrowingClient(new RpcException(new Status(StatusCode.Unavailable, "down"))); - await Sender(["http://central-1:4055"], factory, log).SendAsync(Ack()); + await Sender(["http://central-1:4055"], factory, log).SendAsync(Ack(), TestContext.Current.CancellationToken); log.Entries.ShouldNotBeEmpty(); log.Entries.Any(e => e.Message.Contains(Psk, StringComparison.Ordinal)).ShouldBeFalse(); @@ -251,7 +251,7 @@ public sealed class GrpcApplyAckSenderTests return (CentralCommandService.CentralCommandServiceClient)new RecordingClient([]); }; - await Sender([], factory, log).SendAsync(Ack()); + await Sender([], factory, log).SendAsync(Ack(), TestContext.Current.CancellationToken); dialled.ShouldBeFalse(); log.Entries.Single(e => e.Level == LogLevel.Warning).Message @@ -269,7 +269,7 @@ public sealed class GrpcApplyAckSenderTests var calls = new List<(ApplyAckMessage Request, CallOptions Options)>(); await Sender(["http://central-1:4055"], _ => new RecordingClient(calls), log, roles: ["driver"]) - .SendAsync(Ack()); + .SendAsync(Ack(), TestContext.Current.CancellationToken); calls.Single().Options.Headers!.Any(h => h.Key.Equals("x-otopcua-cluster", StringComparison.Ordinal)).ShouldBeFalse(); @@ -311,7 +311,7 @@ public sealed class GrpcApplyAckSenderTests Options.Create(new AkkaClusterOptions { Roles = ["driver", "cluster-SITE-A"] }), log); - await sender.SendAsync(Ack()); + await sender.SendAsync(Ack(), TestContext.Current.CancellationToken); // The post must have failed (nothing is listening) — which is what put a channel in the cache. log.Entries.Any(e => e.Level == LogLevel.Warning).ShouldBeTrue(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs index e1bb1f9da..78f08d9c3 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs @@ -28,7 +28,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase var dispatch = NewDispatch(); actor.Tell(dispatch); - subscriber.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(dispatch); + subscriber.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch); } [Fact] @@ -43,8 +43,8 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase actor.Tell(NewDispatch()); - subscriber.ExpectMsg(TimeSpan.FromSeconds(5)); - subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + subscriber.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); + subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } [Fact] @@ -57,7 +57,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase var msg = new RestartDriver("C1", "driver-1", "alice", Guid.NewGuid()); actor.Tell(msg); - subscriber.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(msg); + subscriber.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(msg); } [Fact] @@ -70,7 +70,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase var msg = new ReconnectDriver("C1", "driver-1", "alice", Guid.NewGuid()); actor.Tell(msg); - subscriber.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(msg); + subscriber.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(msg); } [Fact] @@ -86,7 +86,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase var msg = new AlarmCommand("alarm-1", "Acknowledge", "alice", "comment", null); actor.Tell(msg); - subscriber.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(msg); + subscriber.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(msg); } [Fact] @@ -104,7 +104,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase new CorrelationId(Guid.NewGuid())); EventFilter.Warning(contains: "dropping ApplyAck").ExpectOne( - TimeSpan.FromSeconds(5), () => actor.Tell(ack)); + TimeSpan.FromSeconds(5), () => actor.Tell(ack), TestContext.Current.CancellationToken); } // ---- Mode=Grpc (the cross-mesh ack leg) ----------------------------------------------------- @@ -129,9 +129,9 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase var ack = NewAck(); actor.Tell(ack); - AwaitAssert(() => sender.Sent.Count.ShouldBe(1), TimeSpan.FromSeconds(5)); + AwaitAssert(() => sender.Sent.Count.ShouldBe(1), TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); sender.Sent[0].ShouldBe(ack); - ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); sender.Sent.Count.ShouldBe(1); } @@ -148,7 +148,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase var dispatch = NewDispatch(); actor.Tell(dispatch); - subscriber.ExpectMsg(TimeSpan.FromSeconds(5)).ShouldBe(dispatch); + subscriber.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch); sender.Sent.ShouldBeEmpty(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Deployment/LocalDbDeploymentArtifactCacheTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Deployment/LocalDbDeploymentArtifactCacheTests.cs index 4fc391ab8..b5b3ff365 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Deployment/LocalDbDeploymentArtifactCacheTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Deployment/LocalDbDeploymentArtifactCacheTests.cs @@ -119,9 +119,9 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable var artifact = RandomArtifact(300 * 1024); var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken); - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.DeploymentId.ShouldBe("dep-1"); @@ -135,15 +135,15 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable { var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(64)); - await cache.StoreAsync(ClusterA, "dep-2", "rev-2", RandomArtifact(64)); - await cache.StoreAsync(ClusterA, "dep-3", "rev-3", RandomArtifact(64)); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(64), TestContext.Current.CancellationToken); + await cache.StoreAsync(ClusterA, "dep-2", "rev-2", RandomArtifact(64), TestContext.Current.CancellationToken); + await cache.StoreAsync(ClusterA, "dep-3", "rev-3", RandomArtifact(64), TestContext.Current.CancellationToken); // Bounded retention is what stops a node that redeploys daily from filling its disk with // address spaces nobody will ever roll back to. (await DistinctDeploymentIdsAsync()).ShouldBe(["dep-2", "dep-3"]); - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.DeploymentId.ShouldBe("dep-3"); } @@ -163,20 +163,20 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable var cache = CreateCache(); var newest = RandomArtifact(1024); - await cache.StoreAsync(ClusterA, "dep-old-1", "rev-1", RandomArtifact(1024)); - await cache.StoreAsync(ClusterA, "dep-old-2", "rev-2", RandomArtifact(1024)); + await cache.StoreAsync(ClusterA, "dep-old-1", "rev-1", RandomArtifact(1024), TestContext.Current.CancellationToken); + await cache.StoreAsync(ClusterA, "dep-old-2", "rev-2", RandomArtifact(1024), TestContext.Current.CancellationToken); await _db.ExecuteAsync( "UPDATE deployment_artifacts SET cached_at_utc = @Future", - new { Future = "2099-01-01T00:00:00.0000000Z" }); + new { Future = "2099-01-01T00:00:00.0000000Z" }, TestContext.Current.CancellationToken); - await cache.StoreAsync(ClusterA, "dep-new", "rev-3", newest); + await cache.StoreAsync(ClusterA, "dep-new", "rev-3", newest, TestContext.Current.CancellationToken); // The pointer names dep-new, so dep-new's chunks must still be there... (await CountChunksAsync("dep-new")).ShouldBeGreaterThan(0); // ...and it must actually read back, which is the behaviour that matters. - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.DeploymentId.ShouldBe("dep-new"); cached.Artifact.ShouldBe(newest); @@ -186,31 +186,31 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable public async Task GetCurrent_ReturnsNullWhenAChunkIsCorrupt() { var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024)); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024), TestContext.Current.CancellationToken); // Valid base64 of the wrong bytes: this passes decoding and the chunk-count check, so only // the SHA-256 comparison can catch it. That is the check being pinned. await _db.ExecuteAsync( "UPDATE deployment_artifacts SET chunk_base64 = @Chunk WHERE deployment_id = @DeploymentId AND chunk_index = 1", - new { Chunk = Convert.ToBase64String(RandomArtifact(128 * 1024)), DeploymentId = "dep-1" }); + new { Chunk = Convert.ToBase64String(RandomArtifact(128 * 1024)), DeploymentId = "dep-1" }, TestContext.Current.CancellationToken); - (await cache.GetCurrentAsync(ClusterA)).ShouldBeNull(); + (await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull(); } [Fact] public async Task GetCurrent_ReturnsNullWhenAChunkIsMissing() { var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024)); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024), TestContext.Current.CancellationToken); // A partially replicated artifact is the realistic version of this: the pointer row arrives // before the last chunk does. Reassembling what is present would yield a plausible-looking // but silently truncated address space. await _db.ExecuteAsync( "DELETE FROM deployment_artifacts WHERE deployment_id = @DeploymentId AND chunk_index = 2", - new { DeploymentId = "dep-1" }); + new { DeploymentId = "dep-1" }, TestContext.Current.CancellationToken); - (await cache.GetCurrentAsync(ClusterA)).ShouldBeNull(); + (await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull(); } [Fact] @@ -218,7 +218,7 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable { var cache = CreateCache(); - (await cache.GetCurrentAsync(ClusterA)).ShouldBeNull(); + (await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull(); } [Fact] @@ -226,17 +226,17 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable { var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024)); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024), TestContext.Current.CancellationToken); // The re-store is smaller, so a delete-before-insert that did not happen would leave the // tail chunks of the first artifact behind — orphans that also break the chunk-count check. var second = RandomArtifact(200 * 1024); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1b", second); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1b", second, TestContext.Current.CancellationToken); (await CountChunksAsync("dep-1")).ShouldBe(2); (await CountChunksAsync()).ShouldBe(2); - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.RevisionHash.ShouldBe("rev-1b"); cached.Artifact.ShouldBe(second); @@ -253,18 +253,18 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable var cache = CreateCache(); var artifact = RandomArtifact(300 * 1024); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken); var oplogDepth = await OplogDepthAsync(); var rowVersions = await RowVersionsAsync(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken); (await OplogDepthAsync()).ShouldBe(oplogDepth); (await RowVersionsAsync()).ShouldBe(rowVersions); // Skipping must not cost readability: the artifact still reads back intact. - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.DeploymentId.ShouldBe("dep-1"); cached.Artifact.ShouldBe(artifact); @@ -280,16 +280,16 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable var cache = CreateCache(); var artifact = RandomArtifact(300 * 1024); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken); await _db.ExecuteAsync( "DELETE FROM deployment_artifacts WHERE deployment_id = @DeploymentId AND chunk_index = 2", - new { DeploymentId = "dep-1" }); - (await cache.GetCurrentAsync(ClusterA)).ShouldBeNull(); + new { DeploymentId = "dep-1" }, TestContext.Current.CancellationToken); + (await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken); (await CountChunksAsync("dep-1")).ShouldBe(3); - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.Artifact.ShouldBe(artifact); } @@ -303,12 +303,12 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable // space with every id-shaped signal saying it was correct. var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(1024)); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(1024), TestContext.Current.CancellationToken); var rebuilt = RandomArtifact(1024); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", rebuilt); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", rebuilt, TestContext.Current.CancellationToken); - var cached = await cache.GetCurrentAsync(ClusterA); + var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.Artifact.ShouldBe(rebuilt); } @@ -319,9 +319,9 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable var artifact = RandomArtifact(300 * 1024); var cache = CreateCache(); - await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact); + await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken); - var cached = await cache.GetCurrentUnkeyedAsync(); + var cached = await cache.GetCurrentUnkeyedAsync(TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.DeploymentId.ShouldBe("dep-1"); @@ -335,10 +335,10 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable var logger = new CapturingLogger(); var cache = CreateCache(logger); - await cache.StoreAsync(ClusterA, "dep-a", "rev-a", RandomArtifact(1024)); - await cache.StoreAsync(ClusterB, "dep-b", "rev-b", newest); + await cache.StoreAsync(ClusterA, "dep-a", "rev-a", RandomArtifact(1024), TestContext.Current.CancellationToken); + await cache.StoreAsync(ClusterB, "dep-b", "rev-b", newest, TestContext.Current.CancellationToken); - var cached = await cache.GetCurrentUnkeyedAsync(); + var cached = await cache.GetCurrentUnkeyedAsync(TestContext.Current.CancellationToken); cached.ShouldNotBeNull(); cached.DeploymentId.ShouldBe("dep-b"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/AbCipAlmaNativeAlarmEndToEndTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/AbCipAlmaNativeAlarmEndToEndTests.cs index 0b827959a..fc8f8f0e4 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/AbCipAlmaNativeAlarmEndToEndTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/AbCipAlmaNativeAlarmEndToEndTests.cs @@ -100,7 +100,7 @@ public sealed class AbCipAlmaNativeAlarmEndToEndTests : RuntimeActorTestBase drop.State.Message.ShouldContain("H alarm"); drop.State.Message.ShouldNotContain("HH"); drop.State.Severity.ShouldBe((ushort)600); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // The PLC acknowledges every declared limit — ONE "Acknowledged" row. foreach (var a in new[] { "HHAcked", "HAcked", "LAcked", "LLAcked" }) plc.Set(a, true); @@ -116,7 +116,7 @@ public sealed class AbCipAlmaNativeAlarmEndToEndTests : RuntimeActorTestBase plc.Set("HHInAlarm", false); var back = NextUpdate(publish); back.State.Acknowledged.ShouldBeFalse("the unacknowledged HH excursion keeps the condition unacknowledged"); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: the clear MUST write its row (the absence checks above would pass on a dead host). plc.Set("HInAlarm", false); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/ActorNodeWriteGatewayTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/ActorNodeWriteGatewayTests.cs index c168cf88a..7370bdf3e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/ActorNodeWriteGatewayTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/ActorNodeWriteGatewayTests.cs @@ -27,7 +27,7 @@ public sealed class ActorNodeWriteGatewayTests : RuntimeActorTestBase var writeTask = gateway.WriteAsync("eq-1/speed", 123.0, AddressSpaceRealm.Uns, CancellationToken.None); - var routed = probe.ExpectMsg(TimeSpan.FromSeconds(5)); + var routed = probe.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); routed.NodeId.ShouldBe("eq-1/speed"); routed.Value.ShouldBe(123.0); probe.Reply(new DriverHostActor.NodeWriteResult(true, null)); @@ -46,7 +46,7 @@ public sealed class ActorNodeWriteGatewayTests : RuntimeActorTestBase var writeTask = gateway.WriteAsync("eq-1/speed", 123.0, AddressSpaceRealm.Uns, CancellationToken.None); - probe.ExpectMsg(TimeSpan.FromSeconds(5)); + probe.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); probe.Reply(new DriverHostActor.NodeWriteResult(false, "not primary")); var outcome = await writeTask; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/CalculationDependencyFlowTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/CalculationDependencyFlowTests.cs index d342023c8..9fd63a003 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/CalculationDependencyFlowTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/CalculationDependencyFlowTests.cs @@ -82,7 +82,7 @@ public sealed class CalculationDependencyFlowTests : RuntimeActorTestBase published.ShouldContain(e => e.FullReference == "calc/engine/out" && Equals(e.Snapshot.Value, 42.0)); // Leg 2 — calc-of-calc: A's 42 re-entered the mux → B computes 43 on calc/engine/b. published.ShouldContain(e => e.FullReference == "calc/engine/b" && Equals(e.Snapshot.Value, 43.0)); - }, PresenceBudget, TimeSpan.FromMilliseconds(100)); + }, PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); } /// @@ -128,7 +128,7 @@ public sealed class CalculationDependencyFlowTests : RuntimeActorTestBase mux.Tell(ValuePublishes.Single("src-drv", "src/a", 2.0, OpcUaStatus.Good, Ts)); published.ShouldContain(e => e.FullReference == "calc/keep" && Equals(e.Snapshot.Value, 20.0)); published.ShouldContain(e => e.FullReference == "calc/remove" && Equals(e.Snapshot.Value, 7.0)); - }, PresenceBudget, TimeSpan.FromMilliseconds(100)); + }, PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); // ---- IN-PLACE RE-BIND (driver ReinitializeAsync) ---- // New authored set: "keep" script EDITED to src/a*1000; "add" is a NEW tag reading a NEW dep src/b; @@ -157,11 +157,11 @@ public sealed class CalculationDependencyFlowTests : RuntimeActorTestBase published.ShouldContain(e => e.FullReference == "calc/add" && Equals(e.Snapshot.Value, 150.0)); // (c) edited script produces the NEW result: 3 * 1000 = 3000 (NOT the pre-edit 3 * 10 = 30). published.ShouldContain(e => e.FullReference == "calc/keep" && Equals(e.Snapshot.Value, 3000.0)); - }, PresenceBudget, TimeSpan.FromMilliseconds(100)); + }, PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); // (b) the removed tag never recomputes on the post-redeploy src/a=3 (it would be 3 + 5 = 8) and the // edited "keep" never serves the stale *10 result (3 * 10 = 30). Give any errant compute a moment to land. - await Task.Delay(200); + await Task.Delay(200, TestContext.Current.CancellationToken); published.ShouldNotContain(e => e.FullReference == "calc/remove" && Equals(e.Snapshot.Value, 8.0)); published.ShouldNotContain(e => e.FullReference == "calc/keep" && Equals(e.Snapshot.Value, 30.0)); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorApplyingStashTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorApplyingStashTests.cs index 4ca52a950..4cc820fa7 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorApplyingStashTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorApplyingStashTests.cs @@ -67,7 +67,7 @@ public sealed class DriverHostActorApplyingStashTests() { // A is in flight: the fetch has started and its task will not complete until released. actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())); - AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var logs = SubscribeToLogs("deferring"); @@ -86,8 +86,8 @@ public sealed class DriverHostActorApplyingStashTests() // Nothing was lost: B re-enters exactly once, after A's apply transitions back to Steady. fetcher.Release(); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); fetcher.Calls.ShouldBe(2); } finally @@ -137,7 +137,7 @@ public sealed class DriverHostActorApplyingStashTests() try { actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())); - AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var logs = SubscribeToLogs("deferring", "already deferred"); @@ -152,9 +152,9 @@ public sealed class DriverHostActorApplyingStashTests() // No second stash, therefore no second re-delivery: exactly one Applied ack for B. fetcher.Release(); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); fetcher.Calls.ShouldBe(2); } finally @@ -194,24 +194,24 @@ public sealed class DriverHostActorApplyingStashTests() try { actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())); - AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new DispatchDeployment(deploymentB, RevB, CorrelationId.NewId())); // A completes; B unstashes and starts its own (still gated) fetch. fetcher.Release(1); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).DeploymentId.ShouldBe(deploymentA); - AwaitAssert(() => fetcher.Calls.ShouldBe(2), duration: PresenceBudget); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(deploymentA); + AwaitAssert(() => fetcher.Calls.ShouldBe(2), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // C arrives mid-B — a fresh stash cycle, on a set that was cleared when B was released. actor.Tell(new DispatchDeployment(deploymentC, RevC, CorrelationId.NewId())); fetcher.Release(2); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).DeploymentId.ShouldBe(deploymentB); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).DeploymentId.ShouldBe(deploymentC); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(deploymentB); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(deploymentC); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)) + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken) .CurrentRevision.ShouldBe(RevC); } finally @@ -251,7 +251,7 @@ public sealed class DriverHostActorApplyingStashTests() try { EventFilter.Warning(contains: deploymentA.ToString()).ExpectOne(TimeSpan.FromSeconds(10), () => - actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId()))); + actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())), TestContext.Current.CancellationToken); } finally { @@ -285,7 +285,7 @@ public sealed class DriverHostActorApplyingStashTests() var logs = SubscribeToLogs("appears WEDGED"); actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); // Well past the (200 ms) delay — a live timer would have fired several times over. logs.Drain(SpinWindow); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs index 94302e10b..f90459b90 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs @@ -42,9 +42,9 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase deploymentArtifactCache: cache)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); - AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3)); + AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); var store = cache.Stores[0]; store.DeploymentId.ShouldBe(deploymentId.ToString()); @@ -69,9 +69,9 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase deploymentArtifactCache: cache)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); - AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3)); + AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); cache.Stores[0].ClusterId.ShouldBe("SITE-A"); } @@ -89,9 +89,9 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase deploymentArtifactCache: cache)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); - AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3)); + AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); cache.Stores[0].ClusterId.ShouldBe("__single"); } @@ -132,7 +132,7 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase // Since #486 the apply FAILS rather than reporting a success it did not achieve: empty bytes are // what an unreadable artifact looks like, not a legitimate no-op deployment. (A configuration that // genuinely declares nothing still serialises to a JSON document with empty arrays.) - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Failed); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed); // The point of this test is unchanged, and now holds for two independent reasons: nothing is cached. Thread.Sleep(500); @@ -156,11 +156,11 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); // Exactly one ACK, and it says Applied. A second Failed ACK arriving here would mean the // cache failure had corrupted the deployment protocol. - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } [Fact] @@ -177,7 +177,7 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); } /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs index 1a2c6c9f5..2abb7cf84 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorAsyncCacheWriteTests.cs @@ -56,21 +56,21 @@ public sealed class DriverHostActorAsyncCacheWriteTests() try { actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); // The store was entered and has NOT completed — the gate is still closed. - AwaitAssert(() => cache.StoresEntered.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => cache.StoresEntered.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cache.StoresCompleted.ShouldBe(0); // THE assertion: with the store still in flight the host is still processing its mailbox. // A blocking `.GetAwaiter().GetResult()` parks the actor thread here and this times out. actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Completion is observed rather than fire-and-forgotten: the store pipes back to Self and // the handler logs it, so an operator can tell a cached apply from an uncached one. EventFilter.Info(contains: "cached applied deployment") - .ExpectOne(TimeSpan.FromSeconds(5), () => cache.Release()); + .ExpectOne(TimeSpan.FromSeconds(5), () => cache.Release(), TestContext.Current.CancellationToken); } finally { @@ -107,9 +107,9 @@ public sealed class DriverHostActorAsyncCacheWriteTests() { actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - }); + }, TestContext.Current.CancellationToken); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// Seeds a sealed, parseable deployment and returns its id. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBatchFanOutTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBatchFanOutTests.cs index 51e3ee874..a17c51e0f 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBatchFanOutTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBatchFanOutTests.cs @@ -57,7 +57,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/temp", 7.5, OpcUaStatus.BadNoCommunication, TsB), })); - var batch = publish.ExpectMsg(PresenceBudget); + var batch = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Count.ShouldBe(3); var byId = batch.Items.ToDictionary(w => w.NodeId); @@ -85,7 +85,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase byId["Plant/Modbus/dev1/temp"].Realm.ShouldBe(AddressSpaceRealm.Raw); // ONE message, not one per value. - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// An item whose ref is bound to no NodeId is dropped; the rest of the burst still lands in the @@ -106,7 +106,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 42.0, OpcUaStatus.Good, TsA), })); - var batch = publish.ExpectMsg(PresenceBudget); + var batch = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Count.ShouldBe(1); batch.Items[0].NodeId.ShouldBe("Plant/Modbus/dev1/speed"); batch.Items[0].Value.ShouldBe(42.0); @@ -116,7 +116,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase { new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 43.0, OpcUaStatus.Good, TsB), })); - publish.ExpectMsg(PresenceBudget) + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items[0].Value.ShouldBe(43.0); } @@ -142,11 +142,11 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/temp", 7.5, OpcUaStatus.Good, TsB), })); - var fwd = mux.ExpectMsg(PresenceBudget); + var fwd = mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); fwd.DriverInstanceId.ShouldBe("drv-1"); fwd.Items.Select(i => i.FullReference).ShouldBe( new[] { "Plant/Modbus/dev1/speed", "Plant/Modbus/dev1/temp" }); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// A burst in which NOTHING resolves sends no publish message at all — an empty batch would be @@ -168,9 +168,9 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/alsonope", 2.0, OpcUaStatus.Good, TsB), })); - mux.ExpectMsg(PresenceBudget) + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.Count.ShouldBe(2); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// A host with NO OPC UA publish actor wired (the ctor default — the dev/Mac shape) still @@ -191,7 +191,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 42.0, OpcUaStatus.Good, TsA), })); - var fwd = mux.ExpectMsg(PresenceBudget); + var fwd = mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); fwd.DriverInstanceId.ShouldBe("drv-1"); fwd.Items.Single().FullReference.ShouldBe("Plant/Modbus/dev1/speed"); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBootFromCacheTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBootFromCacheTests.cs index c19a75f37..23c2d4195 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBootFromCacheTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorBootFromCacheTests.cs @@ -75,7 +75,7 @@ public sealed class DriverHostActorBootFromCacheTests : RuntimeActorTestBase opcUaPublishActor: publishProbe.Ref, deploymentArtifactCache: new StubArtifactCache(cached))); - var rebuild = publishProbe.ExpectMsg(PresenceBudget); + var rebuild = publishProbe.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); rebuild.Artifact.ShouldNotBeNull(); rebuild.Artifact.ShouldBe(blob); } @@ -103,7 +103,7 @@ public sealed class DriverHostActorBootFromCacheTests : RuntimeActorTestBase // Barrier: diagnostics are served only after PreStart's boot work ran, so the absence below is // asserted after the boot path had its chance to Tell (positive control is the valid-bytes test). var snapshot = AskDiagnostics(actor); - publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // Review R1 m4: nothing was applied, so the node is NOT running from cache and has NOT adopted the // cached revision (Stale with no configuration, retrying the ConfigDb) — the valid-bytes test above @@ -226,7 +226,7 @@ public sealed class DriverHostActorBootFromCacheTests : RuntimeActorTestBase // The point: the restore path wrote the served artifact back into the cache. AwaitAssert( () => cache.Stores.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cache.LastStoredDeploymentId.ShouldBe(deploymentId.ToString()); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossTests.cs index c1c1854e3..35e6ac797 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossTests.cs @@ -87,7 +87,7 @@ public sealed class DriverHostActorCommsLossTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: false)); - var batch = subscriber.ExpectMsg(PresenceBudget); + var batch = subscriber.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Select(i => i.TagId).ShouldBe( new[] { "Plant/Modbus/dev1/speed", "Plant/Modbus/dev1/temp" }, ignoreOrder: true); foreach (var item in batch.Items) @@ -98,7 +98,7 @@ public sealed class DriverHostActorCommsLossTests : RuntimeActorTestBase // ONE batch, not one per ref: the whole point of the change. A per-ref fan would land a second // single-item DependencyValueBatch here. - subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// Falsification control for the per-driver index: with TWO drivers configured, one driver's @@ -122,9 +122,9 @@ public sealed class DriverHostActorCommsLossTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: false)); - var batch = subscriber.ExpectMsg(PresenceBudget); + var batch = subscriber.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Select(i => i.TagId).ShouldBe( new[] { "Plant/Modbus/dev1/speed", "Plant/Modbus/dev1/temp" }, ignoreOrder: true); - subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossValueNodesTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossValueNodesTests.cs index ed1d5f0ee..fd0c33b2e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossValueNodesTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorCommsLossValueNodesTests.cs @@ -70,7 +70,7 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: false)); - var batch = publish.ExpectMsg(PresenceBudget); + var batch = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Select(i => i.NodeId).ShouldBe(new[] { "Plant/Modbus/dev1/speed", @@ -86,7 +86,7 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa batch.Items.ShouldNotContain(i => i.NodeId == "Plant/S7/dev2/other"); // ONE batch, not one message per node. - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// @@ -108,11 +108,11 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa var (actor, publish, _) = SpawnHostAndApply(this, db, TestNode, deploymentId, RevA); actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: false)); - publish.ExpectMsg(PresenceBudget); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: true)); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } // --------------------------------------------------------------------------------- @@ -141,15 +141,15 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa using var meter = new MeterRecorder("otopcua.driver.comms_latched_refs"); actor.Tell(new DriverInstanceActor.ConnectivityChanged(SilentDriver, Connected: false)); - publish.ExpectMsg(PresenceBudget); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); EventFilter.Warning(contains: "still BadNoCommunication").ExpectOne(TimeSpan.FromSeconds(10), () => - actor.Tell(new DriverInstanceActor.ConnectivityChanged(SilentDriver, Connected: true))); + actor.Tell(new DriverInstanceActor.ConnectivityChanged(SilentDriver, Connected: true)), TestContext.Current.CancellationToken); // Two REFS, not three nodes: the latch is keyed by RawPath, and the UNS mirror shares its // backing tag's ref — the operator is being told how many tags did not come back, not how many // address-space nodes they project onto. - AwaitCondition(() => meter.WithTag("driver_instance", SilentDriver) > 0, TimeSpan.FromSeconds(5)); + AwaitCondition(() => meter.WithTag("driver_instance", SilentDriver) > 0, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); meter.SumWithTag("driver_instance", SilentDriver).ShouldBe(2); } @@ -174,7 +174,7 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa using var meter = new MeterRecorder("otopcua.driver.comms_latched_refs"); actor.Tell(new DriverInstanceActor.ConnectivityChanged(RepublishingDriver, Connected: false)); - publish.ExpectMsg(PresenceBudget); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); EventFilter.Warning(contains: "still BadNoCommunication").Expect(0, TimeSpan.FromSeconds(5), () => { @@ -188,7 +188,7 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/temp", 7.5, OpcUaStatus.Good, TsA), })); publish.ExpectMsg(PresenceBudget); - }); + }, TestContext.Current.CancellationToken); meter.WithTag("driver_instance", RepublishingDriver).ShouldBe(0); } @@ -215,7 +215,7 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa using var meter = new MeterRecorder("otopcua.driver.comms_latched_refs"); actor.Tell(new DriverInstanceActor.ConnectivityChanged(PartialDriver, Connected: false)); - publish.ExpectMsg(PresenceBudget); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); EventFilter.Warning(contains: "still BadNoCommunication").ExpectOne(TimeSpan.FromSeconds(10), () => { @@ -225,9 +225,9 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 42.0, OpcUaStatus.Good, TsA), })); publish.ExpectMsg(PresenceBudget); - }); + }, TestContext.Current.CancellationToken); - AwaitCondition(() => meter.WithTag("driver_instance", PartialDriver) > 0, TimeSpan.FromSeconds(5)); + AwaitCondition(() => meter.WithTag("driver_instance", PartialDriver) > 0, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); meter.SumWithTag("driver_instance", PartialDriver).ShouldBe(1, "only the ref that never came back is reported"); } @@ -250,7 +250,7 @@ public sealed class DriverHostActorCommsLossValueNodesTests : RuntimeActorTestBa actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-unknown", Connected: false)); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDbLessTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDbLessTests.cs index 2edeee620..29ec6dcf0 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDbLessTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDbLessTests.cs @@ -129,8 +129,8 @@ public sealed class DriverHostActorDbLessTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Applied); - AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: PresenceBudget); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); fetcher.Calls.ShouldBe(1); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDegradedAckTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDegradedAckTests.cs index e79f0ec29..7af4f16e1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDegradedAckTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDegradedAckTests.cs @@ -40,14 +40,14 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var rebuild = publish.FishForMessage(_ => true, PresenceBudget); + var rebuild = publish.FishForMessage(_ => true, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); rebuild.ReportOutcome.ShouldBeTrue(); // The ack is HELD until the outcome lands — the pre-#601 code had already sent it by now. - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); publish.Reply(new OpcUaPublishActor.RebuildCompleted(rebuild.Correlation, rebuild.DeploymentId, false, null)); - var ack = coordinator.ExpectMsg(PresenceBudget); + var ack = coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); ack.Degraded.ShouldBeFalse(); ack.DegradedReason.ShouldBeNull(); @@ -64,11 +64,11 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase opcUaPublishActor: publish.Ref)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var rebuild = publish.FishForMessage(_ => true, PresenceBudget); + var rebuild = publish.FishForMessage(_ => true, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); publish.Reply(new OpcUaPublishActor.RebuildCompleted( rebuild.Correlation, rebuild.DeploymentId, Degraded: true, DegradedReason: "failedNodes=2")); - var ack = coordinator.ExpectMsg(PresenceBudget); + var ack = coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); ack.Degraded.ShouldBeTrue(); ack.DegradedReason.ShouldBe("failedNodes=2"); @@ -91,7 +91,7 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(PresenceBudget); + var ack = coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); ack.Degraded.ShouldBeTrue(); ack.DegradedReason.ShouldNotBeNull(); @@ -113,7 +113,7 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(PresenceBudget); + var ack = coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); ack.Degraded.ShouldBeFalse(); } @@ -130,7 +130,7 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(PresenceBudget); + var ack = coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); ack.Degraded.ShouldBeTrue(); ack.DegradedReason.ShouldNotBeNull(); @@ -139,9 +139,9 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase ack.DegradedReason.ShouldNotContain("failedNodes="); // A late outcome after the timeout must not produce a second ack. - var rebuild = publish.FishForMessage(_ => true, PresenceBudget); + var rebuild = publish.FishForMessage(_ => true, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); publish.Reply(new OpcUaPublishActor.RebuildCompleted(rebuild.Correlation, rebuild.DeploymentId, false, null)); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } [Fact] @@ -155,23 +155,23 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase opcUaPublishActor: publish.Ref)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var first = publish.FishForMessage(_ => true, PresenceBudget); + var first = publish.FishForMessage(_ => true, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); publish.Reply(new OpcUaPublishActor.RebuildCompleted(first.Correlation, first.DeploymentId, true, "failedNodes=1")); - coordinator.ExpectMsg(PresenceBudget).Degraded.ShouldBeTrue(); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeTrue(); // Same revision again: the old idempotent short-circuit would ack a CLEAN Applied right here. It must // re-apply instead (a second rebuild request), and ack whatever THAT apply reports. actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var second = publish.FishForMessage(_ => true, PresenceBudget); - coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + var second = publish.FishForMessage(_ => true, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); publish.Reply(new OpcUaPublishActor.RebuildCompleted(second.Correlation, second.DeploymentId, false, null)); - coordinator.ExpectMsg(PresenceBudget).Degraded.ShouldBeFalse(); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); // Now clean: a third dispatch of the same revision takes the short-circuit again (no rebuild). actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var third = coordinator.ExpectMsg(PresenceBudget); + var third = coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); third.Degraded.ShouldBeFalse(); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } [Fact] @@ -188,9 +188,9 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase opcUaPublishActor: publish.Ref)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var rebuild = publish.FishForMessage(_ => true, PresenceBudget); + var rebuild = publish.FishForMessage(_ => true, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); publish.Reply(new OpcUaPublishActor.RebuildCompleted(rebuild.Correlation, rebuild.DeploymentId, true, "failedNodes=3")); - coordinator.ExpectMsg(PresenceBudget).Degraded.ShouldBeTrue(); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeTrue(); AwaitAssert(() => { @@ -198,7 +198,7 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase var row = ctx.NodeDeploymentStates.Single(r => r.NodeId == TestNode.Value && r.DeploymentId == deploymentId.Value); row.Status.ShouldBe(ZB.MOM.WW.OtOpcUa.Configuration.Enums.NodeDeploymentStatus.Applied); row.FailureReason.ShouldBe(ApplyAck.DegradedReasonPrefix + "failedNodes=3"); - }, PresenceBudget); + }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyOffMailboxTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyOffMailboxTests.cs index 1855a5942..deb2b0b60 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyOffMailboxTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyOffMailboxTests.cs @@ -54,7 +54,7 @@ public sealed class DriverHostActorDirectApplyOffMailboxTests : RuntimeActorTest // Barrier: an answered GetDiagnostics proves PreStart (and its bootstrap ConfigDb read) is // done, so the allowance below counts only the apply's own contexts. actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // Let ONE more context through — the apply's Applying-row upsert, which is a deliberate // on-thread write (bounded, one row) — and block the next, which is the artifact read. @@ -66,20 +66,20 @@ public sealed class DriverHostActorDirectApplyOffMailboxTests : RuntimeActorTest actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); // The read was entered and has NOT completed — the gate is still closed. - AwaitAssert(() => db.Blocked.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => db.Blocked.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); db.Unblocked.ShouldBe(0); // THE assertion: with the artifact read still in flight the host is still processing its // mailbox. Read inline on the actor thread, this times out. actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Releasing the read completes the apply exactly as before — same Applied ACK, same revision. db.Open(); - coordinator.ExpectMsg(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).CurrentRevision.ShouldBe(RevA); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).CurrentRevision.ShouldBe(RevA); } finally { diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyReadFailureTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyReadFailureTests.cs index a0ab5f997..f72aba626 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyReadFailureTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorDirectApplyReadFailureTests.cs @@ -55,7 +55,7 @@ public sealed class DriverHostActorDirectApplyReadFailureTests : RuntimeActorTes // Barrier: an answered GetDiagnostics proves PreStart (and its bootstrap ConfigDb read) is done, // so the allowance below counts only the apply's own contexts. actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(Timeout); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // Let ONE more context through — the apply's Applying-row upsert — and fail the NEXT one, which is // the artifact read. Both misconfigurations fail LOUDLY rather than passing vacuously: too small @@ -65,22 +65,22 @@ public sealed class DriverHostActorDirectApplyReadFailureTests : RuntimeActorTes actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(Timeout); + var ack = coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Failed); ack.FailureReason.ShouldNotBeNullOrWhiteSpace(); db.Throws.ShouldBe(1); // the failure came from the read, not from somewhere incidental // Never applied ⇒ never claimed: the node stays on what it was already serving. actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(Timeout).CurrentRevision.ShouldNotBe(RevA); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).CurrentRevision.ShouldNotBe(RevA); // …and because the revision did not advance, re-dispatching THE SAME revision against a healthy DB // is not waved through by the "already at this rev" short-circuit — it actually applies. actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(Timeout).CurrentRevision.ShouldBe(RevA); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).CurrentRevision.ShouldBe(RevA); } /// Seeds a sealed, parseable deployment and returns its id. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheBootstrapTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheBootstrapTests.cs index b24bf7b83..75492ba8d 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheBootstrapTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheBootstrapTests.cs @@ -53,7 +53,7 @@ public sealed class DriverHostActorFetchAndCacheBootstrapTests : RuntimeActorTes artifactFetcher: new RecordingFetcher(Artifact()))); // Served state rebuilt from the CACHED bytes (no ConfigDb read — the factory throws). - var rebuild = publishProbe.ExpectMsg(TimeSpan.FromSeconds(5)); + var rebuild = publishProbe.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); rebuild.Artifact.ShouldBe(blob); var snapshot = AskDiagnostics(actor); @@ -83,7 +83,7 @@ public sealed class DriverHostActorFetchAndCacheBootstrapTests : RuntimeActorTes // Steady, not Stale: a dispatch is serviced (Stale would ignore it, never acking) and fetches. actor.Tell(new DispatchDeployment(DeploymentId.NewId(), CachedRev, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); fetcher.Calls.ShouldBe(1); } @@ -107,7 +107,7 @@ public sealed class DriverHostActorFetchAndCacheBootstrapTests : RuntimeActorTes // A cache-read fault is NOT Stale (Stale means "central SQL down, retry it" — there is no // config SQL read to recover here). The node is Steady and the next dispatch fetches. actor.Tell(new DispatchDeployment(DeploymentId.NewId(), CachedRev, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); fetcher.Calls.ShouldBe(1); } @@ -139,7 +139,7 @@ public sealed class DriverHostActorFetchAndCacheBootstrapTests : RuntimeActorTes snapshot.RunningFromCache.ShouldBeFalse(); actor.Tell(new DispatchDeployment(DeploymentId.Parse(cached.DeploymentId), CachedRev, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); fetcher.Calls.ShouldBe(1); AskDiagnostics(actor).CurrentRevision.ShouldBe(CachedRev); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheTests.cs index 97d12c1dc..56be860f1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheTests.cs @@ -47,8 +47,8 @@ public sealed class DriverHostActorFetchAndCacheTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Applied); - AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: PresenceBudget); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); cache.Stores[0].RevisionHash.ShouldBe(RevA.Value); fetcher.Calls.ShouldBe(1); } @@ -70,7 +70,7 @@ public sealed class DriverHostActorFetchAndCacheTests : RuntimeActorTestBase artifactFetcher: fetcher)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Failed); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed); Thread.Sleep(300); cache.Stores.ShouldBeEmpty(); @@ -78,7 +78,7 @@ public sealed class DriverHostActorFetchAndCacheTests : RuntimeActorTestBase // The revision was NOT advanced: a re-dispatch of the SAME revision fetches AGAIN rather than // short-circuiting as "already current". (Two fetch attempts prove #485's keep-last-known-good.) actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Failed); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed); fetcher.Calls.ShouldBe(2); } @@ -99,12 +99,12 @@ public sealed class DriverHostActorFetchAndCacheTests : RuntimeActorTestBase artifactFetcher: fetcher)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); // Re-dispatch the now-current revision: the _currentRevision guard short-circuits to an // immediate ACK without a second fetch. actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); Thread.Sleep(200); fetcher.Calls.ShouldBe(1); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheUnreadableTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheUnreadableTests.cs index f74ea8514..445122497 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheUnreadableTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorFetchAndCacheUnreadableTests.cs @@ -54,14 +54,14 @@ public sealed class DriverHostActorFetchAndCacheUnreadableTests : RuntimeActorTe artifactFetcher: fetcher)); actor.Tell(new DispatchDeployment(DeploymentId.NewId(), RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Applied); - publishProbe.ExpectMsg(PresenceBudget); // revA rebuild + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + publishProbe.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // revA rebuild actor.Tell(new DispatchDeployment(DeploymentId.NewId(), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Failed); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed); // The #485 negative: no rebuild for the failed revB — the served address space is untouched. - publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); AskDiagnostics(actor).CurrentRevision.ShouldBe(RevA); } @@ -85,8 +85,8 @@ public sealed class DriverHostActorFetchAndCacheUnreadableTests : RuntimeActorTe actor.Tell(new DispatchDeployment(DeploymentId.NewId(), RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Failed); - publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // nothing applied, nothing rebuilt + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed); + publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // nothing applied, nothing rebuilt } [Fact] @@ -106,7 +106,7 @@ public sealed class DriverHostActorFetchAndCacheUnreadableTests : RuntimeActorTe artifactFetcher: new MapFetcher(new Dictionary()))); // No rebuild at boot — a null (corrupt/empty) cache is not a configuration to apply. - publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(750)); + publishProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(750), TestContext.Current.CancellationToken); AskDiagnostics(actor).CurrentRevision.ShouldBeNull(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs index dee3ccec8..28d1170d1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs @@ -58,7 +58,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest SpawnHost(view, sharesScopeWith: MeshPeer).Tell(Snapshot( (TestNode, RedundancyRole.Secondary), (MeshPeer, RedundancyRole.Primary))); - AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -76,7 +76,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest SpawnHost(view, sharesScopeWith: MeshPeer).Tell(Snapshot( (TestNode, RedundancyRole.Secondary), (ForeignNode, RedundancyRole.Primary))); - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -95,7 +95,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest SpawnHost(view, sharesScopeWith: null).Tell(Snapshot( (TestNode, RedundancyRole.Secondary), (MeshPeer, RedundancyRole.Primary))); - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// Primary captures. @@ -108,7 +108,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest // Published, not current — `true` is also the seed, so asserting the property would pass before // the actor had processed anything. - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -122,7 +122,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest SpawnHost(view, sharesScopeWith: MeshPeer).Tell(Snapshot( (MeshPeer, RedundancyRole.Primary), (ForeignNode, RedundancyRole.Secondary))); - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// A promotion must MOVE the view, not merely leave it at its open seed. @@ -133,10 +133,10 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest IActorRef host = SpawnHost(view, sharesScopeWith: MeshPeer); host.Tell(Snapshot((TestNode, RedundancyRole.Secondary), (MeshPeer, RedundancyRole.Primary))); - AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Snapshot((TestNode, RedundancyRole.Primary), (MeshPeer, RedundancyRole.Secondary))); - AwaitAssert(() => view.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } // ---------------- Gitea #672: an unreachable recording Primary ---------------- @@ -168,15 +168,15 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest IActorRef host = SpawnHost672(hist, others, captureWhilePrimaryUnreachable: true); host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout); - AwaitAssert(() => others.Drained.ShouldNotBeEmpty(), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => others.Drained.ShouldNotBeEmpty(), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); var drainedBefore = others.Drained; var primaryBefore = others.Primary; drainedBefore[^1].ShouldBeFalse(); // queue-sharing mesh peer is Primary ⇒ drain stands down primaryBefore[^1].ShouldBeFalse(); // Secondary ⇒ not the data-plane Primary host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); // Same handler run that published `true` above — anything it published elsewhere is already recorded. others.Drained.ShouldBe(drainedBefore); @@ -185,7 +185,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest others.Current.ShouldActAsPrimary.ShouldBeFalse(); host.Tell(new ClusterEvent.ReachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); others.Drained.ShouldBe(drainedBefore); others.Primary.ShouldBe(primaryBefore); } @@ -200,13 +200,13 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest // Both a foreign-cluster Primary (never stood down for) and the mesh Primary (stood down for). host.Tell(Snapshot( (Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary), (Foreign672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Foreign672))); // Positive control: the mesh peer's loss, sent AFTER, must be the only change — had the foreign // one published anything it would sit between the two. host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// This node's own reachability (as gossiped by others) is not a peer question. @@ -217,18 +217,18 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest IActorRef host = SpawnHost672(hist, others: null, captureWhilePrimaryUnreachable: true); host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Self672))); host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); // Self "returning" is ignored too, and it does not close what the peer's loss opened. Events are // processed in order, so the peer's return (which MUST close the gate) is the only thing that may // appear next: had the self event published anything it would sit between `true` and `false`. host.Tell(new ClusterEvent.ReachableMember(MemberAt(Self672))); host.Tell(new ClusterEvent.ReachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -246,7 +246,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); // Exactly one publication, and it is `true`: no spurious `false` from treating the peer as reachable. - AwaitAssert(() => hist.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// CaptureWhilePrimaryUnreachable = false restores the pre-#672 behaviour. @@ -257,13 +257,13 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest IActorRef host = SpawnHost672(hist, others: null, captureWhilePrimaryUnreachable: false); host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); // Positive control: a re-sent snapshot publishes on every arrival, so a `true` from the // reachability event would show up between the two `false`s. host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false, false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -277,14 +277,14 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest IActorRef host = SpawnHost672(hist, others: null, captureWhilePrimaryUnreachable: true); host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new ClusterEvent.MemberRemoved(MemberAt(Peer672, MemberStatus.Removed), MemberStatus.Down)); host.Tell(Snapshot((Self672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false, true, true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// A re-joined incarnation at the dead peer's address is reachable again. @@ -296,10 +296,10 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new ClusterEvent.MemberUp(MemberAt(Peer672))); - AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -326,7 +326,7 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest var clusterB = Akka.Cluster.Cluster.Get(sysB); AwaitCondition( () => clusterB.State.Members.Count(m => m.Status == MemberStatus.Up) == 2, - TimeSpan.FromSeconds(30)); + TimeSpan.FromSeconds(30), TestContext.Current.CancellationToken); var nodeA = NodeId.Parse($"127.0.0.1:{portA}"); var nodeB = NodeId.Parse($"127.0.0.1:{portB}"); @@ -340,11 +340,11 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest sharesHistorizationScopeWithLocal: node => node == nodeA)); host.Tell(Snapshot((nodeB, RedundancyRole.Secondary), (nodeA, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => hist.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); await sysA.Terminate(); - AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: TimeSpan.FromSeconds(30)); + AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: TimeSpan.FromSeconds(30), cancellationToken: TestContext.Current.CancellationToken); // Still a member: the gate opened on reachability, not on a removal + fresh snapshot. clusterB.State.Members.ShouldContain(m => m.Address.Port == portA); clusterB.State.Unreachable.ShouldContain(m => m.Address.Port == portA); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryUpdateRoleViewTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryUpdateRoleViewTests.cs index 8a8547e4f..91b0881ff 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryUpdateRoleViewTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryUpdateRoleViewTests.cs @@ -31,10 +31,10 @@ public sealed class DriverHostActorHistoryUpdateRoleViewTests : RuntimeActorTest var view = new RecordingHistoryUpdateRoleView(); var host = SpawnHost(view, () => 1); - AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Snapshot(RedundancyRole.Primary)); - AwaitAssert(() => view.Published[^1].ShouldBeTrue(), duration: Timeout); + AwaitAssert(() => view.Published[^1].ShouldBeTrue(), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// Once a snapshot has arrived, an unknown role on a single-driver cluster (a snapshot that omits this @@ -44,10 +44,10 @@ public sealed class DriverHostActorHistoryUpdateRoleViewTests : RuntimeActorTest { var view = new RecordingHistoryUpdateRoleView(); var host = SpawnHost(view, () => 1); - AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new RedundancyStateChanged([], CorrelationId.NewId())); - AwaitAssert(() => view.Published[^1].ShouldBeTrue(), duration: Timeout); + AwaitAssert(() => view.Published[^1].ShouldBeTrue(), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// A Secondary stays closed after its snapshot; becoming Primary opens it. @@ -56,14 +56,14 @@ public sealed class DriverHostActorHistoryUpdateRoleViewTests : RuntimeActorTest { var view = new RecordingHistoryUpdateRoleView(); var host = SpawnHost(view, () => 2); - AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Snapshot(RedundancyRole.Secondary)); - AwaitAssert(() => view.Published.Count.ShouldBeGreaterThanOrEqualTo(2), duration: Timeout); + AwaitAssert(() => view.Published.Count.ShouldBeGreaterThanOrEqualTo(2), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); view.Published.ShouldAllBe(v => !v); host.Tell(Snapshot(RedundancyRole.Primary)); - AwaitAssert(() => view.Published[^1].ShouldBeTrue(), duration: Timeout); + AwaitAssert(() => view.Published[^1].ShouldBeTrue(), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// The policy itself: no snapshot ⇒ closed whatever the role/count; with one ⇒ the data-plane verdict. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryWriterTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryWriterTests.cs index ab17f6ad2..cbedf76e2 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryWriterTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistoryWriterTests.cs @@ -68,7 +68,7 @@ public sealed class DriverHostActorHistoryWriterTests : RuntimeActorTestBase // The deployment Applies — the real VirtualTagHostActor spawned and accepted ApplyVirtualTags // with the writer threaded through (a construction/threading failure would have faulted PreStart // or the apply turn). Record behaviour itself is covered by VirtualTagHostActorTests (H5c). - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); } private static DeploymentId SeedDeploymentWithHistorizedVirtualTag( diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs index d517c8cac..9e70259b6 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorLiveValueTests.cs @@ -64,7 +64,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase // emits for one data change. actor.Tell(Burst(("Plant/Modbus/dev1/speed", 42.0))); - var byId = publish.ExpectMsg(PresenceBudget) + var byId = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.ToDictionary(u => u.NodeId); byId.Keys.ShouldBe(new[] { "Plant/Modbus/dev1/speed", "filling/line1/station1/speed" }, ignoreOrder: true); @@ -80,7 +80,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase } // The raw publish still reaches the dependency mux (VirtualTag inputs), keyed by RawPath. - mux.ExpectMsg(PresenceBudget) + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.Single().FullReference.ShouldBe("Plant/Modbus/dev1/speed"); } @@ -97,11 +97,11 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase actor.Tell(Burst(("Plant/Modbus/dev1/speed", 5.0))); - var only = publish.ExpectMsg(PresenceBudget) + var only = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.ShouldHaveSingleItem(); only.NodeId.ShouldBe("Plant/Modbus/dev1/speed"); only.Realm.ShouldBe(AddressSpaceRealm.Raw); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// A raw tag referenced by TWO equipments fans to the raw node PLUS both UNS nodes (3 updates, @@ -123,7 +123,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase actor.Tell(Burst(("Plant/Modbus/dev1/speed", 7.0))); // 3-wide fan-out inside ONE batch message — the batch path's own 1:N coverage. - var updates = publish.ExpectMsg(PresenceBudget).Items; + var updates = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items; updates.Count.ShouldBe(3); updates.Select(u => u.NodeId).ShouldBe(new[] { @@ -148,8 +148,8 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase actor.Tell(Burst(("Plant/Modbus/dev1/nope", 99.0))); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); - mux.ExpectMsg(PresenceBudget) + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.Single().FullReference.ShouldBe("Plant/Modbus/dev1/nope"); } @@ -180,7 +180,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: false)); // ONE envelope for the whole driver: a driver owning 5k tags cost 5k Tells before this was batched. - var fan = mux.ExpectMsg(PresenceBudget); + var fan = mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); fan.DriverInstanceId.ShouldBe("drv-1"); var pushes = fan.Items; @@ -197,7 +197,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase p.Value.ShouldBeNull(); } - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// Reconnect pushes NOTHING — and this is the falsifying control for the test above, since @@ -220,7 +220,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: true)); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Comms loss for a driver this host knows nothing about pushes nothing — the fan-out is @@ -237,7 +237,7 @@ public sealed class DriverHostActorLiveValueTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-OTHER", Connected: false)); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmAckRoutingTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmAckRoutingTests.cs index 57769eccc..3b123c784 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmAckRoutingTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmAckRoutingTests.cs @@ -75,7 +75,7 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest recorder.Acks[0].Comment.ShouldBe("cmt"); recorder.Acks[0].OperatorUser.ShouldBe("alice"); recorder.Acks[0].ApiKeyId.ShouldBe("0a1b2c"); - }, duration: Timeout); + }, duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// An ack for an unmapped condition NodeId is dropped: no throw, and the driver's @@ -101,7 +101,7 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest // handled. Whatever `Acks` holds then is the complete answer. actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "control", "alice")); - AwaitAssert(() => recorder.Acks.ShouldContain(a => a.Comment == "control"), duration: Timeout); + AwaitAssert(() => recorder.Acks.ShouldContain(a => a.Comment == "control"), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); recorder.Acks.Count.ShouldBe(1, "only the control ack may reach the driver — the unmapped condition NodeId must have been dropped"); } @@ -141,7 +141,7 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest CorrelationId.NewId())); actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "control", "alice")); - AwaitAssert(() => recorder.Acks.ShouldContain(a => a.Comment == "control"), duration: Timeout); + AwaitAssert(() => recorder.Acks.ShouldContain(a => a.Comment == "control"), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); recorder.Acks.ShouldNotContain(a => a.Comment == "gated", "the Primary gate must have dropped the ack sent while this node was Secondary"); recorder.Acks.Count.ShouldBe(1); @@ -216,7 +216,7 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest RaiseAndExpectUnacked(actor, publish); actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "cmt", "alice")); - AwaitAssert(() => recorder.Acks.Count.ShouldBe(1), duration: Timeout); + AwaitAssert(() => recorder.Acks.Count.ShouldBe(1), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.AttributeAlarmPublished("drv-1", Evt(AlarmTransitionKind.Clear))); var next = NextAlarmUpdate(publish); @@ -242,7 +242,7 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest SpawnHostAndApply(db, deploymentId, recorder, ackPreflightView: view); AwaitAssert(() => view.Check(AlarmRawPath, apiKeyId: null).ShouldBe("anonymous sessions cannot acknowledge"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); view.Check(AlarmRawPath, apiKeyId: "0a1b2c").ShouldBeNull(); view.Check("Plant/gw/dev1/does-not-exist", apiKeyId: null).ShouldBeNull(); } @@ -307,7 +307,7 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest // Barrier: Stale answers RouteNodeWrite synchronously, so a reply proves the host is in Stale. var asker = CreateTestProbe(); host.Tell(new DriverHostActor.RouteNodeWrite("eq-x/speed", 0.0, AddressSpaceRealm.Uns), asker.Ref); - asker.ExpectMsg(Timeout).Reason!.ShouldContain("stale"); + asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Reason!.ShouldContain("stale"); view.Check(AlarmRawPath, apiKeyId: "0a1b2c").ShouldNotBeNull().ShouldContain("stale"); view.Check("any/other/condition", apiKeyId: null).ShouldNotBeNull(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmTests.cs index 32149a65a..d7a46a007 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorNativeAlarmTests.cs @@ -73,7 +73,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase SourceTimestampUtc: Ts, Kind: AlarmTransitionKind.Raise))); - var update = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var update = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); update.AlarmNodeId.ShouldBe(AlarmRawPath); update.Realm.ShouldBe(AddressSpaceRealm.Raw); update.State.Active.ShouldBeTrue(); @@ -101,7 +101,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase SourceTimestampUtc: Ts, Kind: AlarmTransitionKind.Raise))); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Gitea #641 (item 4) — routing is an EXACT match on the RawPath: a driver that decorates its @@ -122,7 +122,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase new StubAlarmHandle(), AlarmRawPath, AlarmRawPath, "ALMD", "plain", AlarmSeverity.High, Ts, Kind: AlarmTransitionKind.Raise))); - publish.ExpectMsg(TimeSpan.FromSeconds(5)).State.Message.ShouldBe("plain"); + publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).State.Message.ShouldBe("plain"); } /// Primary (default/unset role) fan-out: a native alarm RAISE on a known ref publishes exactly one @@ -150,11 +150,11 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase Kind: AlarmTransitionKind.Raise))); // The OPC UA condition update is UNGATED — it must arrive. - var update = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var update = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); update.AlarmNodeId.ShouldBe(AlarmRawPath); // Role unknown ⇒ default-emit: exactly one AlarmTransitionEvent on the alerts topic. - var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe(AlarmRawPath); // the raw condition NodeId (v3: ConditionId == RawPath) evt.EquipmentPath.ShouldBe(AlarmRawPath); // v3: the alarm meta keys the display off the RawPath evt.AlarmName.ShouldBe("temp_hi"); // from the raw tag's Name @@ -164,7 +164,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase evt.Message.ShouldBe("temperature high"); evt.User.ShouldBe(string.Empty); // no operator comment ⇒ device-origin (empty user) evt.HistorizeToAveva.ShouldBeNull(); // absent historizeToAveva key ⇒ null ⇒ default-on - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // exactly one + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // exactly one } /// Native-alarm HistorizeToAveva opt-out: a tag whose TagConfig.alarm carries @@ -192,7 +192,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase SourceTimestampUtc: Ts, Kind: AlarmTransitionKind.Raise))); - var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe(AlarmRawPath); evt.HistorizeToAveva.ShouldBe(false); } @@ -220,7 +220,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase SourceTimestampUtc: Ts, Kind: AlarmTransitionKind.Raise))); - var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); evt.HistorizeToAveva.ShouldBe(true); } @@ -252,12 +252,12 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase Kind: AlarmTransitionKind.Raise))); // The OPC UA condition update is UNGATED — it must still arrive on the secondary. - var update = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var update = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); update.AlarmNodeId.ShouldBe(AlarmRawPath); update.State.Active.ShouldBeTrue(); // The cluster-wide alerts publish is gated off on the secondary. - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// A native alarm whose AlarmEventArgs.OperatorComment is set flows through @@ -285,9 +285,9 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase Kind: AlarmTransitionKind.Acknowledge, OperatorComment: "investigating"))); - publish.ExpectMsg(TimeSpan.FromSeconds(5)); + publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); - var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); evt.Comment.ShouldBe("investigating"); evt.User.ShouldBe("device"); } @@ -320,7 +320,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase Kind: AlarmTransitionKind.Unspecified, QualityStatusCode: OpcUaStatus.BadNoCommunication))); - var annotated = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var annotated = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); annotated.AlarmNodeId.ShouldBe(AlarmRawPath); annotated.Realm.ShouldBe(AddressSpaceRealm.Raw); annotated.StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); @@ -338,12 +338,12 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase OpcUaSeverity: 800, QualityStatusCode: OpcUaStatus.Good))); - var raised = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var raised = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); raised.State.Active.ShouldBeTrue(); raised.State.Severity.ShouldBe((ushort)800); raised.State.StatusCode.ShouldBe(OpcUaStatus.Good); - var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + var evt = alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); evt.Message.ShouldBe("spindle fault", "the quality-only annotation must not have published an alerts row"); evt.TransitionKind.ShouldBe("Activated"); evt.Severity.ShouldBe(800); @@ -364,7 +364,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.AttributeAlarmPublished("drv-1", new AlarmEventArgs( new StubAlarmHandle(), "Temp", AlarmRawPath, "SYSTEM", "spindle fault", AlarmSeverity.High, Ts, Kind: AlarmTransitionKind.Raise, OpcUaSeverity: 800, QualityStatusCode: OpcUaStatus.Good))); - publish.ExpectMsg(TimeSpan.FromSeconds(5)).State.Acknowledged.ShouldBeFalse(); + publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).State.Acknowledged.ShouldBeFalse(); // Whatever the projector's prior state is (any apply clears it), a quality-only event must never be // projected — the routing below is the whole guard, so no deploy needs staging here. @@ -380,11 +380,11 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase var next = publish.FishForMessage( m => m is OpcUaPublishActor.AlarmQualityUpdate or OpcUaPublishActor.AlarmStateUpdate, - TimeSpan.FromSeconds(5)); + TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); next.ShouldBeOfType().StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); var control = publish.FishForMessage( m => m is OpcUaPublishActor.AlarmQualityUpdate or OpcUaPublishActor.AlarmStateUpdate, - TimeSpan.FromSeconds(5)); + TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); control.ShouldBeOfType().State.Message.ShouldBe("control", "the quality-only event must produce no state snapshot ahead of the control"); } @@ -404,14 +404,14 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase // Comms lost → the raw condition is annotated BadNoCommunication (#519: the reason survives to // the client; this used to be a bare Bad indistinguishable from any other fault). actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: false)); - var bad = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var bad = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); bad.AlarmNodeId.ShouldBe(AlarmRawPath); bad.Realm.ShouldBe(AddressSpaceRealm.Raw); bad.StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); // Comms restored → annotated Good. actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-1", Connected: true)); - var good = publish.ExpectMsg(TimeSpan.FromSeconds(5)); + var good = publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); good.AlarmNodeId.ShouldBe(AlarmRawPath); good.StatusCode.ShouldBe(OpcUaStatus.Good); } @@ -427,7 +427,7 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase var (actor, publish) = SpawnHostAndApply(db, deploymentId); actor.Tell(new DriverInstanceActor.ConnectivityChanged("drv-OTHER", Connected: false)); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// @@ -443,12 +443,12 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase var (actor, publish, coordinator) = SpawnHostAndApplyWithCoordinator(db, deploymentId); actor.Tell(Transition(AlarmTransitionKind.Raise)); - publish.ExpectMsg(TimeSpan.FromSeconds(5)).State.Active.ShouldBeTrue(); + publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).State.Active.ShouldBeTrue(); Redeploy(db, actor, publish, coordinator); actor.Tell(Transition(AlarmTransitionKind.Acknowledge)); - var acked = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)); + var acked = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); acked.State.Active.ShouldBeTrue("the alarm is still active at the device"); acked.State.Acknowledged.ShouldBeTrue(); } @@ -463,12 +463,12 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase var (actor, publish, coordinator) = SpawnHostAndApplyWithCoordinator(db, deploymentId); actor.Tell(Transition(AlarmTransitionKind.Raise)); - publish.ExpectMsg(TimeSpan.FromSeconds(5)).State.Acknowledged.ShouldBeFalse(); + publish.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).State.Acknowledged.ShouldBeFalse(); Redeploy(db, actor, publish, coordinator); actor.Tell(Transition(AlarmTransitionKind.Clear)); - var cleared = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)); + var cleared = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); cleared.State.Active.ShouldBeFalse(); cleared.State.Acknowledged.ShouldBeFalse("nobody acknowledged it"); } @@ -492,19 +492,19 @@ public sealed class DriverHostActorNativeAlarmTests : RuntimeActorTestBase var deploymentId = SeedV3AlarmDeployment(db, RevA, Driver: "drv-1", Tag: "temp_hi", enabled: true); var (actor, publish, coordinator) = SpawnHostAndApplyWithCoordinator(db, deploymentId, factory); // An ack reaching a still-Connecting child is dropped (and now reverted); wait for Connected. - AwaitCondition(() => factory.Driver.AlarmsSubscribed, TimeSpan.FromSeconds(5)); + AwaitCondition(() => factory.Driver.AlarmsSubscribed, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); actor.Tell(Transition(AlarmTransitionKind.Raise)); - publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)) + publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken) .State.Acknowledged.ShouldBeFalse(); // The client's Acknowledge on the node, routed to the driver (no transition comes back for it). actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "seen", "op")); - AwaitCondition(() => factory.Driver.Acks == 1, TimeSpan.FromSeconds(5)); + AwaitCondition(() => factory.Driver.Acks == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); Redeploy(db, actor, publish, coordinator, enabled: true); actor.Tell(Transition(AlarmTransitionKind.Clear)); - var cleared = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)); + var cleared = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); cleared.State.Active.ShouldBeFalse(); cleared.State.Acknowledged.ShouldBeTrue("the operator acknowledged it before it cleared"); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryGateTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryGateTests.cs index 11d0aaa9b..eae61242e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryGateTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryGateTests.cs @@ -65,7 +65,7 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase var asker = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite(WriteUnsNodeId, 123.0, AddressSpaceRealm.Uns), asker.Ref); - var result = asker.ExpectMsg(Timeout); + var result = asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Reason.ShouldBe("not primary (role unknown)"); recorder.Writes.ShouldBeEmpty(); @@ -92,7 +92,7 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase res.Reason.ShouldNotBe("not primary"); res.Reason.ShouldNotBe("not primary (role unknown)"); res.Success.ShouldBeTrue(res.Reason); - }, duration: TimeSpan.FromSeconds(10)); + }, duration: TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); recorder.Writes.ShouldNotBeEmpty(); } @@ -116,13 +116,13 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase var res = asker1.ExpectMsg(Timeout); res.Reason.ShouldNotBe("not primary"); res.Success.ShouldBeTrue(res.Reason); - }, duration: TimeSpan.FromSeconds(10)); + }, duration: TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); // Secondary snapshot ⇒ denied with the steady-state reason. TellRole(actor, RedundancyRole.Secondary); var asker2 = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite(WriteUnsNodeId, 2.0, AddressSpaceRealm.Uns), asker2.Ref); - var denied = asker2.ExpectMsg(Timeout); + var denied = asker2.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); denied.Success.ShouldBeFalse(); denied.Reason.ShouldBe("not primary"); } @@ -139,14 +139,14 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase var asker = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite(WriteUnsNodeId, 9.0, AddressSpaceRealm.Uns), asker.Ref); - asker.ExpectMsg(Timeout).Success.ShouldBeFalse(); + asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Success.ShouldBeFalse(); AwaitAssert(() => { recorder.Total.ShouldBeGreaterThanOrEqualTo(1); recorder.WithTag("site", "write").ShouldBeGreaterThanOrEqualTo(1); recorder.WithTag("reason", "role-unknown").ShouldBeGreaterThanOrEqualTo(1); - }, duration: Timeout); + }, duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } // ---------------- ack gate ---------------- @@ -162,12 +162,12 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase var (actor, _) = SpawnAlarmHost(db, dep, driverMemberCount: 2); EventFilter.Warning(contains: "role unknown").ExpectOne(() => - actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, Comment: null, OperatorUser: "op"))); + actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, Comment: null, OperatorUser: "op")), TestContext.Current.CancellationToken); AwaitAssert(() => { meter.WithTag("site", "ack").ShouldBeGreaterThanOrEqualTo(1); - }, duration: Timeout); + }, duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } // ---------------- alarm-emit gate ---------------- @@ -186,9 +186,9 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase actor.Tell(RaiseAlarm()); // The ungated OPC UA condition write still arrives. - publish.ExpectMsg(Timeout).AlarmNodeId.ShouldBe(AlarmRawPath); + publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).AlarmNodeId.ShouldBe(AlarmRawPath); // The cluster-wide alerts publish is suppressed (a real Primary peer publishes the single copy). - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Role unknown + count 1 ⇒ ForwardNativeAlarm publishes the alerts transition (single-node @@ -204,8 +204,8 @@ public sealed class DriverHostActorPrimaryGateTests : RuntimeActorTestBase actor.Tell(RaiseAlarm()); - publish.ExpectMsg(Timeout); - alerts.ExpectMsg(Timeout).AlarmId.ShouldBe(AlarmRawPath); + publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); + alerts.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).AlarmId.ShouldBe(AlarmRawPath); } // ---------------- helpers ---------------- diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryRoleSourceTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryRoleSourceTests.cs index 859b78182..6cc379d15 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryRoleSourceTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorPrimaryRoleSourceTests.cs @@ -34,10 +34,10 @@ public sealed class DriverHostActorPrimaryRoleSourceTests : RuntimeActorTestBase var source = new RecordingPrimaryRoleSource(); var host = SpawnHost(source, () => 2); - AwaitAssert(() => source.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => source.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Snapshot(RedundancyRole.Primary)); - AwaitAssert(() => source.Published.ShouldBe([false, true]), duration: Timeout); + AwaitAssert(() => source.Published.ShouldBe([false, true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// A single-driver node keeps the allow posture — it is published, not merely seeded. @@ -47,7 +47,7 @@ public sealed class DriverHostActorPrimaryRoleSourceTests : RuntimeActorTestBase var source = new RecordingPrimaryRoleSource(); SpawnHost(source, () => 1); - AwaitAssert(() => source.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => source.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// A driver peer coming Up while the role is still unknown flips the published verdict to deny, with no @@ -58,12 +58,12 @@ public sealed class DriverHostActorPrimaryRoleSourceTests : RuntimeActorTestBase var source = new RecordingPrimaryRoleSource(); var members = 1; var host = SpawnHost(source, () => Volatile.Read(ref members)); - AwaitAssert(() => source.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => source.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); Volatile.Write(ref members, 2); host.Tell(DriverHostActor.RepublishPrimaryVerdict.Instance); - AwaitAssert(() => source.Published.ShouldBe([true, false]), duration: Timeout); + AwaitAssert(() => source.Published.ShouldBe([true, false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -80,7 +80,7 @@ public sealed class DriverHostActorPrimaryRoleSourceTests : RuntimeActorTestBase var source = new RecordingPrimaryRoleSource(); var members = 1; SpawnHost(source, () => Volatile.Read(ref members)); - AwaitAssert(() => source.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => source.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); Volatile.Write(ref members, 2); var memberUp = new Akka.Cluster.ClusterEvent.MemberUp(Akka.Cluster.Cluster.Get(Sys).SelfMember); @@ -88,7 +88,7 @@ public sealed class DriverHostActorPrimaryRoleSourceTests : RuntimeActorTestBase { Sys.EventStream.Publish(memberUp); source.Published.ShouldBe([true, false]); - }, duration: Timeout, interval: TimeSpan.FromMilliseconds(100)); + }, duration: Timeout, interval: TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); } private IActorRef SpawnHost(IDriverPrimaryRoleSource source, Func driverMemberCount) => diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorProbeResultDropTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorProbeResultDropTests.cs index 6d5a211f7..6bfc7d1f4 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorProbeResultDropTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorProbeResultDropTests.cs @@ -75,7 +75,7 @@ public sealed class DriverHostActorProbeResultDropTests : RuntimeActorTestBase DistributedPubSub.Get(Sys).Mediator.Tell( new Subscribe(OpcUaPublishActor.RedundancyStateTopic, barrierProbe.Ref), barrierProbe.Ref); - barrierProbe.ExpectMsg(Timeout); + barrierProbe.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // Publish an OpcUaProbeResult to the redundancy-state topic. The mediator fans it out to // every subscriber — including the DriverHostActor — so it lands on the actor's mailbox. @@ -89,10 +89,10 @@ public sealed class DriverHostActorProbeResultDropTests : RuntimeActorTestBase // OpcUaProbeResult would already be on the EventStream before we assert. var asker = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite("eq-x/speed", 0.0, AddressSpaceRealm.Uns), asker.Ref); - asker.ExpectMsg(Timeout); + asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // Assert no dead-letter carrying an OpcUaProbeResult was published. - deadLetterProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + deadLetterProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// An whose CreateDbContext always throws, diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReconcileTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReconcileTests.cs index c3c0d6726..ea14a0164 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReconcileTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReconcileTests.cs @@ -41,8 +41,8 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - AwaitAssert(() => factory.CreateCount.ShouldBe(2), duration: TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + AwaitAssert(() => factory.CreateCount.ShouldBe(2), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that applying a deployment with unsupported driver type falls back to stub. @@ -63,14 +63,14 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); // No real driver was constructed — stubbing took over. factory.CreateCount.ShouldBe(0); // GetDiagnostics should still report the (stubbed) child. actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - var snap = coordinator.ExpectMsg(TimeSpan.FromSeconds(2)); + var snap = coordinator.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); snap.Drivers.Count.ShouldBe(1); snap.Drivers[0].State.ShouldBe("Stubbed"); } @@ -94,7 +94,7 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); if (OperatingSystem.IsWindows()) { @@ -122,14 +122,14 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase localRoles: new HashSet { "driver" })); actor.Tell(new DispatchDeployment(d1, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); - AwaitAssert(() => factory.CreateCount.ShouldBe(2), duration: TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => factory.CreateCount.ShouldBe(2), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new DispatchDeployment(d2, RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - var snap = coordinator.ExpectMsg(TimeSpan.FromSeconds(2)); + var snap = coordinator.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); snap.Drivers.Count.ShouldBe(1); } @@ -161,13 +161,13 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); // The node still reaches Applied even though it hosts only its own cluster's slice. - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); // Only the SITE-A driver was constructed — the MAIN driver was filtered out by scoping. - AwaitAssert(() => factory.CreateCount.ShouldBe(1), duration: TimeSpan.FromSeconds(3)); + AwaitAssert(() => factory.CreateCount.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - var snap = coordinator.ExpectMsg(TimeSpan.FromSeconds(2)); + var snap = coordinator.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); snap.Drivers.Count.ShouldBe(1); snap.Drivers[0].Name.ShouldBe("sa-modbus"); } @@ -196,8 +196,8 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase localRoles: new HashSet { "driver" })); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - AwaitAssert(() => factory.CreateCount.ShouldBe(1), duration: TimeSpan.FromSeconds(3)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + AwaitAssert(() => factory.CreateCount.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // The restart must NOT throw InvalidActorNameException (which the buggy in-handler respawn did, // crashing + restarting the host). Expect(0, …) asserts zero such logged events across a @@ -207,13 +207,13 @@ public sealed class DriverHostActorReconcileTests : RuntimeActorTestBase actor.Tell(new RestartDriver("MAIN", "DI-1", "tester", Guid.NewGuid())); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); coordinator.ExpectMsg(TimeSpan.FromSeconds(3)); - }); + }, TestContext.Current.CancellationToken); // The respawn re-created the driver under a fresh name — exactly once more (1 → 2), proving the // restart completed in-handler rather than throwing — and exactly one child remains. - AwaitAssert(() => factory.CreateCount.ShouldBe(2), duration: TimeSpan.FromSeconds(3)); + AwaitAssert(() => factory.CreateCount.ShouldBe(2), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(TimeSpan.FromSeconds(2)) + coordinator.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken) .Drivers.Count.ShouldBe(1); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReprovisionStampTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReprovisionStampTests.cs index 509e7ea9b..5e81d498e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReprovisionStampTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorReprovisionStampTests.cs @@ -36,12 +36,12 @@ public sealed class DriverHostActorReprovisionStampTests : RuntimeActorTestBase var revA = V3ArtifactSeeder.SeedV3Deployment(db, RevA, withDriver, none, enabled: true); var (host, _, _, coordinator) = V3ArtifactSeeder.SpawnHostAndApplyWithCoordinator( this, db, TestNode, revA, RevA, factory, reprovisionBackoff: backoff); - AwaitAssert(() => factory.Created.Count.ShouldBe(1), PresenceBudget); - AwaitAssert(() => factory.Created[0].SubscribeCount.ShouldBe(1), PresenceBudget); + AwaitAssert(() => factory.Created.Count.ShouldBe(1), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => factory.Created[0].SubscribeCount.ShouldBe(1), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Crash 1 on the first incarnation: reprovisioned immediately, which stamps the id. factory.Created[0].FireMalformedDataChange(SpeedPath); - AwaitAssert(() => factory.Created[0].InitializeCount.ShouldBe(2), PresenceBudget); + AwaitAssert(() => factory.Created[0].InitializeCount.ShouldBe(2), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Deploy B removes the driver (StopChild), deploy C re-adds it: a brand-new driver object. var revB = V3ArtifactSeeder.SeedV3Deployment( @@ -49,15 +49,15 @@ public sealed class DriverHostActorReprovisionStampTests : RuntimeActorTestBase V3ArtifactSeeder.Dispatch(host, coordinator, revB, RevB); var revC = V3ArtifactSeeder.SeedV3Deployment(db, RevC, withDriver, none, enabled: true); V3ArtifactSeeder.Dispatch(host, coordinator, revC, RevC); - AwaitAssert(() => factory.Created.Count.ShouldBe(2), PresenceBudget); + AwaitAssert(() => factory.Created.Count.ShouldBe(2), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var reAdded = factory.Created[1]; - AwaitAssert(() => reAdded.SubscribeCount.ShouldBe(1), PresenceBudget); + AwaitAssert(() => reAdded.SubscribeCount.ShouldBe(1), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Its FIRST crash must reprovision immediately — it has no history of its own. reAdded.FireMalformedDataChange(SpeedPath); AwaitAssert( () => reAdded.InitializeCount.ShouldBe( 2, "the re-added driver's first reprovision was deferred by the REMOVED incarnation's stamp"), - duration: TimeSpan.FromSeconds(5)); + duration: TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRestartTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRestartTests.cs index 414932b0c..e561b3267 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRestartTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRestartTests.cs @@ -110,7 +110,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created[0].SubscribeCount.ShouldBe(1); factory.Created[0].LastSubscribedRefs.ShouldBe(new[] { SpeedPath }); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new RestartDriver("c1", DriverId, "tester", Guid.NewGuid())); @@ -124,7 +124,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase "03/S16 defect (an operator pressed Restart to recover a driver and silently killed it)"); factory.Created[1].LastSubscribedRefs.ShouldBe(new[] { SpeedPath }); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -158,7 +158,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created[0].SubscribeAlarmsCount.ShouldBeGreaterThanOrEqualTo(1); factory.Created[0].LastSubscribedAlarmRefs.ShouldBe(new[] { TempPath }); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new RestartDriver("c1", DriverId, "tester", Guid.NewGuid())); @@ -174,7 +174,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase // The value half must survive the alarm half being present. factory.Created[1].LastSubscribedRefs.ShouldBe(new[] { SpeedPath }); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -209,7 +209,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase AwaitAssert( () => factory.Created[0].LastSubscribedRefs.ShouldBe(new[] { SpeedPath }), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var revB = V3ArtifactSeeder.SeedV3Deployment( db, RevB, @@ -226,7 +226,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase new[] { TempPath }, "the newly-spawned child is on the PREVIOUS revision's refs — the spawn-time set was not superseded"); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // A Restart on top of that must hand the new child B's set, not A's — i.e. the authority really is // repopulated WHOLE on every apply rather than accumulated. @@ -237,7 +237,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(3); factory.Created[2].LastSubscribedRefs.ShouldBe(new[] { TempPath }); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -271,7 +271,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(1); factory.Created[0].SubscribeAlarmsCount.ShouldBeGreaterThanOrEqualTo(1); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new RestartDriver("c1", DriverId, "tester", Guid.NewGuid())); @@ -288,7 +288,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase respawned.SubscribeCount.ShouldBe( 0, "an EMPTY value set must route to Unsubscribe / no-op, never to a spurious subscribe"); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -322,7 +322,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(1); factory.Created[0].LastSubscribedAlarmRefs.ShouldBe(new[] { AlarmXPath }, ignoreOrder: true); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Revision B WIDENS the alarm set: X stays, Y is added. var revB = V3ArtifactSeeder.SeedV3Deployment( @@ -351,7 +351,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase "the driver is still subscribed to the PREVIOUS revision's alarm refs — the alarm " + "subscription latched on the transient spawn-time set and never followed the new one"); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -384,7 +384,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(1); factory.Created[0].SubscribeCount.ShouldBe(1); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var driver = factory.Created[0]; driver.InitializeCount.ShouldBe(1); @@ -402,7 +402,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase AwaitAssert( () => driver.ShutdownCount.ShouldBeGreaterThanOrEqualTo( 1, "the crash seam did not trip supervision — nothing below is testing a restart"), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); AwaitAssert( () => @@ -417,7 +417,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase 2, "the restarted child never re-subscribed — its desired set died with the old instance"); driver.LastSubscribedRefs.ShouldBe(new[] { SpeedPath }); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -457,7 +457,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(1); factory.Created[0].InitializeCount.ShouldBe(1); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var driver = factory.Created[0]; // A ReprovisionRequested that did NOT come from the child the host currently holds. @@ -470,7 +470,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase // the absence is asserted — otherwise "nothing happened yet" would pass for "it was ignored". var coordinator = CreateTestProbe(); host.Tell(new Commons.Messages.Fleet.GetDiagnostics(CorrelationId.NewId()), coordinator.Ref); - coordinator.ExpectMsg(PresenceBudget); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.InitializeCount.ShouldBe( 1, @@ -515,7 +515,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(1); factory.Created[0].SubscribeCount.ShouldBe(1); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var driver = factory.Created[0]; // Crash 1: the first reprovision has no prior stamp, so it applies immediately. @@ -526,14 +526,14 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase driver.InitializeCount.ShouldBe(2); driver.SubscribeCount.ShouldBe(2, "the re-subscribe must land before the second crash is armed"); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Crash 2, immediately after - well inside the window. driver.FireMalformedDataChange(SpeedPath); AwaitAssert( () => driver.ShutdownCount.ShouldBeGreaterThanOrEqualTo( 2, "the second crash seam did not trip supervision - nothing below is testing a backoff"), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.InitializeCount.ShouldBe( 2, "the second reprovision was applied immediately - the backoff is not rate-limiting anything"); @@ -542,7 +542,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase AwaitAssert( () => driver.InitializeCount.ShouldBe( 3, "the deferred reprovision was DROPPED, not deferred"), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -580,7 +580,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Single(d => d.DriverInstanceId == "DI-TAGLESS") .InitializeCount.ShouldBe(1); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); factory.Created.Single(d => d.DriverInstanceId == "DI-TAGLESS") .SubscribeCount.ShouldBe( @@ -612,7 +612,7 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase factory.Created.Count.ShouldBe(1); factory.Created[0].SubscribeCount.ShouldBe(1); }, - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var driver = factory.Created[0]; host.Tell(new ReconnectDriver("c1", DriverId, "tester", Guid.NewGuid())); @@ -625,6 +625,6 @@ public sealed class DriverHostActorRestartTests : RuntimeActorTestBase 2, "the reconnected driver never re-subscribed its (surviving) desired set"); driver.LastSubscribedRefs.ShouldBe(new[] { SpeedPath }); }, - duration: TimeSpan.FromSeconds(30)); + duration: TimeSpan.FromSeconds(30), cancellationToken: TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRoleViewTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRoleViewTests.cs index 92d376e9f..950fd3c70 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRoleViewTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorRoleViewTests.cs @@ -52,7 +52,7 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase { var view = SpawnAndTellRole(RedundancyRole.Secondary, driverMemberCount: 2); - AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -73,7 +73,7 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase ], CorrelationId.NewId())); - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -82,7 +82,7 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase var view = SpawnAndTellRole(RedundancyRole.Primary, driverMemberCount: 2); // Published, not current — `true` is the seed. See the note in the unknown-role theory. - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -118,7 +118,7 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase // first poll — before the actor has processed the snapshot — and therefore passes just as // happily when the publish is wrong or absent. Verified: with the old write-gate verdict // restored, the current-value form stayed green and only this form goes red. - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// A demotion must move the view, not just an initial promotion. @@ -129,10 +129,10 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase var host = SpawnHost(view, driverMemberCount: 2); host.Tell(Snapshot(TestNode, RedundancyRole.Primary)); - AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Snapshot(TestNode, RedundancyRole.Secondary)); - AwaitAssert(() => view.Published.ShouldBe([true, false]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([true, false]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -167,7 +167,7 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase ], CorrelationId.NewId())); - AwaitAssert(() => view.Published.ShouldBe([expected]), duration: Timeout); + AwaitAssert(() => view.Published.ShouldBe([expected]), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); Sys.Stop(host); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorScriptedAlarmRestartTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorScriptedAlarmRestartTests.cs index 2ff682f2a..e228b1725 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorScriptedAlarmRestartTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorScriptedAlarmRestartTests.cs @@ -75,7 +75,7 @@ public sealed class DriverHostActorScriptedAlarmRestartTests : RuntimeActorTestB alarmStateStore: new InMemoryAlarmStateStore())); V3ArtifactSeeder.Dispatch(host, coordinator, deploymentId, RevA); - var first = mux.FishForMessage(r => r.TagRefs.Contains("M.T"), Timeout); + var first = mux.FishForMessage(r => r.TagRefs.Contains("M.T"), Timeout, cancellationToken: TestContext.Current.CancellationToken); var alarmHost = first.Subscriber; // The crash: a batch item with a null TagId makes the upstream Push throw (the documented unguarded @@ -84,20 +84,20 @@ public sealed class DriverHostActorScriptedAlarmRestartTests : RuntimeActorTestB { new VirtualTagActor.DependencyValueChanged(null!, 1, DateTime.UtcNow), })); - mux.FishForMessage(u => u.Subscriber.Equals(alarmHost), Timeout); + mux.FishForMessage(u => u.Subscriber.Equals(alarmHost), Timeout, cancellationToken: TestContext.Current.CancellationToken); // The parent re-applies on the restart signal: interest is registered again by the new incarnation. - mux.FishForMessage(r => r.TagRefs.Contains("M.T"), Timeout); + mux.FishForMessage(r => r.TagRefs.Contains("M.T"), Timeout, cancellationToken: TestContext.Current.CancellationToken); // Inputs work: a dependency push activates the alarm. alarmHost.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); publish.FishForMessage( - m => m.AlarmNodeId == "alm-1" && m.State.Active && !m.State.Acknowledged, Timeout); + m => m.AlarmNodeId == "alm-1" && m.State.Active && !m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken); // Acks work: the restarted engine owns alm-1 again, so the command is not filtered out as unowned. alarmHost.Tell(new AlarmCommand(AlarmId: "alm-1", Operation: "Acknowledge", User: "alice", Comment: null, UnshelveAtUtc: null)); publish.FishForMessage( - m => m.AlarmNodeId == "alm-1" && m.State.Acknowledged, Timeout) + m => m.AlarmNodeId == "alm-1" && m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken) .State.Acknowledged.ShouldBeTrue(); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorSourceRemovedTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorSourceRemovedTests.cs index 0e7e48ac9..4cf7ca254 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorSourceRemovedTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorSourceRemovedTests.cs @@ -132,7 +132,7 @@ public sealed class DriverHostActorSourceRemovedTests : RuntimeActorTestBase AwaitAssert( () => Replayed().Keys.ToArray().ShouldBe(new[] { Path2 }, "DI-1 was removed: its last Good must not replay; DI-2's must"), - PresenceBudget); + PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Review I-1: a burst the removed child sent after the eviction (it stops asynchronously, so it keeps @@ -172,11 +172,11 @@ public sealed class DriverHostActorSourceRemovedTests : RuntimeActorTestBase var revB = V3ArtifactSeeder.SeedV3Deployment(_db, RevB, [.. BothDrivers, ("DI-1", "Modbus", "dev1", "temp", "Double", false)], NoRefs, enabled: true); V3ArtifactSeeder.Dispatch(_host, _coordinator, revB, RevB); - AwaitAssert(() => _factory.Created.Count.ShouldBe(3), PresenceBudget); + AwaitAssert(() => _factory.Created.Count.ShouldBe(3), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var respawned = Driver("DI-1"); // The spawn hands the child the PREVIOUS apply's set, then the bulk push re-subscribes with the new one // (which names the added tag); a value fired between the two can be lost, so wait for the final set. - AwaitAssert(() => (respawned.LastSubscribedRefs ?? []).ShouldContain("Plant/Modbus/dev1/temp"), PresenceBudget); + AwaitAssert(() => (respawned.LastSubscribedRefs ?? []).ShouldContain("Plant/Modbus/dev1/temp"), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); ShouldNotBeTheOldGood(Replayed(), Path1, 11.0); @@ -195,7 +195,7 @@ public sealed class DriverHostActorSourceRemovedTests : RuntimeActorTestBase ArrangeTwoDriversWithRetainedValues(); _host.Tell(new RestartDriver("c1", "DI-1", "tester", Guid.NewGuid())); - AwaitAssert(() => _factory.Created.Count.ShouldBe(3), PresenceBudget); + AwaitAssert(() => _factory.Created.Count.ShouldBe(3), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var restarted = Driver("DI-1"); AwaitSubscribed("DI-1", Path1); @@ -214,7 +214,7 @@ public sealed class DriverHostActorSourceRemovedTests : RuntimeActorTestBase // Crash DI-1's child: Akka restarts it around the SAME driver object, and the host reprovisions it. di1.FireMalformedDataChange(Path1); - AwaitAssert(() => di1.InitializeCount.ShouldBe(2), PresenceBudget); + AwaitAssert(() => di1.InitializeCount.ShouldBe(2), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); _factory.Created.Count.ShouldBe(2, "a supervision restart re-uses the driver; no new one is created"); var replay = Replayed(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorStaleDropTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorStaleDropTests.cs index 442f3909a..524856ddc 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorStaleDropTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorStaleDropTests.cs @@ -36,11 +36,11 @@ public sealed class DriverHostActorStaleDropTests : RuntimeActorTestBase // Barrier: Stale answers RouteNodeWrite synchronously, so a reply proves the host is in Stale. var asker = CreateTestProbe(); host.Tell(new DriverHostActor.RouteNodeWrite("eq-x/speed", 0.0, AddressSpaceRealm.Uns), asker.Ref); - asker.ExpectMsg(PresenceBudget).Reason!.ShouldContain("stale"); + asker.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Reason!.ShouldContain("stale"); // Positive control: an unknown message IS reported as unhandled by this host. host.Tell("not a message the host knows"); - unhandled.ExpectMsg(m => ReferenceEquals(m.Recipient, host) || m.Recipient.Equals(host), PresenceBudget); + unhandled.ExpectMsg(m => ReferenceEquals(m.Recipient, host) || m.Recipient.Equals(host), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new DriverInstanceActor.AttributeValueBatchPublished( "DI-1", new[] { new DriverInstanceActor.AttributeValueItem("Plant/x/y", 1.0, OpcUaStatus.Good, DateTime.UtcNow) })); @@ -49,9 +49,9 @@ public sealed class DriverHostActorStaleDropTests : RuntimeActorTestBase AlarmSeverity.High, DateTime.UtcNow))); // Barrier again, then the absence window. host.Tell(new DriverHostActor.RouteNodeWrite("eq-x/speed", 0.0, AddressSpaceRealm.Uns), asker.Ref); - asker.ExpectMsg(PresenceBudget); + asker.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); - unhandled.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + unhandled.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } private sealed class Handle : IAlarmSubscriptionHandle diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorTests.cs index eda42d745..c0cacb780 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorTests.cs @@ -32,7 +32,7 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase var deploymentId = SeedDeployment(db, RevA, DeploymentStatus.Sealed); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + var ack = coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); ack.NodeId.ShouldBe(TestNode); } @@ -63,7 +63,7 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase // Dispatch the SAME deployment again. actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + var ack = coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); // No new NodeDeploymentState row got added — the rev matched, so nothing changed. @@ -83,7 +83,7 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentB, RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); AwaitAssert(() => { @@ -92,7 +92,7 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase s.NodeId == TestNode.Value && s.DeploymentId == deploymentB.Value); row.Status.ShouldBe(NodeDeploymentStatus.Applied); row.AppliedAtUtc.ShouldNotBeNull(); - }, duration: TimeSpan.FromSeconds(3)); + }, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that an orphaned Applying row on bootstrap is replayed. @@ -119,7 +119,7 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase Sys.ActorOf(DriverHostActor.Props(db, TestNode, coordinator.Ref)); // PreStart should replay → ApplyAck back to coordinator with the new correlation id. - var ack = coordinator.ExpectMsg(TimeSpan.FromSeconds(5)); + var ack = coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); ack.DeploymentId.ShouldBe(deploymentId); ack.Outcome.ShouldBe(ApplyAckOutcome.Applied); @@ -148,9 +148,9 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); - var apply = alarmHost.ExpectMsg(TimeSpan.FromSeconds(5)); + var apply = alarmHost.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); var plan = apply.Plans.ShouldHaveSingleItem(); plan.ScriptedAlarmId.ShouldBe("al-1"); plan.EquipmentId.ShouldBe("eq-1"); @@ -192,7 +192,7 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase localRoles: new HashSet { "driver" }, scriptedAlarmHostOverride: alarmHost.Ref)); - var apply = alarmHost.ExpectMsg(TimeSpan.FromSeconds(5)); + var apply = alarmHost.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); var plan = apply.Plans.ShouldHaveSingleItem(); plan.ScriptedAlarmId.ShouldBe("al-1"); plan.EquipmentId.ShouldBe("eq-1"); @@ -233,9 +233,9 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase opcUaPublishActor: publish.Ref)); actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(PresenceBudget).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); - var rebuild = publish.ExpectMsg(PresenceBudget); + var rebuild = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); rebuild.DeploymentId.ShouldBe(deploymentId); rebuild.Artifact.ShouldNotBeNull(); rebuild.Artifact.ShouldBe(seeded); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorUnreadableArtifactTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorUnreadableArtifactTests.cs index c8d16e321..8ab378328 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorUnreadableArtifactTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorUnreadableArtifactTests.cs @@ -59,12 +59,12 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas var (actor, coordinator) = SpawnHostAndApply(db, SeedV3Deployment(db, RevA), factory); // Baseline: the child is up and subscribed to its one raw tag. - AwaitAssert(() => factory.LastSubscribedRefs.ShouldNotBeNull()!.ShouldContain(SpeedRef), duration: Timeout); + AwaitAssert(() => factory.LastSubscribedRefs.ShouldNotBeNull()!.ShouldContain(SpeedRef), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldContain("drv-1"); // The next dispatch's artifact comes back as no bytes at all. actor.Tell(new DispatchDeployment(SeedUnreadableDeployment(db, RevB), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // The ACK precedes the SubscribeBulk pass, and the child's unsubscribe is a further async self-tell, // so settle before asserting an ABSENCE. SettleWindow is calibrated by @@ -96,13 +96,13 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas var factory = new SubscribingDriverFactory("Modbus"); var (actor, coordinator) = SpawnHostAndApply(db, SeedV3Deployment(db, RevA), factory); - AwaitAssert(() => factory.LastSubscribedRefs.ShouldNotBeNull()!.ShouldContain(SpeedRef), duration: Timeout); + AwaitAssert(() => factory.LastSubscribedRefs.ShouldNotBeNull()!.ShouldContain(SpeedRef), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); factory.ShutdownCount.ShouldBe(0); actor.Tell(new DispatchDeployment(SeedTaglessDriverDeployment(db, RevB), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); - AwaitAssert(() => factory.ShutdownCount.ShouldBeGreaterThanOrEqualTo(1), duration: SettleWindow); + AwaitAssert(() => factory.ShutdownCount.ShouldBeGreaterThanOrEqualTo(1), duration: SettleWindow, cancellationToken: TestContext.Current.CancellationToken); AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldContain("drv-1"); // respawned under the same id } @@ -116,15 +116,15 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas var factory = new SubscribingDriverFactory("Modbus"); var (actor, coordinator) = SpawnHostAndApply(db, SeedV3Deployment(db, RevA), factory); - AwaitAssert(() => factory.LastSubscribedRefs.ShouldNotBeNull()!.ShouldContain(SpeedRef), duration: Timeout); + AwaitAssert(() => factory.LastSubscribedRefs.ShouldNotBeNull()!.ShouldContain(SpeedRef), duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); // A real artifact that simply carries no drivers — an operator deleting the last driver. actor.Tell(new DispatchDeployment(SeedDriverlessDeployment(db, RevB), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); AwaitAssert( () => AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldNotContain("drv-1"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -142,7 +142,7 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas AskDiagnostics(actor).CurrentRevision.ShouldBe(RevA); actor.Tell(new DispatchDeployment(SeedUnreadableDeployment(db, RevB), RevB, CorrelationId.NewId())); - var ack = coordinator.ExpectMsg(Timeout); + var ack = coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); ack.Outcome.ShouldBe(ApplyAckOutcome.Failed); ack.FailureReason.ShouldNotBeNullOrWhiteSpace(); @@ -165,16 +165,16 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas var deploymentId = SeedUnreadableDeployment(db, RevB); actor.Tell(new DispatchDeployment(deploymentId, RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Failed); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed); // The ConfigDb recovers: the row now carries a real artifact, under the SAME revision. SetArtifact(db, deploymentId, TwoDriverArtifact()); actor.Tell(new DispatchDeployment(deploymentId, RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); AwaitAssert( () => AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldContain("drv-2"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); AskDiagnostics(actor).CurrentRevision.ShouldBe(RevB); } @@ -197,7 +197,7 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas telemetryHub: hub)); actor.Tell(new DispatchDeployment(SeedV3Deployment(db, RevA), RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); hub.Emit(new ZB.MOM.WW.OtOpcUa.Runtime.Telemetry.TelemetryItem.Health(new( "c1", "drv-1", "Healthy", null, null, 0, DateTime.UtcNow))); @@ -214,10 +214,10 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas } actor.Tell(new DispatchDeployment(SeedDriverlessDeployment(db, RevB), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); AwaitAssert( () => AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldNotContain("drv-1"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); using var after = hub.Subscribe(16); var replayed = new List(); @@ -261,8 +261,8 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas telemetryHub: hub)); actor.Tell(new DispatchDeployment(SeedV3Deployment(db, RevA), RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); - invokerFactory.SubscribeEntered.Wait(Timeout).ShouldBeTrue("the child never reached its subscribe"); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + invokerFactory.SubscribeEntered.Wait(Timeout, TestContext.Current.CancellationToken).ShouldBeTrue("the child never reached its subscribe"); // Resilience counters for the running driver and for an instance this host does not own. tracker.RecordBreakerOpen("drv-1", "dev1", DateTime.UtcNow); @@ -271,16 +271,16 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas ReplayedResilienceIds(hub).ShouldContain("drv-1"); // control: drv-1 is primed before the removal actor.Tell(new DispatchDeployment(SeedDriverlessDeployment(db, RevB), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); AwaitAssert( () => AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldNotContain("drv-1"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); // Let the removed child's held subscribe run to completion through the REAL invoker (RecordCallStart / // RecordSuccess / RecordCallComplete). Positive control: the late write has demonstrably happened before // the tick below, so the absence asserted after it is not "nothing has arrived yet". invokerFactory.OpenGate(); - invokerFactory.SubscribeCompleted.Wait(Timeout).ShouldBeTrue("the held subscribe never completed"); + invokerFactory.SubscribeCompleted.Wait(Timeout, TestContext.Current.CancellationToken).ShouldBeTrue("the held subscribe never completed"); // The publisher's next tick, after the removal AND after the late writer. PublisherTick(tracker, hub); @@ -314,19 +314,19 @@ public sealed class DriverHostActorUnreadableArtifactTests : RuntimeActorTestBas telemetryHub: new ZB.MOM.WW.OtOpcUa.Runtime.Telemetry.TelemetryLocalHub())); actor.Tell(new DispatchDeployment(SeedV3Deployment(db, RevA), RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); actor.Tell(new DispatchDeployment(SeedDriverlessDeployment(db, RevB), RevB, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); AwaitAssert( () => AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldNotContain("drv-1"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); var revC = RevisionHash.Parse(new string('c', 64)); actor.Tell(new DispatchDeployment(SeedV3Deployment(db, revC), revC, CorrelationId.NewId())); - coordinator.ExpectMsg(Timeout).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); AwaitAssert( () => AskDiagnostics(actor).Drivers.Select(d => d.Name).ShouldContain("drv-1"), - duration: Timeout); + duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); // An explicit counter for the re-added instance is retained, not swallowed as a late write. tracker.RecordBreakerOpen("drv-1", "dev1", DateTime.UtcNow); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorVirtualTagTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorVirtualTagTests.cs index 640b7509b..7b7a2a8ff 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorVirtualTagTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorVirtualTagTests.cs @@ -50,9 +50,9 @@ public sealed class DriverHostActorVirtualTagTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); - var apply = vtHost.ExpectMsg(TimeSpan.FromSeconds(5)); + var apply = vtHost.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); var plan = apply.Plans.ShouldHaveSingleItem(); plan.VirtualTagId.ShouldBe("vt-1"); plan.EquipmentId.ShouldBe("eq-1"); @@ -80,7 +80,7 @@ public sealed class DriverHostActorVirtualTagTests : RuntimeActorTestBase virtualTagEvaluator: NullVirtualTagEvaluator.Instance, virtualTagHostOverride: vtHost.Ref)); - var apply = vtHost.ExpectMsg(TimeSpan.FromSeconds(5)); + var apply = vtHost.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); apply.Plans.ShouldHaveSingleItem().VirtualTagId.ShouldBe("vt-1"); } @@ -103,8 +103,8 @@ public sealed class DriverHostActorVirtualTagTests : RuntimeActorTestBase actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId())); - coordinator.ExpectMsg(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied); - var apply = vtHost.ExpectMsg(TimeSpan.FromSeconds(5)); + coordinator.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied); + var apply = vtHost.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); apply.Plans.Select(p => (p.VirtualTagId, p.Enabled)) .OrderBy(x => x.VirtualTagId, StringComparer.Ordinal) .ShouldBe(new[] { ("vt-1", true), ("vt-off", false) }); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteAskTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteAskTests.cs index 2ace8c577..e8acd2320 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteAskTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteAskTests.cs @@ -49,10 +49,10 @@ public sealed class DriverHostActorWriteAskTests : RuntimeActorTestBase var asker = RouteHeldWrite(host, driver, clock); clock.Advance(TimeSpan.FromSeconds(8.5)); - asker.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + asker.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); driver.Release(); - var result = asker.ExpectMsg(Timeout); + var result = asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); result.Success.ShouldBeTrue(result.Reason); } @@ -71,10 +71,10 @@ public sealed class DriverHostActorWriteAskTests : RuntimeActorTestBase var asker = RouteHeldWrite(host, driver, clock); clock.Advance(bounds.Timeout); - asker.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + asker.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); clock.Advance(bounds.AbandonGrace); - var result = asker.ExpectMsg(Timeout); + var result = asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Reason!.ShouldContain("800A0000", customMessage: "the child's BadTimeout, not the host Ask's \"write timeout\""); result.Reason!.ShouldContain("within 9000 ms"); @@ -102,7 +102,7 @@ public sealed class DriverHostActorWriteAskTests : RuntimeActorTestBase ask.IsCompleted.ShouldBeFalse("the Ask ended before its clock deadline"); clock.Advance(TimeSpan.FromMilliseconds(1)); - AwaitCondition(() => ask.IsCompleted, Timeout, TimeSpan.FromMilliseconds(20)); + AwaitCondition(() => ask.IsCompleted, Timeout, TimeSpan.FromMilliseconds(20), cancellationToken: TestContext.Current.CancellationToken); ask.IsCompletedSuccessfully.ShouldBeFalse(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteRoutingTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteRoutingTests.cs index f94acd8f4..27ac44291 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteRoutingTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorWriteRoutingTests.cs @@ -63,7 +63,7 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase recorder.Writes.Count.ShouldBe(1); recorder.Writes[0].FullReference.ShouldBe(RawPath); // the driver writes by RawPath, not the UNS id recorder.Writes[0].Value.ShouldBe(123.0); - }, duration: Timeout); + }, duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// On the PRIMARY, a RouteNodeWrite for the ns-qualified RAW NodeId resolves to the same driver @@ -84,7 +84,7 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase recorder.Writes.Count.ShouldBe(1); recorder.Writes[0].FullReference.ShouldBe(RawPath); recorder.Writes[0].Value.ShouldBe(456.0); - }, duration: Timeout); + }, duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -151,7 +151,7 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase var asker = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite($"ns=3;s={UnsNodeId}", 123.0, AddressSpaceRealm.Uns), asker.Ref); - var result = asker.ExpectMsg(Timeout); + var result = asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Reason.ShouldBe("not primary"); recorder.Writes.ShouldBeEmpty(); @@ -170,7 +170,7 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase var asker = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite("ns=3;s=filling/line1/station1/does-not-exist", 1.0, AddressSpaceRealm.Uns), asker.Ref); - var result = asker.ExpectMsg(Timeout); + var result = asker.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Reason.ShouldNotBeNull(); recorder.Writes.ShouldBeEmpty(); @@ -190,7 +190,7 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase var asker = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite($"ns=3;s={UnsNodeId}", 123.0, AddressSpaceRealm.Uns), asker.Ref); - var result = asker.ExpectMsg(TimeSpan.FromSeconds(2)); + var result = asker.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Reason.ShouldNotBeNull(); result.Reason!.ShouldContain("stale"); @@ -215,12 +215,12 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase // Write to the RAW node A/B/C/D → drv-1, forwarded by the raw tag's RawPath A/B/C/D. var asker1 = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite("ns=2;s=A/B/C/D", 111.0, AddressSpaceRealm.Raw), asker1.Ref); - asker1.ExpectMsg(Timeout).Success.ShouldBeTrue(); + asker1.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Success.ShouldBeTrue(); // Write to the UNS node A/B/C/D → drv-2, forwarded by the BACKING raw tag's RawPath X/Y/Z/W. var asker2 = CreateTestProbe(); actor.Tell(new DriverHostActor.RouteNodeWrite("ns=3;s=A/B/C/D", 222.0, AddressSpaceRealm.Uns), asker2.Ref); - asker2.ExpectMsg(Timeout).Success.ShouldBeTrue(); + asker2.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).Success.ShouldBeTrue(); AwaitAssert(() => { @@ -233,7 +233,7 @@ public sealed class DriverHostActorWriteRoutingTests : RuntimeActorTestBase w2.Count.ShouldBe(1); w2[0].FullReference.ShouldBe("X/Y/Z/W"); // the backing RawPath, NOT the shared bare id w2[0].Value.ShouldBe(222.0); - }, duration: Timeout); + }, duration: Timeout, cancellationToken: TestContext.Current.CancellationToken); } /// Spawns the host with the recording driver factory, dispatches the deployment, and waits for diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostCommsLossRecoveryTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostCommsLossRecoveryTests.cs index a2d0b711a..ca7eb36e4 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostCommsLossRecoveryTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostCommsLossRecoveryTests.cs @@ -60,26 +60,26 @@ public sealed class DriverHostCommsLossRecoveryTests : RuntimeActorTestBase // Steady state: the driver's FIRST subscribe already pushed 5 Good, and the host fanned it to // raw + UNS. (The push on subscribe is the behaviour under test, so it is also how the pre-outage // value gets there — no separate seeding call, which would leave an extra batch in the probe.) - var first = publish.ExpectMsg(PresenceBudget); + var first = publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); first.Items.Select(i => i.NodeId).ShouldBe(new[] { SpeedRef, UnsSpeedNode }, ignoreOrder: true); first.Items.ShouldAllBe(i => Equals(i.Value, 5.0)); // The outage: the child re-enters Reconnecting and announces comms loss to the host. host.Tell(new ReconnectDriver("c1", "drv-recover", "test-operator", Guid.NewGuid())); - var staled = publish.ExpectMsg(ReconnectBudget); + var staled = publish.ExpectMsg(ReconnectBudget, cancellationToken: TestContext.Current.CancellationToken); staled.Items.Select(i => i.NodeId).ShouldBe(new[] { SpeedRef, UnsSpeedNode }, ignoreOrder: true); staled.Items.ShouldAllBe(i => i.StatusCode == OpcUaStatus.BadNoCommunication); // Recovery: the reconnect re-subscribes, and the driver pushes the SAME value it held before. - var recovered = publish.ExpectMsg(ReconnectBudget); + var recovered = publish.ExpectMsg(ReconnectBudget, cancellationToken: TestContext.Current.CancellationToken); recovered.Items.Select(i => i.NodeId).ShouldBe(new[] { SpeedRef, UnsSpeedNode }, ignoreOrder: true); recovered.Items.ShouldAllBe(i => i.StatusCode == OpcUaStatus.Good); recovered.Items.ShouldAllBe(i => Equals(i.Value, 5.0)); // …and the audit finds nothing outstanding. EventFilter.Warning(contains: "still BadNoCommunication") - .Expect(0, AuditDelay + TimeSpan.FromSeconds(3), () => { }); + .Expect(0, AuditDelay + TimeSpan.FromSeconds(3), () => { }, TestContext.Current.CancellationToken); meter.SumWithTag("driver_instance", "drv-recover").ShouldBe(0); } @@ -97,7 +97,7 @@ public sealed class DriverHostCommsLossRecoveryTests : RuntimeActorTestBase using var meter = new MeterRecorder("otopcua.driver.comms_latched_refs"); driver.FireDataChange(SpeedRef, 5.0, OpcUaStatus.Good); - publish.ExpectMsg(PresenceBudget); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); EventFilter.Warning(contains: "still BadNoCommunication").ExpectOne(ReconnectBudget, () => { @@ -106,10 +106,10 @@ public sealed class DriverHostCommsLossRecoveryTests : RuntimeActorTestBase // Nothing Good ever arrives — assert it rather than merely not looking. publish.ExpectNoMsg(TimeSpan.FromSeconds(1)); - }); + }, TestContext.Current.CancellationToken); // One ref (the raw tag); its UNS mirror shares it. - AwaitCondition(() => meter.SumWithTag("driver_instance", "drv-silent") == 1, TimeSpan.FromSeconds(5)); + AwaitCondition(() => meter.SumWithTag("driver_instance", "drv-silent") == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); driver.SubscribeCount.ShouldBeGreaterThanOrEqualTo(2, "the child must actually have re-subscribed — otherwise this proves nothing about the push"); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorBatchForwardTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorBatchForwardTests.cs index 1efaed5ea..ad4fef2bf 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorBatchForwardTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorBatchForwardTests.cs @@ -35,14 +35,14 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase // #477: DriverInstanceActor Tells the parent ConnectivityChanged on each connect/disconnect; // this suite asserts on the value forwards only, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b", "tag-c" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); var sourceTs = new DateTime(2026, 8, 15, 10, 0, 0, DateTimeKind.Utc); var serverTs = new DateTime(2026, 8, 15, 10, 0, 5, DateTimeKind.Utc); @@ -52,7 +52,7 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase // Null source timestamp — the server timestamp is the fallback, per OnDataChangeForward. new DataChangeItem("tag-c", new DataValueSnapshot(null, OpcUaStatus.BadNoCommunication, null, serverTs))); - var batch = parent.ExpectMsg(PresenceBudget); + var batch = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.DriverInstanceId.ShouldBe(driver.DriverInstanceId); batch.Items.Count.ShouldBe(3); @@ -73,7 +73,7 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase batch.Items[2].TimestampUtc.ShouldBe(serverTs, "a null SourceTimestampUtc falls back to the server timestamp"); // Exactly ONE message for the burst — no per-item trailer. - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// @@ -88,14 +88,14 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase var driver = new BatchSubscribableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); // Deterministic half of the assertion: the per-tag handler was never attached at all. ExpectNoMsg // below only proves nothing arrived within its window; this proves it structurally cannot. @@ -106,12 +106,12 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase new DataChangeItem("tag-a", new DataValueSnapshot(1, OpcUaStatus.Good, ts, ts)), new DataChangeItem("tag-b", new DataValueSnapshot(2, OpcUaStatus.Good, ts, ts))); - var batch = parent.ExpectMsg(PresenceBudget); + var batch = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Count.ShouldBe(2); // The per-tag raises for the SAME burst produced nothing extra — no second batch, no // single-item batch. Anything here means both handlers were attached. - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); } /// @@ -125,18 +125,18 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase var driver = new SubscribableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); driver.FireDataChange("tag-a", value: 3.14, statusCode: OpcUaStatus.Good); - var batch = parent.ExpectMsg(PresenceBudget); + var batch = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.DriverInstanceId.ShouldBe(driver.DriverInstanceId); batch.Items.Count.ShouldBe(1); batch.Items[0].FullReference.ShouldBe("tag-a"); @@ -155,25 +155,25 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase var driver = new BatchSubscribableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); driver.OnDataChangeBatchSubscriberCount.ShouldBe(1, "the batch handler is attached for an IBatchSubscribable driver"); actor.Tell(new DriverInstanceActor.Unsubscribe()); - AwaitCondition(() => driver.OnDataChangeBatchSubscriberCount == 0, PresenceBudget); + AwaitCondition(() => driver.OnDataChangeBatchSubscriberCount == 0, PresenceBudget, TestContext.Current.CancellationToken); var ts = new DateTime(2026, 8, 15, 12, 0, 0, DateTimeKind.Utc); driver.FireBatch(includePerTagRaises: true, new DataChangeItem("tag-a", new DataValueSnapshot(42, OpcUaStatus.Good, ts, ts))); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); } /// @@ -187,26 +187,26 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase var driver = new BatchSubscribableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromSeconds(30))); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromSeconds(30)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); driver.OnDataChangeBatchSubscriberCount.ShouldBe(1); actor.Tell(new DriverInstanceActor.DisconnectObserved("backend went away")); // Race window — once the disconnect is processed the handler is gone and a late burst is inert. - AwaitCondition(() => driver.OnDataChangeBatchSubscriberCount == 0, PresenceBudget); + AwaitCondition(() => driver.OnDataChangeBatchSubscriberCount == 0, PresenceBudget, TestContext.Current.CancellationToken); var ts = new DateTime(2026, 8, 15, 13, 0, 0, DateTimeKind.Utc); driver.FireBatch(includePerTagRaises: true, new DataChangeItem("tag-a", new DataValueSnapshot(42, OpcUaStatus.Good, ts, ts))); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// @@ -220,19 +220,19 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase var driver = new BatchSubscribableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); driver.OnDataChangeBatchSubscriberCount.ShouldBe(1); Sys.Stop(actor); - AwaitCondition(() => driver.OnDataChangeBatchSubscriberCount == 0, PresenceBudget); + AwaitCondition(() => driver.OnDataChangeBatchSubscriberCount == 0, PresenceBudget, TestContext.Current.CancellationToken); } /// @@ -247,19 +247,19 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase var driver = new BatchSubscribableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget); + AwaitCondition(() => driver.InitializeCount > 0, PresenceBudget, TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - PresenceBudget); + PresenceBudget, TestContext.Current.CancellationToken); driver.FireBatch(includePerTagRaises: false /* no items — an empty burst */); driver.FireNullBatch(); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); driver.OnDataChangeBatchSubscriberCount.ShouldBe(1, "a restart would have detached the handler in PostStop"); @@ -268,7 +268,7 @@ public sealed class DriverInstanceActorBatchForwardTests : RuntimeActorTestBase driver.FireBatch(includePerTagRaises: false, new DataChangeItem("tag-a", new DataValueSnapshot(5, OpcUaStatus.Good, ts, ts))); - var batch = parent.ExpectMsg(PresenceBudget); + var batch = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Count.ShouldBe(1); batch.Items[0].FullReference.ShouldBe("tag-a"); batch.Items[0].Value.ShouldBe(5); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorCommsLossWatchdogTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorCommsLossWatchdogTests.cs index 8010dd024..4605bae9b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorCommsLossWatchdogTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorCommsLossWatchdogTests.cs @@ -81,7 +81,7 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest }; var (_, parent) = ConnectDriver(driver); - parent.ExpectMsg(TimeSpan.FromSeconds(3)) + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeFalse(); } @@ -116,10 +116,10 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest AwaitCondition( () => driver.HealthReadCount >= 12, TimeSpan.FromSeconds(5), - "health-poll ticks did not run — the absence assertion below would be vacuous"); + "health-poll ticks did not run — the absence assertion below would be vacuous", TestContext.Current.CancellationToken); driver.ObservedSpan.ShouldBeGreaterThan(FastTimeout, "the observation window never exceeded the trip threshold"); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// @@ -138,9 +138,9 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest AwaitCondition( () => driver.HealthReadCount >= 12, TimeSpan.FromSeconds(5), - "health-poll ticks did not run — the absence assertion below would be vacuous"); + "health-poll ticks did not run — the absence assertion below would be vacuous", TestContext.Current.CancellationToken); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// @@ -157,7 +157,7 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest }; var (_, parent) = ConnectDriver(driver); - parent.ExpectMsg(TimeSpan.FromSeconds(3)) + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeFalse(); } @@ -174,9 +174,9 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest AwaitCondition( () => driver.HealthReadCount >= 12, TimeSpan.FromSeconds(5), - "health-poll ticks did not run — the absence assertion below would be vacuous"); + "health-poll ticks did not run — the absence assertion below would be vacuous", TestContext.Current.CancellationToken); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// @@ -193,13 +193,13 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest }; var (_, parent) = ConnectDriver(driver, reconnectInterval: TimeSpan.FromMilliseconds(100)); - parent.ExpectMsg(TimeSpan.FromSeconds(3)) + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeFalse(); // The device comes back: the next initialise attempt succeeds and reads resume. driver.Health = new DriverHealth(DriverState.Healthy, DateTime.UtcNow, null); - parent.ExpectMsg(TimeSpan.FromSeconds(5)) + parent.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeTrue(); driver.InitializeCount.ShouldBeGreaterThan(1, "the driver was never re-initialised"); } @@ -220,13 +220,13 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest }; var (_, parent) = ConnectDriver(driver, reconnectInterval: TimeSpan.FromMilliseconds(100)); - parent.ExpectMsg(TimeSpan.FromSeconds(3)) + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeFalse(); AwaitCondition( () => driver.InitializeCount >= 2, TimeSpan.FromSeconds(5), - "the reconnect retry never re-initialised the driver"); + "the reconnect retry never re-initialised the driver", TestContext.Current.CancellationToken); // Ordering, not just counts: EVERY re-initialise must be immediately preceded by a shutdown, since // it is the initialise that overwrites the transport field. Asserting ShutdownCount > 0 alone would @@ -266,9 +266,9 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest AwaitCondition( () => driver.HealthReadCount >= 12, TimeSpan.FromSeconds(5), - "health-poll ticks did not run — the absence assertion below would be vacuous"); + "health-poll ticks did not run — the absence assertion below would be vacuous", TestContext.Current.CancellationToken); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// @@ -306,14 +306,14 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest var (_, parent) = ConnectDriver(driver, commsLossTimeout: Grace, reconnectInterval: TimeSpan.FromMilliseconds(100)); // The first trip is correct and expected — the device really has been silent for five minutes. - parent.ExpectMsg(PresenceBudget) + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeFalse(); // The retry re-initialises successfully (the socket still accepts), so a NEW connection starts here. - parent.ExpectMsg(PresenceBudget) + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeTrue(); var ticksAtReconnect = driver.HealthReadCount; - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); (driver.HealthReadCount - ticksAtReconnect).ShouldBeGreaterThanOrEqualTo( 4, "health-poll ticks did not run after the reconnect — the absence assertion above would be vacuous"); @@ -335,11 +335,11 @@ public sealed class DriverInstanceActorCommsLossWatchdogTests : RuntimeActorTest }; var (_, parent) = ConnectDriver(driver, commsLossTimeout: Grace, reconnectInterval: TimeSpan.FromMilliseconds(100)); - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeFalse(); - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeTrue(); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeTrue(); // The second connection serves no data either, so once ITS grace elapses the watchdog trips again. - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeFalse(); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs index ee1d99706..d031fe5ff 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs @@ -65,9 +65,9 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var sw = Stopwatch.StartNew(); var askBudget = TimeSpan.FromSeconds(8); // the host's Ask var first = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-1", 1), askBudget); + new DriverInstanceActor.WriteAttribute("tag-1", 1), askBudget, TestContext.Current.CancellationToken); var second = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-2", 2), askBudget); + new DriverInstanceActor.WriteAttribute("tag-2", 2), askBudget, TestContext.Current.CancellationToken); var results = await Task.WhenAll(first, second); sw.Stop(); @@ -85,14 +85,14 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var driver = new SlowWritableDriver { HoldWrites = true }; var (actor, parent) = Connect(driver); await actor.Ask( - new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), PresenceBudget); + new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), PresenceBudget, TestContext.Current.CancellationToken); var pending = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 7), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 7), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); driver.FireDataChange("tag-a", 3.5); - var published = parent.ExpectMsg(PresenceBudget); + var published = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); published.Items.Single().Value.ShouldBe(3.5); pending.IsCompleted.ShouldBeFalse("the write is still held — the data change did not wait behind it"); @@ -114,7 +114,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa AwaitWritesEntered(driver, 2); // Settle: a third write entering the driver would show up here. - await Task.Delay(300); + await Task.Delay(300, TestContext.Current.CancellationToken); driver.WritesEntered.ShouldBe(2, "the cap let a third write into the driver"); driver.ReleaseWrites(); @@ -140,17 +140,17 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, _) = Connect(driver, maxWritesInFlight: 4); var first = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); var second = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 2), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 2), PresenceBudget, TestContext.Current.CancellationToken); // Positive control: a DIFFERENT tag is not held behind tag-a. var other = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-b", 9), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-b", 9), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 2); driver.EnteredRefs.ShouldBe(new[] { "tag-a", "tag-b" }); - await Task.Delay(300); + await Task.Delay(300, TestContext.Current.CancellationToken); driver.WritesEntered.ShouldBe(2, "the second tag-a write entered the driver while the first was still inside it"); second.IsCompleted.ShouldBeFalse(); @@ -170,12 +170,12 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, _) = Connect(driver, maxWritesInFlight: 1, queuedWriteBudget: TimeSpan.FromMilliseconds(300)); var inFlight = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-0", 0), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-0", 0), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); var stale = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-1", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-1", 1), PresenceBudget, TestContext.Current.CancellationToken); - await Task.Delay(600); + await Task.Delay(600, TestContext.Current.CancellationToken); driver.ReleaseWrites(); (await inFlight).Success.ShouldBeTrue(); @@ -202,7 +202,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, _) = Connect(driver, maxWritesInFlight: 1, writeTimeout: TimeSpan.FromMilliseconds(300)); var hung = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); var hungResult = await hung; @@ -214,7 +214,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa // The tag (and the single slot) are free again: a later tag-a write reaches the device even though the // abandoned one is still inside it. var later = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 2), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 2), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 2); driver.EnteredRefs.ShouldBe(new[] { "tag-a", "tag-a" }); var laterResult = await later; @@ -237,12 +237,12 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, _) = Connect(driver, writeTimeout: TimeSpan.FromSeconds(10), writeClock: clock); var write = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); AwaitWriteBoundsArmed(clock); clock.Advance(TimeSpan.FromSeconds(6)); - await Task.Delay(100); + await Task.Delay(100, TestContext.Current.CancellationToken); write.IsCompleted.ShouldBeFalse("the write was cut off before its configured 10 s bound"); driver.ReleaseWrites(); @@ -263,7 +263,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, _) = Connect(driver, writeTimeout: TimeSpan.FromSeconds(10), writeClock: clock); var write = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); AwaitWriteBoundsArmed(clock); @@ -286,12 +286,12 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, _) = Connect(driver, writeTimeout: TimeSpan.FromSeconds(10), writeClock: clock); var write = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); AwaitWriteBoundsArmed(clock); clock.Advance(TimeSpan.FromSeconds(10)); - await Task.Delay(100); + await Task.Delay(100, TestContext.Current.CancellationToken); write.IsCompleted.ShouldBeFalse("abandoned at the cancel instant — no grace for the driver to answer"); clock.Advance(DriverInstanceActor.WriteAbandonGrace(TimeSpan.FromSeconds(10))); @@ -325,7 +325,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var issuedAt = clock.GetTimestamp(); clock.Advance(bounds.QueueBudget + TimeSpan.FromMilliseconds(1)); var result = await actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1, issuedAt), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1, issuedAt), PresenceBudget, TestContext.Current.CancellationToken); result.Success.ShouldBeFalse(); result.Reason!.ShouldContain("800A0000", customMessage: "BadTimeout"); @@ -344,7 +344,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var issuedAt = clock.GetTimestamp(); clock.Advance(bounds.QueueBudget); var result = await actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-a", 1, issuedAt), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-a", 1, issuedAt), PresenceBudget, TestContext.Current.CancellationToken); result.Success.ShouldBeTrue(result.Reason); driver.Writes.Select(w => w.FullReference).ShouldBe(new[] { "tag-a" }); @@ -357,13 +357,13 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var (actor, parent) = Connect(driver, maxWritesInFlight: 1); var inFlight = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-0", 0), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-0", 0), PresenceBudget, TestContext.Current.CancellationToken); AwaitWritesEntered(driver, 1); var queued = actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-1", 1), PresenceBudget); + new DriverInstanceActor.WriteAttribute("tag-1", 1), PresenceBudget, TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.ForceReconnect()); - parent.ExpectMsg(m => !m.Connected, PresenceBudget); + parent.ExpectMsg(m => !m.Connected, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var queuedResult = await queued; queuedResult.Success.ShouldBeFalse(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConnectionLossSignalTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConnectionLossSignalTests.cs index 17d0c2f7e..10fedfe6e 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConnectionLossSignalTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConnectionLossSignalTests.cs @@ -70,7 +70,7 @@ public sealed class DriverInstanceActorConnectionLossSignalTests : RuntimeActorT await driver.RaiseAsync("keep-alive reported BadServerHalted"); - parent.ExpectMsg(TimeSpan.FromSeconds(3)).Connected.ShouldBeFalse(); + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); (DateTime.UtcNow - started).ShouldBeLessThan(TimeSpan.FromSeconds(3), "the watchdog is configured at an hour, so only the connection-loss signal can have done this"); } @@ -86,7 +86,7 @@ public sealed class DriverInstanceActorConnectionLossSignalTests : RuntimeActorT var (_, parent) = Connect(driver); var ticksAtConnect = driver.HealthReadCount; - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(600)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(600), TestContext.Current.CancellationToken); (driver.HealthReadCount - ticksAtConnect).ShouldBeGreaterThanOrEqualTo(5, "health-poll ticks did not run — the absence assertion above would be vacuous"); driver.HandlerCount.ShouldBe(1, "attached, so the absence above is not merely an unwired event"); @@ -113,17 +113,17 @@ public sealed class DriverInstanceActorConnectionLossSignalTests : RuntimeActorT driver, reconnectInterval: TimeSpan.FromHours(1), healthPollInterval: FastPoll, - commsLossTimeout: TimeSpan.FromHours(1))); + commsLossTimeout: TimeSpan.FromHours(1)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - await initStarted.Task.WaitAsync(TimeSpan.FromSeconds(3)); + await initStarted.Task.WaitAsync(TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); // The raise is enqueued before the connect can complete (RaiseAsync returns after the Tell), so the mailbox // delivers it while the actor is still Connecting. await driver.RaiseAsync("a loss reported mid-connect"); release.SetResult(); - parent.ExpectMsg(TimeSpan.FromSeconds(3)).Connected.ShouldBeTrue(); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(600)); + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeTrue(); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(600), TestContext.Current.CancellationToken); driver.InitializeCount.ShouldBe(1); } @@ -137,11 +137,11 @@ public sealed class DriverInstanceActorConnectionLossSignalTests : RuntimeActorT var unhandled = CreateTestProbe(); Sys.EventStream.Subscribe(unhandled.Ref, typeof(UnhandledMessage)); await driver.RaiseAsync("keep-alive reported BadServerHalted"); - parent.ExpectMsg(TimeSpan.FromSeconds(3)).Connected.ShouldBeFalse(); + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); actor.Tell(new DriverInstanceActor.DisconnectObserved("late watchdog trip")); - unhandled.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + unhandled.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } [Fact] @@ -154,7 +154,7 @@ public sealed class DriverInstanceActorConnectionLossSignalTests : RuntimeActorT parent.Watch(actor); Sys.Stop(actor); // PostStop announces comms loss (03/S15) before the Terminated arrives. - parent.FishForMessage(m => m is Terminated t && t.ActorRef.Equals(actor), TimeSpan.FromSeconds(3)); + parent.FishForMessage(m => m is Terminated t && t.ActorRef.Equals(actor), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); driver.HandlerCount.ShouldBe(0, "the IDriver outlives the actor on a respawn — a leaked handler holds a dead Self"); } @@ -167,9 +167,9 @@ public sealed class DriverInstanceActorConnectionLossSignalTests : RuntimeActorT var parent = CreateTestProbe(); var actor = parent.ChildActorOf(DriverInstanceActor.Props( driver, reconnectInterval: TimeSpan.FromHours(1), healthPollInterval: FastPoll, - commsLossTimeout: TimeSpan.FromHours(1))); + commsLossTimeout: TimeSpan.FromHours(1)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - parent.ExpectMsg(TimeSpan.FromSeconds(3)).Connected.ShouldBeTrue(); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectMsg(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeTrue(); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiagnosticsTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiagnosticsTests.cs index 19b0ee39b..8e2dbe1a8 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiagnosticsTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDiagnosticsTests.cs @@ -33,7 +33,7 @@ public sealed class DriverInstanceActorDiagnosticsTests : RuntimeActorTestBase var latest = publisher.Published.LastOrDefault(); latest.ShouldNotBeNull(); latest!["S7.NegotiatedPduSize"].ShouldBe("480"); - }, TimeSpan.FromSeconds(3)); + }, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -49,13 +49,13 @@ public sealed class DriverInstanceActorDiagnosticsTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; driver.Set(("S7.NegotiatedPduSize", "480")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(before), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(before), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published[^1]!["S7.NegotiatedPduSize"].ShouldBe("480"); } @@ -69,13 +69,13 @@ public sealed class DriverInstanceActorDiagnosticsTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; driver.Set(("S7.NegotiatedPduSize", "480")); - ExpectNoMsg(TimeSpan.FromMilliseconds(600)); + ExpectNoMsg(TimeSpan.FromMilliseconds(600), TestContext.Current.CancellationToken); publisher.Published.Count.ShouldBe(before); } @@ -87,7 +87,7 @@ public sealed class DriverInstanceActorDiagnosticsTests : RuntimeActorTestBase var actor = SpawnDriverActor(new StubDriver(), publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published.ShouldAllBe(p => p == null); } @@ -102,7 +102,7 @@ public sealed class DriverInstanceActorDiagnosticsTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published[^1].ShouldBeNull(); } @@ -123,7 +123,7 @@ public sealed class DriverInstanceActorDiagnosticsTests : RuntimeActorTestBase latest.ShouldNotBeNull(); latest!.Count.ShouldBe(DriverDiagnostics.MaxEntries); latest.Values.ShouldAllBe(v => v.Length == DriverDiagnostics.MaxValueLength); - }, TimeSpan.FromSeconds(3)); + }, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } private IActorRef SpawnDriverActor(IDriver driver, IDriverHealthPublisher publisher) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDisposeTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDisposeTests.cs index 815575fb5..a6ebafaba 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDisposeTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorDisposeTests.cs @@ -26,13 +26,13 @@ public sealed class DriverInstanceActorDisposeTests : RuntimeActorTestBase var driver = new DisposableRecordingDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount == 1, PresenceBudget); + AwaitCondition(() => driver.InitializeCount == 1, PresenceBudget, TestContext.Current.CancellationToken); Watch(actor); Sys.Stop(actor); - ExpectTerminated(actor, PresenceBudget); + ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.ShutdownCount.ShouldBe(1); driver.DisposeCount.ShouldBe(1, "a child stopped for good must dispose the driver it wrapped"); @@ -47,17 +47,17 @@ public sealed class DriverInstanceActorDisposeTests : RuntimeActorTestBase // The probe parent's own strategy STOPS a failing child; the production host (no strategy anywhere under // src/Server) gets Akka's default decider, which RESTARTS it — the route under test. var actor = parent.ChildActorOf( - DriverInstanceActor.Props(driver), new OneForOneStrategy(_ => Directive.Restart)); + DriverInstanceActor.Props(driver), new OneForOneStrategy(_ => Directive.Restart), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // Barrier: Subscribe is only handled once Connected (it is dropped in Connecting). - parent.ExpectMsg(m => m.Connected, PresenceBudget); + parent.ExpectMsg(m => m.Connected, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); await actor.Ask( - new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), PresenceBudget); + new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), PresenceBudget, TestContext.Current.CancellationToken); // The crash seam (see DriverHostActorRestartTests): a null snapshot NREs on the actor thread, and the // probe parent's default decider restarts the child in place around the SAME driver object. driver.FireMalformedDataChange("tag-a"); - parent.FishForMessage(m => m is DriverInstanceActor.ReprovisionRequested, PresenceBudget); + parent.FishForMessage(m => m is DriverInstanceActor.ReprovisionRequested, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.ShutdownCount.ShouldBe(1, "PreRestart runs PostStop, which shuts the driver down"); driver.DisposeCount.ShouldBe(0, "a restart re-uses the driver object — disposing it would kill the new incarnation"); @@ -65,19 +65,19 @@ public sealed class DriverInstanceActorDisposeTests : RuntimeActorTestBase // Positive control: the restarted incarnation serves through the undisposed driver. The host would // re-provision it exactly like this (InitializeRequested + the desired set). actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget); + parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); await actor.Ask( - new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), PresenceBudget); + new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), PresenceBudget, TestContext.Current.CancellationToken); driver.FireDataChange("tag-a", 42.0); var published = (DriverInstanceActor.AttributeValueBatchPublished)parent.FishForMessage( - m => m is DriverInstanceActor.AttributeValueBatchPublished, PresenceBudget); + m => m is DriverInstanceActor.AttributeValueBatchPublished, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); published.Items.Single().FullReference.ShouldBe("tag-a"); published.Items.Single().Value.ShouldBe(42.0); // ...and the eventual real stop still disposes it — once. Watch(actor); Sys.Stop(actor); - ExpectTerminated(actor, PresenceBudget); + ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.DisposeCount.ShouldBe(1); } @@ -90,14 +90,14 @@ public sealed class DriverInstanceActorDisposeTests : RuntimeActorTestBase var bound = TimeSpan.FromMilliseconds(300); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, shutdownTimeout: bound)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, shutdownTimeout: bound), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount == 1, PresenceBudget); + AwaitCondition(() => driver.InitializeCount == 1, PresenceBudget, TestContext.Current.CancellationToken); Watch(actor); var sw = Stopwatch.StartNew(); Sys.Stop(actor); - ExpectTerminated(actor, PresenceBudget); + ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sw.Stop(); driver.ShutdownCount.ShouldBe(1, "the shutdown was attempted (positive control for the bound)"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorHostStatusTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorHostStatusTests.cs index 6c0d7e5f3..69e439dc4 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorHostStatusTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorHostStatusTests.cs @@ -42,7 +42,7 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase latest.HostStatuses!.Select(h => h.HostName).OrderBy(n => n, StringComparer.Ordinal) .ShouldBe(["plc-a", "plc-b"]); }, - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -69,18 +69,18 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Settle, so the baseline is a quiet actor: from here only the host state changes. The driver's // OWN health stays Healthy throughout — that is the point. - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; driver.SetHost("plc-b", HostState.Stopped, raise: true); AwaitAssert( () => publisher.Published.Count.ShouldBeGreaterThan(before), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); var latest = publisher.Published[^1]; latest.Health.State.ShouldBe(DriverState.Healthy, "the driver itself never faulted — only one of its devices did"); @@ -105,8 +105,8 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; // The driver re-shuffles its host order without changing any state. A real driver builds this list @@ -115,7 +115,7 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase // Poke the actor into re-publishing without changing anything material. driver.RaiseHostStatusChanged(); - ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); publisher.Published.Count.ShouldBe(before, "an order flip with no state change must be deduped, not re-published"); } @@ -130,14 +130,14 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase var driver = new ProbeStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(_ => true); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => driver.SubscriberCount.ShouldBe(1), TimeSpan.FromSeconds(3)); + AwaitAssert(() => driver.SubscriberCount.ShouldBe(1), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + ExpectTerminated(actor, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); driver.SubscriberCount.ShouldBe(0); } @@ -154,7 +154,7 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase var actor = SpawnDriverActor(new StubDriver(), publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published.ShouldAllBe(p => p.HostStatuses == null); } @@ -174,7 +174,7 @@ public sealed class DriverInstanceActorHostStatusTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published[^1].Health.State.ShouldBe(DriverState.Healthy); publisher.Published[^1].HostStatuses.ShouldBeNull(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorNativeAlarmTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorNativeAlarmTests.cs index c3895b534..2194c0cb1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorNativeAlarmTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorNativeAlarmTests.cs @@ -33,17 +33,17 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // The alarm handler is attached after Become(Connected); wait for it to be wired before raising. - AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Also establish a data-change subscription so the OnDataChange path is wired (the alarm attach is // independent of Subscribe — this proves both event paths coexist on one driver). await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-z" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); driver.RaiseAlarm(new AlarmEventArgs( new StubAlarmHandle(), @@ -55,7 +55,7 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase SourceTimestampUtc: DateTime.UtcNow, Kind: AlarmTransitionKind.Raise)); - var published = parent.ExpectMsg(TimeSpan.FromSeconds(2)); + var published = parent.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); published.DriverInstanceId.ShouldBe(driver.DriverInstanceId); published.Args.SourceNodeId.ShouldBe("src-node-7"); published.Args.ConditionId.ShouldBe("cond-1"); @@ -63,7 +63,7 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // The same driver's OnDataChange still flows independently — alarm + value events coexist. driver.FireDataChange("tag-z", value: 9.0, statusCode: 0u); - parent.ExpectMsg(TimeSpan.FromSeconds(2)) + parent.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken) .Items[0].FullReference.ShouldBe("tag-z"); } @@ -83,17 +83,17 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2)); // reached Connected + AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // reached Connected driver.SubscribeAlarmsCallCount.ShouldBe(0, "no alarm subscription before any alarm refs are pushed"); // A deploy delivers the desired set with a native-alarm ref while the driver is already Connected. actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( Array.Empty(), TimeSpan.FromMilliseconds(100), new[] { "Temp.HiHi" })); - AwaitCondition(() => driver.SubscribeAlarmsCallCount == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.SubscribeAlarmsCallCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); driver.LastAlarmRefs.ShouldBe(new[] { "Temp.HiHi" }); // Redeploying the same alarm set must NOT re-subscribe (idempotent — already un-gated). Round-trip a @@ -102,7 +102,7 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase Array.Empty(), TimeSpan.FromMilliseconds(100), new[] { "Temp.HiHi" })); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-z" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); driver.SubscribeAlarmsCallCount.ShouldBe(1, "an already-established alarm subscription is not re-issued"); } @@ -119,22 +119,22 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); driver.FireDataChange("tag-a", value: 1.5, statusCode: 0u); // Data-change forwarding still works (no crash on AttachAlarmSource for a non-IAlarmSource driver). - var dc = parent.ExpectMsg(TimeSpan.FromSeconds(2)); + var dc = parent.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); dc.Items[0].FullReference.ShouldBe("tag-a"); // An AttributeAlarmPublished can never be produced for a non-IAlarmSource driver. - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// @@ -182,11 +182,11 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); // Drive to Connected; confirm the alarm handler is attached. actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Enqueue ForceReconnect FIRST — the actor hasn't processed it yet, so the handler is // still wired. The test thread then immediately fires the alarm on the driver; the handler's @@ -206,16 +206,16 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // The actor processes: (1) ForceReconnect → Reconnecting (handler detached); // (2) NativeAlarmRaised → dropped (debug log, no forward). // Wait generously — the default reconnect interval of 10 s means no retry fires here. - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // (b) The alarm must also NOT have dead-lettered — the Reconnecting state's explicit // Receive handler must have consumed it. If that handler were removed the // message would become unhandled → AllDeadLetters, and this assertion would catch the regression. - deadLetters.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + deadLetters.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); // The actor must still be alive — Watch + no Terminated. Watch(actor); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); } /// @@ -231,18 +231,18 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase var driver = new AlarmSourceStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( new[] { "tag-a" }, TimeSpan.FromMilliseconds(100), new[] { "Temp.HiHi" })); - AwaitCondition(() => driver.SubscribeAlarmsCallCount == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.SubscribeAlarmsCallCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // A second data subscribe while the first handle is live — the drop-then-resubscribe path. await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); driver.AlarmSubscriberCount.ShouldBe(1, "the data re-subscribe must not unhook OnAlarmEvent"); driver.SubscribeAlarmsCallCount.ShouldBe(1, @@ -251,7 +251,7 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase driver.RaiseAlarm(new AlarmEventArgs( new StubAlarmHandle(), "Temp.HiHi", "Temp.HiHi", "T", "after re-subscribe", AlarmSeverity.High, DateTime.UtcNow, Kind: AlarmTransitionKind.Raise)); - parent.FishForMessage(_ => true, TimeSpan.FromSeconds(2)) + parent.FishForMessage(_ => true, TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken) .Args.ConditionId.ShouldBe("Temp.HiHi"); } @@ -273,20 +273,20 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase var parent = CreateTestProbe(); parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); var actor = parent.ChildActorOf(DriverInstanceActor.Props( - driver, healthPublisher: publisher, healthPollInterval: TimeSpan.FromMilliseconds(100))); + driver, healthPublisher: publisher, healthPollInterval: TimeSpan.FromMilliseconds(100)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( Array.Empty(), TimeSpan.FromMilliseconds(100), new[] { "Temp.HiHi" })); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.SubscribeAlarmsCallCount >= 1, PresenceBudget); - AwaitCondition(() => driver.SubscribeAlarmsCallCount >= failures + 1, PresenceBudget); + AwaitCondition(() => driver.SubscribeAlarmsCallCount >= 1, PresenceBudget, TestContext.Current.CancellationToken); + AwaitCondition(() => driver.SubscribeAlarmsCallCount >= failures + 1, PresenceBudget, TestContext.Current.CancellationToken); driver.LastAlarmRefs.ShouldBe(new[] { "Temp.HiHi" }); driver.RaiseAlarm(new AlarmEventArgs( new StubAlarmHandle(), "Temp.HiHi", "Temp.HiHi", "T", "after retry", AlarmSeverity.High, DateTime.UtcNow, Kind: AlarmTransitionKind.Raise)); - parent.FishForMessage(_ => true, TimeSpan.FromSeconds(2)) + parent.FishForMessage(_ => true, TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken) .Args.ConditionId.ShouldBe("Temp.HiHi"); // Converged: a live, matching handle is left alone on later ticks. POSITIVE CONTROL: each tick @@ -296,7 +296,7 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // stable health would publish once and this wait would time out (loudly, not vacuously). var settled = driver.SubscribeAlarmsCallCount; var before = publisher.Count; - AwaitCondition(() => publisher.Count >= before + 4, PresenceBudget); + AwaitCondition(() => publisher.Count >= before + 4, PresenceBudget, TestContext.Current.CancellationToken); driver.SubscribeAlarmsCallCount.ShouldBe(settled, "a live, matching alarm handle must not be re-subscribed every tick"); } @@ -314,17 +314,17 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(50))); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(50)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Force Connected → Reconnecting (detaches) → Connected (re-attaches). InitializeCount climbs on // the retry; the alarm handler count must settle back to exactly one (no leaked extra handler). var initBefore = driver.InitializeCount; actor.Tell(new DriverInstanceActor.DisconnectObserved("backend blip")); - AwaitCondition(() => driver.InitializeCount > initBefore, TimeSpan.FromSeconds(3)); - AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(3)); + AwaitCondition(() => driver.InitializeCount > initBefore, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); + AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); driver.RaiseAlarm(new AlarmEventArgs( new StubAlarmHandle(), @@ -337,9 +337,9 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase Kind: AlarmTransitionKind.Raise)); // Exactly one forward — a double-attach would surface as a second AttributeAlarmPublished. - parent.ExpectMsg(TimeSpan.FromSeconds(2)) + parent.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken) .Args.SourceNodeId.ShouldBe("src-node-1"); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// #477 Layer 2 — the actor announces connectivity to its parent (the host): Connected on @@ -351,21 +351,21 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase { var driver = new AlarmSourceStubDriver(); var parent = CreateTestProbe(); // deliberately does NOT ignore ConnectivityChanged — it's the subject. - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(50))); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(50)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // Initial connect → Connected=true. - parent.FishForMessage(m => true, TimeSpan.FromSeconds(3)) + parent.FishForMessage(m => true, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeTrue(); // Disconnect → Connected=false (this is what drives the conditions Bad). actor.Tell(new DriverInstanceActor.DisconnectObserved("backend blip")); - parent.FishForMessage(m => true, TimeSpan.FromSeconds(3)) + parent.FishForMessage(m => true, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeFalse(); // Reconnect → Connected=true again (drives them back Good). - parent.FishForMessage(m => true, TimeSpan.FromSeconds(3)) + parent.FishForMessage(m => true, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken) .Connected.ShouldBeTrue(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorReconnectSingleFlightTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorReconnectSingleFlightTests.cs index 51914d870..6e7935025 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorReconnectSingleFlightTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorReconnectSingleFlightTests.cs @@ -26,13 +26,13 @@ public sealed class DriverInstanceActorReconnectSingleFlightTests : RuntimeActor var driver = new InterleavingDriver(initDuration: TimeSpan.FromMilliseconds(400), failUntilAttempt: 4); var parent = CreateTestProbe(); var actor = parent.ChildActorOf(DriverInstanceActor.Props( - driver, reconnectInterval: TimeSpan.FromMilliseconds(50), commsLossTimeout: TimeSpan.Zero)); + driver, reconnectInterval: TimeSpan.FromMilliseconds(50), commsLossTimeout: TimeSpan.Zero), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // Positive control: the gate skips ticks, it does not stop retrying — the 4th attempt succeeds and the // actor connects. - parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget); + parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.Attempts.ShouldBeGreaterThanOrEqualTo(4); driver.Overlaps.ShouldBe(0, @@ -51,11 +51,11 @@ public sealed class DriverInstanceActorReconnectSingleFlightTests : RuntimeActor var parent = CreateTestProbe(); var actor = parent.ChildActorOf(DriverInstanceActor.Props( driver, reconnectInterval: TimeSpan.FromMilliseconds(50), commsLossTimeout: TimeSpan.Zero, - shutdownTimeout: TimeSpan.FromMilliseconds(200))); + shutdownTimeout: TimeSpan.FromMilliseconds(200)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget); + parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); never.Task.IsCompleted.ShouldBeFalse("the teardown is still hung — the attempt did not wait for it"); driver.Attempts.ShouldBe(2); } @@ -77,11 +77,11 @@ public sealed class DriverInstanceActorReconnectSingleFlightTests : RuntimeActor var parent = CreateTestProbe(); var actor = parent.ChildActorOf(DriverInstanceActor.Props( driver, reconnectInterval: TimeSpan.FromMilliseconds(50), commsLossTimeout: TimeSpan.Zero, - initializeTimeout: TimeSpan.FromMilliseconds(300))); + initializeTimeout: TimeSpan.FromMilliseconds(300)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget); + parent.FishForMessage(m => m is DriverInstanceActor.ConnectivityChanged { Connected: true }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); driver.Attempts.ShouldBe(2); driver.HungAttemptStillRunning.ShouldBeTrue("positive control: the first attempt really was wedged"); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorRediscoverySignalTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorRediscoverySignalTests.cs index 88dc00243..56fb791f7 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorRediscoverySignalTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorRediscoverySignalTests.cs @@ -31,10 +31,10 @@ public sealed class DriverInstanceActorRediscoverySignalTests : RuntimeActorTest var publisher = new RecordingHealthPublisher(); var parent = CreateTestProbe(); parent.IgnoreMessages(_ => true); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, healthPublisher: publisher)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, healthPublisher: publisher), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); driver.RaiseRediscovery("deploy-time-changed"); @@ -45,7 +45,7 @@ public sealed class DriverInstanceActorRediscoverySignalTests : RuntimeActorTest withSignal.ShouldNotBeNull(); withSignal!.RediscoveryReason.ShouldBe("deploy-time-changed"); }, - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -65,21 +65,21 @@ public sealed class DriverInstanceActorRediscoverySignalTests : RuntimeActorTest var publisher = new RecordingHealthPublisher(); var parent = CreateTestProbe(); parent.IgnoreMessages(_ => true); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, healthPublisher: publisher)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, healthPublisher: publisher), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Let the actor settle so nothing else is in flight, then capture the baseline. Everything about // the driver's health is now stable — only the rediscovery flag will change. - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; driver.RaiseRediscovery("symbol-version-changed"); AwaitAssert( () => publisher.Published.Count.ShouldBeGreaterThan(before), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published[^1].RediscoveryNeededUtc.ShouldNotBeNull(); } @@ -96,20 +96,20 @@ public sealed class DriverInstanceActorRediscoverySignalTests : RuntimeActorTest var parent = CreateTestProbe(); parent.IgnoreMessages(_ => true); var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, healthPublisher: publisher, - healthPollInterval: TimeSpan.FromMilliseconds(200))); + healthPollInterval: TimeSpan.FromMilliseconds(200)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); driver.RaiseRediscovery("agent-instance-changed"); AwaitAssert( () => publisher.Published.Any(p => p.RediscoveryNeededUtc is not null).ShouldBeTrue(), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Force a further publish by changing something else about the driver's health. driver.SetLastError("transient read failure"); AwaitAssert( () => publisher.Published.Any(p => p.LastError == "transient read failure").ShouldBeTrue(), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); var latest = publisher.Published[^1]; latest.RediscoveryNeededUtc.ShouldNotBeNull(); @@ -128,14 +128,14 @@ public sealed class DriverInstanceActorRediscoverySignalTests : RuntimeActorTest var driver = new RediscoverableStubDriver(); var parent = CreateTestProbe(); parent.IgnoreMessages(_ => true); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => driver.SubscriberCount.ShouldBe(1), TimeSpan.FromSeconds(3)); + AwaitAssert(() => driver.SubscriberCount.ShouldBe(1), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + ExpectTerminated(actor, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); driver.SubscriberCount.ShouldBe(0); } @@ -148,10 +148,10 @@ public sealed class DriverInstanceActorRediscoverySignalTests : RuntimeActorTest var publisher = new RecordingHealthPublisher(); var parent = CreateTestProbe(); parent.IgnoreMessages(_ => true); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(new StubDriver(), healthPublisher: publisher)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(new StubDriver(), healthPublisher: publisher), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published.ShouldAllBe(p => p.RediscoveryNeededUtc == null); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorResilienceWiringTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorResilienceWiringTests.cs index 6908e4456..b5dfd2f0b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorResilienceWiringTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorResilienceWiringTests.cs @@ -36,12 +36,12 @@ public sealed class DriverInstanceActorResilienceWiringTests : RuntimeActorTestB actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(100))); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.SubscribeCount >= 1, TimeSpan.FromSeconds(3)); + AwaitCondition(() => driver.SubscribeCount >= 1, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); // Barrier: round-trip a Connected-only Ask so the async subscribe handler has fully recorded. await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); invoker.Calls.ShouldContain(c => c.Capability == DriverCapability.Subscribe && c.Host == driver.DriverInstanceId); } @@ -67,8 +67,8 @@ public sealed class DriverInstanceActorResilienceWiringTests : RuntimeActorTestB for (var i = 0; i < 60 && reply is not { Success: true }; i++) { reply = await actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-1", 42), TimeSpan.FromSeconds(1)); - if (reply is not { Success: true }) await Task.Delay(25); + new DriverInstanceActor.WriteAttribute("tag-1", 42), TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); + if (reply is not { Success: true }) await Task.Delay(25, TestContext.Current.CancellationToken); } reply.ShouldNotBeNull(); @@ -98,7 +98,7 @@ public sealed class DriverInstanceActorResilienceWiringTests : RuntimeActorTestB AwaitCondition( () => invoker.Calls.Any(c => c.Capability == DriverCapability.Initialize), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); invoker.Calls.ShouldContain(c => c.Capability == DriverCapability.Initialize && c.Host == driver.DriverInstanceId); } @@ -128,8 +128,8 @@ public sealed class DriverInstanceActorResilienceWiringTests : RuntimeActorTestB for (var i = 0; i < 120 && reply is not { Success: true }; i++) { reply = await actor.Ask( - new DriverInstanceActor.WriteAttribute("tag-1", 42), TimeSpan.FromSeconds(1)); - if (reply is not { Success: true }) await Task.Delay(25); + new DriverInstanceActor.WriteAttribute("tag-1", 42), TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); + if (reply is not { Success: true }) await Task.Delay(25, TestContext.Current.CancellationToken); } reply.ShouldNotBeNull(); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSkippedTagTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSkippedTagTests.cs index 48729fa09..666338781 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSkippedTagTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSkippedTagTests.cs @@ -40,7 +40,7 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase latest.SkippedTags.Sample.Select(s => s.RawPath) .ShouldBe(["Makino/Identity/SeriesNumber", "Makino/Identity/Version"]); }, - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -65,18 +65,18 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); // Settle, so the baseline is a quiet actor: from here only the skip report changes. The driver's // own health stays Healthy throughout — that is the point. - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; driver.SetReport(authored: 22, skipped: [.. Enumerable.Range(0, 20).Select(i => $"Makino/Tag{i}")]); AwaitAssert( () => publisher.Published.Count.ShouldBeGreaterThan(before), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); var latest = publisher.Published[^1]; latest.Health.State.ShouldBe(DriverState.Healthy, @@ -99,13 +99,13 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; driver.SetReport(authored: 5, skipped: ["newly-broken"]); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(before), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(before), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published[^1].SkippedTags!.Sample.Single().RawPath.ShouldBe("newly-broken"); } @@ -123,15 +123,15 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase var actor = SpawnDriverActor(driver, publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); var before = publisher.Published.Count; // A fresh-but-equal report, rebuilt exactly as a redeploy that changed nothing would. driver.SetReport(authored: 22, skipped: ["Makino/Identity/SeriesNumber"]); // Long enough for many heartbeats to fire and be deduped away. - ExpectNoMsg(TimeSpan.FromMilliseconds(600)); + ExpectNoMsg(TimeSpan.FromMilliseconds(600), TestContext.Current.CancellationToken); publisher.Published.Count.ShouldBe(before, "an equal-but-distinct report must be deduped, not re-published — the digest must compare by VALUE"); } @@ -148,7 +148,7 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase var actor = SpawnDriverActor(new StubDriver(), publisher); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published.ShouldAllBe(p => p.SkippedTags == null); } @@ -166,7 +166,7 @@ public sealed class DriverInstanceActorSkippedTagTests : RuntimeActorTestBase actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3)); + AwaitAssert(() => publisher.Published.Count.ShouldBeGreaterThan(0), TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); publisher.Published[^1].Health.State.ShouldBe(DriverState.Healthy); publisher.Published[^1].SkippedTags.ShouldBeNull(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSubscriptionReconcileTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSubscriptionReconcileTests.cs index 126adeb1e..439c31af1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSubscriptionReconcileTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorSubscriptionReconcileTests.cs @@ -44,10 +44,10 @@ public sealed class DriverInstanceActorSubscriptionReconcileTests : RuntimeActor actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // The connect-path subscribe runs and throws, so the handle stays null. - AwaitCondition(() => driver.SubscribeCount >= 1, PresenceBudget); + AwaitCondition(() => driver.SubscribeCount >= 1, PresenceBudget, TestContext.Current.CancellationToken); // Only the reconcile can produce this second attempt — and it must succeed this time. - AwaitCondition(() => driver.SubscribeCount >= 2, PresenceBudget); + AwaitCondition(() => driver.SubscribeCount >= 2, PresenceBudget, TestContext.Current.CancellationToken); driver.LastSubscribedRefs.ShouldBe(new[] { "tag-a" }); } @@ -66,14 +66,14 @@ public sealed class DriverInstanceActorSubscriptionReconcileTests : RuntimeActor actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( new[] { "tag-a" }, TimeSpan.FromMilliseconds(100))); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.SubscribeCount >= 1, PresenceBudget); + AwaitCondition(() => driver.SubscribeCount >= 1, PresenceBudget, TestContext.Current.CancellationToken); // POSITIVE CONTROL for the absence below. Every health-poll tick publishes a snapshot, so the // publisher count is a direct observation of ticks PROCESSED. Waiting for several of them is what // makes "SubscribeCount is still 1" mean "the reconcile ran and declined" rather than "the timer // had not fired yet" — the latter would pass instantly and prove nothing. var before = publisher.Count; - AwaitCondition(() => publisher.Count >= before + 4, PresenceBudget); + AwaitCondition(() => publisher.Count >= before + 4, PresenceBudget, TestContext.Current.CancellationToken); driver.SubscribeCount.ShouldBe(1, "the handle is live, so the reconcile must leave it alone"); @@ -103,7 +103,7 @@ public sealed class DriverInstanceActorSubscriptionReconcileTests : RuntimeActor { var before = publisher.Count; AwaitCondition(() => publisher.Count >= before + 4, PresenceBudget); - }); + }, TestContext.Current.CancellationToken); } /// Counts health-snapshot publishes, which is one per health-poll tick once the actor diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs index 1de952b56..83b95aa1b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorTests.cs @@ -22,7 +22,7 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // The actor should keep trying — we expect multiple Initialize calls because the // reconnect timer fires every 50ms. - AwaitCondition(() => driver.InitializeCount >= 3, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount >= 3, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); } /// Verifies that writing to a non-IWritable driver returns failure. @@ -33,11 +33,11 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase var actor = Sys.ActorOf(DriverInstanceActor.Props(driver)); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var reply = await actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 42), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); reply.Success.ShouldBeFalse(); reply.Reason!.ShouldContain("IWritable"); @@ -51,11 +51,11 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase var actor = Sys.ActorOf(DriverInstanceActor.Props(driver)); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var reply = await actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 42), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); reply.Success.ShouldBeTrue(); driver.Writes.Single().FullReference.ShouldBe("tag-1"); @@ -71,11 +71,11 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase var actor = Sys.ActorOf(DriverInstanceActor.Props(driver)); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var reply = await actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 42), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); reply.Success.ShouldBeFalse(); reply.Reason!.ShouldContain("80340000"); @@ -90,21 +90,21 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(250)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); // Driver fires an OnDataChange — actor should forward it to its parent as an // AttributeValueBatchPublished (single-item, since this driver is per-tag-only) carrying the // driver's status code. driver.FireDataChange("tag-a", value: 3.14, statusCode: 0u); - var published = parent.ExpectMsg(TimeSpan.FromSeconds(2)); + var published = parent.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); published.Items.Count.ShouldBe(1); published.Items[0].FullReference.ShouldBe("tag-a"); published.Items[0].Value.ShouldBe(3.14); @@ -131,30 +131,30 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-1" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); // Uncertain — severity bits 01 (top 2 bits = 01). driver.FireDataChange("tag-1", value: 1, statusCode: 0x40000000u); - parent.ExpectMsg().Items[0].StatusCode + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Items[0].StatusCode .ShouldBe(OpcUaStatus.Uncertain); // Bad — severity bits 10. driver.FireDataChange("tag-1", value: 2, statusCode: 0x80000000u); - parent.ExpectMsg().Items[0].StatusCode + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Items[0].StatusCode .ShouldBe(OpcUaStatus.Bad); // The regression itself: a Bad SUB-code must arrive intact, not flattened to OpcUaStatus.Bad. // This is the exact code MTConnect maps the Agent's UNAVAILABLE sentinel to, and the live gate // that opened #519 observed it reaching a client as 0x80000000. driver.FireDataChange("tag-1", value: null, statusCode: OpcUaStatus.BadNoCommunication); - var noComms = parent.ExpectMsg().Items[0]; + var noComms = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Items[0]; noComms.StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); noComms.StatusCode.ShouldNotBe(OpcUaStatus.Bad, "the sub-code was collapsed to a generic Bad"); OpcUaStatus.IsBad(noComms.StatusCode).ShouldBeTrue("it must still classify as Bad"); @@ -162,7 +162,7 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // A second, DIFFERENT Bad sub-code — the two were indistinguishable before the fix, so asserting // one alone would not prove the sub-code is carried rather than hard-coded. driver.FireDataChange("tag-1", value: null, statusCode: 0x80740000u /* BadTypeMismatch */); - parent.ExpectMsg().Items[0].StatusCode + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Items[0].StatusCode .ShouldBe(0x80740000u); } @@ -179,7 +179,7 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(50))); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromMilliseconds(50)), TestContext.Current.CancellationToken); // Desired set arrives BEFORE connect — retained, not yet applied. actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( @@ -187,17 +187,17 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // Connecting → Connected triggers the auto-subscribe. actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.SubscribeCount >= 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.SubscribeCount >= 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); driver.LastSubscribedRefs.ShouldBe(new[] { "tag-a", "tag-b" }); // The auto-subscription is live — a data change reaches the parent. driver.FireDataChange("tag-a", value: 7, statusCode: 0u); - parent.ExpectMsg(TimeSpan.FromSeconds(2)) + parent.ExpectMsg(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken) .Items[0].Value.ShouldBe(7); // Reconnect → the desired set is re-established without any new host message. actor.Tell(new DriverInstanceActor.DisconnectObserved("backend blip")); - AwaitCondition(() => driver.SubscribeCount >= 2, TimeSpan.FromSeconds(3)); + AwaitCondition(() => driver.SubscribeCount >= 2, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); } /// @@ -218,21 +218,21 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // First subscribe — establishes the handle. await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(250)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); // Second subscribe — `_subscriptionHandle is not null`, so the handler awaits // UnsubscribeAsync first, then reads Sender. Must still reply (today it threw → no reply). var reply = await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(250)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); reply.ReferenceCount.ShouldBe(2); driver.SubscribeCount.ShouldBe(2); @@ -248,11 +248,11 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase var actor = Sys.ActorOf(DriverInstanceActor.Props(driver)); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var reply = await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-1" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); reply.Reason.ShouldContain("ISubscribable"); } @@ -266,21 +266,21 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase // #477: DriverInstanceActor now Tells the parent ConnectivityChanged on each connect/ // disconnect; these tests assert on the data/alarm/discovery forwards, so ignore it. parent.IgnoreMessages(m => m is DriverInstanceActor.ConnectivityChanged); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromSeconds(30))); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromSeconds(30)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount > 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-1" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.DisconnectObserved("backend went away")); // Race window — once disconnect is processed, subsequent FireDataChange calls hit a // detached handler and don't push anything to the parent. - AwaitCondition(() => driver.OnDataChangeSubscriberCount == 0, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.OnDataChangeSubscriberCount == 0, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); driver.FireDataChange("tag-1", value: 99, statusCode: 0u); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// @@ -297,16 +297,16 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var driver = new StubDriver(); - var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver)); + var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeTrue(); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeTrue(); parent.Watch(actor); parent.Send(actor, PoisonPill.Instance); - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeFalse(); - parent.ExpectTerminated(actor, PresenceBudget); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); + parent.ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -320,17 +320,17 @@ public sealed class DriverInstanceActorTests : RuntimeActorTestBase var parent = CreateTestProbe(); var driver = new StubDriver { InitializeShouldThrow = true }; var actor = parent.ChildActorOf(DriverInstanceActor.Props( - driver, reconnectInterval: TimeSpan.FromSeconds(30))); + driver, reconnectInterval: TimeSpan.FromSeconds(30)), TestContext.Current.CancellationToken); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // The failed initial connect announces Connected=false from the Reconnecting entry; consume it, so // what follows is only what STOPPING produced. - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeFalse(); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); parent.Watch(actor); parent.Send(actor, PoisonPill.Instance); - parent.ExpectTerminated(actor, PresenceBudget); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorWriteAndSubscribeTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorWriteAndSubscribeTests.cs index 38d27fead..53e3c2bad 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorWriteAndSubscribeTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorWriteAndSubscribeTests.cs @@ -34,7 +34,7 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest var reply = await actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 42), - TimeSpan.FromSeconds(2)); + TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Stubbed drivers deterministically succeed writes without touching hardware (existing behaviour). reply.Success.ShouldBeTrue(); @@ -53,12 +53,12 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest var actor = Sys.ActorOf(DriverInstanceActor.Props(driver)); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.InitializeStarted, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeStarted, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var sw = System.Diagnostics.Stopwatch.StartNew(); var reply = await actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 42), - TimeSpan.FromSeconds(2)); + TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); sw.Stop(); reply.Success.ShouldBeFalse(); @@ -80,11 +80,11 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // The first Initialize attempt throws and pushes the actor into Reconnecting. - AwaitCondition(() => driver.InitializeCount >= 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount >= 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var reply = await actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 42), - TimeSpan.FromSeconds(2)); + TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); reply.Success.ShouldBeFalse(); reply.Reason.ShouldNotBeNull(); @@ -114,11 +114,11 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest actor.Tell(new DriverInstanceActor.SetDesiredSubscriptions( new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(100))); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); - AwaitCondition(() => driver.SubscribeCount >= 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.SubscribeCount >= 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Force a reconnect to exercise the resubscribe path a second time. actor.Tell(new DriverInstanceActor.DisconnectObserved("backend blip")); - AwaitCondition(() => driver.SubscribeCount >= 2, TimeSpan.FromSeconds(3)); + AwaitCondition(() => driver.SubscribeCount >= 2, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); // BARRIER: the self-resubscribe reply is published asynchronously, so SubscribeCount>=2 alone // doesn't guarantee the actor has yet PROCESSED the self-sent SubscriptionEstablished (the point @@ -129,9 +129,9 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest // race-free. await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "tag-a", "tag-b" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); - deadLetters.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + deadLetters.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// @@ -157,7 +157,7 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest var actor = Sys.ActorOf(DriverInstanceActor.Props(driver, reconnectInterval: TimeSpan.FromSeconds(30))); actor.Tell(new DriverInstanceActor.InitializeRequested("{}")); // Wait until Connected (subscribe path available). - AwaitCondition(() => driver.InitializeCount >= 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => driver.InitializeCount >= 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); // Tell SubscriptionFailed directly — a faithful stand-in for the self-reply landing on the // actor on the self-resubscribe path. This exercises exactly the Receive @@ -168,9 +168,9 @@ public sealed class DriverInstanceActorWriteAndSubscribeTests : RuntimeActorTest // Subscribe Ask (which the Connected state can answer) as the barrier. await actor.Ask( new DriverInstanceActor.Subscribe(new[] { "barrier-ref" }, TimeSpan.FromMilliseconds(100)), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); - deadLetterProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + deadLetterProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/FocasNativeAlarmEndToEndTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/FocasNativeAlarmEndToEndTests.cs index 97887ac27..17fb112cb 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/FocasNativeAlarmEndToEndTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/FocasNativeAlarmEndToEndTests.cs @@ -73,7 +73,7 @@ public sealed class FocasNativeAlarmEndToEndTests : RuntimeActorTestBase try { cnc.SetAlarms(new FocasActiveAlarm(401, FocasAlarmType.Servo, 1, "SERVO V-READY OFF")); - var active = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)); + var active = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); active.AlarmNodeId.ShouldBe(ExpectedRawPath); active.Realm.ShouldBe(AddressSpaceRealm.Raw); active.State.Active.ShouldBeTrue("the Raise must project to an ACTIVE condition, not 'keep prior state'"); @@ -82,7 +82,7 @@ public sealed class FocasNativeAlarmEndToEndTests : RuntimeActorTestBase active.State.Message.ShouldStartWith("401 SERVO V-READY OFF"); cnc.SetAlarms(); - var cleared = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)); + var cleared = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); cleared.AlarmNodeId.ShouldBe(ExpectedRawPath); cleared.State.Active.ShouldBeFalse(); } @@ -118,7 +118,7 @@ public sealed class FocasNativeAlarmEndToEndTests : RuntimeActorTestBase var blob = BuildArtifact(enabled: true); var alerts = CreateTestProbe(); DistributedPubSub.Get(Sys).Mediator.Tell(new Subscribe(ScriptedAlarmHostActor.AlertsTopic, alerts.Ref), alerts.Ref); - alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // One fake CNC per driver instance, so what the fresh instance reads is fixed before it can poll. var firstCnc = new AlarmOnlyFocasClient(); firstCnc.SetAlarms(new FocasActiveAlarm(401, FocasAlarmType.Servo, 1, "SERVO V-READY OFF")); @@ -132,9 +132,9 @@ public sealed class FocasNativeAlarmEndToEndTests : RuntimeActorTestBase var factory = new HostFocasDriverFactory(firstCnc, respawnCnc); var (host, publish, coordinator) = SpawnHostAndApply(db, Seed(db, blob, Rev), factory); - publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)) + publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken) .State.Acknowledged.ShouldBeFalse(); - alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // The operator acknowledges (the running driver takes it), then a deploy changes the driver's config => // the host stops it and spawns a fresh instance. @@ -142,21 +142,21 @@ public sealed class FocasNativeAlarmEndToEndTests : RuntimeActorTestBase var revB = RevisionHash.Parse(new string('e', 64)); var changedConfig = BuildArtifact(enabled: true, driverConfig: "{\"respawn\":1}"); host.Tell(new DispatchDeployment(Seed(db, changedConfig, revB), revB, CorrelationId.NewId())); - coordinator.FishForMessage(a => a.Outcome == ApplyAckOutcome.Applied, TimeSpan.FromSeconds(5)); + coordinator.FishForMessage(a => a.Outcome == ApplyAckOutcome.Applied, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); - var restated = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5)); + var restated = publish.FishForMessage(_ => true, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); factory.Created.ShouldBe(2, "the config change must have respawned the host's driver"); restated.AlarmNodeId.ShouldBe(ExpectedRawPath); restated.State.Active.ShouldBeTrue(); if (newAlarmDuringRespawn) { restated.State.Acknowledged.ShouldBeFalse("an alarm the operator has not seen must not inherit the old ack"); - alerts.ExpectMsg(TimeSpan.FromSeconds(5)); + alerts.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); } else { restated.State.Acknowledged.ShouldBeTrue("a respawned driver's re-statement must not un-acknowledge"); - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmFeedSurvivesDeployTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmFeedSurvivesDeployTests.cs index d2b447f39..f7c7d6717 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmFeedSurvivesDeployTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmFeedSurvivesDeployTests.cs @@ -58,7 +58,7 @@ public sealed class NativeAlarmFeedSurvivesDeployTests : RuntimeActorTestBase driver.SubscribeCount.ShouldBeGreaterThanOrEqualTo(1); driver.LastSubscribedAlarmRefs.ShouldBe(new[] { AlarmXPath }); driver.AlarmSubscriberCount.ShouldBe(1); - }, duration: PresenceBudget); + }, 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, @@ -72,7 +72,7 @@ public sealed class NativeAlarmFeedSurvivesDeployTests : RuntimeActorTestBase { driver.SubscribeCount.ShouldBeGreaterThan(dataSubscribesBefore, "precondition: the deploy must re-subscribe DI-1's data, or this test exercises nothing"); - }, duration: PresenceBudget); + }, 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); @@ -81,7 +81,7 @@ public sealed class NativeAlarmFeedSurvivesDeployTests : RuntimeActorTestBase driver.FireAlarm(AlarmXPath, AlarmTransitionKind.Raise); var update = publish.FishForMessage( - m => m.AlarmNodeId == AlarmXPath && m.State.Active, PresenceBudget); + m => m.AlarmNodeId == AlarmXPath && m.State.Active, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); update.State.Acknowledged.ShouldBeFalse(); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmRestatementEndToEndTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmRestatementEndToEndTests.cs index b463be499..bcd4faf89 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmRestatementEndToEndTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/NativeAlarmRestatementEndToEndTests.cs @@ -79,14 +79,14 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase update.State.Active.ShouldBeTrue("an acked active alarm must project ACTIVE, not keep the cold default"); update.State.Acknowledged.ShouldBeTrue(); - var activated = rig.Alerts.ExpectMsg(Wait); + var activated = rig.Alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken); activated.TransitionKind.ShouldBe("Activated"); activated.Comment.ShouldBeNull("the operator comment belongs to the acknowledgement"); activated.TimestampUtc.ShouldBe(raisedAt); - var acked = rig.Alerts.ExpectMsg(Wait); + var acked = rig.Alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken); acked.TransitionKind.ShouldBe("Acknowledged"); acked.Comment.ShouldBe("investigating"); - rig.Alerts.ExpectNoMsg(Quiet); + rig.Alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: a live Clear MUST write its row — and the NEXT condition update is the clear, so no // unacked intermediate update sat between the snapshot's projection and it. @@ -94,7 +94,7 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var cleared = NextAlarmUpdate(rig.Publish); cleared.State.Active.ShouldBeFalse(); cleared.State.Acknowledged.ShouldBeTrue(); - rig.Alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); + rig.Alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); } finally { @@ -117,7 +117,7 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var raisedAt = new DateTime(2026, 10, 8, 9, 0, 0, DateTimeKind.Utc); rig.Send(Snapshot(Proto.AlarmConditionState.Active, raisedAt, raisedAt)); NextAlarmUpdate(rig.Publish).State.Active.ShouldBeTrue(); - rig.Alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated", + rig.Alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated", "the first statement on a cold host is a real activation"); // The gateway stream faults; the feed reopens and the gateway replays the same snapshot. @@ -126,24 +126,24 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var replayed = NextAlarmUpdate(rig.Publish); replayed.State.Active.ShouldBeTrue(); replayed.State.Acknowledged.ShouldBeFalse(); - rig.Alerts.ExpectNoMsg(Quiet); + rig.Alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: a live Clear MUST write its row. rig.Send(Live(Proto.AlarmTransitionKind.Clear, raisedAt.AddMinutes(1))); NextAlarmUpdate(rig.Publish).State.Active.ShouldBeFalse(); - rig.Alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); + rig.Alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); // Reconnect again; the snapshot now carries the alarm as Inactive — a re-stated Clear of an inactive // condition, no row. rig.Fault(); rig.Send(Snapshot(Proto.AlarmConditionState.Inactive, raisedAt, raisedAt.AddMinutes(1))); NextAlarmUpdate(rig.Publish).State.Active.ShouldBeFalse(); - rig.Alerts.ExpectNoMsg(Quiet); + rig.Alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: a LIVE Retrigger (a genuine MXAccess re-occurrence) is never a re-statement. rig.Send(Live(Proto.AlarmTransitionKind.Retrigger, raisedAt.AddMinutes(3))); NextAlarmUpdate(rig.Publish).State.Active.ShouldBeTrue(); - rig.Alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); + rig.Alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); } finally { @@ -277,27 +277,27 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase raised.AlarmNodeId.ShouldBe(OpcRawPath); raised.State.Active.ShouldBeTrue(); raised.State.Acknowledged.ShouldBeFalse(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); } // A config-changing deploy: the host keeps the still-deployed condition's state, the driver is respawned. var revB = RevisionHash.Parse(new string('b', 64)); host.Tell(new DispatchDeployment(Seed(db, blob, revB), revB, CorrelationId.NewId())); - coordinator.FishForMessage(x => x.Outcome == ApplyAckOutcome.Applied, Wait); - publish.FishForMessage(_ => true, Wait); + coordinator.FishForMessage(x => x.Outcome == ApplyAckOutcome.Applied, Wait, cancellationToken: TestContext.Current.CancellationToken); + publish.FishForMessage(_ => true, Wait, cancellationToken: TestContext.Current.CancellationToken); using var b = NewOpcUaDriver(host, "b"); b.RouteAlarmEvent(ConditionFields(active: true, acked: false), SourceIndex, handle); // ConditionRefresh copy var restated = NextAlarmUpdate(publish); restated.State.Active.ShouldBeTrue(); restated.State.Acknowledged.ShouldBeFalse("upstream says unacknowledged"); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: the upstream operator acknowledges — that transition MUST write its row. b.RouteAlarmEvent(ConditionFields(active: true, acked: true), SourceIndex, handle); var acked = NextAlarmUpdate(publish); acked.State.Acknowledged.ShouldBeTrue(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Acknowledged"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Acknowledged"); } /// @@ -319,15 +319,15 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var first = NextAlarmUpdate(publish); first.State.Active.ShouldBeTrue(); first.State.Acknowledged.ShouldBeTrue("no Active+unacked intermediate may reach clients"); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Acknowledged"); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Acknowledged"); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: the next update is the clear (not a leftover second update), and it writes its row. drv.RouteAlarmEvent(ConditionFields(active: false, acked: true), SourceIndex, handle); var cleared = NextAlarmUpdate(publish); cleared.State.Active.ShouldBeFalse(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); } /// @@ -346,12 +346,12 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase { a.RouteAlarmEvent(ConditionFields(active: true, acked: false), SourceIndex, handle); NextAlarmUpdate(publish).State.Active.ShouldBeTrue(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); a.RouteAlarmEvent(ConditionFields(active: false, acked: false), SourceIndex, handle); var cleared = NextAlarmUpdate(publish); cleared.State.Active.ShouldBeFalse(); cleared.State.Acknowledged.ShouldBeFalse(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); } Respawn(host, publish, coordinator, db, blob); @@ -361,7 +361,7 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var restated = NextAlarmUpdate(publish); restated.State.Active.ShouldBeFalse("no Active flicker may reach clients"); restated.State.Acknowledged.ShouldBeFalse("upstream says unacknowledged"); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: the upstream operator acknowledges — the NEXT update is that ack (no leftover second // update of the re-statement), and it writes its row. @@ -369,7 +369,7 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var acked = NextAlarmUpdate(publish); acked.State.Active.ShouldBeFalse(); acked.State.Acknowledged.ShouldBeTrue(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Acknowledged"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Acknowledged"); } /// @@ -388,7 +388,7 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase { a.RouteAlarmEvent(ConditionFields(active: true, acked: false), SourceIndex, handle); NextAlarmUpdate(publish).State.Active.ShouldBeTrue(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); } Respawn(host, publish, coordinator, db, blob); @@ -398,9 +398,9 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var cleared = NextAlarmUpdate(publish); cleared.State.Active.ShouldBeFalse("the first update must already be the clear"); cleared.State.Acknowledged.ShouldBeFalse(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); - alerts.ExpectNoMsg(Quiet); - publish.ReceiveWhile(Quiet, m => m).OfType() + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); + publish.ReceiveWhile(Quiet, m => m, cancellationToken: TestContext.Current.CancellationToken).OfType() .ShouldBeEmpty("one update, not a re-statement followed by the clear"); } @@ -420,8 +420,8 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase { a.RouteAlarmEvent(ConditionFields(active: true, acked: true), SourceIndex, handle); NextAlarmUpdate(publish).State.Active.ShouldBeTrue(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Acknowledged"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Acknowledged"); } Respawn(host, publish, coordinator, db, blob); @@ -431,11 +431,11 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var cleared = NextAlarmUpdate(publish); cleared.State.Active.ShouldBeFalse("a retained Active condition must be cleared"); cleared.State.Acknowledged.ShouldBeTrue(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); var reAcked = NextAlarmUpdate(publish); reAcked.State.Active.ShouldBeFalse(); reAcked.State.Acknowledged.ShouldBeTrue(); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); } /// @@ -455,9 +455,9 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase NextAlarmUpdate(publish); a.RouteAlarmEvent(ConditionFields(active: false, acked: true), SourceIndex, handle); NextAlarmUpdate(publish).State.Active.ShouldBeFalse(); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Acknowledged"); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Cleared"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Acknowledged"); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Cleared"); } Respawn(host, publish, coordinator, db, blob); @@ -467,12 +467,12 @@ public sealed class NativeAlarmRestatementEndToEndTests : RuntimeActorTestBase var restated = NextAlarmUpdate(publish); restated.State.Active.ShouldBeFalse(); restated.State.Acknowledged.ShouldBeTrue(); - alerts.ExpectNoMsg(Quiet); + alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken); // Positive control: a genuine re-activation MUST write its row. b.RouteAlarmEvent(ConditionFields(active: true, acked: false), SourceIndex, handle); - publish.FishForMessage(u => u.State.Active, Wait); - alerts.ExpectMsg(Wait).TransitionKind.ShouldBe("Activated"); + publish.FishForMessage(u => u.State.Active, Wait, cancellationToken: TestContext.Current.CancellationToken); + alerts.ExpectMsg(Wait, cancellationToken: TestContext.Current.CancellationToken).TransitionKind.ShouldBe("Activated"); } private (IActorRef Host, TestProbe Publish, TestProbe Coordinator, TestProbe Alerts, diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/S7AccessDeniedWatchdogTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/S7AccessDeniedWatchdogTests.cs index b7ace6d42..c9339c17f 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/S7AccessDeniedWatchdogTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/S7AccessDeniedWatchdogTests.cs @@ -106,7 +106,7 @@ public sealed class S7AccessDeniedWatchdogTests : RuntimeActorTestBase passes.ShouldBeGreaterThanOrEqualTo(10); faulted.ShouldBe(passes, "every pass reports the PUT/GET config alert"); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); driver.GetHealth().State.ShouldBe(DriverState.Faulted); } @@ -117,7 +117,7 @@ public sealed class S7AccessDeniedWatchdogTests : RuntimeActorTestBase var (driver, parent) = Connect(() => new TimeoutException("frozen peer")); var poll = PollUntilAsync(driver, DateTime.UtcNow + Threshold + Threshold + TimeSpan.FromMilliseconds(500)); - parent.ExpectMsg(PresenceBudget).Connected.ShouldBeFalse(); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Connected.ShouldBeFalse(); await poll; } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Harness/RuntimeActorTestBase.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Harness/RuntimeActorTestBase.cs index 8f1300355..a5a9edbc5 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Harness/RuntimeActorTestBase.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Harness/RuntimeActorTestBase.cs @@ -1,6 +1,6 @@ using Akka.Cluster; using Akka.TestKit; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/DbHealthProbeOffThreadTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/DbHealthProbeOffThreadTests.cs index c10e24f2b..9bee99b33 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/DbHealthProbeOffThreadTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/DbHealthProbeOffThreadTests.cs @@ -23,10 +23,10 @@ public sealed class DbHealthProbeOffThreadTests : RuntimeActorTestBase try { var actor = Sys.ActorOf(DbHealthProbeActor.Props(factory)); - AwaitCondition(() => factory.Entered > 0, PresenceBudget); + AwaitCondition(() => factory.Entered > 0, PresenceBudget, TestContext.Current.CancellationToken); var status = await actor.Ask( - DbHealthProbeActor.GetStatus.Instance, TimeSpan.FromSeconds(3)); + DbHealthProbeActor.GetStatus.Instance, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); status.Reachable.ShouldBeFalse(); status.LastError.ShouldBe("not probed yet"); @@ -50,7 +50,7 @@ public sealed class DbHealthProbeOffThreadTests : RuntimeActorTestBase var status = await actor.Ask( DbHealthProbeActor.GetStatus.Instance, TimeSpan.FromSeconds(3)); status.Reachable.ShouldBeTrue(); - }, PresenceBudget); + }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Blocks every CreateDbContext until released — a connect that never answers. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/HealthProbeActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/HealthProbeActorTests.cs index 4a113136f..a02df107c 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/HealthProbeActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/HealthProbeActorTests.cs @@ -30,7 +30,7 @@ public sealed class HealthProbeActorTests : RuntimeActorTestBase status.Reachable.ShouldBeTrue(); status.LastError.ShouldBeNull(); - }, PresenceBudget); + }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the peer OPC UA probe actor reports Ok true against a live listener. @@ -50,7 +50,7 @@ public sealed class HealthProbeActorTests : RuntimeActorTestBase broadcast: msg => received.Add(msg))); AwaitCondition(() => received.OfType().Any(r => r.Ok), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); } /// Verifies that the peer OPC UA probe actor reports Ok false against an unreachable endpoint. @@ -67,7 +67,7 @@ public sealed class HealthProbeActorTests : RuntimeActorTestBase broadcast: msg => received.Add(msg))); AwaitCondition(() => received.OfType().Any(r => !r.Ok), - TimeSpan.FromSeconds(3)); + TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); } /// Verifies that the historian adapter actor forwards events to the injected sink. @@ -91,7 +91,7 @@ public sealed class HealthProbeActorTests : RuntimeActorTestBase TimestampUtc: DateTime.UtcNow, AlarmTypeName: "LimitAlarm")); - AwaitCondition(() => sink.Enqueued.Count == 5, TimeSpan.FromSeconds(2)); + AwaitCondition(() => sink.Enqueued.Count == 5, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); sink.Enqueued.Select(e => e.AlarmId).OrderBy(s => s).ShouldBe( new[] { "alm-0", "alm-1", "alm-2", "alm-3", "alm-4" }); } @@ -106,10 +106,10 @@ public sealed class HealthProbeActorTests : RuntimeActorTestBase actor.Tell(new ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts.AlarmTransitionEvent( AlarmId: "alm-x", EquipmentPath: "Plant/LineB", AlarmName: "OffNormal", TransitionKind: "Activated", Severity: 200, Message: "msg", User: "system", TimestampUtc: DateTime.UtcNow, AlarmTypeName: "OffNormalAlarm")); - AwaitCondition(() => sink.Enqueued.Count == 1, TimeSpan.FromSeconds(2)); + AwaitCondition(() => sink.Enqueued.Count == 1, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); var status = await actor.Ask( - HistorianAdapterActor.GetStatus.Instance, TimeSpan.FromSeconds(2)); + HistorianAdapterActor.GetStatus.Instance, TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken); status.QueueDepth.ShouldBe(1); status.DrainState.ShouldBe(HistorianDrainState.Idle); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/PeerProbeSupervisorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/PeerProbeSupervisorTests.cs index 46c2987d0..28358b896 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/PeerProbeSupervisorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Health/PeerProbeSupervisorTests.cs @@ -57,7 +57,7 @@ public sealed class PeerProbeSupervisorTests : RuntimeActorTestBase State(Adm, RedundancyRole.Detached))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies the child for a departed peer is stopped when the next snapshot omits it. @@ -71,11 +71,11 @@ public sealed class PeerProbeSupervisorTests : RuntimeActorTestBase State(Local, RedundancyRole.Primary), State(Peer, RedundancyRole.Secondary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sup.Tell(Snapshot(State(Local, RedundancyRole.Primary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(0), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies a single-node snapshot (just the local node) spawns no children — the local node @@ -103,7 +103,7 @@ public sealed class PeerProbeSupervisorTests : RuntimeActorTestBase State(Peer, RedundancyRole.Secondary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies a previously-removed peer is respawned when it re-appears, without an @@ -118,17 +118,17 @@ public sealed class PeerProbeSupervisorTests : RuntimeActorTestBase State(Local, RedundancyRole.Primary), State(Peer, RedundancyRole.Secondary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sup.Tell(Snapshot(State(Local, RedundancyRole.Primary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(0), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sup.Tell(Snapshot( State(Local, RedundancyRole.Primary), State(Peer, RedundancyRole.Secondary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Locks in the stale-Terminated guard: when an OLD (already-replaced) child's @@ -147,27 +147,27 @@ public sealed class PeerProbeSupervisorTests : RuntimeActorTestBase State(Local, RedundancyRole.Primary), State(Peer, RedundancyRole.Secondary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); - AwaitAssert(() => spawned.Count.ShouldBe(1), duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => spawned.Count.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var oldRef = spawned[0]; // Drop the peer -> child #0 stopped, ChildCount back to 0. sup.Tell(Snapshot(State(Local, RedundancyRole.Primary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(0), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Re-add the SAME peer -> a NEW child #1 (the FRESH ref) is spawned. sup.Tell(Snapshot( State(Local, RedundancyRole.Primary), State(Peer, RedundancyRole.Secondary))); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); - AwaitAssert(() => spawned.Count.ShouldBe(2), duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => spawned.Count.ShouldBe(2), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Now deliver a STALE Terminated for the OLD ref. The current child for Peer is the fresh // child #1, so ref-equality finds no match and the supervisor must leave ChildCount at 1. sup.Tell(new Terminated(oldRef, existenceConfirmed: true, addressTerminated: false)); AwaitAssert(() => sup.UnderlyingActor.ChildCount.ShouldBe(1), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AddHistorianProvisioningTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AddHistorianProvisioningTests.cs index 6a11e7975..8be82b260 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AddHistorianProvisioningTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/AddHistorianProvisioningTests.cs @@ -169,7 +169,7 @@ public sealed class AddHistorianProvisioningTests HistorizedTag(displayName: "Temp", historianName: "Pump1.Temp", dataType: "Float32"), NonHistorizedTag(displayName: "Run", dataType: "Boolean"))); - await prov.Called.WaitAsync(TimeSpan.FromSeconds(5)); + await prov.Called.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); prov.Seen.Count.ShouldBe(1); prov.Seen[0].TagName.ShouldBe("Pump1.Temp"); prov.Seen[0].DataType.ShouldBe(DriverDataType.Float32); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderAppendPipelineTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderAppendPipelineTests.cs index 4759dc87e..5da844cd2 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderAppendPipelineTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderAppendPipelineTests.cs @@ -1,5 +1,5 @@ using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; @@ -39,7 +39,7 @@ public sealed class ContinuousHistorizationRecorderAppendPipelineTests : TestKit var rec = Start(outbox, new LoggingPassWriter(log)); rec.Tell(Burst(3)); - await outbox.AppendEntered.Task.WaitAsync(Wait); + await outbox.AppendEntered.Task.WaitAsync(Wait, TestContext.Current.CancellationToken); // The fsync is "stuck": the actor still answers — the append is not on the mailbox. var during = await StatusAsync(rec); @@ -47,7 +47,7 @@ public sealed class ContinuousHistorizationRecorderAppendPipelineTests : TestKit during.TotalRecorded.ShouldBe(0); outbox.AppendGate.SetResult(); - await AwaitAssertAsync(async () => (await StatusAsync(rec)).TotalRecorded.ShouldBe(3), Wait); + await AwaitAssertAsync(async () => (await StatusAsync(rec)).TotalRecorded.ShouldBe(3), Wait, cancellationToken: TestContext.Current.CancellationToken); (await StatusAsync(rec)).PendingAppends.ShouldBe(0); } @@ -59,12 +59,12 @@ public sealed class ContinuousHistorizationRecorderAppendPipelineTests : TestKit var rec = Start(outbox, new LoggingPassWriter(log)); rec.Tell(Burst(2, 0)); - await outbox.AppendEntered.Task.WaitAsync(Wait); + await outbox.AppendEntered.Task.WaitAsync(Wait, TestContext.Current.CancellationToken); for (var b = 1; b < 6; b++) rec.Tell(Burst(2, b * 2)); - await AwaitAssertAsync(async () => (await StatusAsync(rec)).PendingAppends.ShouldBe(6), Wait); + await AwaitAssertAsync(async () => (await StatusAsync(rec)).PendingAppends.ShouldBe(6), Wait, cancellationToken: TestContext.Current.CancellationToken); outbox.AppendGate.SetResult(); - await AwaitAssertAsync(async () => (await StatusAsync(rec)).TotalRecorded.ShouldBe(12), Wait); + await AwaitAssertAsync(async () => (await StatusAsync(rec)).TotalRecorded.ShouldBe(12), Wait, cancellationToken: TestContext.Current.CancellationToken); outbox.Appended.Select(e => e.Value.Number).ShouldBe(Enumerable.Range(0, 12).Select(i => (double)i), "FIFO across bursts"); outbox.AppendBatchCalls.ShouldBeLessThan(6, "the bursts waiting behind the slow append share its successor's commit"); @@ -78,18 +78,18 @@ public sealed class ContinuousHistorizationRecorderAppendPipelineTests : TestKit var rec = Start(outbox, new LoggingPassWriter(log), maxAppendsInFlight: 2); rec.Tell(Burst(1, 0)); - await outbox.AppendEntered.Task.WaitAsync(Wait); + await outbox.AppendEntered.Task.WaitAsync(Wait, TestContext.Current.CancellationToken); for (var b = 1; b < 5; b++) rec.Tell(Burst(1, b)); // Two in flight (one in the stuck append, one queued), the third burst's write waits on the queue — and with it // the mailbox: a status request is not answered until the queue drains. var status = StatusAsync(rec, TimeSpan.FromSeconds(10)); - await Task.Delay(300); + await Task.Delay(300, TestContext.Current.CancellationToken); status.IsCompleted.ShouldBeFalse("a full append queue must hold the mailbox (bounded memory)"); outbox.AppendGate.SetResult(); (await status).ShouldNotBeNull(); - await AwaitAssertAsync(async () => (await StatusAsync(rec)).TotalRecorded.ShouldBe(5), Wait); + await AwaitAssertAsync(async () => (await StatusAsync(rec)).TotalRecorded.ShouldBe(5), Wait, cancellationToken: TestContext.Current.CancellationToken); outbox.Appended.Select(e => e.Value.Number).ShouldBe([0d, 1d, 2d, 3d, 4d]); } @@ -103,14 +103,14 @@ public sealed class ContinuousHistorizationRecorderAppendPipelineTests : TestKit var first = StatusAsync(rec); // The mailbox keeps moving while the count hangs: a burst sent now is appended. rec.Tell(Burst(2)); - await AwaitAssertAsync(() => outbox.Appended.Count.ShouldBe(2), Wait); + await AwaitAssertAsync(() => outbox.Appended.Count.ShouldBe(2), Wait, cancellationToken: TestContext.Current.CancellationToken); var answered = await first; answered.QueuedDepth.ShouldBe(-1, "an outbox count that did not answer within the timeout is unknown"); // Positive control: a count that answers is reported. outbox.CountGate.SetResult(); - await AwaitAssertAsync(async () => (await StatusAsync(rec)).QueuedDepth.ShouldBeGreaterThanOrEqualTo(0), Wait); + await AwaitAssertAsync(async () => (await StatusAsync(rec)).QueuedDepth.ShouldBeGreaterThanOrEqualTo(0), Wait, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -130,14 +130,14 @@ public sealed class ContinuousHistorizationRecorderAppendPipelineTests : TestKit var outbox = new LoggingOutbox(log) { AppendGate = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) }; var rec = Start(outbox, new LoggingPassWriter(log)); rec.Tell(Burst(1, 0)); - await outbox.AppendEntered.Task.WaitAsync(Wait); + await outbox.AppendEntered.Task.WaitAsync(Wait, TestContext.Current.CancellationToken); rec.Tell(Burst(1, 1)); - await AwaitAssertAsync(async () => (await StatusAsync(rec)).PendingAppends.ShouldBe(2), Wait); + await AwaitAssertAsync(async () => (await StatusAsync(rec)).PendingAppends.ShouldBe(2), Wait, cancellationToken: TestContext.Current.CancellationToken); - _ = Task.Run(async () => { await Task.Delay(200); outbox.AppendGate.SetResult(); }); + _ = Task.Run(async () => { await Task.Delay(200); outbox.AppendGate.SetResult(); }, TestContext.Current.CancellationToken); Watch(rec); Sys.Stop(rec); - ExpectTerminated(rec, Wait); + ExpectTerminated(rec, Wait, cancellationToken: TestContext.Current.CancellationToken); outbox.Appended.Select(e => e.Value.Number).ShouldBe([0d, 1d], "the queued burst is appended within the stop budget, not dropped"); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderGaugeTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderGaugeTests.cs index 1510540a7..cf2c7d792 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderGaugeTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderGaugeTests.cs @@ -1,5 +1,5 @@ using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; @@ -37,14 +37,14 @@ public sealed class ContinuousHistorizationRecorderGaugeTests : TestKit CreateTestProbe().Ref, new LoggingPassWriter(log), outbox, historizedRefs: ["A"], drainInterval: TimeSpan.FromSeconds(30))); rec.Tell(Burst(0)); - await outbox.AppendEntered.Task.WaitAsync(Wait); + await outbox.AppendEntered.Task.WaitAsync(Wait, TestContext.Current.CancellationToken); rec.Tell(Burst(1)); // The second burst is accepted while the first append is stuck: the gauge shows the queue. - await AwaitAssertAsync(() => ContinuousHistorizationMetrics.PendingAppendsGauge.ShouldBe(2), Wait); + await AwaitAssertAsync(() => ContinuousHistorizationMetrics.PendingAppendsGauge.ShouldBe(2), Wait, cancellationToken: TestContext.Current.CancellationToken); // No GetStatus from here on (production never asks one): the append loop itself must publish the drained depth. outbox.AppendGate.SetResult(); - await AwaitAssertAsync(() => outbox.Appended.Count.ShouldBe(2), Wait); - await AwaitAssertAsync(() => ContinuousHistorizationMetrics.PendingAppendsGauge.ShouldBe(0), Wait); + await AwaitAssertAsync(() => outbox.Appended.Count.ShouldBe(2), Wait, cancellationToken: TestContext.Current.CancellationToken); + await AwaitAssertAsync(() => ContinuousHistorizationMetrics.PendingAppendsGauge.ShouldBe(0), Wait, cancellationToken: TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderPassFlushTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderPassFlushTests.cs index 13f27c6d4..6e75f2d61 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderPassFlushTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderPassFlushTests.cs @@ -1,5 +1,5 @@ using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; @@ -36,7 +36,7 @@ public sealed class ContinuousHistorizationRecorderPassFlushTests : TestKit { var (log, _, _) = Start("A", "B", "C"); - AwaitAssert(() => log.Snapshot().ShouldContain("ack"), Wait); + AwaitAssert(() => log.Snapshot().ShouldContain("ack"), Wait, cancellationToken: TestContext.Current.CancellationToken); var events = log.Snapshot().SkipWhile(e => !e.StartsWith("write:")).ToList(); var flush = events.IndexOf("flush"); flush.ShouldBe(3, $"three in-pass writes, then the flush: {string.Join(", ", events)}"); @@ -61,7 +61,7 @@ public sealed class ContinuousHistorizationRecorderPassFlushTests : TestKit [new("A", 1.0, T0), new("B", 2.0, T0)])); // The pass cannot ack (B blocks the head), but A's rows must be visible to the next pass's redelivery check. - AwaitAssert(() => log.Snapshot().ShouldContain("flush"), Wait); + AwaitAssert(() => log.Snapshot().ShouldContain("flush"), Wait, cancellationToken: TestContext.Current.CancellationToken); log.Snapshot().ShouldNotContain("ack"); } @@ -79,7 +79,7 @@ public sealed class ContinuousHistorizationRecorderPassFlushTests : TestKit rec.Tell(new VirtualTagActor.DependencyValueBatch([new("A", 1.0, T0)])); // Positive control first: the failing write itself happened (twice — a retry ran), so "no flush" is not vacuous. - AwaitAssert(() => log.Snapshot().Count(e => e == "write:A").ShouldBeGreaterThanOrEqualTo(2), Wait); + AwaitAssert(() => log.Snapshot().Count(e => e == "write:A").ShouldBeGreaterThanOrEqualTo(2), Wait, cancellationToken: TestContext.Current.CancellationToken); log.Snapshot().ShouldNotContain("flush"); } @@ -94,7 +94,7 @@ public sealed class ContinuousHistorizationRecorderPassFlushTests : TestKit drainInterval: TimeSpan.FromMilliseconds(50))); rec.Tell(new VirtualTagActor.DependencyValueBatch([new("A", 1.0, T0)])); - AwaitAssert(() => log.Snapshot().ShouldContain("ack"), Wait); + AwaitAssert(() => log.Snapshot().ShouldContain("ack"), Wait, cancellationToken: TestContext.Current.CancellationToken); outbox.Entries.ShouldBeEmpty("the rows are committed; only their visibility is unproven"); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTests.cs index 712db0dc0..2d92acb25 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTests.cs @@ -1,6 +1,6 @@ using System.Diagnostics; using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; @@ -34,7 +34,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: new[] { "Pump1.Temp" })); - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Contains("Pump1.Temp", reg.TagRefs); } @@ -47,14 +47,14 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit mux.Ref, new FakeValueWriter(), new InMemoryOutbox(), historizedRefs: Array.Empty())); // PreStart registers the (empty) initial set first. - var initial = mux.ExpectMsg(); + var initial = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Empty(initial.TagRefs); rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { HistorizedTagRef.ForSelf("Pump1.Temp"), HistorizedTagRef.ForSelf("Pump2.Flow") }, Array.Empty())); - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(new[] { "Pump1.Temp", "Pump2.Flow" }, reg.TagRefs.OrderBy(x => x, StringComparer.Ordinal)); } @@ -65,18 +65,18 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, new FakeValueWriter(), new InMemoryOutbox(), historizedRefs: Array.Empty())); - mux.ExpectMsg(); // PreStart (empty) + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart (empty) rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { HistorizedTagRef.ForSelf("A"), HistorizedTagRef.ForSelf("B") }, Array.Empty())); - var first = mux.ExpectMsg(); + var first = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(new[] { "A", "B" }, first.TagRefs.OrderBy(x => x, StringComparer.Ordinal)); // Add "C", remove "A" → converge to {B, C}. The mux's RegisterInterest is a full-REPLACE, so the // recorder sends ONE RegisterInterest carrying exactly the converged set (C registered, A gone). rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { HistorizedTagRef.ForSelf("C") }, new[] { HistorizedTagRef.ForSelf("A") })); - var second = mux.ExpectMsg(); + var second = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(new[] { "B", "C" }, second.TagRefs.OrderBy(x => x, StringComparer.Ordinal)); Assert.DoesNotContain("A", second.TagRefs); } @@ -88,13 +88,13 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, new FakeValueWriter(), new InMemoryOutbox(), historizedRefs: new[] { "A", "B" })); - var initial = mux.ExpectMsg(); // PreStart {A, B} + var initial = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart {A, B} Assert.Equal(new[] { "A", "B" }, initial.TagRefs.OrderBy(x => x, StringComparer.Ordinal)); // A delta with no net effect (add an already-present ref, remove an absent one) → no mux churn. rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { HistorizedTagRef.ForSelf("A") }, new[] { HistorizedTagRef.ForSelf("Z") })); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } [Fact] @@ -104,12 +104,12 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, new FakeValueWriter(), new InMemoryOutbox(), historizedRefs: new[] { "A" })); - mux.ExpectMsg(); // PreStart {A} + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart {A} // The mux has no per-ref unregister; an empty converged set drops ALL interest in one message. rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( Array.Empty(), new[] { HistorizedTagRef.ForSelf("A") })); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -124,21 +124,21 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: Array.Empty())); - mux.ExpectMsg(); // PreStart (empty) + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart (empty) rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { new HistorizedTagRef("Area/Line/Pump1", "HIST.Pump1.Temp") }, Array.Empty())); // Interest is registered under the MUX REF (the driver ref the mux keys fan-out by), NOT the name. - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(new[] { "Area/Line/Pump1" }, reg.TagRefs); // The mux fans the value keyed by the driver ref; the recorder must historize it under the OVERRIDE. rec.Tell(new VirtualTagActor.DependencyValueChanged("Area/Line/Pump1", 55.0, DateTime.UtcNow)); await AwaitAssertAsync(() => - Assert.Contains(writer.Snapshot(), w => w.Tag == "HIST.Pump1.Temp" && w.Value == 55.0)); + Assert.Contains(writer.Snapshot(), w => w.Tag == "HIST.Pump1.Temp" && w.Value == 55.0), cancellationToken: TestContext.Current.CancellationToken); // ...and NEVER under the mux ref (the pre-fix behaviour wrote under msg.TagId == the mux ref). Assert.DoesNotContain(writer.Snapshot(), w => w.Tag == "Area/Line/Pump1"); } @@ -154,23 +154,23 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, new InMemoryOutbox(), historizedRefs: Array.Empty())); - mux.ExpectMsg(); // PreStart (empty) + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart (empty) rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { new HistorizedTagRef("Area/Pump1", "HIST.Old") }, Array.Empty())); - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(new[] { "Area/Pump1" }, reg.TagRefs); // Override renamed: same mux ref, new historian name. Key-set unchanged → no further mux message. rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] { new HistorizedTagRef("Area/Pump1", "HIST.New") }, new[] { new HistorizedTagRef("Area/Pump1", "HIST.Old") })); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // ...and the WRITE TARGET actually updated: a value now historizes under HIST.New, never HIST.Old. rec.Tell(new VirtualTagActor.DependencyValueChanged("Area/Pump1", 9.0, DateTime.UtcNow)); await AwaitAssertAsync(() => - Assert.Contains(writer.Snapshot(), w => w.Tag == "HIST.New" && w.Value == 9.0)); + Assert.Contains(writer.Snapshot(), w => w.Tag == "HIST.New" && w.Value == 9.0), cancellationToken: TestContext.Current.CancellationToken); Assert.DoesNotContain(writer.Snapshot(), w => w.Tag == "HIST.Old"); } @@ -186,7 +186,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: Array.Empty())); - mux.ExpectMsg(); // PreStart (empty) + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart (empty) rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] @@ -197,7 +197,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Array.Empty())); // ONE mux ref is registered (both aliases share it). - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(new[] { "Area/Pump1" }, reg.TagRefs); rec.Tell(new VirtualTagActor.DependencyValueChanged("Area/Pump1", 71.0, DateTime.UtcNow)); @@ -208,7 +208,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var snap = writer.Snapshot(); Assert.Contains(snap, w => w.Tag == "HIST.MachineA.Temp" && w.Value == 71.0); Assert.Contains(snap, w => w.Tag == "HIST.MachineB.Temp" && w.Value == 71.0); - }); + }, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -220,7 +220,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, new FakeValueWriter(), new InMemoryOutbox(), historizedRefs: Array.Empty())); - mux.ExpectMsg(); // PreStart (empty) + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // PreStart (empty) rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( new[] @@ -229,12 +229,12 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit new HistorizedTagRef("Area/Pump1", "HIST.B"), }, Array.Empty())); - mux.ExpectMsg(); // {Area/Pump1} + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // {Area/Pump1} // Drop only HIST.A — Area/Pump1 still fans for HIST.B, so the key-set is unchanged: no mux churn. rec.Tell(new ContinuousHistorizationRecorder.UpdateHistorizedRefs( Array.Empty(), new[] { new HistorizedTagRef("Area/Pump1", "HIST.A") })); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } [Fact] @@ -250,9 +250,9 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit rec.Tell(new VirtualTagActor.DependencyValueChanged("Pump1.Temp", 42.0, DateTime.UtcNow)); await AwaitAssertAsync(() => - Assert.Contains(writer.Snapshot(), w => w.Tag == "Pump1.Temp" && w.Value == 42.0)); + Assert.Contains(writer.Snapshot(), w => w.Tag == "Pump1.Temp" && w.Value == 42.0), cancellationToken: TestContext.Current.CancellationToken); await AwaitAssertAsync(async () => - Assert.Equal(0, await outbox.CountAsync(default))); // acked -> truncated + Assert.Equal(0, await outbox.CountAsync(default)), cancellationToken: TestContext.Current.CancellationToken); // acked -> truncated } [Fact] @@ -275,7 +275,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit { Assert.Contains(writer.Snapshot(), w => w.Tag == "Pump1.Temp" && w.Value == 7.0); Assert.Equal(1, await outbox.CountAsync(default)); - }); + }, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -314,7 +314,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit "the writer must have been called at least twice (a retry happened)"); Assert.Equal(0, await outbox.CountAsync(default)); }, - TimeSpan.FromSeconds(5)); + TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -336,7 +336,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(1, status.DroppedUnsupportedKind); Assert.Equal(0, status.QueuedDepth); - }); + }, cancellationToken: TestContext.Current.CancellationToken); Assert.Empty(writer.Snapshot()); } @@ -379,7 +379,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit IActorRef rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: new[] { "Pump1.Temp" }, drainInterval: TimeSpan.FromMilliseconds(200))); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var stamp = new DateTime(2026, 8, 1, 12, 0, 0, DateTimeKind.Utc); rec.Tell(new VirtualTagActor.DependencyValueChanged("Pump1.Temp", 42.5, stamp)); @@ -389,15 +389,15 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit { Assert.Single(writer.Snapshot()); return Task.CompletedTask; - }); + }, cancellationToken: TestContext.Current.CancellationToken); // ...and — the load-bearing half — the actor is still the SAME incarnation, holding the ref set // it was seeded with. A restart would reset TotalRecorded to 0 and re-register interest, so this // assertion is what goes red when Self is touched after the await. var status = await rec.Ask( - ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); + ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); Assert.Equal(1, status.TotalRecorded); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // no PreStart re-register == no restart + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // no PreStart re-register == no restart } // ---------------- Gitea #538: one poison tag must not pin the FIFO head ---------------- @@ -449,7 +449,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); Assert.Equal(1, status.AbandonedEntries); - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); // And the healthy tag was submitted a BOUNDED number of times — once per blocked pass plus the // pass that unblocked, never indefinitely. Snapshot it, wait on the DRAIN'S OWN clock until three @@ -500,10 +500,10 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.True(writer.CallCount >= 6, "the drain must have retried several times"); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); var status = await rec.Ask( - ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); + ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); Assert.Equal(0, status.AbandonedEntries); Assert.Equal(2, status.QueuedDepth); // both values still durably queued for when it returns } @@ -539,7 +539,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.True(writer.Snapshot().Count(w => w.Tag == "Flaky.Tag") >= 2); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); writer.SetFailing("Flaky.Tag", false); @@ -549,7 +549,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); Assert.Equal(0, status.AbandonedEntries); // recovered, so nothing was ever discarded - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } // ------- Shutdown cancellation is a pass ABORT, not a per-tag failure (guards #538) ------- @@ -609,17 +609,17 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit // Presence waits, budgeted from the shared RuntimeActorTestBase constant rather than 10 s: this // one timed out on a loaded Gitea runner (#606). They return the instant the writer signals. - await writer.Entered.WaitAsync(RuntimeActorTestBase.PresenceBudget); + await writer.Entered.WaitAsync(RuntimeActorTestBase.PresenceBudget, TestContext.Current.CancellationToken); // Preconditions, so a green result can never be vacuous: both entries are still queued (they were // peeked as ONE batch — a lone healthy entry would have been acked and gone), and the healthy tag // has written on this very pass. - Assert.Equal(2, await outbox.CountAsync(default)); + Assert.Equal(2, await outbox.CountAsync(TestContext.Current.CancellationToken)); Assert.Equal(2, writer.CallCountFor("Good.Tag")); Sys.Stop(rec); // PostStop cancels the drain token underneath the in-flight write - await writer.Cancelled.WaitAsync(RuntimeActorTestBase.PresenceBudget); + await writer.Cancelled.WaitAsync(RuntimeActorTestBase.PresenceBudget, TestContext.Current.CancellationToken); // Bounded negative watch — red is immediate when the fold truncates; green costs the window once. // This guards a NEGATIVE ("the pass unwound without acking"), so instead of sleeping and hoping the @@ -643,12 +643,12 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.True( removeCalls == 0, $"the cancelled pass acked: RemoveAsync ran {removeCalls}x, truncating the queue"); - int queued = await outbox.CountAsync(default); + int queued = await outbox.CountAsync(TestContext.Current.CancellationToken); Assert.True(queued == 2, $"the cancelled pass changed the queue depth: expected 2, saw {queued}"); - await Task.Delay(25); + await Task.Delay(25, TestContext.Current.CancellationToken); } - Assert.Equal(2, await outbox.CountAsync(default)); + Assert.Equal(2, await outbox.CountAsync(TestContext.Current.CancellationToken)); } /// @@ -688,7 +688,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); Assert.Equal(1, status.AbandonedEntries); - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -730,7 +730,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); Assert.Equal(1, status.AbandonedEntries); - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } // ---------------- Gitea #537: only the recording Primary captures ---------------- @@ -767,7 +767,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(1, status.SuppressedNotPrimary); Assert.Equal(0, status.TotalRecorded); - }); + }, cancellationToken: TestContext.Current.CancellationToken); Assert.Empty(writer.Snapshot()); } @@ -799,7 +799,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var suppressed = await rec.Ask( ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(1, suppressed.SuppressedNotPrimary); - }); + }, cancellationToken: TestContext.Current.CancellationToken); Volatile.Write(ref open, true); // promoted rec.Tell(new VirtualTagActor.DependencyValueChanged("Pump1.Temp", 22.0, DateTime.UtcNow)); @@ -813,7 +813,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.Contains(written, w => w.Value == 22.0); Assert.DoesNotContain(written, w => w.Value == 11.0); return Task.CompletedTask; - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } // ---------------- Bounded-parallel drain (DrainConcurrency) ---------------- @@ -861,7 +861,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit $"expected overlapping writes; peak in-flight was {writer.MaxObservedConcurrency}"); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); writer.Release(); @@ -872,7 +872,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); Assert.Equal(0, status.AbandonedEntries); - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -909,7 +909,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.True(writer.CallCount >= 1, "the drain must have started a write"); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); // …and stays alone: with a window of 8 the other two groups would have entered by now. Bounded // negative watch rather than a sleep — red is immediate the moment a second group enters; green @@ -930,7 +930,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit writer.MaxObservedConcurrency == 1 && writer.CallCount == 1, $"a second group entered under a window of 1: calls={writer.CallCount}, " + $"peak={writer.MaxObservedConcurrency}"); - await Task.Delay(25); + await Task.Delay(25, TestContext.Current.CancellationToken); } Assert.Equal(1, writer.MaxObservedConcurrency); @@ -943,7 +943,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit var status = await rec.Ask( ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(1, writer.MaxObservedConcurrency); } @@ -972,7 +972,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit IActorRef rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: refs, drainInterval: TimeSpan.FromMilliseconds(50))); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var stamp = new DateTime(2026, 8, 15, 9, 0, 0, DateTimeKind.Utc); rec.Tell(new VirtualTagActor.DependencyValueBatch( @@ -988,7 +988,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit written.Select(w => w.Tag).OrderBy(x => x, StringComparer.Ordinal)); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(1, outbox.AppendBatchCallCount); Assert.Equal(0, outbox.AppendCallCount); @@ -1000,7 +1000,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); Assert.Equal(0, status.QueuedDepth); Assert.Equal(5, status.TotalRecorded); - }, TimeSpan.FromSeconds(10)); + }, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } /// @@ -1041,7 +1041,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit IActorRef rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: new[] { "A", "B", "C" }, drainInterval: TimeSpan.FromMilliseconds(200))); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var stamp = new DateTime(2026, 8, 15, 10, 0, 0, DateTimeKind.Utc); rec.Tell(new VirtualTagActor.DependencyValueBatch(new[] @@ -1058,14 +1058,14 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.Equal(3, writer.Snapshot().Count); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); // …and — the load-bearing half — the actor is still the SAME incarnation, holding the ref set it // was seeded with. A restart would reset TotalRecorded and re-register interest from PreStart. var status = await rec.Ask( - ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); + ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); Assert.Equal(3, status.TotalRecorded); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // no PreStart re-register == no restart + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // no PreStart re-register == no restart } /// @@ -1105,7 +1105,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.Equal(3, status.SuppressedNotPrimary); Assert.Equal(0, status.TotalRecorded); Assert.Equal(0, status.QueuedDepth); - }); + }, cancellationToken: TestContext.Current.CancellationToken); Assert.Empty(writer.Snapshot()); Assert.Equal(0, outbox.AppendBatchCallCount); @@ -1144,7 +1144,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit captureGate: () => Interlocked.Increment(ref gateCalls) == 2 ? throw new InvalidOperationException("role view unavailable") : true)); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var stamp = new DateTime(2026, 8, 15, 12, 0, 0, DateTimeKind.Utc); rec.Tell(new VirtualTagActor.DependencyValueBatch(new[] @@ -1163,16 +1163,16 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.DoesNotContain(written, w => w.Tag == "B"); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); var status = await rec.Ask( - ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3)); + ContinuousHistorizationRecorder.GetStatus.Instance, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); Assert.Equal(1, status.IngestFaults); Assert.Equal(2, status.TotalRecorded); // The fault is NOT a "value the historian cannot carry" — that counter must stay clean, or an // ingest-dependency outage is indistinguishable from bad data on the /hosts surface. Assert.Equal(0, status.DroppedUnsupportedKind); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // no PreStart re-register == no restart + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // no PreStart re-register == no restart } /// @@ -1223,7 +1223,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit IActorRef rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: new[] { "A", "B", "C" }, drainInterval: TimeSpan.FromSeconds(30))); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var stamp = new DateTime(2026, 8, 15, 13, 0, 0, DateTimeKind.Utc); rec.Tell(new VirtualTagActor.DependencyValueBatch(new[] @@ -1243,7 +1243,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.Equal(1, status.OutboxAppendFailures); Assert.Equal(0, status.TotalRecorded); }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); await AwaitAssertAsync( () => @@ -1252,12 +1252,12 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.Contains(writer.Snapshot(), w => w.Tag == "A" && w.Value == 1.0); return Task.CompletedTask; }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); // What never committed stays gone — the prefix drained, the remainder did not resurrect. Assert.DoesNotContain(writer.Snapshot(), w => w.Tag == "B"); Assert.DoesNotContain(writer.Snapshot(), w => w.Tag == "C"); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // no PreStart re-register == no restart + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // no PreStart re-register == no restart } /// @@ -1291,7 +1291,7 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit IActorRef rec = Sys.ActorOf(ContinuousHistorizationRecorder.Props( mux.Ref, writer, outbox, historizedRefs: new[] { "A" }, drainInterval: TimeSpan.FromSeconds(30))); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); rec.Tell(new VirtualTagActor.DependencyValueChanged( "A", 1.0, new DateTime(2026, 8, 15, 14, 0, 0, DateTimeKind.Utc))); @@ -1304,9 +1304,9 @@ public sealed class ContinuousHistorizationRecorderTests : TestKit Assert.Equal(1, status.OutboxAppendFailures); Assert.Equal(0, status.TotalRecorded); }, - TimeSpan.FromSeconds(10)); + TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // no PreStart re-register == no restart + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // no PreStart re-register == no restart } /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTypedValueTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTypedValueTests.cs index 67c304cfb..8d8e52329 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTypedValueTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ContinuousHistorizationRecorderTypedValueTests.cs @@ -1,5 +1,5 @@ using Akka.Actor; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.OpcUa; diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/HistorianAdapterActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/HistorianAdapterActorTests.cs index 826137fc2..ed7429131 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/HistorianAdapterActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/HistorianAdapterActorTests.cs @@ -106,7 +106,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase actor.Tell(SampleTransition()); - AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout); + AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Secondary suppression (T2): when the cached local role is Secondary, the adapter MUST @@ -121,7 +121,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase actor.Tell(SampleTransition()); // Give the (suppressed) fire-and-forget a stable window, then assert nothing landed. - ExpectNoMsg(Settle); + ExpectNoMsg(Settle, TestContext.Current.CancellationToken); sink.EnqueueCount.ShouldBe(0); } @@ -135,7 +135,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase TellRedundancyRole(actor, RedundancyRole.Detached); actor.Tell(SampleTransition()); - ExpectNoMsg(Settle); + ExpectNoMsg(Settle, TestContext.Current.CancellationToken); sink.EnqueueCount.ShouldBe(0); } @@ -150,7 +150,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase TellRedundancyRole(actor, RedundancyRole.Primary); actor.Tell(SampleTransition()); - AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout); + AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Absent-node default-historize (T5): a snapshot that mentions only a DIFFERENT node @@ -179,7 +179,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase actor.Tell(SampleTransition()); // Local role is still unknown ⇒ default-historize path: sink must record exactly one enqueue. - AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout); + AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Builds an (the shape published on the alerts @@ -227,7 +227,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase e.Severity.ShouldBe(AlarmSeverity.High); e.Comment.ShouldBe("note"); }, - AssertTimeout); + AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Secondary suppression for alerts (T7): a Secondary node must NOT historize a transition @@ -242,7 +242,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase TellRedundancyRole(actor, RedundancyRole.Secondary); actor.Tell(SampleTransition()); - ExpectNoMsg(Settle); + ExpectNoMsg(Settle, TestContext.Current.CancellationToken); sink.EnqueueCount.ShouldBe(0); } @@ -257,7 +257,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase TellRedundancyRole(actor, RedundancyRole.Primary); actor.Tell(SampleTransition()); - AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout); + AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Per-alarm opt-out (T8b): a Primary node must NOT historize a transition whose @@ -273,7 +273,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase TellRedundancyRole(actor, RedundancyRole.Primary); actor.Tell(SampleTransition(historizeToAveva: false)); - ExpectNoMsg(Settle); + ExpectNoMsg(Settle, TestContext.Current.CancellationToken); sink.EnqueueCount.ShouldBe(0); } @@ -291,7 +291,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase TellRedundancyRole(actor, RedundancyRole.Primary); actor.Tell(SampleTransition(historizeToAveva: null)); - AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout); + AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Severity buckets (T9): the OPC UA 1–1000 numeric severity on the transition maps onto @@ -313,7 +313,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase AwaitAssert( () => sink.Events.ShouldHaveSingleItem().Severity.ShouldBe(expected), - AssertTimeout); + AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// Rolling-restart null default (T10): an old-format transition deserialized by Akka's JSON @@ -331,7 +331,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase AwaitAssert( () => sink.Events.ShouldHaveSingleItem().AlarmTypeName.ShouldBe("AlarmCondition"), - AssertTimeout); + AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -349,8 +349,8 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase "Activated", "level high", "system", null, DateTime.UtcNow)); actor.Tell(SampleTransition()); - AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout); - ExpectNoMsg(Settle); + AwaitAssert(() => sink.EnqueueCount.ShouldBe(1), AssertTimeout, cancellationToken: TestContext.Current.CancellationToken); + ExpectNoMsg(Settle, TestContext.Current.CancellationToken); sink.Events.ShouldHaveSingleItem().AlarmId.ShouldBe("alm-9"); } @@ -390,7 +390,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase for (var i = 0; i < 40; i++) actor.Tell(SampleTransition() with { Message = $"m{i:00}" }); - AwaitAssert(() => sink.Order.Count.ShouldBe(40), TimeSpan.FromSeconds(10)); + AwaitAssert(() => sink.Order.Count.ShouldBe(40), TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); sink.MaxInFlight.ShouldBe(1, "one loop drains the queue — never one concurrent insert per transition"); sink.Order.ShouldBe(Enumerable.Range(0, 40).Select(i => $"m{i:00}")); } @@ -407,7 +407,7 @@ public sealed class HistorianAdapterActorTests : RuntimeActorTestBase // into the adapter's queue, most still waiting on the slow sink when the actor stops — and they are flushed. Watch(actor); actor.Tell(PoisonPill.Instance); - ExpectTerminated(actor, TimeSpan.FromSeconds(10)); + ExpectTerminated(actor, TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); sink.Order.Count.ShouldBe(10); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Observability/OtOpcUaTelemetryHookTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Observability/OtOpcUaTelemetryHookTests.cs index c833beadb..1b544b75d 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Observability/OtOpcUaTelemetryHookTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Observability/OtOpcUaTelemetryHookTests.cs @@ -27,9 +27,9 @@ public sealed class OtOpcUaTelemetryHookTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new ConstEval(42); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-tel-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-tel-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectMsg(); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); recorder.Total.ShouldBeGreaterThanOrEqualTo(1); recorder.WithTag("outcome", "ok").ShouldBeGreaterThanOrEqualTo(1); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorApplyOutcomeTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorApplyOutcomeTests.cs index f3e0602ed..5a1f91e49 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorApplyOutcomeTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorApplyOutcomeTests.cs @@ -36,7 +36,7 @@ public sealed class OpcUaPublishActorApplyOutcomeTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace( correlation, deployment, BuildEquipmentArtifact("eq-1"), ReportOutcome: true)); - var done = ExpectMsg(PresenceBudget); + var done = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); done.Correlation.ShouldBe(correlation); done.DeploymentId.ShouldBe(deployment); done.Degraded.ShouldBeFalse(); @@ -55,7 +55,7 @@ public sealed class OpcUaPublishActorApplyOutcomeTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace( CorrelationId.NewId(), DeploymentId.NewId(), BuildEquipmentArtifact("eq-1"), ReportOutcome: true)); - var done = ExpectMsg(PresenceBudget); + var done = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); done.Degraded.ShouldBeTrue(); done.DegradedReason.ShouldNotBeNull(); done.DegradedReason.ShouldContain("failedNodes="); @@ -72,7 +72,7 @@ public sealed class OpcUaPublishActorApplyOutcomeTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace( CorrelationId.NewId(), DeploymentId.NewId(), "not json"u8.ToArray(), ReportOutcome: true)); - var done = ExpectMsg(PresenceBudget); + var done = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); done.Degraded.ShouldBeTrue(); done.DegradedReason.ShouldNotBeNull(); } @@ -86,11 +86,11 @@ public sealed class OpcUaPublishActorApplyOutcomeTests : RuntimeActorTestBase var artifact = BuildEquipmentArtifact("eq-1"); actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), artifact, ReportOutcome: true)); - ExpectMsg(PresenceBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); // Same composition again ⇒ empty plan ⇒ nothing to apply, nothing degraded. actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), artifact, ReportOutcome: true)); - ExpectMsg(PresenceBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); } [Fact] @@ -107,9 +107,9 @@ public sealed class OpcUaPublishActorApplyOutcomeTests : RuntimeActorTestBase // message was fully processed before the absence assertion is read. actor.Tell(new OpcUaPublishActor.RebuildAddressSpace( CorrelationId.NewId(), DeploymentId.NewId(), BuildEquipmentArtifact("eq-2"), ReportOutcome: true)); - var only = ExpectMsg(PresenceBudget); + var only = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); only.Degraded.ShouldBeTrue(); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } [Fact] @@ -124,31 +124,31 @@ public sealed class OpcUaPublishActorApplyOutcomeTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace( CorrelationId.NewId(), DeploymentId.NewId(), BuildEquipmentArtifact("eq-1", "First"), ReportOutcome: true)); - ExpectMsg(PresenceBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); // A changed equipment row routes to a full rebuild; its re-materialise faults. sink.ThrowOnEnsureFolder = true; var changed = BuildEquipmentArtifact("eq-1", "Second"); actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), changed, ReportOutcome: true)); - var degraded = ExpectMsg(PresenceBudget); + var degraded = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); degraded.Degraded.ShouldBeTrue(); sink.RebuildCount.ShouldBe(1); // Same revision again, sink still faulting: must NOT claim clean. actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), changed, ReportOutcome: true)); - ExpectMsg(PresenceBudget).Degraded.ShouldBeTrue(); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeTrue(); // Fault gone: the same revision re-materialises the whole composition and only THEN reports clean. sink.ThrowOnEnsureFolder = false; var foldersBefore = sink.EnsureFolderCount; actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), changed, ReportOutcome: true)); - ExpectMsg(PresenceBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.EnsureFolderCount.ShouldBeGreaterThan(foldersBefore); // And now it is genuinely clean, so a further identical deploy is the ordinary empty no-op. var foldersAfterRepair = sink.EnsureFolderCount; actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), changed, ReportOutcome: true)); - ExpectMsg(PresenceBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.EnsureFolderCount.ShouldBe(foldersAfterRepair); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorBatchTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorBatchTests.cs index f6443f2e0..850d84c90 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorBatchTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorBatchTests.cs @@ -81,7 +81,7 @@ public sealed class OpcUaPublishActorBatchTests : RuntimeActorTestBase "Plant/Modbus/dev1/Temp", 2, OpcUaStatus.Good, t0, AddressSpaceRealm.Raw)); AwaitAssert(() => sink.SingleWrites.Count.ShouldBe(1), PresenceBudget); - }); + }, TestContext.Current.CancellationToken); sink.Threw.ShouldBeTrue(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorDeferredWriteTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorDeferredWriteTests.cs index 2ab304a8b..bc161f71b 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorDeferredWriteTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorDeferredWriteTests.cs @@ -39,10 +39,10 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase var at = DateTime.UtcNow; actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate( [new ValueWrite(NewTag, 7, OpcUaStatus.Good, at, AddressSpaceRealm.Raw)])); - AwaitAssert(() => sink.SkippedWrites.ShouldContain(NewTag), PlanBudget); // non-vacuity: it really missed + AwaitAssert(() => sink.SkippedWrites.ShouldContain(NewTag), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); // non-vacuity: it really missed gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.Exists(NewTag).ShouldBeTrue("the plan materialised the node"); sink.ValueOf(NewTag).ShouldBe((7, OpcUaStatus.Good, at), "the value sent while the plan was in flight must end up on the node"); @@ -61,7 +61,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate([new ValueWrite(NewTag, 7, OpcUaStatus.Good, t1, AddressSpaceRealm.Raw)])); actor.Tell(new OpcUaPublishActor.AttributeStatusBatchUpdate([new StatusWrite(NewTag, OpcUaStatus.BadNoCommunication, t2, AddressSpaceRealm.Raw)])); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.ValueOf(NewTag).ShouldBe((7, OpcUaStatus.BadNoCommunication, t2), "last known value under the later status"); } @@ -74,7 +74,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase var actor = Spawn(sink, gate); actor.Tell(Rebuild(RawTagArtifact(1))); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); var existing = "Plant/Modbus/dev1/T0"; sink.Exists(existing).ShouldBeTrue(); @@ -84,11 +84,11 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate([new ValueWrite(existing, 5, OpcUaStatus.Good, at, AddressSpaceRealm.Raw)])); // Applied immediately, while the plan is still held open. - AwaitAssert(() => sink.ValueOf(existing).ShouldBe((5, OpcUaStatus.Good, at)), PlanBudget); + AwaitAssert(() => sink.ValueOf(existing).ShouldBe((5, OpcUaStatus.Good, at)), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.Exists(NewTag).ShouldBeFalse("the plan is still in flight"); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.ValueOf(existing).ShouldBe((5, OpcUaStatus.Good, at)); } @@ -103,10 +103,10 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase var state = new AlarmConditionSnapshot(Active: true, Acknowledged: false, Confirmed: true, Enabled: true, AlarmShelvingKind.Unshelved, 500, "high"); actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("al-1", state, DateTime.UtcNow, AddressSpaceRealm.Uns)); actor.Tell(new OpcUaPublishActor.AlarmQualityUpdate("al-1", OpcUaStatus.BadNoCommunication, DateTime.UtcNow, AddressSpaceRealm.Uns)); - AwaitAssert(() => sink.SkippedAlarms.ShouldContain("al-1"), PlanBudget); + AwaitAssert(() => sink.SkippedAlarms.ShouldContain("al-1"), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.ConditionExists("al-1").ShouldBeTrue("the plan materialised the condition"); sink.AlarmStateOf("al-1").ShouldBeSameAs(state); @@ -128,10 +128,10 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase .Select(i => new ValueWrite($"Plant/Modbus/dev1/T{i}", i, OpcUaStatus.Good, at, AddressSpaceRealm.Raw)).ToList())); // An existing key still updates when the buffer is full. actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate([new ValueWrite("Plant/Modbus/dev1/T0", 100, OpcUaStatus.Good, at, AddressSpaceRealm.Raw)])); - AwaitAssert(() => sink.SkippedWrites.Count.ShouldBe(6), PlanBudget); + AwaitAssert(() => sink.SkippedWrites.Count.ShouldBe(6), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.ValueOf("Plant/Modbus/dev1/T0")!.Value.Value.ShouldBe(100); sink.ValueOf("Plant/Modbus/dev1/T1")!.Value.Value.ShouldBe(1); @@ -149,13 +149,13 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(Rebuild(RawTagArtifact(3))); actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate([new ValueWrite(NewTag, 7, OpcUaStatus.Good, DateTime.UtcNow, AddressSpaceRealm.Raw)])); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); var replayed = sink.ReplayedItems; replayed.ShouldBe(1); // A later plan replays only what arrived during IT. actor.Tell(Rebuild(RawTagArtifact(3))); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.ReplayedItems.ShouldBe(replayed); } @@ -171,7 +171,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(Rebuild(RawTagArtifact(2))); - var completed = ExpectMsg(PlanBudget); + var completed = ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); completed.Degraded.ShouldBeFalse(completed.DegradedReason); sink.Exists("Plant/Modbus/dev1/T1").ShouldBeTrue("the address space is applied"); authority.Applies.ShouldBe(0, "nothing is installed from an authority that could not be read"); @@ -181,7 +181,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase new ExistenceSink(), new AddressSpaceApplier(new ExistenceSink(), NullLogger.Instance), hooks: null, dataPlaneAuthority: authority)); healthy.Tell(Rebuild(RawTagArtifact(2))); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); authority.Applies.ShouldBe(1); } @@ -196,11 +196,11 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase var second = new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), Artifact: null, ReportOutcome: true); actor.Tell(first); actor.Tell(second); - ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // the bytes-less one must not overtake the held plan + ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // the bytes-less one must not overtake the held plan gate.Set(); - var a = ExpectMsg(PlanBudget); - var b = ExpectMsg(PlanBudget); + var a = ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); + var b = ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); a.Correlation.ShouldBe(first.Correlation); a.Degraded.ShouldBeFalse(); b.Correlation.ShouldBe(second.Correlation); @@ -216,22 +216,22 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase var childProps = OpcUaPublishActor.PropsForTests(sink, applier, new OpcUaPublishActor.TestHooks { BeforePlan = () => gate.Wait() }); var parent = Sys.ActorOf(Props.Create(() => new RestartingParent(childProps))); parent.Tell(RestartingParent.GetChild.Instance); - var child = ExpectMsg(); + var child = ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var stale = Rebuild(RawTagArtifact(3)); child.Tell(stale); child.Tell(Kill.Instance); // the parent's strategy RESTARTS on any failure, ActorKilledException included - ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); gate.Set(); - var answer = ExpectMsg(PlanBudget); + var answer = ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); answer.Correlation.ShouldBe(stale.Correlation); answer.Degraded.ShouldBeTrue("the requester is still answered"); sink.Exists(NewTag).ShouldBeFalse("a plan from the previous incarnation must not be applied"); // Positive control: the restarted instance applies a fresh rebuild. child.Tell(Rebuild(RawTagArtifact(3))); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.Exists(NewTag).ShouldBeTrue(); } @@ -249,9 +249,9 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(Rebuild(AlarmArtifact())); actor.Tell(new OpcUaPublishActor.AlarmQualityUpdate("al-1", OpcUaStatus.BadNoCommunication, DateTime.UtcNow, AddressSpaceRealm.Uns)); actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("al-1", ActiveUnacked(OpcUaStatus.Good), DateTime.UtcNow, AddressSpaceRealm.Uns)); - AwaitAssert(() => sink.SkippedAlarms.Count.ShouldBe(2), PlanBudget); + AwaitAssert(() => sink.SkippedAlarms.Count.ShouldBe(2), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.AlarmQualityOf("al-1").ShouldBe(OpcUaStatus.Good, "the later snapshot's quality is what live would leave"); sink.AlarmStateOf("al-1")!.Active.ShouldBeTrue(); @@ -268,9 +268,9 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(Rebuild(AlarmArtifact())); actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("al-1", ActiveUnacked(OpcUaStatus.Good), DateTime.UtcNow, AddressSpaceRealm.Uns)); actor.Tell(new OpcUaPublishActor.AlarmQualityUpdate("al-1", OpcUaStatus.BadNoCommunication, DateTime.UtcNow, AddressSpaceRealm.Uns)); - AwaitAssert(() => sink.SkippedAlarms.Count.ShouldBe(2), PlanBudget); + AwaitAssert(() => sink.SkippedAlarms.Count.ShouldBe(2), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.AlarmQualityOf("al-1").ShouldBe(OpcUaStatus.BadNoCommunication); } @@ -287,18 +287,18 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(AlarmArtifact())); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.ConditionExists("al-1").ShouldBeTrue(); gate.Reset(); // A surgical pure-add (new raw tags) that keeps al-1. actor.Tell(Rebuild(AlarmArtifact(rawTags: 2))); actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("al-1", ActiveUnacked(OpcUaStatus.Good), DateTime.UtcNow, AddressSpaceRealm.Uns)); - AwaitAssert(() => sink.AlarmWritesTo("al-1").ShouldBe(1), PlanBudget); // the live write landed + AwaitAssert(() => sink.AlarmWritesTo("al-1").ShouldBe(1), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); // the live write landed sink.AcknowledgeOutOfBand("al-1"); // an operator acks in the window gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.SawRebuild.ShouldBeFalse("non-vacuity: a surgical apply"); sink.AlarmStateOf("al-1")!.Acknowledged.ShouldBeTrue("the operator's ack must survive the apply"); @@ -314,7 +314,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(RawTagArtifact(1))); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); const string kept = "Plant/Modbus/dev1/T0"; gate.Reset(); @@ -325,11 +325,11 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase new ValueWrite(kept, 5, OpcUaStatus.Good, at, AddressSpaceRealm.Raw), new ValueWrite(NewTag, 7, OpcUaStatus.Good, at, AddressSpaceRealm.Raw), ])); - AwaitAssert(() => sink.ValueOf(kept)!.Value.Value.ShouldBe(5), PlanBudget); + AwaitAssert(() => sink.ValueOf(kept)!.Value.Value.ShouldBe(5), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.SetValueOutOfBand(kept, 9); // a client Write in the window gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.SawRebuild.ShouldBeFalse("non-vacuity: a surgical apply"); sink.ValueOf(kept)!.Value.Value.ShouldBe(9, "the kept node's client write is not reverted"); @@ -345,7 +345,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(AlarmArtifact(rawTags: 1))); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); const string kept = "Plant/Modbus/dev1/T0"; sink.Exists(kept).ShouldBeTrue(); @@ -354,10 +354,10 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase actor.Tell(Rebuild(AlarmArtifact(equipmentName: "Eq-renamed", rawTags: 1))); var at = DateTime.UtcNow; actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate([new ValueWrite(kept, 5, OpcUaStatus.Good, at, AddressSpaceRealm.Raw)])); - AwaitAssert(() => sink.ValueOf(kept)!.Value.Value.ShouldBe(5), PlanBudget); + AwaitAssert(() => sink.ValueOf(kept)!.Value.Value.ShouldBe(5), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.SawRebuild.ShouldBeTrue("non-vacuity: a full rebuild ran"); sink.ValueOf(kept)!.Value.Value.ShouldBe(5, "the recreated node gets the value its predecessor took"); @@ -372,21 +372,21 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(RawTagArtifact(2))); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Reset(); actor.Tell(Rebuild("not json"u8.ToArray())); actor.Tell(new OpcUaPublishActor.AttributeValueBatchUpdate( [new ValueWrite("Plant/Modbus/dev1/T2", 42, OpcUaStatus.Good, DateTime.UtcNow, AddressSpaceRealm.Raw)])); - AwaitAssert(() => sink.SkippedWrites.ShouldContain("Plant/Modbus/dev1/T2"), PlanBudget); + AwaitAssert(() => sink.SkippedWrites.ShouldContain("Plant/Modbus/dev1/T2"), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); // Snapshot AFTER the live write (the sink's counter cannot tell a live write after an apply from a replay). var replayedBefore = sink.ReplayedItems; gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeTrue(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeTrue(); sink.ReplayedItems.ShouldBe(replayedBefore, "nothing was applied, so nothing is replayed"); actor.Tell(Rebuild(RawTagArtifact(3))); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.Exists("Plant/Modbus/dev1/T2").ShouldBeTrue("positive control: the later plan adds it"); sink.ValueOf("Plant/Modbus/dev1/T2").ShouldBeNull("the failed window's buffer was dropped"); } @@ -402,7 +402,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase var childProps = OpcUaPublishActor.PropsForTests(sink, applier, new OpcUaPublishActor.TestHooks { BeforePlan = () => gate.Wait() }); var parent = Sys.ActorOf(Props.Create(() => new RestartingParent(childProps))); parent.Tell(RestartingParent.GetChild.Instance); - var child = ExpectMsg(); + var child = ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var inFlight = Rebuild(RawTagArtifact(2)); var queued = Rebuild(RawTagArtifact(3)); @@ -410,12 +410,12 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase child.Tell(queued); child.Tell(Kill.Instance); - var first = ExpectMsg(PlanBudget); + var first = ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); first.Correlation.ShouldBe(queued.Correlation, "the queued requester is answered at the restart"); first.Degraded.ShouldBeTrue(); gate.Set(); - var second = ExpectMsg(PlanBudget); + var second = ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); second.Correlation.ShouldBe(inFlight.Correlation); second.Degraded.ShouldBeTrue(); } @@ -433,15 +433,15 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(AlarmArtifact())); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); gate.Reset(); actor.Tell(Rebuild(AlarmArtifact(equipmentName: "Eq-renamed"))); // ChangedEquipment ⇒ full rebuild, al-1 kept actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("al-1", ActiveUnacked(OpcUaStatus.Good), DateTime.UtcNow, AddressSpaceRealm.Uns)); - AwaitAssert(() => sink.AlarmWritesTo("al-1").ShouldBe(1), PlanBudget); // landed live on the old instance + AwaitAssert(() => sink.AlarmWritesTo("al-1").ShouldBe(1), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); // landed live on the old instance gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.SawRebuild.ShouldBeTrue("non-vacuity: a full rebuild ran"); sink.AlarmStateOf("al-1").ShouldNotBeNull("the re-assert must reach the recreated condition") @@ -460,17 +460,17 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(AlarmArtifact(nativeAlarm: true))); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.ConditionExists(NativeAlarm).ShouldBeTrue(); gate.Reset(); actor.Tell(Rebuild(AlarmArtifact(equipmentName: "Eq-renamed", nativeAlarm: true))); actor.Tell(new OpcUaPublishActor.AlarmStateUpdate(NativeAlarm, ActiveUnacked(OpcUaStatus.Good), DateTime.UtcNow, AddressSpaceRealm.Raw)); - AwaitAssert(() => sink.AlarmWritesTo(NativeAlarm).ShouldBe(1), PlanBudget); + AwaitAssert(() => sink.AlarmWritesTo(NativeAlarm).ShouldBe(1), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.AcknowledgeOutOfBand(NativeAlarm); gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.SawRebuild.ShouldBeTrue("non-vacuity: a full rebuild ran"); sink.AlarmStateOf(NativeAlarm)!.Acknowledged.ShouldBeTrue("the carried-over ack survives"); @@ -489,7 +489,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(ShapeArtifact("Float"))); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.Exists(RawSpeed).ShouldBeTrue(); sink.Exists(UnsSpeed).ShouldBeTrue("non-vacuity: the mirror exists"); @@ -501,10 +501,10 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase new ValueWrite(RawSpeed, 5.0f, OpcUaStatus.Good, at, AddressSpaceRealm.Raw), new ValueWrite(UnsSpeed, 5.0f, OpcUaStatus.Good, at, AddressSpaceRealm.Uns), ])); - AwaitAssert(() => sink.ValueOf(RawSpeed)!.Value.Value.ShouldBe(5.0f), PlanBudget); + AwaitAssert(() => sink.ValueOf(RawSpeed)!.Value.Value.ShouldBe(5.0f), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget).Degraded.ShouldBeFalse(); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken).Degraded.ShouldBeFalse(); sink.SawRebuild.ShouldBeFalse("non-vacuity: a surgical in-place edit"); sink.ReshapedNodes.ShouldContain(RawSpeed, "non-vacuity: the raw node was reset"); @@ -523,7 +523,7 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase using var gate = new ManualResetEventSlim(true); var actor = Spawn(sink, gate); actor.Tell(Rebuild(ShapeArtifact("String"))); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Reset(); actor.Tell(Rebuild(ShapeArtifact("Int32"))); @@ -533,10 +533,10 @@ public sealed class OpcUaPublishActorDeferredWriteTests : RuntimeActorTestBase new ValueWrite(RawSpeed, "fast", OpcUaStatus.Good, at, AddressSpaceRealm.Raw), new ValueWrite(UnsSpeed, "fast", OpcUaStatus.Good, at, AddressSpaceRealm.Uns), ])); - AwaitAssert(() => sink.ValueOf(RawSpeed)!.Value.Value.ShouldBe("fast"), PlanBudget); + AwaitAssert(() => sink.ValueOf(RawSpeed)!.Value.Value.ShouldBe("fast"), PlanBudget, cancellationToken: TestContext.Current.CancellationToken); gate.Set(); - ExpectMsg(PlanBudget); + ExpectMsg(PlanBudget, cancellationToken: TestContext.Current.CancellationToken); sink.ReshapedNodes.ShouldContain(RawSpeed, "non-vacuity: the raw node was reset"); sink.ValueOf(RawSpeed).ShouldBeNull("\"fast\" is not an Int32 — the node keeps waiting"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorEndpointStateTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorEndpointStateTests.cs index 9e51513d1..2a1bc090f 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorEndpointStateTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorEndpointStateTests.cs @@ -71,10 +71,10 @@ public sealed class OpcUaPublishActorEndpointStateTests : RuntimeActorTestBase serviceLevel: publisher, localNode: local, dbLess: true, healthTickInterval: TimeSpan.FromMilliseconds(25), endpointState: state)); actor.Tell(Snapshot(local, primary: true)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 250 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 250 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); state.MarkFailed("port 4840 in use"); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 250, 0 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 250, 0 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } private byte PublishedLevelFor(OpcUaEndpointStatus status, bool primary, bool dbLess, bool dbBacked = false) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorFreshnessTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorFreshnessTests.cs index 70dde5d2d..00a4d2f10 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorFreshnessTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorFreshnessTests.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using Akka.Actor; using Akka.TestKit; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Redundancy; @@ -29,23 +29,23 @@ public sealed class OpcUaPublishActorFreshnessTests : RuntimeActorTestBase staleWindow: TimeSpan.FromSeconds(1), healthTickInterval: TimeSpan.FromMilliseconds(25), utcNow: clock.Read)); actor.Tell(Snapshot(local, primary, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Equality remains fresh. Observe several real ticks with a fixed clock; no actor Tell. clock.Advance(TimeSpan.FromSeconds(1)); var reads = clock.Reads; - AwaitCondition(() => clock.Reads >= reads + 4, PresenceBudget); + AwaitCondition(() => clock.Reads >= reads + 4, PresenceBudget, TestContext.Current.CancellationToken); // Drain earlier self-enqueued publications without triggering a health recomputation. actor.Tell(new Identify("freshness-barrier"), TestActor); - ExpectMsg().Subject.ShouldBe(actor); + ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Subject.ShouldBe(actor); publisher.Levels.ShouldBe(new[] { fresh }); clock.Advance(TimeSpan.FromTicks(1)); // No further Tell: only the production timer can notice age crossing the window. - AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh, stale }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh, stale }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // A new fresh snapshot restores service, after which ticks continue to age that snapshot. actor.Tell(Snapshot(local, primary, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh, stale, fresh }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh, stale, fresh }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(1) + TimeSpan.FromTicks(1)); - AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh, stale, fresh, stale }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new[] { fresh, stale, fresh, stale }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -59,18 +59,18 @@ public sealed class OpcUaPublishActorFreshnessTests : RuntimeActorTestBase staleWindow: TimeSpan.FromSeconds(10), probeFreshnessWindow: TimeSpan.FromSeconds(2), healthTickInterval: TimeSpan.FromMilliseconds(25), utcNow: clock.Read)); actor.Tell(Snapshot(local, false, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new PeerOpcUaProbeActor.OpcUaProbeResult(local, Ok: false)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(2)); var reads = clock.Reads; - AwaitCondition(() => clock.Reads >= reads + 4, PresenceBudget); + AwaitCondition(() => clock.Reads >= reads + 4, PresenceBudget, TestContext.Current.CancellationToken); // Drain earlier self-enqueued publications without triggering a health recomputation. actor.Tell(new Identify("freshness-barrier"), TestActor); - ExpectMsg().Subject.ShouldBe(actor); + ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Subject.ShouldBe(actor); publisher.Levels.ShouldBe(new byte[] { 240, 0 }); clock.Advance(TimeSpan.FromTicks(1)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0, 240 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0, 240 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -84,15 +84,15 @@ public sealed class OpcUaPublishActorFreshnessTests : RuntimeActorTestBase staleWindow: TimeSpan.FromSeconds(1), healthTickInterval: TimeSpan.FromMilliseconds(25), utcNow: clock.Read)); actor.Tell(Snapshot(local, false, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var reads = clock.Reads; - AwaitCondition(() => clock.Reads >= reads + 4, PresenceBudget); + AwaitCondition(() => clock.Reads >= reads + 4, PresenceBudget, TestContext.Current.CancellationToken); Watch(actor); Sys.Stop(actor); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); reads = clock.Reads; clock.Advance(TimeSpan.FromSeconds(2)); - ExpectNoMsg(TimeSpan.FromMilliseconds(150)); // Six timer periods after confirmed termination. + ExpectNoMsg(TimeSpan.FromMilliseconds(150), TestContext.Current.CancellationToken); // Six timer periods after confirmed termination. clock.Reads.ShouldBe(reads); publisher.Levels.ShouldBe(new byte[] { 240 }); } @@ -108,13 +108,13 @@ public sealed class OpcUaPublishActorFreshnessTests : RuntimeActorTestBase serviceLevel: publisher, localNode: local, dbHealthProbe: db.Ref, healthTickInterval: TimeSpan.FromMilliseconds(100), utcNow: clock.Read)); actor.Tell(Snapshot(local, false, clock.Now)); - db.ExpectMsg(); + db.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); db.Reply(new DbHealthProbeActor.DbHealthStatus(true, clock.Now, null)); - AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // A second request comes from the periodic timer, not the immediate startup tick. - db.ExpectMsg(); + db.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); db.Reply(new DbHealthProbeActor.DbHealthStatus(false, clock.Now, "unreachable")); - AwaitAssert(() => publisher.Levels.Last().ShouldBe((byte)100), PresenceBudget); + AwaitAssert(() => publisher.Levels.Last().ShouldBe((byte)100), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -128,12 +128,12 @@ public sealed class OpcUaPublishActorFreshnessTests : RuntimeActorTestBase serviceLevel: publisher, localNode: local, dbHealthProbe: db.Ref, healthTickInterval: TimeSpan.FromMilliseconds(100), utcNow: clock.Read)); actor.Tell(Snapshot(local, true, clock.Now)); - db.ExpectMsg(); + db.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); db.Reply(new DbHealthProbeActor.DbHealthStatus(true, clock.Now, null)); - AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Leave subsequent real Ask requests unanswered: the timeout status must reach the actor. - db.ExpectMsg(); - AwaitAssert(() => publisher.Levels.Last().ShouldBe((byte)110), PresenceBudget); + db.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => publisher.Levels.Last().ShouldBe((byte)110), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -146,19 +146,19 @@ public sealed class OpcUaPublishActorFreshnessTests : RuntimeActorTestBase serviceLevel: publisher, localNode: local, dbLess: true, healthTickInterval: TimeSpan.FromMilliseconds(25), utcNow: clock.Read)); actor.Tell(Snapshot(local, false, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Only this snapshot Tell requests the first zero. Fail it and the next attempt so // subsequent zero attempts must come from the periodic timer, exercising the null-entry // branch repeatedly rather than mistaking the Detached branch for missing-node coverage. actor.Tell(Snapshot(NodeId.Parse("other-node"), false, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); publisher.ZeroAttempts.ShouldBeGreaterThanOrEqualTo(3); actor.Tell(Snapshot(local, false, clock.Now)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0, 240 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0, 240 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(30) + TimeSpan.FromTicks(1)); - AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0, 240, 200 }), PresenceBudget); + AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 0, 240, 200 }), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } [Fact] diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorOffThreadPlanTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorOffThreadPlanTests.cs index 734edbba8..e643860af 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorOffThreadPlanTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorOffThreadPlanTests.cs @@ -33,7 +33,7 @@ public sealed class OpcUaPublishActorOffThreadPlanTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), RawTagArtifact(50_000), ReportOutcome: true)); actor.Tell(batch); - var completed = ExpectMsg(TimeSpan.FromSeconds(120)); + var completed = ExpectMsg(TimeSpan.FromSeconds(120), cancellationToken: TestContext.Current.CancellationToken); completed.Degraded.ShouldBeFalse(completed.DegradedReason); var calls = sink.Calls.ToList(); @@ -57,8 +57,8 @@ public sealed class OpcUaPublishActorOffThreadPlanTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), RawTagArtifact(20_000), ReportOutcome: true)); actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), RawTagArtifact(2), ReportOutcome: true)); - var first = ExpectMsg(TimeSpan.FromSeconds(120)); - var second = ExpectMsg(TimeSpan.FromSeconds(120)); + var first = ExpectMsg(TimeSpan.FromSeconds(120), cancellationToken: TestContext.Current.CancellationToken); + var second = ExpectMsg(TimeSpan.FromSeconds(120), cancellationToken: TestContext.Current.CancellationToken); first.Degraded.ShouldBeFalse(first.DegradedReason); second.Degraded.ShouldBeFalse(second.DegradedReason); @@ -76,7 +76,7 @@ public sealed class OpcUaPublishActorOffThreadPlanTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), DeploymentId.NewId(), "not json"u8.ToArray(), ReportOutcome: true)); - var completed = ExpectMsg(PresenceBudget); + var completed = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); completed.Degraded.ShouldBeTrue(); completed.DegradedReason.ShouldNotBeNull().ShouldContain("threw"); sink.Calls.ShouldBeEmpty("nothing may be torn down for bytes that could not be read"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorRebuildTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorRebuildTests.cs index fd05748c7..518ae6625 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorRebuildTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorRebuildTests.cs @@ -57,7 +57,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase sink.Calls.ShouldContain("EF:eq-1"); sink.Calls.ShouldContain("EF:eq-2"); sink.Calls.ShouldContain("NA:line-1"); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); } @@ -77,7 +77,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep)); - AwaitAssert(() => sink.CompletedAfterCallCount.Count.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.CompletedAfterCallCount.Count.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.CompletedAfterCallCount.Single().ShouldBe(sink.Calls.Count, "nothing may follow the completion"); sink.Calls.ShouldContain("NA:line-1"); } @@ -92,7 +92,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId())); - AwaitAssert(() => sink.CompletedAfterCallCount.Count.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.CompletedAfterCallCount.Count.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(1); } @@ -132,7 +132,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep)); - AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-1"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); // PureAdd — no full rebuild var callsAfterFirst = sink.Calls.Count; @@ -154,7 +154,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId())); - AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -179,7 +179,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId(), Artifact: artifact)); // The in-hand bytes drove the real diff-and-apply — the equipment folder was materialised… - AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-1"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // …and the address-space-wiping raw-sink fallback was NOT taken. sink.RebuildCalls.ShouldBe(0); } @@ -223,7 +223,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId())); - AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// @@ -244,7 +244,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase actor.Tell(Rebuild(dep)); - AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-1"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); } @@ -278,7 +278,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase siteActor.Tell(Rebuild(depA)); // PureAdd ⇒ no full rebuild; the materialise passes still run the cluster slice. - AwaitAssert(() => sinkA.Calls.ShouldContain("EV:SA/Modbus/dev-sa/S1"), duration: PresenceBudget); + AwaitAssert(() => sinkA.Calls.ShouldContain("EV:SA/Modbus/dev-sa/S1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sinkA.RebuildCalls.ShouldBe(0); // MAIN's raw tag must NOT leak onto the SITE-A node. sinkA.Calls.ShouldNotContain("EV:Main/Modbus/dev-main/M1"); @@ -297,7 +297,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase mainActor.Tell(Rebuild(depM)); - AwaitAssert(() => sinkM.Calls.ShouldContain("EV:Main/Modbus/dev-main/M1"), duration: PresenceBudget); + AwaitAssert(() => sinkM.Calls.ShouldContain("EV:Main/Modbus/dev-main/M1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sinkM.RebuildCalls.ShouldBe(0); sinkM.Calls.ShouldNotContain("EV:SA/Modbus/dev-sa/S1"); } @@ -376,7 +376,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep)); - AwaitAssert(() => sink.Calls.ShouldContain("NA:line-1"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("NA:line-1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); var calls = sink.Calls.ToList(); @@ -401,7 +401,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.Count(c => c.StartsWith("NA:")).ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.Count(c => c.StartsWith("NA:")).ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); var naAfterFirst = sink.Calls.Count(c => c.StartsWith("NA:")); @@ -410,7 +410,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var dep2 = SeedNamedEquipmentDeployment(db, ("eq-1", "Pump-1-RENAMED"), ("eq-2", "Pump-2")); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // No new NodeAdded announcement was raised for the rebuild-kind deploy. sink.Calls.Count(c => c.StartsWith("NA:")).ShouldBe(naAfterFirst); } @@ -433,7 +433,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-2"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-2"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var callsAfterFirst = sink.Calls.Count; // The next deploy arrives while the ConfigDb is briefly unreachable. @@ -468,13 +468,13 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-2"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EF:eq-2"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // eq-2 really is gone from the next artifact — that IS a configuration change, so it must apply. var dep2 = SeedNamedEquipmentDeployment(db, ("eq-1", "Pump-1")); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.Calls.ShouldContain("RE:eq-2"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("RE:eq-2"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// An whose CreateDbContext can be made to @@ -573,7 +573,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase // First deploy: the area folder is materialised with the OLD name. R2-07 — this is now a PureAdd // (area + line + equipment all added), so NO full rebuild; the folder is materialised directly. actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EF:area-1"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EF:area-1"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); // Second deploy: ONLY the area Name changed — a rename. The actor must reach the apply path and @@ -584,7 +584,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase AwaitAssert(() => { sink.FolderRenameCalls.ShouldContain(("area-1", "Plant South")); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); // the rename did NOT force a full rebuild } @@ -632,13 +632,13 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/C"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/C"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var afterFirst = sink.Calls.Count; // One new tag on an otherwise identical configuration. var dep2 = SeedRawTagDeployment(db, new[] { "A", "B", "C", "D" }); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/D"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/D"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); Thread.Sleep(200); // let any (unwanted) extra pass finish before counting var second = sink.Calls.Skip(afterFirst).ToList(); @@ -662,16 +662,16 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/b"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/b"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var afterFirst = sink.Calls.Count; // Renaming the equipment is a ChangedEquipment delta ⇒ the classifier's default-closed Rebuild. var dep2 = SeedUnsRawDeployment(db, equipmentName: "station1-renamed", referencedTag: "a"); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); AwaitAssert( () => sink.Calls.Skip(afterFirst).ShouldContain("EV:Plant/Modbus/dev1/b"), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var second = sink.Calls.Skip(afterFirst).ToList(); // The FULL raw subtree came back: both containers and both tags, not just a delta. @@ -701,13 +701,13 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:filling/line1/station1/a"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:filling/line1/station1/a"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var afterFirst = sink.Calls.Count; // Re-point the SAME reference row at raw tag "b". var dep2 = SeedUnsRawDeployment(db, equipmentName: "station1", referencedTag: "b"); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:filling/line1/station1/b"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:filling/line1/station1/b"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var second = sink.Calls.Skip(afterFirst).ToList(); sink.RebuildCalls.ShouldBe(0); // repaired in place, not by wiping the server @@ -739,7 +739,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:filling/line1/station1/a"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:filling/line1/station1/a"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var afterFirst = sink.Calls.Count; var dep2 = SeedUnsRawDeployment(db, equipmentName: "station1", referencedTag: "a", @@ -751,7 +751,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase () => sink.UpdateTagDetails.ShouldContain( u => u.NodeId == "filling/line1/station1/a" && u.HistorianTagname == "WW.A" && u.Realm == AddressSpaceRealm.Uns), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); Thread.Sleep(200); // let any (unwanted) remove+recreate land before asserting its absence var second = sink.Calls.Skip(afterFirst).ToList(); @@ -773,12 +773,12 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/B"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/B"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var afterFirst = sink.Calls.Count; var dep2 = SeedRawTagDeployment(db, new[] { "A", "B" }, historizedTag: "A"); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.Calls.ShouldContain("UT:Plant/Modbus/dev1/A"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("UT:Plant/Modbus/dev1/A"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); Thread.Sleep(200); // let any (unwanted) extra pass finish before counting var second = sink.Calls.Skip(afterFirst).ToList(); @@ -810,7 +810,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/B"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/B"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); Thread.Sleep(200); sink.RebuildCalls.ShouldBe(0); // genuinely a non-rebuild (PureAdd) apply var afterFirst = sink.Calls.Count; @@ -825,7 +825,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase AwaitAssert( () => sink.Calls.Skip(afterFirst).ShouldContain("EV:Plant/Modbus/dev1/A"), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(0); } @@ -849,14 +849,14 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink, dbFactory: db, applier: applier)); actor.Tell(Rebuild(dep1)); - AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/b"), duration: PresenceBudget); + AwaitAssert(() => sink.Calls.ShouldContain("EV:Plant/Modbus/dev1/b"), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Renaming the equipment is ChangedEquipment ⇒ the classifier's default-closed Rebuild; the sink // faults on one variable of the full re-materialise that follows. sink.FailEnsureVariableFor = "Plant/Modbus/dev1/b"; var dep2 = SeedUnsRawDeployment(db, equipmentName: "station1-renamed", referencedTag: "a"); actor.Tell(Rebuild(dep2)); - AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); Thread.Sleep(200); var afterSecond = sink.Calls.Count; @@ -868,7 +868,7 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase actor.Tell(Rebuild(dep3)); AwaitAssert( () => sink.Calls.Skip(afterSecond).ShouldContain("EV:Plant/Modbus/dev1/b"), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); sink.RebuildCalls.ShouldBe(1); // Repaired ⇒ genuinely clean now, so a FOURTH identical deploy is the ordinary empty-plan no-op. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorStatusBatchTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorStatusBatchTests.cs index c76903971..1bd1880cb 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorStatusBatchTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorStatusBatchTests.cs @@ -96,7 +96,7 @@ public sealed class OpcUaPublishActorStatusBatchTests : RuntimeActorTestBase "Plant/Modbus/dev1/Temp", 2, OpcUaStatus.Good, t0, AddressSpaceRealm.Raw)); AwaitAssert(() => sink.SingleWrites.Count.ShouldBe(1), PresenceBudget); - }); + }, TestContext.Current.CancellationToken); sink.Threw.ShouldBeTrue(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorTests.cs index 2b0d4b59a..7a4d4a83c 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/OpcUa/OpcUaPublishActorTests.cs @@ -68,7 +68,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId())); actor.Tell(new OpcUaPublishActor.ServiceLevelChanged(240)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// Verifies that production props target the OPC UA synchronized dispatcher. @@ -96,7 +96,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase sink.Values[0].Value.ShouldBe(3.14); sink.Values[0].StatusCode.ShouldBe(OpcUaStatus.Good); sink.Values[1].StatusCode.ShouldBe(OpcUaStatus.Uncertain); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that AlarmStateUpdate routes to sink WriteAlarmCondition with the full snapshot. @@ -116,7 +116,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase sink.Alarms[0].State.Active.ShouldBeTrue(); sink.Alarms[0].State.Acknowledged.ShouldBeFalse(); sink.Alarms[0].State.Severity.ShouldBe((ushort)700); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// #477 — AlarmQualityUpdate routes to sink.WriteAlarmQuality with the quality + realm. @@ -136,7 +136,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase q.AlarmNodeId.ShouldBe("Plant/Modbus/dev1/temp_hi"); q.StatusCode.ShouldBe(OpcUaStatus.Bad); q.Realm.ShouldBe(AddressSpaceRealm.Raw); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Builds a test with sensible defaults so each test @@ -160,7 +160,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId())); - AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget); + AwaitAssert(() => sink.RebuildCalls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that ServiceLevelChanged publishes to IServiceLevelPublisher once per unique level. @@ -175,7 +175,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase actor.Tell(new OpcUaPublishActor.ServiceLevelChanged(100)); AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240, 100 }), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the very first computed ServiceLevel is always published even when it is @@ -200,7 +200,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 0 }), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that RedundancyStateChanged drives local ServiceLevel publish for primary leader. @@ -224,7 +224,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase actor.Tell(snapshot); AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 240 }), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that RedundancyStateChanged for secondary publishes 100. @@ -245,7 +245,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase actor.Tell(snapshot); AwaitAssert(() => publisher.Levels.ShouldBe(new byte[] { 100 }), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the calculator path computes 250 for a healthy primary role-leader @@ -270,7 +270,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the calculator path computes 240 for a healthy non-leader secondary @@ -296,7 +296,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the calculator path computes 100 when the DB is unreachable @@ -321,7 +321,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)100), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the calculator path computes 200 for a stale snapshot when the DB is @@ -349,7 +349,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)200), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that a detached local node publishes 0 (the calculator does not model @@ -376,7 +376,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase }, CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Now detach — expect the guard to drive ServiceLevel down to 0. actor.Tell(new RedundancyStateChanged( @@ -388,7 +388,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)0), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that an actively-observed, recent peer probe of MY endpoint that came back @@ -417,7 +417,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)0), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies branch (3) of OpcUaProbeOk(): a peer's NEGATIVE verdict about this node @@ -447,7 +447,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase // Let the verdict age WELL past the 1ms freshness window before any level is computed. The 30ms // wait is >> 1ms, so the verdict is reliably stale by the time OpcUaProbeOk() runs — no reliance // on sub-ms clock ties. - ExpectNoMsg(TimeSpan.FromMilliseconds(30)); + ExpectNoMsg(TimeSpan.FromMilliseconds(30), TestContext.Current.CancellationToken); // Now trigger the recompute with a fresh healthy primary-leader snapshot. By now _probeAboutMe is // >1ms old, so branch (3) ignores the negative verdict and OpcUaProbeOk() returns true. @@ -461,7 +461,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase // Healthy (250), NOT 0 — proves an aged negative verdict does not demote. AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that with no peer probe result ever received, OpcUaProbeOk() defaults @@ -488,7 +488,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that a later Ok==true peer probe supersedes an earlier Ok==false @@ -517,7 +517,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that a peer probe result about a DIFFERENT node is ignored — it does not @@ -545,7 +545,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies the legacy back-compat seam: with no DB-health probe wired, the handler @@ -567,7 +567,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Verifies that the periodic HealthTick Asks the local @@ -591,7 +591,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase IsClusterLeader: true, IsDriverPrimary: true, DateTime.UtcNow), }, CorrelationId.NewId())); // No direct DbHealthStatus Tell — the periodic HealthTick Ask must populate it → 250. - AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), TimeSpan.FromSeconds(2)); + AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken); } // ── Per-cluster mesh Phase 4: DB-less driver-only nodes ───────────────────────────────────── @@ -621,7 +621,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// DB-less, member Up, snapshot fresh, Primary → 250 (basis 240 + 10 Primary bonus). @@ -643,7 +643,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// DB-less, snapshot stale (entry.AsOfUtc older than the stale window) → 200. Staleness @@ -668,7 +668,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)200), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// DB-less, Detached → 0. The Detached / missing-entry guard runs ABOVE the DB-less @@ -691,7 +691,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase }, CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new RedundancyStateChanged( Nodes: new[] @@ -702,7 +702,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)0), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Regression: a DB-BACKED node (probe wired + DbHealthStatus reachable, dbLess default @@ -728,7 +728,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase CorrelationId.NewId())); AwaitAssert(() => publisher.Levels.ShouldContain((byte)240), - duration: PresenceBudget); + duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); } /// Stub DB-health probe actor that answers diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Redundancy/OpcUaEndpointStepDownActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Redundancy/OpcUaEndpointStepDownActorTests.cs index aa2ea86a0..9c051ffdb 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Redundancy/OpcUaEndpointStepDownActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Redundancy/OpcUaEndpointStepDownActorTests.cs @@ -1,6 +1,6 @@ using Akka.Actor; using Akka.Cluster; -using Akka.TestKit.Xunit2; +using Akka.TestKit.Xunit; using Shouldly; using Xunit; using ZB.MOM.WW.Health.Akka; @@ -90,19 +90,19 @@ akka { Sys.ActorOf(OpcUaEndpointStepDownActor.Props(stateB, Tick), "step-down"); // Guard: no listening peer -> A stays Up and Primary. - await Task.Delay(AbsenceWindow); + await Task.Delay(AbsenceWindow, TestContext.Current.CancellationToken); _nodeA.Sys.WhenTerminated.IsCompleted.ShouldBeFalse("A must not leave while no peer can serve either"); ClusterActiveNode.SelfIsActive(Akka.Cluster.Cluster.Get(_nodeA.Sys), RoleParser.Driver).ShouldBeTrue(); // B's endpoint comes up -> A leaves (its ActorSystem terminates, which in production exits the process)... stateB.MarkStarted(); - await _nodeA.Sys.WhenTerminated.WaitAsync(RuntimeActorTestBase.PresenceBudget); + await _nodeA.Sys.WhenTerminated.WaitAsync(RuntimeActorTestBase.PresenceBudget, TestContext.Current.CancellationToken); // ... and B is now the oldest Up driver member, i.e. what RedundancyStateActor elects Primary. var clusterB = Akka.Cluster.Cluster.Get(Sys); AwaitCondition(() => ClusterActiveNode.SelfIsActive(clusterB, RoleParser.Driver) && clusterB.State.Members.Count(m => m.Status == MemberStatus.Up) == 1, - RuntimeActorTestBase.PresenceBudget, RuntimeActorTestBase.PollInterval); + RuntimeActorTestBase.PresenceBudget, RuntimeActorTestBase.PollInterval, cancellationToken: TestContext.Current.CancellationToken); } /// A failed endpoint on the SECONDARY changes nothing: there is no role to hand over. @@ -114,7 +114,7 @@ akka { _nodeA.Sys.ActorOf(OpcUaEndpointStepDownActor.Props(stateA, Tick), "step-down"); Sys.ActorOf(OpcUaEndpointStepDownActor.Props(Failed(), Tick), "step-down"); - await Task.Delay(AbsenceWindow); + await Task.Delay(AbsenceWindow, TestContext.Current.CancellationToken); Sys.WhenTerminated.IsCompleted.ShouldBeFalse(); _nodeA.Sys.WhenTerminated.IsCompleted.ShouldBeFalse(); UpCount(Akka.Cluster.Cluster.Get(Sys)).ShouldBe(2); @@ -132,7 +132,7 @@ akka { _nodeA.Sys.ActorOf(OpcUaEndpointStepDownActor.Props(stateA, Tick), "step-down"); Sys.ActorOf(OpcUaEndpointStepDownActor.Props(stateB, Tick), "step-down"); - await Task.Delay(AbsenceWindow); + await Task.Delay(AbsenceWindow, TestContext.Current.CancellationToken); _nodeA.Sys.WhenTerminated.IsCompleted.ShouldBeFalse(); UpCount(Akka.Cluster.Cluster.Get(Sys)).ShouldBe(2); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/DependencyMuxTagUpstreamSourceTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/DependencyMuxTagUpstreamSourceTests.cs index a38616480..16ffd3976 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/DependencyMuxTagUpstreamSourceTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/DependencyMuxTagUpstreamSourceTests.cs @@ -175,7 +175,7 @@ public sealed class DependencyMuxTagUpstreamSourceTests src.Push("hot/path", Good(i, now)); } catch (Exception ex) { failure = ex; } - }); + }, TestContext.Current.CancellationToken); var subscriber = Task.Run(() => { @@ -188,7 +188,7 @@ public sealed class DependencyMuxTagUpstreamSourceTests } } catch (Exception ex) { failure = ex; } - }); + }, TestContext.Current.CancellationToken); await Task.WhenAll(pusher, subscriber); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorRestartTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorRestartTests.cs index c4063d57f..565e267bf 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorRestartTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorRestartTests.cs @@ -106,9 +106,9 @@ public sealed class ScriptedAlarmHostActorRestartTests : RuntimeActorTestBase commandTimeout: TimeSpan.FromMilliseconds(300))); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() })); - mux.ExpectMsg(Timeout); + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.ExpectMsg(Timeout).State.Active.ShouldBeTrue(); + publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).State.Active.ShouldBeTrue(); store.HangNext = true; host.Tell(new AlarmCommand("alm-1", "Acknowledge", "alice", "ack", null)); @@ -116,7 +116,7 @@ public sealed class ScriptedAlarmHostActorRestartTests : RuntimeActorTestBase // The hung save would have suspended the mailbox indefinitely; the bound releases it, so the // next dependency value is processed and the alarm clears. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 10, DateTime.UtcNow)); - publish.ExpectMsg(Timeout).State.Active.ShouldBeFalse(); + publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).State.Active.ShouldBeFalse(); store.ObservedCancellation.ShouldBeTrue(); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorTests.cs index 02cded5dd..55dbe0872 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ScriptedAlarms/ScriptedAlarmHostActorTests.cs @@ -150,7 +150,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase Plan(id: "alm-2", depRef: "M.X", enabled: false), // disabled — not loaded, dep absent })); - var reg = mux.ExpectMsg(Timeout); + var reg = mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); reg.TagRefs.ShouldContain("M.T"); reg.TagRefs.ShouldNotContain("M.X"); } @@ -168,12 +168,12 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed var now = DateTime.UtcNow; host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, now)); - var state = publish.ExpectMsg(Timeout); + var state = publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); // The full Part 9 snapshot bridges through (T15) — every Core condition field maps: // on activation the engine sets Active, clears Ack AND Confirm (a new active occurrence needs a @@ -186,7 +186,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase state.State.Severity.ShouldBe((ushort)1000); // 800 → Critical bucket → 1000 state.State.Message.ShouldBe("condition"); // e.Message - var evt = alerts.ExpectMsg(Timeout); + var evt = alerts.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.TransitionKind.ShouldBe("Activated"); evt.Severity.ShouldBe(1000); // 800 → Critical bucket → 1000 @@ -209,20 +209,20 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() })); - mux.ExpectMsg(Timeout); + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // Activate first. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active, Timeout); - alerts.FishForMessage(e => e.TransitionKind == "Activated", Timeout); + publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); + alerts.FishForMessage(e => e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken); // Now clear. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 10, DateTime.UtcNow)); - var cleared = publish.FishForMessage(m => !m.State.Active, Timeout); + var cleared = publish.FishForMessage(m => !m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); cleared.AlarmNodeId.ShouldBe("alm-1"); - var evt = alerts.FishForMessage(e => e.TransitionKind == "Cleared", Timeout); + var evt = alerts.FishForMessage(e => e.TransitionKind == "Cleared", Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); } @@ -236,11 +236,11 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - var first = mux.ExpectMsg(Timeout); + var first = mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); first.TagRefs.ShouldContain("M.T"); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-9", depRef: "M.Y") })); - var second = mux.ExpectMsg(Timeout); + var second = mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); second.TagRefs.ShouldContain("M.Y"); second.TagRefs.ShouldNotContain("M.T"); } @@ -258,11 +258,11 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase // Apply a plan that fails to compile — LoadAsync faults, the host swallows it as a Warning and // does NOT register interest (no dep refs to register, and the load never completed). host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { BadPlan() })); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // Prove the actor is still responsive: a later valid apply loads + registers interest as normal. host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-ok", depRef: "M.T") })); - var reg = mux.ExpectMsg(Timeout); + var reg = mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); reg.TagRefs.ShouldContain("M.T"); } @@ -289,10 +289,10 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-b", depRef: "M.B") })); // The latest generation must win: fish for the RegisterInterest reflecting the second apply. - mux.FishForMessage(r => r.TagRefs.Contains("M.B"), Timeout); + mux.FishForMessage(r => r.TagRefs.Contains("M.B"), Timeout, cancellationToken: TestContext.Current.CancellationToken); // No LATER RegisterInterest may re-introduce the first (superseded) apply's "M.A" ref. - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Command path: an AlarmCommand("Acknowledge") for an alarm this host owns (and that is @@ -309,23 +309,23 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Activate so there is something to acknowledge (Acknowledge no-ops on an already-acked alarm). host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active && !m.State.Acknowledged, Timeout); - alerts.FishForMessage(e => e.TransitionKind == "Activated", Timeout); + publish.FishForMessage(m => m.State.Active && !m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken); + alerts.FishForMessage(e => e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken); // Acknowledge via the command topic — the host owns alm-1, so AcknowledgeAsync runs. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "Acknowledge", User: "alice", Comment: "ack-note", UnshelveAtUtc: null)); - var acked = publish.FishForMessage(m => m.State.Acknowledged, Timeout); + var acked = publish.FishForMessage(m => m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken); acked.AlarmNodeId.ShouldBe("alm-1"); acked.State.Acknowledged.ShouldBeTrue(); // The transition carries the acting user mapped from cmd.User (proves AcknowledgeAsync got it). - var evt = alerts.FishForMessage(e => e.TransitionKind == "Acknowledged", Timeout); + var evt = alerts.FishForMessage(e => e.TransitionKind == "Acknowledged", Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.User.ShouldBe("alice"); // Historian feed prep: the operator's ack comment threads through to the transition event. @@ -345,14 +345,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed; host owns only alm-1 + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed; host owns only alm-1 // Command targets an alarm this engine never loaded — it must be a no-op. host.Tell(new AlarmCommand( AlarmId: "not-mine", Operation: "Acknowledge", User: "alice", Comment: null, UnshelveAtUtc: null)); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // no engine op → no projection - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // no engine op → no transition + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // no engine op → no projection + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // no engine op → no transition } /// Validation: an AddComment command with empty or whitespace comment text is rejected (logged), @@ -370,17 +370,17 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // AddComment with empty/whitespace text is rejected before reaching the engine. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "AddComment", User: "alice", Comment: emptyComment, UnshelveAtUtc: null)); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // rejected → no engine op → no OPC UA projection - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); // rejected → no alerts event + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // rejected → no engine op → no OPC UA projection + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); // rejected → no alerts event // Prove the actor survived: activate the alarm and observe the normal projection flow. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - var state = publish.FishForMessage(m => m.State.Active, Timeout); + var state = publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); } @@ -397,13 +397,13 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // AddComment with a non-empty comment drives the engine — CommentAdded transition emitted. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "AddComment", User: "bob", Comment: "note from operator", UnshelveAtUtc: null)); - var evt = alerts.FishForMessage(e => e.TransitionKind == "CommentAdded", Timeout); + var evt = alerts.FishForMessage(e => e.TransitionKind == "CommentAdded", Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); } @@ -422,14 +422,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Send AddComment — the alarm does NOT need to be active; AddCommentAsync is unconditional. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "AddComment", User: "carol", Comment: "operator note", UnshelveAtUtc: null)); // The CommentAdded AlarmTransitionEvent must carry the operator's identity, not "system". - var evt = alerts.FishForMessage(e => e.TransitionKind == "CommentAdded", Timeout); + var evt = alerts.FishForMessage(e => e.TransitionKind == "CommentAdded", Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.User.ShouldBe("carol"); // operator — NOT "system" evt.Comment.ShouldBe("operator note"); @@ -447,16 +447,16 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // TimedShelve with a null UnshelveAtUtc is malformed — the host rejects + logs, does not throw. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "TimedShelve", User: "alice", Comment: null, UnshelveAtUtc: null)); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // rejected → no engine op → no projection + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // rejected → no engine op → no projection // Prove the actor survived: activate the alarm and observe the normal projection flow. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - var state = publish.FishForMessage(m => m.State.Active, Timeout); + var state = publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); } @@ -474,14 +474,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux, LocalNode); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // No RedundancyStateChanged sent — local role unknown ⇒ default-emit. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); // The OPC UA node write happens AND the alerts transition is published. - publish.FishForMessage(m => m.State.Active, Timeout); - var evt = alerts.ExpectMsg(Timeout); + publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); + var evt = alerts.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.TransitionKind.ShouldBe("Activated"); } @@ -499,18 +499,18 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux, LocalNode); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Mark this node Secondary, then activate. TellRedundancyRole(host, RedundancyRole.Secondary); host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); // The local OPC UA node write is UNGATED — it must still arrive. - var state = publish.FishForMessage(m => m.State.Active, Timeout); + var state = publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); // The cluster-wide alerts publish is gated off on the secondary. - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Primary publishes (T1): when the cached local role is Primary, the host publishes the @@ -525,14 +525,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux, LocalNode); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Mark this node Primary, then activate. TellRedundancyRole(host, RedundancyRole.Primary); host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active, Timeout); - var evt = alerts.ExpectMsg(Timeout); + publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); + var evt = alerts.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.TransitionKind.ShouldBe("Activated"); } @@ -550,13 +550,13 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = SpawnWithMemberCount(publish, mux, LocalNode, driverMemberCount: 2); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // No snapshot sent ⇒ role unknown; count 2 ⇒ default-DENY. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active, Timeout); - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Boot-window single-driver emit (archreview 03/S4): role unknown + no driver peer (member @@ -571,12 +571,12 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = SpawnWithMemberCount(publish, mux, LocalNode, driverMemberCount: 1); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active, Timeout); - alerts.ExpectMsg(Timeout).AlarmId.ShouldBe("alm-1"); + publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); + alerts.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken).AlarmId.ShouldBe("alm-1"); } private (IActorRef Host, DependencyMuxTagUpstreamSource Upstream) SpawnWithMemberCount( @@ -601,20 +601,20 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux, LocalNode); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Mark this node Secondary — the alerts publish is gated, but command processing is NOT. TellRedundancyRole(host, RedundancyRole.Secondary); // Activate so there is something to acknowledge. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active && !m.State.Acknowledged, Timeout); + publish.FishForMessage(m => m.State.Active && !m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken); // Acknowledge via the command topic — the engine must process it even on the secondary. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "Acknowledge", User: "alice", Comment: "ack-note", UnshelveAtUtc: null)); - var acked = publish.FishForMessage(m => m.State.Acknowledged, Timeout); + var acked = publish.FishForMessage(m => m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken); acked.AlarmNodeId.ShouldBe("alm-1"); acked.State.Acknowledged.ShouldBeTrue(); } @@ -632,18 +632,18 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux, LocalNode); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Mark this node Detached, then activate. TellRedundancyRole(host, RedundancyRole.Detached); host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); // The local OPC UA node write is UNGATED — it must still arrive. - var state = publish.FishForMessage(m => m.State.Active, Timeout); + var state = publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); // The cluster-wide alerts publish is gated off on a detached node. - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// HistorizeToAveva flag threading (middle-link): the host MUST carry the plan's @@ -667,18 +667,18 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { planTrue, planFalse })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Activate the true-flag alarm. host.Tell(new VirtualTagActor.DependencyValueChanged("H.T", 99, DateTime.UtcNow)); var evtTrue = alerts.FishForMessage( - e => e.AlarmId == "alm-hist-true" && e.TransitionKind == "Activated", Timeout); + e => e.AlarmId == "alm-hist-true" && e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken); evtTrue.HistorizeToAveva.ShouldBe(true); // Activate the false-flag alarm. host.Tell(new VirtualTagActor.DependencyValueChanged("H.F", 99, DateTime.UtcNow)); var evtFalse = alerts.FishForMessage( - e => e.AlarmId == "alm-hist-false" && e.TransitionKind == "Activated", Timeout); + e => e.AlarmId == "alm-hist-false" && e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken); evtFalse.HistorizeToAveva.ShouldBe(false); } @@ -695,19 +695,19 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T", threshold: 90) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Baseline is Good (unread inputs are not a quality signal — no load-time annotation). Drive the // input Bad with a value below threshold: the predicate freezes (no transition), but the worst-input // quality bucket moves Good→Bad → a QualityChanged annotation flows out of band. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 10, DateTime.UtcNow, OpcUaStatus.Bad)); - var q = publish.FishForMessage(m => m.StatusCode == OpcUaStatus.Bad, Timeout); + var q = publish.FishForMessage(m => m.StatusCode == OpcUaStatus.Bad, Timeout, cancellationToken: TestContext.Current.CancellationToken); q.AlarmNodeId.ShouldBe("alm-1"); q.Realm.ShouldBe(AddressSpaceRealm.Uns); // A pure quality change is NOT a state transition: no /alerts row. - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); } /// #478 — when a transition fires while an input is Uncertain, the projected full snapshot @@ -723,12 +723,12 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T", threshold: 90) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Above threshold (activates) but Uncertain quality — Uncertain is "ready", so the predicate runs. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow, OpcUaStatus.Uncertain)); - var state = publish.FishForMessage(m => m.State.Active, Timeout); + var state = publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); state.State.StatusCode.ShouldBe(OpcUaStatus.Uncertain); } @@ -748,19 +748,19 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Activate so there is something to shelve. host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); - publish.FishForMessage(m => m.State.Active, Timeout); - alerts.FishForMessage(e => e.TransitionKind == "Activated", Timeout); + publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); + alerts.FishForMessage(e => e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken); // Shelve via a one-shot shelve command — the host owns alm-1, so OneShotShelveAsync runs. host.Tell(new AlarmCommand( AlarmId: "alm-1", Operation: "OneShotShelve", User: "carol", Comment: null, UnshelveAtUtc: null)); // The Shelved AlarmTransitionEvent must carry the operator's identity, not "system". - var evt = alerts.FishForMessage(e => e.TransitionKind == "Shelved", Timeout); + var evt = alerts.FishForMessage(e => e.TransitionKind == "Shelved", Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.User.ShouldBe("carol"); // operator — NOT "system" } @@ -779,7 +779,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase var (host, _) = Spawn(publish, mux, LocalNode); host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - mux.ExpectMsg(Timeout); // load completed + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed // Send a snapshot that mentions only a DIFFERENT node — LocalNode is absent. // The host cannot determine its own role from this snapshot, so the cached role @@ -789,10 +789,10 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); // Both the OPC UA write AND the alerts publish must arrive (default-emit, role unknown). - var state = publish.FishForMessage(m => m.State.Active, Timeout); + var state = publish.FishForMessage(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); - var evt = alerts.ExpectMsg(Timeout); + var evt = alerts.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); evt.AlarmId.ShouldBe("alm-1"); evt.TransitionKind.ShouldBe("Activated"); } @@ -852,7 +852,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) })); - var state = publish.ExpectMsg(Timeout); + var state = publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); state.AlarmNodeId.ShouldBe("alm-1"); state.Realm.ShouldBe(AddressSpaceRealm.Uns); state.State.Active.ShouldBeTrue("the persisted state was Active — the rebuilt node must say so"); @@ -880,10 +880,10 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase // Wait for the re-assert to actually happen FIRST — otherwise the absence window below could // elapse before the load even completed and would prove nothing. - publish.ExpectMsg(Timeout); + publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); // Absence assertion: the elapsed time IS the assertion, so this stays deliberately short. - alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// An alarm sitting in the Part 9 no-event position already matches what the materialise @@ -900,8 +900,8 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase // Sequence the absence: the re-assert (if any) happens right after the mux registration, so // waiting for that first makes the window below cover the moment the write would have landed. - mux.ExpectMsg(Timeout); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + mux.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } /// Not just Active: every field a rebuild would silently lose is restored. A shelved (but @@ -919,7 +919,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() })); - var state = publish.ExpectMsg(Timeout); + var state = publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken); state.State.Active.ShouldBeFalse(); state.State.Shelving.ShouldBe(AlarmShelvingKind.OneShot); } @@ -936,7 +936,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() })); - publish.ExpectMsg(Timeout) + publish.ExpectMsg(Timeout, cancellationToken: TestContext.Current.CancellationToken) .TimestampUtc.ShouldBe(wentActiveAt); } @@ -952,6 +952,6 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(enabled: false) })); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Scripting/DpsScriptLogPublisherTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Scripting/DpsScriptLogPublisherTests.cs index 04bd633f0..7a439f897 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Scripting/DpsScriptLogPublisherTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Scripting/DpsScriptLogPublisherTests.cs @@ -39,7 +39,7 @@ public sealed class DpsScriptLogPublisherTests : RuntimeActorTestBase // Send the Subscribe FROM the probe so the SubscribeAck returns to the probe's mailbox // (a bare mediator.Tell would route the ack to the implicit TestActor instead). probe.Send(mediator, new Subscribe(VirtualTagActor.ScriptLogsTopic, probe.Ref)); - probe.ExpectMsg(TimeSpan.FromSeconds(10)); + probe.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); var publisher = new DpsScriptLogPublisher(() => Sys, new NoopHub()); var entry = SampleEntry(); @@ -51,7 +51,7 @@ public sealed class DpsScriptLogPublisherTests : RuntimeActorTestBase { publisher.Publish(entry); probe.ExpectMsg(TimeSpan.FromMilliseconds(200)).ShouldBe(entry); - }, duration: TimeSpan.FromSeconds(5), interval: TimeSpan.FromMilliseconds(250)); + }, duration: TimeSpan.FromSeconds(5), interval: TimeSpan.FromMilliseconds(250), TestContext.Current.CancellationToken); } /// diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs index 37111fd07..a287d9156 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs @@ -48,7 +48,7 @@ public sealed class ServiceCollectionExtensionsTests }) .Build(); - await host.StartAsync(); + await host.StartAsync(TestContext.Current.CancellationToken); try { var driverHost = host.Services.GetRequiredService>(); @@ -73,7 +73,7 @@ public sealed class ServiceCollectionExtensionsTests } finally { - await host.StopAsync(); + await host.StopAsync(TestContext.Current.CancellationToken); } } @@ -86,7 +86,7 @@ public sealed class ServiceCollectionExtensionsTests { using var host = BuildRuntimeActorHost(extra: null); - await host.StartAsync(); + await host.StartAsync(TestContext.Current.CancellationToken); try { var registry = host.Services.GetRequiredService(); @@ -94,7 +94,7 @@ public sealed class ServiceCollectionExtensionsTests } finally { - await host.StopAsync(); + await host.StopAsync(TestContext.Current.CancellationToken); } } @@ -112,7 +112,7 @@ public sealed class ServiceCollectionExtensionsTests services.AddSingleton(new FakeOutbox()); }); - await host.StartAsync(); + await host.StartAsync(TestContext.Current.CancellationToken); try { var recorder = host.Services.GetRequiredService>(); @@ -121,7 +121,7 @@ public sealed class ServiceCollectionExtensionsTests } finally { - await host.StopAsync(); + await host.StopAsync(TestContext.Current.CancellationToken); } } @@ -156,7 +156,7 @@ public sealed class ServiceCollectionExtensionsTests }); }); - await host.StartAsync(); + await host.StartAsync(TestContext.Current.CancellationToken); try { var driverHost = host.Services.GetRequiredService>(); @@ -171,13 +171,13 @@ public sealed class ServiceCollectionExtensionsTests // never completes. var deadline = DateTime.UtcNow.AddSeconds(15); while (sender.Sent.Count == 0 && DateTime.UtcNow < deadline) - await Task.Delay(50); + await Task.Delay(50, TestContext.Current.CancellationToken); sender.Sent.Count.ShouldBe(1); } finally { - await host.StopAsync(); + await host.StopAsync(TestContext.Current.CancellationToken); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Telemetry/PublishSeamEmitsToHubTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Telemetry/PublishSeamEmitsToHubTests.cs index f252dd7c1..6ccc87d04 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Telemetry/PublishSeamEmitsToHubTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Telemetry/PublishSeamEmitsToHubTests.cs @@ -121,10 +121,10 @@ public sealed class PublishSeamEmitsToHubTests : RuntimeActorTestBase Kind: AlarmTransitionKind.Raise))); // The OPC UA condition update confirms the transition was processed (ordered before the alerts fan-out). - publish.ExpectMsg(PresenceBudget); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Exactly one Alarm item, carrying the same transition the alerts topic received. - AwaitAssert(() => hub.Items.Count.ShouldBe(1), PresenceBudget, TimeSpan.FromMilliseconds(100)); + AwaitAssert(() => hub.Items.Count.ShouldBe(1), PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); var item = hub.Items[0].ShouldBeOfType(); item.E.AlarmId.ShouldBe(AlarmRawPath); item.E.AlarmName.ShouldBe("temp_hi"); @@ -150,15 +150,15 @@ public sealed class PublishSeamEmitsToHubTests : RuntimeActorTestBase // One enabled alarm firing when M.T > 90; wait for load to complete (RegisterInterest lands). host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { ScriptedPlan() })); - mux.ExpectMsg(PresenceBudget); + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow)); // The OPC UA condition update confirms the transition was processed. - publish.FishForMessage(m => m.State.Active, PresenceBudget); + publish.FishForMessage(m => m.State.Active, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // Exactly one Alarm item, carrying the Activated transition the alerts topic received. - AwaitAssert(() => hub.Items.Count.ShouldBe(1), PresenceBudget, TimeSpan.FromMilliseconds(100)); + AwaitAssert(() => hub.Items.Count.ShouldBe(1), PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); var item = hub.Items[0].ShouldBeOfType(); item.E.AlarmId.ShouldBe("alm-1"); item.E.TransitionKind.ShouldBe("Activated"); @@ -183,7 +183,7 @@ public sealed class PublishSeamEmitsToHubTests : RuntimeActorTestBase evaluator: new FailingEvaluator("syntax error"), scriptId: "script-7", publisherFactory: () => new DPSPublisher((_, payload) => published.Add((ScriptLogEntry)payload)), - telemetryHub: hub)); + telemetryHub: hub), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); @@ -191,7 +191,7 @@ public sealed class PublishSeamEmitsToHubTests : RuntimeActorTestBase { published.Count.ShouldBe(1); hub.Items.Count.ShouldBe(1); - }, TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(100)); + }, TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); var item = hub.Items[0].ShouldBeOfType(); item.E.ShouldBeSameAs(published[0]); // the exact instance handed to the publisher diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyConsumerMuxAdapterTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyConsumerMuxAdapterTests.cs index 33e4bb9ab..f8d7e80ed 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyConsumerMuxAdapterTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyConsumerMuxAdapterTests.cs @@ -33,7 +33,7 @@ public sealed class DependencyConsumerMuxAdapterTests : RuntimeActorTestBase adapter.Tell(new VirtualTagActor.DependencyValueChanged( TagA, Value: null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - AwaitCondition(() => consumer.Received.Count == 1, PresenceBudget); + AwaitCondition(() => consumer.Received.Count == 1, PresenceBudget, TestContext.Current.CancellationToken); consumer.Received[0].Ref.ShouldBe(TagA); consumer.Received[0].StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); consumer.Received[0].StatusCode.ShouldNotBe(OpcUaStatus.Good, @@ -57,7 +57,7 @@ public sealed class DependencyConsumerMuxAdapterTests : RuntimeActorTestBase new VirtualTagActor.DependencyValueChanged(TagB, null, ts, OpcUaStatus.BadNoCommunication), })); - AwaitCondition(() => consumer.Received.Count == 2, PresenceBudget); + AwaitCondition(() => consumer.Received.Count == 2, PresenceBudget, TestContext.Current.CancellationToken); consumer.Received[0].StatusCode.ShouldBe(OpcUaStatus.Good); consumer.Received[1].StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxActorTests.cs index 792371924..98c97e9ab 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxActorTests.cs @@ -29,21 +29,21 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase // subA hears tag-1 + tag-2. var aMsgs = new[] { - subA.ExpectMsg(), - subA.ExpectMsg(), + subA.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken), + subA.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken), }.Select(b => b.Items.ShouldHaveSingleItem()).OrderBy(m => m.TagId).ToList(); aMsgs.Select(m => m.TagId).ShouldBe(new[] { "tag-1", "tag-2" }); // subB hears tag-3 + tag-2. var bMsgs = new[] { - subB.ExpectMsg(), - subB.ExpectMsg(), + subB.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken), + subB.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken), }.Select(b => b.Items.ShouldHaveSingleItem()).OrderBy(m => m.TagId).ToList(); bMsgs.Select(m => m.TagId).ShouldBe(new[] { "tag-2", "tag-3" }); - subA.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); - subB.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); + subA.ExpectNoMsg(TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); + subB.ExpectNoMsg(TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); } /// Verifies that publishes for unregistered references are silently dropped. @@ -56,7 +56,7 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref)); mux.Tell(ValuePublishes.Single("driver-1", "nobody-cares", 99, OpcUaStatus.Good, DateTime.UtcNow)); - sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// Verifies that unregistering interest stops forwarding to that subscriber. @@ -68,11 +68,11 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-1" }, sub.Ref)); mux.Tell(ValuePublishes.Single("driver-1", "tag-1", 10, OpcUaStatus.Good, DateTime.UtcNow)); - sub.ExpectMsg(); + sub.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); mux.Tell(new DependencyMuxActor.UnregisterInterest(sub.Ref)); mux.Tell(ValuePublishes.Single("driver-1", "tag-1", 20, OpcUaStatus.Good, DateTime.UtcNow)); - sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// Verifies that re-registering a subscriber replaces the prior interest set. @@ -86,10 +86,10 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "tag-2" }, sub.Ref)); // replaces tag-1 mux.Tell(ValuePublishes.Single("driver-1", "tag-1", 10, OpcUaStatus.Good, DateTime.UtcNow)); - sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); mux.Tell(ValuePublishes.Single("driver-1", "tag-2", 20, OpcUaStatus.Good, DateTime.UtcNow)); - sub.ExpectMsg().Items.ShouldHaveSingleItem().TagId.ShouldBe("tag-2"); + sub.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Items.ShouldHaveSingleItem().TagId.ShouldBe("tag-2"); } /// Verifies that a virtual tag actor registers dependencies with the mux during PreStart and that evaluation fires end-to-end. @@ -103,7 +103,7 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase "vt-1", "a+b", evaluator: evaluator, dependencyRefs: new[] { "ref-a", "ref-b" }, - mux: mux)); + mux: mux), TestContext.Current.CancellationToken); // Race-safe end-to-end check: AwaitAssert retries until the PreStart RegisterInterest // has actually landed at the mux + the publish has fanned out. Until then the publish @@ -114,15 +114,15 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase "driver-1", "ref-a", 10, OpcUaStatus.Good, DateTime.UtcNow)); parent.ExpectMsg(TimeSpan.FromMilliseconds(200)) .Value.ShouldBe(10); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // From here the actor is wired — second publish drives the sum. mux.Tell(ValuePublishes.Single("driver-1", "ref-b", 32, OpcUaStatus.Good, DateTime.UtcNow)); - parent.ExpectMsg(PresenceBudget).Value.ShouldBe(42); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(42); // Unrelated ref shouldn't fire eval. mux.Tell(ValuePublishes.Single("driver-1", "ref-unrelated", 99, OpcUaStatus.Good, DateTime.UtcNow)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// Verifies that the DriverHostActor forwards a driver burst through the dependency mux to the @@ -149,7 +149,7 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("ref-1", 42, OpcUaStatus.Good, DateTime.UtcNow), })); - subscriber.ExpectMsg() + subscriber.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken) .Items.ShouldHaveSingleItem().TagId.ShouldBe("ref-1"); } @@ -173,14 +173,14 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase })); // Nothing for tag-y — no subscriber registered for it, so the batch carries tag-x alone. - var dep = sub.ExpectMsg(PresenceBudget).Items.ShouldHaveSingleItem(); + var dep = sub.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.ShouldHaveSingleItem(); dep.TagId.ShouldBe("tag-x"); dep.Value.ShouldBe(10); dep.TimestampUtc.ShouldBe(tsX); // #519: the item's own status sub-code rides through, not a Good/Bad bucket. dep.StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); - sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// One driver burst costs each subscriber exactly ONE mailbox message, not one per matched @@ -203,13 +203,13 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase new DriverInstanceActor.AttributeValueItem("c", 3, OpcUaStatus.Good, tsB), })); - var batch = sub.ExpectMsg(PresenceBudget); + var batch = sub.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); batch.Items.Select(i => i.TagId).ShouldBe(new[] { "a", "b" }); batch.Items.Select(i => i.Value).ShouldBe(new object?[] { 1, 2 }); batch.Items.Select(i => i.TimestampUtc).ShouldBe(new[] { tsA, tsB }); // The unmatched "c" produced nothing, and the two matched items did NOT arrive as two messages. - sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + sub.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// #478 — the mux forwards the published source quality onto DependencyValueChanged so the @@ -224,7 +224,7 @@ public sealed class DependencyMuxActorTests : RuntimeActorTestBase mux.Tell(ValuePublishes.Single( "driver-1", "tag-1", 10, OpcUaStatus.Bad, DateTime.UtcNow)); - sub.ExpectMsg() + sub.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken) .Items.ShouldHaveSingleItem().StatusCode.ShouldBe(OpcUaStatus.Bad); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxReplayTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxReplayTests.cs index 6d66cb8b0..24b66d277 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxReplayTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/DependencyMuxReplayTests.cs @@ -32,7 +32,7 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase var first = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a", "b" }, first.Ref, ReplayRetained: true)); mux.Tell(Burst(("a", 1), ("b", 2), ("c", 3))); - first.ExpectMsg(PresenceBudget).Items.Count.ShouldBe(2); + first.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.Count.ShouldBe(2); // The first subscriber goes away; retention must survive the unregister (that is the Restart shape). mux.Tell(new DependencyMuxActor.UnregisterInterest(first.Ref)); @@ -40,7 +40,7 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase var second = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a", "c" }, second.Ref, ReplayRetained: true)); - var replay = second.ExpectMsg(PresenceBudget); + var replay = second.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); replay.Items.Select(i => i.TagId).ShouldBe(new[] { "a" }, "'c' was never registered by a replay-opting subscriber, so it is not retained; 'b' is not in this subscriber's refs"); replay.Items[0].Value.ShouldBe(1); } @@ -52,15 +52,15 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase var seed = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a" }, seed.Ref)); mux.Tell(Burst(("a", 1))); - seed.ExpectMsg(PresenceBudget); + seed.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var opted = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a" }, opted.Ref)); // default: no replay - opted.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + opted.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); var fresh = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "never-published" }, fresh.Ref, ReplayRetained: true)); - fresh.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + fresh.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// I2 — a subscriber that did NOT opt into replay (the continuous-historization recorder registers @@ -74,14 +74,14 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "h1", "h2", "h3", "shared" }, recorder.Ref)); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "r", "shared" }, calc.Ref, ReplayRetained: true)); mux.Tell(Burst(("h1", 1), ("h2", 2), ("h3", 3), ("r", 4), ("shared", 5))); - recorder.ExpectMsg(PresenceBudget).Items.Count.ShouldBe(4, "the recorder is still DELIVERED to"); - calc.ExpectMsg(PresenceBudget); + recorder.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.Count.ShouldBe(4, "the recorder is still DELIVERED to"); + calc.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); mux.UnderlyingActor.RetainedRefCount.ShouldBe(2, "only 'r' and 'shared' — the recorder-only refs retain nothing"); var late = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "h1", "r" }, late.Ref, ReplayRetained: true)); - late.ExpectMsg(PresenceBudget) + late.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.Select(i => i.TagId).ShouldBe(new[] { "r" }, "positive control: the opted-in ref replays; 'h1' was never retained"); // Unregistering everyone ends up with no entries at all once the grace is over — tested below; here @@ -100,12 +100,12 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase var gen1 = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a" }, gen1.Ref, ReplayRetained: true)); mux.Tell(Burst(("a", 1))); - gen1.ExpectMsg(PresenceBudget); + gen1.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); mux.Tell(new DependencyMuxActor.UnregisterInterest(gen1.Ref)); var gen2 = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a" }, gen2.Ref, ReplayRetained: true)); - gen2.ExpectMsg(PresenceBudget).Items.Single().Value.ShouldBe(1); + gen2.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.Single().Value.ShouldBe(1); } /// I2 — once the grace is over, the retained value is dropped, no entry is left behind, and a @@ -118,7 +118,7 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase var gen1 = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a" }, gen1.Ref, ReplayRetained: true)); mux.Tell(Burst(("a", 1))); - gen1.ExpectMsg(PresenceBudget); + gen1.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); mux.UnderlyingActor.RetainedRefCount.ShouldBe(1, "positive control: the value is retained while a replay subscriber is registered"); mux.Tell(new DependencyMuxActor.UnregisterInterest(gen1.Ref)); @@ -127,10 +127,10 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase { mux.UnderlyingActor.RetainedRefCount.ShouldBe(0); mux.UnderlyingActor.TrackedRefCount.ShouldBe(0); - }, PresenceBudget); + }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var gen3 = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a" }, gen3.Ref, ReplayRetained: true)); - gen3.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + gen3.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// I2 — a removed source's retained values are evicted, so its last Good is never replayed to a @@ -145,15 +145,15 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase [new DriverInstanceActor.AttributeValueItem("a", 1, OpcUaStatus.Good, DateTime.UtcNow)])); mux.Tell(new DriverInstanceActor.AttributeValueBatchPublished("driver-2", [new DriverInstanceActor.AttributeValueItem("b", 2, OpcUaStatus.Good, DateTime.UtcNow)])); - sub.ExpectMsg(PresenceBudget); - sub.ExpectMsg(PresenceBudget); + sub.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + sub.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); mux.Tell(new DependencyMuxActor.SourceRemoved("driver-1")); mux.UnderlyingActor.RetainedRefCount.ShouldBe(1); var restarted = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "a", "b" }, restarted.Ref, ReplayRetained: true)); - restarted.ExpectMsg(PresenceBudget) + restarted.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .Items.Select(i => i.TagId).ShouldBe(new[] { "b" }, "driver-1 was removed: its last Good must not be replayed"); } @@ -177,7 +177,7 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase { mux.Tell(Burst(("src/a", 10.0), ("src/b", 20.0))); published1.Count.ShouldBeGreaterThan(0); - }, PresenceBudget); + }, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); published1.Last().Snapshot.Value.ShouldBe(30.0); // Restart: the old instance goes away; a NEW driver + adapter come up. Nothing changes upstream. @@ -191,7 +191,7 @@ public sealed class DependencyMuxReplayTests : RuntimeActorTestBase // The replayed inputs satisfied the change-gate; the first trigger after init is the replay itself // (if it lands post-init) or the next change (here: none) — so publish happens from the replay. - AwaitAssert(() => published2.Count.ShouldBeGreaterThan(0), PresenceBudget); + AwaitAssert(() => published2.Count.ShouldBeGreaterThan(0), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); published2.Last().Snapshot.Value.ShouldBe(30.0); published2.Last().Snapshot.StatusCode.ShouldBe(OpcUaStatus.Good); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorAsyncEvaluationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorAsyncEvaluationTests.cs index 452c7dc10..5243656a7 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorAsyncEvaluationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorAsyncEvaluationTests.cs @@ -44,26 +44,26 @@ one-thread-dispatcher { var slowParent = CreateTestProbe(); var fastParent = CreateTestProbe(); var slowActor = slowParent.ChildActorOf( - VirtualTagActor.Props("vt-slow", "slow", evaluator: slow).WithDispatcher(OneThreadDispatcher)); + VirtualTagActor.Props("vt-slow", "slow", evaluator: slow).WithDispatcher(OneThreadDispatcher), TestContext.Current.CancellationToken); var fastActor = fastParent.ChildActorOf( - VirtualTagActor.Props("vt-fast", "fast", evaluator: new ConstEvaluator(42)).WithDispatcher(OneThreadDispatcher)); + VirtualTagActor.Props("vt-fast", "fast", evaluator: new ConstEvaluator(42)).WithDispatcher(OneThreadDispatcher), TestContext.Current.CancellationToken); slowActor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - AwaitCondition(() => slow.Calls == 1, PresenceBudget); + AwaitCondition(() => slow.Calls == 1, PresenceBudget, TestContext.Current.CancellationToken); // The slow script is mid-run. Before the fix it held the ONLY thread of this dispatcher, so the sibling's // evaluation could not even start until the slow one finished. fastActor.Tell(new VirtualTagActor.DependencyValueChanged("b", 1, DateTime.UtcNow)); - fastParent.ExpectMsg(TimeSpan.FromSeconds(5)).Value.ShouldBe(42); + fastParent.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(42); // And the slow actor itself still answers (Identify is handled by the actor cell on its own thread). - var identity = await slowActor.Ask(new Identify(7), TimeSpan.FromSeconds(5)); + var identity = await slowActor.Ask(new Identify(7), TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); identity.MessageId.ShouldBe(7); - slowParent.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); + slowParent.ExpectNoMsg(TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken); // Positive control: the slow evaluation still lands once its script returns. slow.Release(); - slowParent.ExpectMsg().Value.ShouldBe(1); + slowParent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(1); } finally { @@ -76,7 +76,7 @@ one-thread-dispatcher { { var parent = CreateTestProbe(); var evaluator = new ManualEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); var t1 = new DateTime(2026, 11, 9, 0, 0, 1, DateTimeKind.Utc); var t2 = t1.AddSeconds(1); @@ -86,18 +86,18 @@ one-thread-dispatcher { actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 3, t3)); // One run at a time: the second and third triggers wait behind the first. - AwaitCondition(() => evaluator.Calls.Count == 1, PresenceBudget); - ExpectNoMsg(TimeSpan.FromMilliseconds(150)); + AwaitCondition(() => evaluator.Calls.Count == 1, PresenceBudget, TestContext.Current.CancellationToken); + ExpectNoMsg(TimeSpan.FromMilliseconds(150), TestContext.Current.CancellationToken); evaluator.Calls.Count.ShouldBe(1); evaluator.Complete(0, VirtualTagEvalResult.Ok(10)); - var r1 = parent.ExpectMsg(); - AwaitCondition(() => evaluator.Calls.Count == 2, PresenceBudget); + var r1 = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); + AwaitCondition(() => evaluator.Calls.Count == 2, PresenceBudget, TestContext.Current.CancellationToken); evaluator.Complete(1, VirtualTagEvalResult.Ok(20)); - var r2 = parent.ExpectMsg(); - AwaitCondition(() => evaluator.Calls.Count == 3, PresenceBudget); + var r2 = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); + AwaitCondition(() => evaluator.Calls.Count == 3, PresenceBudget, TestContext.Current.CancellationToken); evaluator.Complete(2, VirtualTagEvalResult.Ok(30)); - var r3 = parent.ExpectMsg(); + var r3 = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // Each evaluation saw the input as of ITS trigger — not the newest value — exactly as the blocking path did. evaluator.Calls.ShouldBe(new object?[] { 1, 2, 3 }); @@ -110,18 +110,18 @@ one-thread-dispatcher { { var parent = CreateTestProbe(); var evaluator = new ManualEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - AwaitCondition(() => evaluator.Calls.Count == 1, PresenceBudget); + AwaitCondition(() => evaluator.Calls.Count == 1, PresenceBudget, TestContext.Current.CancellationToken); actor.Tell(VirtualTagActor.ReassertValue.Instance); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(150)); // nothing published yet: the reassert waits its turn + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(150), TestContext.Current.CancellationToken); // nothing published yet: the reassert waits its turn evaluator.Complete(0, VirtualTagEvalResult.Ok(99)); - var fresh = parent.ExpectMsg(); + var fresh = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); fresh.Value.ShouldBe(99); fresh.IsReassert.ShouldBeFalse(); - var reassert = parent.ExpectMsg(); + var reassert = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); reassert.IsReassert.ShouldBeTrue(); reassert.Value.ShouldBe(99, "the reassert re-emits what the run produced, as it did when the run blocked"); } @@ -131,19 +131,19 @@ one-thread-dispatcher { { var parent = CreateTestProbe(); var evaluator = new ManualEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - AwaitCondition(() => evaluator.Calls.Count == 1, PresenceBudget); + AwaitCondition(() => evaluator.Calls.Count == 1, PresenceBudget, TestContext.Current.CancellationToken); evaluator.Fault(0, new InvalidOperationException("boom")); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadInternalError); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadInternalError); // The actor left the evaluating behaviour: the next trigger is evaluated normally. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - AwaitCondition(() => evaluator.Calls.Count == 2, PresenceBudget); + AwaitCondition(() => evaluator.Calls.Count == 2, PresenceBudget, TestContext.Current.CancellationToken); evaluator.Complete(1, VirtualTagEvalResult.Ok(5)); - var recovered = parent.ExpectMsg(); + var recovered = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); recovered.StatusCode.ShouldBe(OpcUaStatus.Good); recovered.Value.ShouldBe(5); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorFailureLogRateLimitTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorFailureLogRateLimitTests.cs index c1a6253e7..87438d555 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorFailureLogRateLimitTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorFailureLogRateLimitTests.cs @@ -25,21 +25,21 @@ public sealed class VirtualTagActorFailureLogRateLimitTests : RuntimeActorTestBa "vt-chatty", "broken", evaluator: new FailingEvaluator("boom"), publisherFactory: () => new DPSPublisher((_, p) => capture.Enqueue((ScriptLogEntry)p)), - failureLogLimiter: limiter)); + failureLogLimiter: limiter), TestContext.Current.CancellationToken); for (var i = 0; i < 10; i++) actor.Tell(new VirtualTagActor.DependencyValueChanged("a", i, DateTime.UtcNow)); // Positive control: the FIRST failure still degrades the node (once per transition, as before). - parent.ExpectMsg(PresenceBudget).StatusCode.ShouldBe(Commons.OpcUa.OpcUaStatus.BadInternalError); - AwaitAssert(() => capture.Count.ShouldBe(3), PresenceBudget); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(Commons.OpcUa.OpcUaStatus.BadInternalError); + AwaitAssert(() => capture.Count.ShouldBe(3), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); capture.Count.ShouldBe(3, "7 of 10 failures were suppressed from the cluster-wide log"); now = now.AddMinutes(1); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 99, DateTime.UtcNow)); - AwaitAssert(() => capture.Count.ShouldBe(5), PresenceBudget); + AwaitAssert(() => capture.Count.ShouldBe(5), PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); var entries = capture.ToArray(); entries[3].Message.ShouldContain("7"); entries[3].Message.ShouldContain("suppressed"); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorInputQualityTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorInputQualityTests.cs index 6cb014fb8..c39b886d1 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorInputQualityTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorInputQualityTests.cs @@ -46,17 +46,17 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new CountingSumEvaluator(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a + b", evaluator: evaluator, dependencyRefs: ["a", "b"])); + "vt-1", "a + b", evaluator: evaluator, dependencyRefs: ["a", "b"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); actor.Tell(new VirtualTagActor.DependencyValueChanged("b", 32, DateTime.UtcNow)); - parent.ExpectMsg().Value.ShouldBe(10); - parent.ExpectMsg().Value.ShouldBe(42); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(10); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(42); var callsBefore = evaluator.Calls; actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - var bad = parent.ExpectMsg(PresenceBudget); + var bad = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); bad.StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); bad.StatusCode.ShouldNotBe(OpcUaStatus.BadInternalError, "the script did not fail — its input did"); bad.Value.ShouldBe(42, "the last known value rides out with the Bad status"); @@ -64,7 +64,7 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase // Transition-once: the same Bad again changes nothing. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// A different Bad sub-code republishes — the sub-code is the diagnostic. @@ -73,16 +73,16 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a + b", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a"])); + "vt-1", "a + b", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); - parent.ExpectMsg(); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadWaitingForInitialData)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadWaitingForInitialData); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadWaitingForInitialData); } /// @@ -95,18 +95,18 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a"])); + "vt-1", "a", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); - parent.ExpectMsg().Value.ShouldBe(10); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(10); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); // Same value as before the outage. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); - var recovered = parent.ExpectMsg(PresenceBudget); + var recovered = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); recovered.Value.ShouldBe(10, "the same value as before — that is the point"); recovered.StatusCode.ShouldBe(OpcUaStatus.Good); } @@ -122,14 +122,14 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new CountingSumEvaluator(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a", evaluator: evaluator, dependencyRefs: ["a"])); + "vt-1", "a", evaluator: evaluator, dependencyRefs: ["a"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); - parent.ExpectMsg(); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 11, DateTime.UtcNow, Uncertain)); - var result = parent.ExpectMsg(PresenceBudget); + var result = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); result.Value.ShouldBe(11, "the script DID run — Uncertain is usable"); result.StatusCode.ShouldBe(Uncertain); evaluator.LastSeenStatus.ShouldBe(Uncertain, "the script must observe the input's REAL status"); @@ -144,11 +144,11 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a + b", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a", "b"])); + "vt-1", "a + b", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a", "b"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); } /// @@ -164,20 +164,20 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a + b", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a", "b"])); + "vt-1", "a + b", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a", "b"]), TestContext.Current.CancellationToken); using var meter = new OutcomeRecorder(); // Warm-up: "b" has never arrived, so this is cold start, not a fault. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); meter.Count("inputs-bad").ShouldBe(0, "warm-up is not an inputs-bad report"); // Positive control: once every dep has arrived, the tag reports — and the counter ticks. actor.Tell(new VirtualTagActor.DependencyValueChanged("b", 32, DateTime.UtcNow)); - parent.ExpectMsg(PresenceBudget) + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); - AwaitCondition(() => meter.Count("inputs-bad") == 1, TimeSpan.FromSeconds(3)); + AwaitCondition(() => meter.Count("inputs-bad") == 1, TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken); } /// @@ -190,16 +190,16 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a"])); + "vt-1", "a", evaluator: new CountingSumEvaluator(), dependencyRefs: ["a"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); - parent.ExpectMsg(); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication)); - parent.ExpectMsg(); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); actor.Tell(VirtualTagActor.ReassertValue.Instance); - var reassert = parent.ExpectMsg(PresenceBudget); + var reassert = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); reassert.IsReassert.ShouldBeTrue(); reassert.StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); reassert.Value.ShouldBe(10); @@ -216,7 +216,7 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new CountingSumEvaluator(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "a + b", evaluator: evaluator, dependencyRefs: ["a", "b"])); + "vt-1", "a + b", evaluator: evaluator, dependencyRefs: ["a", "b"]), TestContext.Current.CancellationToken); var ts = DateTime.UtcNow; actor.Tell(new VirtualTagActor.DependencyValueBatch(new[] @@ -224,7 +224,7 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase new VirtualTagActor.DependencyValueChanged("a", 10, ts), new VirtualTagActor.DependencyValueChanged("b", 32, ts), })); - parent.ExpectMsg().Value.ShouldBe(42); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(42); var callsBefore = evaluator.Calls; actor.Tell(new VirtualTagActor.DependencyValueBatch(new[] @@ -233,7 +233,7 @@ public sealed class VirtualTagActorInputQualityTests : RuntimeActorTestBase new VirtualTagActor.DependencyValueChanged("b", 33, ts), })); - parent.ExpectMsg(PresenceBudget) + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken) .StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication); evaluator.Calls.ShouldBe(callsBefore); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorRunnerUnavailableTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorRunnerUnavailableTests.cs index 2d5bb0f8b..84adbf951 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorRunnerUnavailableTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorRunnerUnavailableTests.cs @@ -33,23 +33,23 @@ public sealed class VirtualTagActorRunnerUnavailableTests : RuntimeActorTestBase evaluator.Results.Enqueue(VirtualTagEvalResult.Unavailable("script worker restarted")); evaluator.Results.Enqueue(VirtualTagEvalResult.Unavailable("script worker restarted")); evaluator.Results.Enqueue(VirtualTagEvalResult.Failure("script threw: boom")); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "x", evaluator: evaluator, dependencyRefs: ["a"])); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "x", evaluator: evaluator, dependencyRefs: ["a"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.Good); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.Good); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - var unavailable = parent.ExpectMsg(PresenceBudget); + var unavailable = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); unavailable.StatusCode.ShouldBe(OpcUaStatus.BadResourceUnavailable); unavailable.Value.ShouldBe(5, "the last known value rides out with the Bad status"); // Once per transition: the same code again publishes nothing. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 3, DateTime.UtcNow)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // A script failure is a different Bad and is published. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 4, DateTime.UtcNow)); - var scriptFault = parent.ExpectMsg(PresenceBudget); + var scriptFault = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); scriptFault.StatusCode.ShouldBe(OpcUaStatus.BadInternalError); scriptFault.Value.ShouldBe(5); } @@ -68,20 +68,20 @@ public sealed class VirtualTagActorRunnerUnavailableTests : RuntimeActorTestBase evaluator.Results.Enqueue(VirtualTagEvalResult.Ok(5)); evaluator.Results.Enqueue(VirtualTagEvalResult.Unavailable("script worker restarted") with { RetryWhen = recovered.Task }); evaluator.Results.Enqueue(VirtualTagEvalResult.Ok(7)); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "x", evaluator: evaluator, dependencyRefs: ["a"])); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "x", evaluator: evaluator, dependencyRefs: ["a"]), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectMsg().Value.ShouldBe(5); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(5); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - parent.ExpectMsg(PresenceBudget).StatusCode.ShouldBe(OpcUaStatus.BadResourceUnavailable); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // nothing re-runs before the runner is back + parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadResourceUnavailable); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // nothing re-runs before the runner is back recovered.SetResult(); - var again = parent.ExpectMsg(PresenceBudget); + var again = parent.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); again.StatusCode.ShouldBe(OpcUaStatus.Good, "re-evaluated on recovery, with no new input"); again.Value.ShouldBe(7); evaluator.Results.ShouldBeEmpty(); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); // exactly one re-evaluation + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // exactly one re-evaluation } [Fact] diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs index eaf394348..807b9d479 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTests.cs @@ -20,7 +20,7 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase Watch(actor); actor.Tell(new VirtualTagActor.DependencyValueChanged("tag-a", 10, DateTime.UtcNow)); actor.Tell(new VirtualTagActor.DependencyValueChanged("tag-b", 20, DateTime.UtcNow)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// Verifies evaluator results flow to parent as EvaluationResult. @@ -29,15 +29,15 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new SumEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "a + b", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "a + b", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 10, DateTime.UtcNow)); actor.Tell(new VirtualTagActor.DependencyValueChanged("b", 32, DateTime.UtcNow)); // First dep: a alone -> 10. Second dep: a + b -> 42. - var first = parent.ExpectMsg(); + var first = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); first.Value.ShouldBe(10); - var second = parent.ExpectMsg(); + var second = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); second.Value.ShouldBe(42); } @@ -47,14 +47,14 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new ConstEvaluator(42); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - var first = parent.ExpectMsg(); + var first = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); first.Value.ShouldBe(42); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// Verifies evaluator failure publishes ScriptLogEntry warning. @@ -67,7 +67,7 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase "vt-1", "broken", evaluator: new FailingEvaluator("syntax error"), scriptId: "script-7", - publisherFactory: () => new DPSPublisher(capture.Publish))); + publisherFactory: () => new DPSPublisher(capture.Publish)), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); @@ -79,12 +79,12 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase entry.Message.ShouldContain("syntax error"); entry.ScriptId.ShouldBe("script-7"); entry.VirtualTagId.ShouldBe("vt-1"); - }, duration: PresenceBudget); + }, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // 02/S13: the failure now ALSO degrades the node — with no declared dependencyRefs the // inputs-ready gate is vacuously satisfied, so a Bad EvaluationResult reaches the parent (this // replaces the old ExpectNoMsg, which encoded the stale-Good bug). - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadInternalError); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadInternalError); } /// 02/S13 once-per-transition: two consecutive failures publish exactly ONE Bad @@ -95,13 +95,13 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new QueuedEvaluator( VirtualTagEvalResult.Failure("boom1"), VirtualTagEvalResult.Failure("boom2")); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadInternalError); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadInternalError); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// 02/S13 same-value recovery: Ok(42) → fail → Ok(42) must republish Good with 42 even @@ -112,18 +112,18 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new QueuedEvaluator( VirtualTagEvalResult.Ok(42), VirtualTagEvalResult.Failure("boom"), VirtualTagEvalResult.Ok(42)); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - var first = parent.ExpectMsg(); + var first = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); first.StatusCode.ShouldBe(OpcUaStatus.Good); first.Value.ShouldBe(42); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadInternalError); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadInternalError); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 3, DateTime.UtcNow)); - var recovered = parent.ExpectMsg(); + var recovered = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); recovered.StatusCode.ShouldBe(OpcUaStatus.Good); recovered.Value.ShouldBe(42); } @@ -142,13 +142,13 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new QueuedEvaluator( VirtualTagEvalResult.Ok(42), VirtualTagEvalResult.Failure("boom")); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectMsg().Value.ShouldBe(42); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(42); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, DateTime.UtcNow)); - var bad = parent.ExpectMsg(); + var bad = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); bad.StatusCode.ShouldBe(OpcUaStatus.BadInternalError); bad.Value.ShouldBe(42); } @@ -160,11 +160,11 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "expr", evaluator: new ThrowingEvaluator())); + "vt-1", "expr", evaluator: new ThrowingEvaluator()), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadInternalError); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadInternalError); } /// 02/S13 inputs-ready gate: a multi-dep script that fails before all its declared @@ -177,12 +177,12 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase var actor = parent.ChildActorOf(VirtualTagActor.Props( "vt-1", "expr", evaluator: new FailingEvaluator("boom"), - dependencyRefs: new[] { "a", "b" })); + dependencyRefs: new[] { "a", "b" }), TestContext.Current.CancellationToken); // Only "a" has arrived — "b" is still missing, so the gate suppresses the Bad publish. actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// 02/S13 inputs-ready gate: once every declared dependency has arrived at least once, a @@ -194,15 +194,15 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase var actor = parent.ChildActorOf(VirtualTagActor.Props( "vt-1", "expr", evaluator: new FailingEvaluator("boom"), - dependencyRefs: new[] { "a", "b" })); + dependencyRefs: new[] { "a", "b" }), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); // Still missing "b" — no publish yet. - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); // "b" arrives — all refs now seen, so the failure degrades to Bad. actor.Tell(new VirtualTagActor.DependencyValueChanged("b", 2, DateTime.UtcNow)); - parent.ExpectMsg().StatusCode.ShouldBe(OpcUaStatus.BadInternalError); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).StatusCode.ShouldBe(OpcUaStatus.BadInternalError); } /// A batch carrying K changed inputs evaluates the script ONCE, with every input already @@ -213,7 +213,7 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new CountingSumEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "a+b+c", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "a+b+c", evaluator: evaluator), TestContext.Current.CancellationToken); var ts = new DateTime(2026, 8, 15, 10, 0, 0, DateTimeKind.Utc); actor.Tell(new VirtualTagActor.DependencyValueBatch(new[] @@ -223,12 +223,12 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase new VirtualTagActor.DependencyValueChanged("c", 4, ts), })); - var result = parent.ExpectMsg(); + var result = parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); result.Value.ShouldBe(7); // The single evaluation is stamped with the newest item in the burst. result.TimestampUtc.ShouldBe(ts); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); evaluator.Calls.ShouldBe(1); } @@ -242,16 +242,16 @@ public sealed class VirtualTagActorTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new CountingSumEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "a+b+c", evaluator: evaluator)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "a+b+c", evaluator: evaluator), TestContext.Current.CancellationToken); var ts = new DateTime(2026, 8, 15, 10, 0, 0, DateTimeKind.Utc); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, ts.AddSeconds(-2))); actor.Tell(new VirtualTagActor.DependencyValueChanged("b", 2, ts.AddSeconds(-1))); actor.Tell(new VirtualTagActor.DependencyValueChanged("c", 4, ts)); - parent.ExpectMsg().Value.ShouldBe(1); - parent.ExpectMsg().Value.ShouldBe(3); - parent.ExpectMsg().Value.ShouldBe(7); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(1); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(3); + parent.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(7); evaluator.Calls.ShouldBe(3); } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTimerTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTimerTests.cs index 935299b98..1889325d4 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTimerTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagActorTimerTests.cs @@ -18,10 +18,10 @@ public sealed class VirtualTagActorTimerTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new CountingEvaluator(); - parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator, timerIntervalMs: 50)); + parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator, timerIntervalMs: 50), TestContext.Current.CancellationToken); - var first = parent.ExpectMsg(TimeSpan.FromSeconds(5)); - var second = parent.ExpectMsg(TimeSpan.FromSeconds(5)); + var first = parent.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); + var second = parent.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); ((int)second.Value!).ShouldBeGreaterThan((int)first.Value!); } @@ -30,8 +30,8 @@ public sealed class VirtualTagActorTimerTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new CountingEvaluator(); - parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator), TestContext.Current.CancellationToken); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); evaluator.Calls.ShouldBe(0); } @@ -41,11 +41,11 @@ public sealed class VirtualTagActorTimerTests : RuntimeActorTestBase var parent = CreateTestProbe(); var evaluator = new CountingEvaluator(); var actor = parent.ChildActorOf(VirtualTagActor.Props( - "vt-1", "expr", evaluator: evaluator, changeTriggered: false, timerIntervalMs: 2000)); + "vt-1", "expr", evaluator: evaluator, changeTriggered: false, timerIntervalMs: 2000), TestContext.Current.CancellationToken); actor.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - parent.ExpectNoMsg(TimeSpan.FromMilliseconds(150)); + parent.ExpectNoMsg(TimeSpan.FromMilliseconds(150), TestContext.Current.CancellationToken); evaluator.Calls.ShouldBe(0); - parent.ExpectMsg(TimeSpan.FromSeconds(10)); + parent.ExpectMsg(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken); } [Fact] @@ -53,14 +53,14 @@ public sealed class VirtualTagActorTimerTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new CountingEvaluator(gateFirst: true); - parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator, timerIntervalMs: 50)); + parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator, timerIntervalMs: 50), TestContext.Current.CancellationToken); - AwaitCondition(() => evaluator.Calls == 1, PresenceBudget); + AwaitCondition(() => evaluator.Calls == 1, PresenceBudget, TestContext.Current.CancellationToken); Thread.Sleep(600); // ~12 ticks fire while the first run is in flight evaluator.Calls.ShouldBe(1); evaluator.Release(); - parent.ExpectMsg(TimeSpan.FromSeconds(5)); + parent.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // A backlog replay would run ~11 more evaluations immediately; coalesced, at most one tick lands here. Thread.Sleep(30); evaluator.Calls.ShouldBeLessThanOrEqualTo(3); @@ -71,16 +71,16 @@ public sealed class VirtualTagActorTimerTests : RuntimeActorTestBase { var parent = CreateTestProbe(); var evaluator = new CountingEvaluator(); - var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator, timerIntervalMs: 50)); - parent.ExpectMsg(TimeSpan.FromSeconds(5)); + var actor = parent.ChildActorOf(VirtualTagActor.Props("vt-1", "expr", evaluator: evaluator, timerIntervalMs: 50), TestContext.Current.CancellationToken); + parent.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); Watch(actor); Sys.Stop(actor); - ExpectTerminated(actor); + ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); Thread.Sleep(100); // let any already-enqueued tick drain // A scheduler that was never cancelled keeps Telling the dead actor: those land as dead letters. Sys.EventStream.Subscribe(TestActor, typeof(DeadLetter)); - ExpectNoMsg(TimeSpan.FromMilliseconds(200)); // ~12 intervals at TIMEFACTOR 3 + ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); // ~12 intervals at TIMEFACTOR 3 } private sealed class CountingEvaluator(bool gateFirst = false) : IVirtualTagEvaluator diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagHostActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagHostActorTests.cs index 7c82c06c6..1205eb200 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagHostActorTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagHostActorTests.cs @@ -47,7 +47,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // The child self-registers with the mux in PreStart, so a RegisterInterest landing on the // mux probe is proof the host spawned a live child. - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); reg.TagRefs.ShouldContain("a"); } @@ -64,7 +64,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase var ts = new DateTime(2026, 6, 7, 12, 0, 0, DateTimeKind.Utc); host.Tell(new VirtualTagActor.EvaluationResult("vt-1", 42.0, ts, CorrelationId.NewId())); - var update = publish.ExpectMsg(); + var update = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); update.NodeId.ShouldBe("eq-1/speed-rpm"); update.Value.ShouldBe(42.0); update.StatusCode.ShouldBe(OpcUaStatus.Good); @@ -83,7 +83,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult("vt-1", 1.0, DateTime.UtcNow, CorrelationId.NewId())); - var update = publish.ExpectMsg(); + var update = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); update.NodeId.ShouldBe("eq-1/metrics/speed-rpm"); } @@ -101,7 +101,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult("vt-1", 99.0, DateTime.UtcNow, CorrelationId.NewId())); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// lmxopcua#787: a disabled plan is never spawned (no mux registration, so nothing to evaluate) and @@ -116,18 +116,18 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase var before = DateTime.UtcNow; host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { Plan("vt-1", "eq-1", "speed-rpm") with { Enabled = false } })); - var update = publish.ExpectMsg(); + var update = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); update.NodeId.ShouldBe("eq-1/speed-rpm"); update.Value.ShouldBeNull(); update.StatusCode.ShouldBe(OpcUaStatus.BadOutOfService); update.Realm.ShouldBe(AddressSpaceRealm.Uns); update.TimestampUtc.ShouldBeGreaterThanOrEqualTo(before); // lmxopcua#791: the same status is routed to the tag's dependents through the mux... - var routed = mux.ExpectMsg(); + var routed = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); routed.TagRef.ShouldBe("eq-1/speed-rpm"); routed.StatusCode.ShouldBe(OpcUaStatus.BadOutOfService); // ...but it is never evaluated: an enabled child would have registered its dependency refs with the mux. - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// lmxopcua#787: enabled -> disabled stops the live child (so it stops evaluating) and publishes @@ -141,18 +141,18 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase var plan = Plan("vt-1", "eq-1", "speed-rpm"); host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { plan })); - mux.ExpectMsg(); // enabled: child registered + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // enabled: child registered host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { plan with { Enabled = false } })); // The child's PostStop unregister and the host's routed BadOutOfService (lmxopcua#791) race to the mux. - var onDisable = mux.ReceiveN(2).Select(m => m.GetType()).ToList(); + var onDisable = mux.ReceiveN(2, TestContext.Current.CancellationToken).Select(m => m.GetType()).ToList(); onDisable.ShouldContain(typeof(DependencyMuxActor.UnregisterInterest)); // child stopped (PostStop) onDisable.ShouldContain(typeof(DependencyMuxActor.VirtualTagOutput)); publish.FishForMessage( - m => m.StatusCode == OpcUaStatus.BadOutOfService, TimeSpan.FromSeconds(5)); + m => m.StatusCode == OpcUaStatus.BadOutOfService, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { plan })); - mux.ExpectMsg(); // re-enabled: evaluation resumes + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // re-enabled: evaluation resumes } /// Unmapped result dropped: a result for an unknown vtagId is silently ignored. @@ -166,7 +166,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult("vt-unknown", 7.0, DateTime.UtcNow, CorrelationId.NewId())); - publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } /// @@ -184,18 +184,18 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // First apply — child self-registers; capture the child ref from the message sender. host.Tell(new VirtualTagHostActor.ApplyVirtualTags(plan)); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var firstChild = mux.LastSender; // Watch the child from the test side so we can await its death deterministically before // re-applying, avoiding any race between Terminated delivery to the host and the re-apply. Watch(firstChild); Sys.Stop(firstChild); - ExpectTerminated(firstChild); + ExpectTerminated(firstChild, cancellationToken: TestContext.Current.CancellationToken); // The dying child's PostStop sends UnregisterInterest to the mux — drain it so the mux probe // mailbox is clean before we look for the new RegisterInterest. - mux.ExpectMsg(TimeSpan.FromSeconds(5)); + mux.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // Re-apply with the same plan — host should see vt-1 absent from _children and spawn fresh. // The test saw the child die, but the host's own Terminated (which prunes _children) can land @@ -206,7 +206,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase { host.Tell(new VirtualTagHostActor.ApplyVirtualTags(plan)); reg2 = mux.ExpectMsg(TimeSpan.FromMilliseconds(500)); - }, TimeSpan.FromSeconds(10), TimeSpan.FromMilliseconds(50)); + }, TimeSpan.FromSeconds(10), TimeSpan.FromMilliseconds(50), TestContext.Current.CancellationToken); reg2!.TagRefs.ShouldContain("a"); // The new child must be a different actor ref than the one we killed. @@ -245,11 +245,11 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult("vt-1", 42.0, ts, CorrelationId.NewId())); // The publish still happens — historization is additive, not a replacement. - var update = publish.ExpectMsg(); + var update = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); update.NodeId.ShouldBe("eq-1/speed-rpm"); // Wait for the history record to land (delivered on the same actor turn as the publish). - AwaitAssert(() => writer.Calls.Count.ShouldBe(1)); + AwaitAssert(() => writer.Calls.Count.ShouldBe(1), cancellationToken: TestContext.Current.CancellationToken); writer.Calls.TryPeek(out var captured).ShouldBeTrue(); var (path, value) = captured; path.ShouldBe("eq-1/speed-rpm"); @@ -281,12 +281,12 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult( "vt-1", 42.0, ts, CorrelationId.NewId(), badNoCommunication)); - var update = publish.ExpectMsg(); + var update = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); update.NodeId.ShouldBe("eq-1/speed-rpm"); update.StatusCode.ShouldBe(badNoCommunication); update.Value.ShouldBe(42.0, "the last known value rides out under the Bad status"); - AwaitAssert(() => writer.Calls.Count.ShouldBe(1)); + AwaitAssert(() => writer.Calls.Count.ShouldBe(1), cancellationToken: TestContext.Current.CancellationToken); writer.Calls.TryPeek(out var captured).ShouldBeTrue(); captured.Item2.StatusCode.ShouldBe(badNoCommunication); } @@ -307,7 +307,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // The value is still published — and the publish completing means OnResult's turn is done, // so the historian (which would have been called on the same turn) was never touched. - publish.ExpectMsg(); + publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); writer.Calls.IsEmpty.ShouldBeTrue(); } @@ -339,7 +339,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // First apply — child self-registers interest in "A". host.Tell(new VirtualTagHostActor.ApplyVirtualTags( new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "ctx.GetTag(\"A\")", "A") })); - var reg1 = mux.ExpectMsg(); + var reg1 = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); reg1.TagRefs.ShouldContain("A"); var firstChild = mux.LastSender; @@ -362,7 +362,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase var sawUnregister = false; for (var i = 0; i < 2; i++) { - var msg = mux.ReceiveOne(TimeSpan.FromSeconds(5)); + var msg = mux.ReceiveOne(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); switch (msg) { case DependencyMuxActor.RegisterInterest r: @@ -400,7 +400,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // First apply — exactly one RegisterInterest. host.Tell(new VirtualTagHostActor.ApplyVirtualTags(plan)); - var reg = mux.ExpectMsg(); + var reg = mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); reg.TagRefs.ShouldContain("A"); // Re-apply an identical plan (fresh list instances, but value-equal) — no churn expected. @@ -408,7 +408,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "ctx.GetTag(\"A\")", "A") })); // No second Register and no Unregister: the child was left in place. - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); } /// @@ -427,19 +427,19 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // First apply — child registers interest in "A". host.Tell(new VirtualTagHostActor.ApplyVirtualTags( new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "ctx.GetTag(\"A\")", "A") })); - mux.ExpectMsg().TagRefs.ShouldContain("A"); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).TagRefs.ShouldContain("A"); var firstChild = mux.LastSender; // Kill the child deterministically and drain its PostStop UnregisterInterest. Watch(firstChild); Sys.Stop(firstChild); - ExpectTerminated(firstChild); - mux.ExpectMsg(TimeSpan.FromSeconds(5)); + ExpectTerminated(firstChild, cancellationToken: TestContext.Current.CancellationToken); + mux.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); // Re-apply with a CHANGED plan (refs "B"). The replacement must register the NEW refs. host.Tell(new VirtualTagHostActor.ApplyVirtualTags( new[] { PlanWithRefs("vt-1", "eq-1", "speed-rpm", "ctx.GetTag(\"B\")", "B") })); - var reg2 = mux.ExpectMsg(TimeSpan.FromSeconds(5)); + var reg2 = mux.ExpectMsg(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); reg2.TagRefs.ShouldContain("B"); reg2.TagRefs.ShouldNotContain("A"); mux.LastSender.ShouldNotBe(firstChild); @@ -486,7 +486,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // Sync point: a result for the still-mapped vt-1 bridges to a publish; when it lands, every // apply above has been processed. host.Tell(new VirtualTagActor.EvaluationResult("vt-1", 1.0, DateTime.UtcNow, CorrelationId.NewId())); - publish.ExpectMsg(); + publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // Exactly two clears: the first generation and the E1→E2 edit. The identical redeploy and the // shared-expression add/remove did not clear. @@ -514,7 +514,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // The child self-registers with the mux in PreStart — capture its ref from the sender so we can // drive it directly through the real evaluate → parent-Tell → bridge path. - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var child = mux.LastSender; var ts1 = new DateTime(2026, 7, 12, 12, 0, 0, DateTimeKind.Utc); @@ -522,7 +522,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // First dep change → evaluator Ok(42) → Good publish. child.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, ts1)); - var good = publish.ExpectMsg(); + var good = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); good.StatusCode.ShouldBe(OpcUaStatus.Good); good.Value.ShouldBe(42.0); @@ -530,7 +530,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // #519: the child names the reason (BadInternalError) instead of a bare Bad, and the host bridges // it verbatim — so this asserts the sub-code a client would actually read. child.Tell(new VirtualTagActor.DependencyValueChanged("a", 2, ts2)); - var bad = publish.ExpectMsg(); + var bad = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); bad.StatusCode.ShouldBe(OpcUaStatus.BadInternalError); bad.Value.ShouldBe(42.0); bad.TimestampUtc.ShouldBe(ts2); @@ -551,7 +551,7 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult( "vt-1", 42.0, ts, CorrelationId.NewId(), OpcUaStatus.Bad)); - var update = publish.ExpectMsg(); + var update = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); update.StatusCode.ShouldBe(OpcUaStatus.Bad); update.Value.ShouldBe(42.0); } @@ -575,8 +575,8 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase host.Tell(new VirtualTagActor.EvaluationResult( "vt-1", 42.0, ts, CorrelationId.NewId(), OpcUaStatus.BadInternalError)); - publish.ExpectMsg(); - AwaitAssert(() => writer.Calls.Count.ShouldBe(1)); + publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => writer.Calls.Count.ShouldBe(1), cancellationToken: TestContext.Current.CancellationToken); writer.Calls.TryPeek(out var captured).ShouldBeTrue(); captured.Value.StatusCode.ShouldBe(OpcUaStatus.BadInternalError); } @@ -602,29 +602,29 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase // Capture the spawned child from the RegisterInterest sender so we can drive a dependency change // through the real evaluate → parent-Tell → bridge path. - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var child = mux.LastSender; // First dependency change → Ok(7) → Good publish; the child now holds 7 as its dedup'd last value. child.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - var first = publish.ExpectMsg(); + var first = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); first.Value.ShouldBe(7.0); first.StatusCode.ShouldBe(OpcUaStatus.Good); // lmxopcua#791: the real result is also routed to dependents through the mux. - mux.ExpectMsg().Value.ShouldBe(7.0); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken).Value.ShouldBe(7.0); // The redeploy re-materialises the node to BadWaitingForInitialData and re-applies the UNCHANGED // plan — the child survives with its dedup state. The fix re-asserts the last value; without it the // node would stay Bad (an unchanged recompute of 7 is dedup-suppressed) and this ExpectMsg times out. host.Tell(new VirtualTagHostActor.ApplyVirtualTags(plan)); - var reasserted = publish.ExpectMsg(); + var reasserted = publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); reasserted.NodeId.ShouldBe("eq-1/speed-rpm"); reasserted.Value.ShouldBe(7.0); reasserted.StatusCode.ShouldBe(OpcUaStatus.Good); // No child churn on the unchanged redeploy — the re-assert must not respawn or re-register — and the // re-assert is not routed to the mux either (lmxopcua#791: the mux already holds that state). - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); } /// @@ -645,17 +645,17 @@ public sealed class VirtualTagHostActorTests : RuntimeActorTestBase var plan = new[] { PlanH("vt-1", "eq-1", "speed-rpm", historize: true) }; host.Tell(new VirtualTagHostActor.ApplyVirtualTags(plan)); - mux.ExpectMsg(); + mux.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); var child = mux.LastSender; // First real evaluation → Good publish + exactly one historian record. child.Tell(new VirtualTagActor.DependencyValueChanged("a", 1, DateTime.UtcNow)); - publish.ExpectMsg(); - AwaitAssert(() => writer.Calls.Count.ShouldBe(1)); + publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); + AwaitAssert(() => writer.Calls.Count.ShouldBe(1), cancellationToken: TestContext.Current.CancellationToken); // Redeploy (unchanged plan) → the survivor re-asserts: a SECOND publish repairs the reset node. host.Tell(new VirtualTagHostActor.ApplyVirtualTags(plan)); - publish.ExpectMsg(); + publish.ExpectMsg(cancellationToken: TestContext.Current.CancellationToken); // The publish landing means the reassert's OnResult turn is complete (Record runs on the same turn, // right after the Tell) — so a second Record would already be visible. It must NOT be: still 1. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagOutputRoutingTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagOutputRoutingTests.cs index 3df7fa433..17dad0c9f 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagOutputRoutingTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/VirtualTags/VirtualTagOutputRoutingTests.cs @@ -37,7 +37,7 @@ public sealed class VirtualTagOutputRoutingTests : RuntimeActorTestBase mux.Tell(new DependencyMuxActor.VirtualTagOutput("EQ-1/A", 10d, DateTime.UtcNow, OpcUaStatus.Good)); - var item = sub.ExpectMsg(PresenceBudget).Items.ShouldHaveSingleItem(); + var item = sub.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.ShouldHaveSingleItem(); item.TagId.ShouldBe("EQ-1/A"); item.Value.ShouldBe(10d); item.StatusCode.ShouldBe(OpcUaStatus.Good); @@ -54,7 +54,7 @@ public sealed class VirtualTagOutputRoutingTests : RuntimeActorTestBase var late = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "EQ-1/A" }, late.Ref, ReplayRetained: true)); - late.ExpectMsg(PresenceBudget).Items.ShouldHaveSingleItem() + late.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.ShouldHaveSingleItem() .StatusCode.ShouldBe(OpcUaStatus.BadOutOfService); } @@ -67,25 +67,25 @@ public sealed class VirtualTagOutputRoutingTests : RuntimeActorTestBase var sub = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "EQ-1/A" }, sub.Ref, ReplayRetained: true)); mux.Tell(new DependencyMuxActor.VirtualTagOutput("EQ-1/A", 1d, DateTime.UtcNow, OpcUaStatus.Good)); - sub.ExpectMsg(PresenceBudget); + sub.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); mux.Tell(new DependencyMuxActor.UnregisterInterest(sub.Ref)); mux.Tell(new DependencyMuxActor.SourceRemoved("drv-1")); // Several grace periods: a non-pinned value would have been swept by now. - ExpectNoMsg(TimeSpan.FromMilliseconds(400)); + ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken); mux.UnderlyingActor.RetainedRefCount.ShouldBe(1); var again = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "EQ-1/A" }, again.Ref, ReplayRetained: true)); - again.ExpectMsg(PresenceBudget).Items.ShouldHaveSingleItem().Value.ShouldBe(1d); + again.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Items.ShouldHaveSingleItem().Value.ShouldBe(1d); mux.Tell(new DependencyMuxActor.UnregisterInterest(again.Ref)); mux.Tell(new DependencyMuxActor.VirtualTagOutputsRemoved(new[] { "EQ-1/A" })); - AwaitCondition(() => mux.UnderlyingActor.TrackedRefCount == 0, PresenceBudget); + AwaitCondition(() => mux.UnderlyingActor.TrackedRefCount == 0, PresenceBudget, TestContext.Current.CancellationToken); var after = CreateTestProbe(); mux.Tell(new DependencyMuxActor.RegisterInterest(new[] { "EQ-1/A" }, after.Ref, ReplayRetained: true)); - after.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + after.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } // ---- host half ------------------------------------------------------------------------------------------ @@ -97,18 +97,18 @@ public sealed class VirtualTagOutputRoutingTests : RuntimeActorTestBase var mux = CreateTestProbe(); var host = Sys.ActorOf(VirtualTagHostActor.Props(publish.Ref, mux.Ref, new InertEvaluator())); host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { Plan("vt-a", "A", "drv/x") })); - mux.ExpectMsg(PresenceBudget); + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Result("vt-a", 10d)); - var output = mux.ExpectMsg(PresenceBudget); + var output = mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); output.TagRef.ShouldBe("EQ-1/A"); - publish.ExpectMsg(PresenceBudget).NodeId.ShouldBe(output.TagRef, + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).NodeId.ShouldBe(output.TagRef, "the mux key is the published NodeId — one identity for the node and its dependents"); output.Value.ShouldBe(10d); host.Tell(Result("vt-a", 10d, reassert: true)); - publish.ExpectMsg(PresenceBudget); // positive control: it was handled - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + publish.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // positive control: it was handled + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); } [Fact] @@ -119,13 +119,13 @@ public sealed class VirtualTagOutputRoutingTests : RuntimeActorTestBase var host = Sys.ActorOf(VirtualTagHostActor.Props(publish.Ref, mux.Ref, new InertEvaluator())); host.Tell(new VirtualTagHostActor.ApplyVirtualTags(new[] { Plan("vt-a", "A", "drv/x") with { Enabled = false } })); - var output = mux.ExpectMsg(PresenceBudget); + var output = mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); output.TagRef.ShouldBe("EQ-1/A"); output.StatusCode.ShouldBe(OpcUaStatus.BadOutOfService); output.Value.ShouldBeNull(); host.Tell(new VirtualTagHostActor.ApplyVirtualTags(Array.Empty())); - mux.ExpectMsg(PresenceBudget).TagRefs.ShouldBe(new[] { "EQ-1/A" }); + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).TagRefs.ShouldBe(new[] { "EQ-1/A" }); } /// Runtime backstop: an A↔B cycle that reached the host anyway is not fed back into itself, while a @@ -143,17 +143,17 @@ public sealed class VirtualTagOutputRoutingTests : RuntimeActorTestBase Plan("vt-s", "S", "EQ-1/S"), // self-dependency Plan("vt-c", "C", "EQ-1/A"), // reads the cycle, is not on it })); - for (var i = 0; i < 4; i++) mux.ExpectMsg(PresenceBudget); + for (var i = 0; i < 4; i++) mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); host.Tell(Result("vt-a", 1d)); host.Tell(Result("vt-b", 2d)); host.Tell(Result("vt-s", 3d)); host.Tell(Result("vt-c", 4d)); - mux.ExpectMsg(PresenceBudget).TagRef.ShouldBe("EQ-1/C"); - mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300)); + mux.ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).TagRef.ShouldBe("EQ-1/C"); + mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); // Still published: a cycle member keeps its node, it just does not feed the loop. - publish.ReceiveN(4, PresenceBudget); + publish.ReceiveN(4, PresenceBudget, TestContext.Current.CancellationToken); } private sealed class InertEvaluator : IVirtualTagEvaluator diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj index a77b3fa7f..098bb3c12 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ZB.MOM.WW.OtOpcUa.Runtime.Tests.csproj @@ -7,11 +7,10 @@ - - + - + all