test: pass TestContext.Current.CancellationToken at xUnit1051 call sites (analyzer code fix, 451 sites) (lmxopcua#725)
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -21,9 +21,9 @@ public class AlarmsCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -46,9 +46,9 @@ public class AlarmsCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -71,9 +71,9 @@ public class AlarmsCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -91,13 +91,13 @@ public class AlarmsCommandTests
|
||||
var command = new AlarmsCommand(factory) { Url = "opc.tcp://localhost:4840" };
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
fakeService.RaiseAlarmEvent(new Client.Shared.Models.AlarmEventArgs(
|
||||
"src", "cond", 500, "msg", true, true, false, DateTime.UtcNow,
|
||||
eventId: [0xAB, 0x01, 0xFF], conditionNodeId: "ns=2;s=Plant/cond"));
|
||||
await Task.Delay(500);
|
||||
await Task.Delay(500, TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -123,9 +123,9 @@ public class AlarmsCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -146,9 +146,9 @@ public class AlarmsCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -168,9 +168,9 @@ public class AlarmsCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ public class EventHandlerLifecycleTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => await command.ExecuteAsync(console));
|
||||
var task = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SubscribeCommandSummaryTests
|
||||
};
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
var runTask = Task.Run(async () => await command.ExecuteAsync(console));
|
||||
var runTask = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
|
||||
|
||||
// Wait until the subscription is registered by the fake.
|
||||
await WaitForAsync(() => fakeService.SubscribeCalls.Count == 1);
|
||||
@@ -86,7 +86,7 @@ public class SubscribeCommandSummaryTests
|
||||
};
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
var runTask = Task.Run(async () => await command.ExecuteAsync(console));
|
||||
var runTask = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
|
||||
|
||||
await WaitForAsync(() => fakeService.SubscribeCalls.Count == 1);
|
||||
|
||||
@@ -143,7 +143,7 @@ public class SubscribeCommandSummaryTests
|
||||
};
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
var runTask = Task.Run(async () => await command.ExecuteAsync(console));
|
||||
var runTask = Task.Run(async () => await command.ExecuteAsync(console), TestContext.Current.CancellationToken);
|
||||
|
||||
await WaitForAsync(() => fakeService.SubscribeCalls.Count == 1);
|
||||
fakeService.RaiseDataChanged(
|
||||
@@ -180,7 +180,7 @@ public class SubscribeCommandSummaryTests
|
||||
await command.ExecuteAsync(console);
|
||||
|
||||
File.Exists(tempFile).ShouldBeTrue();
|
||||
var contents = await File.ReadAllTextAsync(tempFile);
|
||||
var contents = await File.ReadAllTextAsync(tempFile, TestContext.Current.CancellationToken);
|
||||
contents.ShouldContain("SUMMARY");
|
||||
contents.ShouldContain("Total subscribed: 1");
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ public class SubscribeCommandTests
|
||||
|
||||
// The subscribe command waits for cancellation. We need to cancel it.
|
||||
// Use the console's cancellation to trigger stop.
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
// Wait until the command has actually subscribed (deterministic — no fixed-sleep race), then cancel
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -50,9 +50,9 @@ public class SubscribeCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -73,9 +73,9 @@ public class SubscribeCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
@@ -98,9 +98,9 @@ public class SubscribeCommandTests
|
||||
|
||||
using var console = TestConsoleHelper.CreateConsole();
|
||||
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); });
|
||||
var task = Task.Run(async () => { await command.ExecuteAsync(console); }, TestContext.Current.CancellationToken);
|
||||
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
console.RequestCancellation();
|
||||
await task;
|
||||
|
||||
|
||||
@@ -58,12 +58,12 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
_factory.EnqueueSession(userSession); // dequeued by the user's Connect (the failover is still held)
|
||||
_factory.EnqueueSession(failoverSession); // dequeued by the failover once released
|
||||
|
||||
await _service.DisconnectAsync();
|
||||
await _service.ConnectAsync(Settings());
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
|
||||
lock (_states) _states.Clear();
|
||||
|
||||
gate.SetResult();
|
||||
await failover.WaitAsync(Wait);
|
||||
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
|
||||
|
||||
failoverSession.Disposed.ShouldBeTrue();
|
||||
userSession.Disposed.ShouldBeFalse();
|
||||
@@ -79,14 +79,14 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
var (_, failover, gate) = await StartHeldFailoverAsync();
|
||||
_factory.EnqueueSession(new FakeSessionAdapter());
|
||||
|
||||
await _service.DisconnectAsync();
|
||||
await _service.ConnectAsync(Settings());
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
|
||||
lock (_states) _states.Clear();
|
||||
var createsBefore = _factory.CreateCallCount;
|
||||
|
||||
_factory.ThrowOnCreate = true;
|
||||
gate.SetResult();
|
||||
await failover.WaitAsync(Wait);
|
||||
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
|
||||
|
||||
_service.IsConnected.ShouldBeTrue();
|
||||
lock (_states) _states.ShouldBeEmpty();
|
||||
@@ -94,8 +94,8 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
_factory.CreateCallCount.ShouldBe(createsBefore);
|
||||
// And the user can still disconnect / reconnect normally afterwards.
|
||||
_factory.ThrowOnCreate = false;
|
||||
await _service.DisconnectAsync();
|
||||
await _service.ConnectAsync(Settings());
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
|
||||
_service.IsConnected.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
_factory.EnqueueSession(new FakeSessionAdapter());
|
||||
|
||||
gate.SetResult();
|
||||
await failover.WaitAsync(Wait);
|
||||
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
|
||||
|
||||
_service.IsConnected.ShouldBeTrue();
|
||||
_service.CurrentConnectionInfo!.EndpointUrl.ShouldBe(Backup);
|
||||
@@ -123,15 +123,15 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
{
|
||||
var s1 = new FakeSessionAdapter();
|
||||
_factory.EnqueueSession(s1);
|
||||
await _service.ConnectAsync(Settings());
|
||||
await _service.DisconnectAsync();
|
||||
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
_factory.EnqueueSession(new FakeSessionAdapter());
|
||||
await _service.ConnectAsync(Settings());
|
||||
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
|
||||
var creates = _factory.CreateCallCount;
|
||||
lock (_states) _states.Clear();
|
||||
|
||||
s1.SimulateKeepAlive(false);
|
||||
if (_service.LastFailover is { } t) await t.WaitAsync(Wait);
|
||||
if (_service.LastFailover is { } t) await t.WaitAsync(Wait, TestContext.Current.CancellationToken);
|
||||
|
||||
_factory.CreateCallCount.ShouldBe(creates);
|
||||
lock (_states) _states.ShouldBeEmpty();
|
||||
@@ -150,7 +150,7 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
failover.IsCompleted.ShouldBeFalse();
|
||||
_factory.EnqueueSession(new FakeSessionAdapter());
|
||||
gate.SetResult();
|
||||
await failover.WaitAsync(Wait);
|
||||
await failover.WaitAsync(Wait, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -163,15 +163,15 @@ public sealed class FailoverEpochTests : IDisposable
|
||||
{
|
||||
var sub = new FakeSubscriptionAdapter();
|
||||
_factory.EnqueueSession(new FakeSessionAdapter { NextSubscription = sub });
|
||||
await _service.ConnectAsync(Settings());
|
||||
await _service.ConnectAsync(Settings(), TestContext.Current.CancellationToken);
|
||||
var node = new Opc.Ua.NodeId("ns=2;s=X");
|
||||
await _service.SubscribeAsync(node);
|
||||
await _service.SubscribeAsync(node, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
sub.ThrowOnRemove = true;
|
||||
await Should.ThrowAsync<Opc.Ua.ServiceResultException>(() => _service.UnsubscribeAsync(node));
|
||||
sub.ThrowOnRemove = false;
|
||||
|
||||
await _service.SubscribeAsync(node);
|
||||
await _service.SubscribeAsync(node, ct: TestContext.Current.CancellationToken);
|
||||
sub.AddDataChangeCount.ShouldBe(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task ConnectAsync_ValidSettings_ReturnsConnectionInfo()
|
||||
{
|
||||
var info = await _service.ConnectAsync(ValidSettings());
|
||||
var info = await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
info.ShouldNotBeNull();
|
||||
info.EndpointUrl.ShouldBe("opc.tcp://localhost:4840");
|
||||
@@ -84,7 +84,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
|
||||
var info = await _service.ConnectAsync(ValidSettings());
|
||||
var info = await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
info.ServerName.ShouldBe("MyServer");
|
||||
info.SecurityMode.ShouldBe("Sign");
|
||||
@@ -102,7 +102,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var events = new List<ConnectionStateChangedEventArgs>();
|
||||
_service.ConnectionStateChanged += (_, e) => events.Add(e);
|
||||
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
events.Count.ShouldBe(2);
|
||||
events[0].OldState.ShouldBe(ConnectionState.Disconnected);
|
||||
@@ -137,7 +137,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
settings.Username = "admin";
|
||||
settings.Password = "secret";
|
||||
|
||||
await _service.ConnectAsync(settings);
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
|
||||
_configFactory.LastSettings!.Username.ShouldBe("admin");
|
||||
_configFactory.LastSettings!.Password.ShouldBe("secret");
|
||||
@@ -151,10 +151,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task DisconnectAsync_WhenConnected_ClosesSession()
|
||||
{
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
var session = _sessionFactory.CreatedSessions[0];
|
||||
|
||||
await _service.DisconnectAsync();
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
session.Closed.ShouldBeTrue();
|
||||
_service.IsConnected.ShouldBeFalse();
|
||||
@@ -167,7 +167,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task DisconnectAsync_WhenNotConnected_IsIdempotent()
|
||||
{
|
||||
await _service.DisconnectAsync(); // Should not throw
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken); // Should not throw
|
||||
_service.IsConnected.ShouldBeFalse();
|
||||
}
|
||||
|
||||
@@ -177,9 +177,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task DisconnectAsync_CalledTwice_IsIdempotent()
|
||||
{
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.DisconnectAsync();
|
||||
await _service.DisconnectAsync(); // Should not throw
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken); // Should not throw
|
||||
}
|
||||
|
||||
// --- Read tests ---
|
||||
@@ -195,9 +195,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
ReadResponse = new DataValue(new Variant(42), StatusCodes.Good)
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.ReadValueAsync(new NodeId("ns=2;s=MyNode"));
|
||||
var result = await _service.ReadValueAsync(new NodeId("ns=2;s=MyNode"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Value.ShouldBe(42);
|
||||
session.ReadCount.ShouldBe(1);
|
||||
@@ -221,7 +221,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter { ThrowOnRead = true };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await Should.ThrowAsync<ServiceResultException>(() =>
|
||||
_service.ReadValueAsync(new NodeId("ns=2;s=MyNode")));
|
||||
@@ -241,9 +241,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
WriteResponse = StatusCodes.Good
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42);
|
||||
var result = await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.WriteCount.ShouldBe(1);
|
||||
@@ -260,9 +260,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
ReadResponse = new DataValue(new Variant(0), StatusCodes.Good) // int type
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42");
|
||||
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42", TestContext.Current.CancellationToken);
|
||||
|
||||
session.WriteCount.ShouldBe(1);
|
||||
session.ReadCount.ShouldBe(1); // Read for type inference
|
||||
@@ -276,9 +276,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42);
|
||||
await _service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), 42, TestContext.Current.CancellationToken);
|
||||
|
||||
session.WriteCount.ShouldBe(1);
|
||||
session.ReadCount.ShouldBe(0); // No read for non-string values
|
||||
@@ -306,7 +306,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
ReadResponse = new DataValue(StatusCodes.BadNodeIdUnknown) // Bad status, null Value
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
|
||||
_service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42"));
|
||||
@@ -326,7 +326,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
ReadResponse = new DataValue(StatusCodes.BadNotReadable)
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var ex = await Should.ThrowAsync<InvalidOperationException>(() =>
|
||||
_service.WriteValueAsync(new NodeId("ns=2;s=MyNode"), "42"));
|
||||
@@ -355,9 +355,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
]
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _service.BrowseAsync();
|
||||
var results = await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(1);
|
||||
results[0].DisplayName.ShouldBe("Child1");
|
||||
@@ -376,9 +376,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
BrowseResponse = []
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.BrowseAsync();
|
||||
await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
session.BrowseCount.ShouldBe(1);
|
||||
}
|
||||
@@ -416,9 +416,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
]
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _service.BrowseAsync();
|
||||
var results = await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
results.Select(r => r.HasChildren).ShouldBe([true, true, false]);
|
||||
session.BrowseCount.ShouldBe(1);
|
||||
@@ -455,9 +455,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
BrowseNextContinuationPoint = null
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _service.BrowseAsync();
|
||||
var results = await _service.BrowseAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(2);
|
||||
session.BrowseNextCount.ShouldBe(1);
|
||||
@@ -482,9 +482,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500);
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500, TestContext.Current.CancellationToken);
|
||||
|
||||
session.CreatedSubscriptions.Count.ShouldBe(1);
|
||||
session.CreatedSubscriptions[0].AddDataChangeCount.ShouldBe(1);
|
||||
@@ -498,10 +498,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"));
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode")); // duplicate
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), ct: TestContext.Current.CancellationToken);
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), ct: TestContext.Current.CancellationToken); // duplicate
|
||||
|
||||
session.CreatedSubscriptions[0].AddDataChangeCount.ShouldBe(1);
|
||||
}
|
||||
@@ -515,12 +515,12 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
DataChangedEventArgs? received = null;
|
||||
_service.DataChanged += (_, e) => received = e;
|
||||
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500);
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), 500, TestContext.Current.CancellationToken);
|
||||
|
||||
// Simulate data change
|
||||
var handle = fakeSub.ActiveHandles.First();
|
||||
@@ -540,10 +540,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"));
|
||||
await _service.UnsubscribeAsync(new NodeId("ns=2;s=MyNode"));
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=MyNode"), ct: TestContext.Current.CancellationToken);
|
||||
await _service.UnsubscribeAsync(new NodeId("ns=2;s=MyNode"), TestContext.Current.CancellationToken);
|
||||
|
||||
fakeSub.RemoveCount.ShouldBe(1);
|
||||
}
|
||||
@@ -556,9 +556,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.UnsubscribeAsync(new NodeId("ns=2;s=NotSubscribed"));
|
||||
await _service.UnsubscribeAsync(new NodeId("ns=2;s=NotSubscribed"), TestContext.Current.CancellationToken);
|
||||
// Should not throw
|
||||
}
|
||||
|
||||
@@ -582,9 +582,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
session.CreatedSubscriptions.Count.ShouldBe(1);
|
||||
session.CreatedSubscriptions[0].AddEventCount.ShouldBe(1);
|
||||
@@ -598,10 +598,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(); // duplicate
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken); // duplicate
|
||||
|
||||
session.CreatedSubscriptions.Count.ShouldBe(1);
|
||||
}
|
||||
@@ -615,12 +615,12 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
AlarmEventArgs? received = null;
|
||||
_service.AlarmEvent += (_, e) => received = e;
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Simulate alarm event with proper field count
|
||||
var handle = fakeSub.ActiveHandles.First();
|
||||
@@ -663,10 +663,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.UnsubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
await _service.UnsubscribeAlarmsAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
fakeSub.Deleted.ShouldBeTrue();
|
||||
}
|
||||
@@ -679,9 +679,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.UnsubscribeAlarmsAsync(); // Should not throw
|
||||
await _service.UnsubscribeAlarmsAsync(TestContext.Current.CancellationToken); // Should not throw
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -693,10 +693,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.RequestConditionRefreshAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
await _service.RequestConditionRefreshAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
fakeSub.ConditionRefreshCalled.ShouldBeTrue();
|
||||
}
|
||||
@@ -709,7 +709,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await Should.ThrowAsync<InvalidOperationException>(() =>
|
||||
_service.RequestConditionRefreshAsync());
|
||||
@@ -740,10 +740,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
};
|
||||
var session = new FakeSessionAdapter { HistoryReadRawResponse = expectedValues };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _service.HistoryReadRawAsync(
|
||||
new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow);
|
||||
var results = await _service.HistoryReadRawAsync(new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(2);
|
||||
session.HistoryReadRawCount.ShouldBe(1);
|
||||
@@ -767,7 +766,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter { ThrowOnHistoryReadRaw = true };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await Should.ThrowAsync<ServiceResultException>(() =>
|
||||
_service.HistoryReadRawAsync(new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow));
|
||||
@@ -785,11 +784,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
};
|
||||
var session = new FakeSessionAdapter { HistoryReadAggregateResponse = expectedValues };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var results = await _service.HistoryReadAggregateAsync(
|
||||
new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow,
|
||||
AggregateType.Average);
|
||||
var results = await _service.HistoryReadAggregateAsync(new NodeId("ns=2;s=Temp"), DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, AggregateType.Average, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(1);
|
||||
session.HistoryReadAggregateCount.ShouldBe(1);
|
||||
@@ -815,7 +812,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter { ThrowOnHistoryReadAggregate = true };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await Should.ThrowAsync<ServiceResultException>(() =>
|
||||
_service.HistoryReadAggregateAsync(
|
||||
@@ -847,9 +844,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
}
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var info = await _service.GetRedundancyInfoAsync();
|
||||
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
info.Mode.ShouldBe("Warm");
|
||||
info.ServiceLevel.ShouldBe((byte)200);
|
||||
@@ -877,9 +874,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
}
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var info = await _service.GetRedundancyInfoAsync();
|
||||
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
info.Mode.ShouldBe("None");
|
||||
info.ServiceLevel.ShouldBe((byte)100);
|
||||
@@ -917,9 +914,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
}
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var info = await _service.GetRedundancyInfoAsync();
|
||||
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
info.Mode.ShouldBe("Warm");
|
||||
info.ServiceLevel.ShouldBe((byte)200);
|
||||
@@ -944,9 +941,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
}
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var info = await _service.GetRedundancyInfoAsync();
|
||||
var info = await _service.GetRedundancyInfoAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
info.Mode.ShouldBe("None");
|
||||
info.ServiceLevel.ShouldBe((byte)0);
|
||||
@@ -964,12 +961,12 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
AlarmEventArgs? received = null;
|
||||
_service.AlarmEvent += (_, e) => received = e;
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
var handle = fakeSub.ActiveHandles.First();
|
||||
// Only 3 fields — EventId, EventType, SourceName — fewer than the old < 6 threshold
|
||||
@@ -998,12 +995,12 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var eventCount = 0;
|
||||
_service.AlarmEvent += (_, _) => eventCount++;
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
var handle = fakeSub.ActiveHandles.First();
|
||||
fakeSub.SimulateEvent(handle, new EventFieldList { EventFields = [] });
|
||||
@@ -1022,9 +1019,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked");
|
||||
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked", TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1045,9 +1042,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
StatusCodes.BadConditionAlreadyEnabled, "already acked")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked");
|
||||
var result = await _service.AcknowledgeAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "acked", TestContext.Current.CancellationToken);
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
|
||||
@@ -1062,10 +1059,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.AcknowledgeAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "acked");
|
||||
await _service.ConfirmAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "confirmed");
|
||||
await _service.AcknowledgeAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "acked", TestContext.Current.CancellationToken);
|
||||
await _service.ConfirmAlarmAsync("ns=2;s=Plant/Alarm1", new byte[] { 1, 2 }, "confirmed", TestContext.Current.CancellationToken);
|
||||
|
||||
session.CallMethodObjectIds.ShouldAllBe(id => id == NodeId.Parse("ns=2;s=Plant/Alarm1"));
|
||||
session.CallMethodMethodIds[0].ShouldBe(MethodIds.AcknowledgeableConditionType_Acknowledge);
|
||||
@@ -1083,9 +1080,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed");
|
||||
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed", TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1104,9 +1101,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
StatusCodes.BadConditionAlreadyEnabled, "already confirmed")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed");
|
||||
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed", TestContext.Current.CancellationToken);
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
|
||||
@@ -1122,9 +1119,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot);
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1142,10 +1139,10 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
const double durationSeconds = 30.0;
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Timed, durationSeconds);
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Timed, durationSeconds, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1161,9 +1158,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Unshelve);
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Unshelve, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1185,9 +1182,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
StatusCodes.BadConditionAlreadyShelved, "already shelved")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot);
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyShelved);
|
||||
@@ -1204,9 +1201,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.EnableAsync("ns=2;s=Cond");
|
||||
var result = await _service.EnableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1224,9 +1221,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.DisableAsync("ns=2;s=Cond");
|
||||
var result = await _service.DisableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
@@ -1248,9 +1245,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
StatusCodes.BadConditionAlreadyEnabled, "already enabled")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.EnableAsync("ns=2;s=Cond");
|
||||
var result = await _service.EnableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
|
||||
@@ -1269,9 +1266,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
StatusCodes.BadConditionAlreadyDisabled, "already disabled")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await _service.DisableAsync("ns=2;s=Cond");
|
||||
var result = await _service.DisableAsync("ns=2;s=Cond", TestContext.Current.CancellationToken);
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyDisabled);
|
||||
@@ -1290,12 +1287,12 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
AlarmEventArgs? received = null;
|
||||
_service.AlarmEvent += (_, e) => received = e;
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
var handle = fakeSub.ActiveHandles.First();
|
||||
var fields = new EventFieldList
|
||||
@@ -1348,13 +1345,13 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var stateChanges = new List<ConnectionStateChangedEventArgs>();
|
||||
_service.ConnectionStateChanged += (_, e) => stateChanges.Add(e);
|
||||
|
||||
await _service.ConnectAsync(settings);
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
|
||||
// Simulate keep-alive failure
|
||||
session1.SimulateKeepAlive(false);
|
||||
|
||||
// Give async failover time to complete
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
// Should have reconnected
|
||||
stateChanges.ShouldContain(e => e.NewState == ConnectionState.Reconnecting);
|
||||
@@ -1380,9 +1377,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var settings = ValidSettings("opc.tcp://primary:4840");
|
||||
settings.FailoverUrls = ["opc.tcp://backup:4840"];
|
||||
|
||||
await _service.ConnectAsync(settings);
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
session1.SimulateKeepAlive(false);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
_service.CurrentConnectionInfo!.EndpointUrl.ShouldBe("opc.tcp://backup:4840");
|
||||
_service.CurrentConnectionInfo.ServerName.ShouldBe("BackupServer");
|
||||
@@ -1397,12 +1394,12 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var session1 = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session1);
|
||||
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
// After the first session, make factory fail
|
||||
_sessionFactory.ThrowOnCreate = true;
|
||||
session1.SimulateKeepAlive(false);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
_service.IsConnected.ShouldBeFalse();
|
||||
}
|
||||
@@ -1423,7 +1420,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var settings = ValidSettings("opc.tcp://primary:4840");
|
||||
settings.FailoverUrls = ["opc.tcp://backup:4840"];
|
||||
|
||||
await _service.ConnectAsync(settings);
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
var createCountAfterConnect = _sessionFactory.CreateCallCount; // 1
|
||||
|
||||
// Hold the failover's session creation open so it stays in-flight.
|
||||
@@ -1439,7 +1436,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
|
||||
// Release the gate so the in-flight failover completes.
|
||||
gate.SetResult();
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
// Exactly one extra session created by the single failover loop.
|
||||
_sessionFactory.CreateCallCount.ShouldBe(createCountAfterConnect + 1);
|
||||
@@ -1455,7 +1452,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session1 = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session1);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await Should.ThrowAsync<InvalidOperationException>(() => _service.ConnectAsync(ValidSettings()));
|
||||
|
||||
@@ -1463,8 +1460,8 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
session1.Disposed.ShouldBeFalse();
|
||||
_service.IsConnected.ShouldBeTrue();
|
||||
|
||||
await _service.DisconnectAsync();
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
_sessionFactory.CreateCallCount.ShouldBe(2);
|
||||
_service.IsConnected.ShouldBeTrue();
|
||||
}
|
||||
@@ -1484,26 +1481,26 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
_sessionFactory.EnqueueSession(session2);
|
||||
var settings = ValidSettings("opc.tcp://primary:4840");
|
||||
settings.FailoverUrls = ["opc.tcp://backup:4840"];
|
||||
await _service.ConnectAsync(settings);
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
|
||||
var gate = new TaskCompletionSource();
|
||||
session1.CreateSubscriptionGate = gate;
|
||||
var subscribe = _service.SubscribeAsync(new NodeId("ns=2;s=Racer"), 1000);
|
||||
await session1.CreateSubscriptionEntered.Task.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
var subscribe = _service.SubscribeAsync(new NodeId("ns=2;s=Racer"), 1000, TestContext.Current.CancellationToken);
|
||||
await session1.CreateSubscriptionEntered.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
session1.SimulateKeepAlive(false);
|
||||
var failover = _service.LastFailover.ShouldNotBeNull();
|
||||
|
||||
gate.SetResult();
|
||||
await subscribe.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await failover.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await subscribe.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
await failover.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
_service.CurrentConnectionInfo!.EndpointUrl.ShouldBe("opc.tcp://backup:4840");
|
||||
var replayed = session2.CreatedSubscriptions.ShouldHaveSingleItem();
|
||||
replayed.AddDataChangeCount.ShouldBe(1);
|
||||
|
||||
// And it is the live subscription: unsubscribing removes it from session2's subscription, not session1's.
|
||||
await _service.UnsubscribeAsync(new NodeId("ns=2;s=Racer"));
|
||||
await _service.UnsubscribeAsync(new NodeId("ns=2;s=Racer"), TestContext.Current.CancellationToken);
|
||||
replayed.RemoveCount.ShouldBe(1);
|
||||
replayed.ActiveHandles.ShouldBeEmpty();
|
||||
}
|
||||
@@ -1522,16 +1519,16 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
_sessionFactory.EnqueueSession(session2);
|
||||
var settings = ValidSettings("opc.tcp://primary:4840");
|
||||
settings.FailoverUrls = ["opc.tcp://backup:4840"];
|
||||
await _service.ConnectAsync(settings);
|
||||
await _service.ConnectAsync(settings, TestContext.Current.CancellationToken);
|
||||
|
||||
var gate = new TaskCompletionSource();
|
||||
_sessionFactory.CreateGate = gate;
|
||||
session1.SimulateKeepAlive(false);
|
||||
var failover = _service.LastFailover.ShouldNotBeNull();
|
||||
|
||||
await _service.DisconnectAsync();
|
||||
await _service.DisconnectAsync(TestContext.Current.CancellationToken);
|
||||
gate.SetResult();
|
||||
await failover.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await failover.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
_service.IsConnected.ShouldBeFalse();
|
||||
session2.Disposed.ShouldBeTrue();
|
||||
@@ -1547,7 +1544,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
var tasks = new List<Task>();
|
||||
for (var i = 0; i < 50; i++)
|
||||
@@ -1574,7 +1571,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
_service.Dispose();
|
||||
|
||||
@@ -1645,14 +1642,14 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
// Second CreateSubscription call should return the alarm fake.
|
||||
// We wire it by overriding the factory queue after the first subscribe.
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
// Subscribe data (uses dataFakeSub from session.NextSubscription).
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=Node1"));
|
||||
await _service.SubscribeAsync(new NodeId("ns=2;s=Node1"), ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Now set NextSubscription on the session so SubscribeAlarmsAsync gets the alarm fake.
|
||||
session.NextSubscription = alarmFakeSub;
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
// Both subscriptions are now active. Disconnect with a cancellable token.
|
||||
using var cts = new CancellationTokenSource();
|
||||
@@ -1681,7 +1678,7 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
// Fire many concurrent SubscribeAlarmsAsync calls — only one should "win".
|
||||
var tasks = Enumerable.Range(0, 20).Select(_ =>
|
||||
@@ -1706,9 +1703,9 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
var fakeSub = new FakeSubscriptionAdapter();
|
||||
var session = new FakeSessionAdapter { NextSubscription = fakeSub };
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
await _service.ConnectAsync(ValidSettings(), TestContext.Current.CancellationToken);
|
||||
|
||||
await _service.SubscribeAlarmsAsync();
|
||||
await _service.SubscribeAlarmsAsync(ct: TestContext.Current.CancellationToken);
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
await _service.UnsubscribeAlarmsAsync(cts.Token);
|
||||
|
||||
@@ -106,7 +106,7 @@ public class BrowseTreeViewModelTests
|
||||
parent.IsExpanded = true;
|
||||
|
||||
// Allow async operation to complete
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
|
||||
_service.BrowseCallCount.ShouldBe(initialBrowseCount + 1);
|
||||
parent.Children.Count.ShouldBe(1);
|
||||
@@ -131,13 +131,13 @@ public class BrowseTreeViewModelTests
|
||||
|
||||
var parent = _vm.RootNodes[0];
|
||||
parent.IsExpanded = true;
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
|
||||
var browseCountAfterFirst = _service.BrowseCallCount;
|
||||
|
||||
parent.IsExpanded = false;
|
||||
parent.IsExpanded = true;
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
|
||||
_service.BrowseCallCount.ShouldBe(browseCountAfterFirst);
|
||||
}
|
||||
@@ -157,7 +157,7 @@ public class BrowseTreeViewModelTests
|
||||
|
||||
var parent = _vm.RootNodes[0];
|
||||
parent.IsExpanded = true;
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
|
||||
// After completion, IsLoading should be false
|
||||
parent.IsLoading.ShouldBeFalse();
|
||||
|
||||
@@ -67,9 +67,9 @@ public sealed class ClusterAuditQueryTests : IDisposable
|
||||
// Noise that must NOT appear: other cluster's structured row + an orphan NodeId.
|
||||
SeedAudit("Published", clusterId: null, nodeId: "OTHER-A", ts: t0.AddMinutes(3));
|
||||
SeedAudit("Published", clusterId: null, nodeId: "UNKNOWN-NODE", ts: t0.AddMinutes(4));
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 200);
|
||||
var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 200, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
rows.Select(r => r.EventType).ShouldBe(
|
||||
["NodeApplied", "DraftEdited", "Published"], // newest first
|
||||
@@ -83,9 +83,9 @@ public sealed class ClusterAuditQueryTests : IDisposable
|
||||
SeedNode("LINE3-OPCUA", "LINE3-OPCUA-A");
|
||||
var t0 = new DateTime(2026, 6, 2, 10, 0, 0, DateTimeKind.Utc);
|
||||
SeedAudit("Published", clusterId: "OTHER-CLUSTER", nodeId: null, ts: t0);
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 200);
|
||||
var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 200, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
rows.ShouldBeEmpty();
|
||||
}
|
||||
@@ -98,9 +98,9 @@ public sealed class ClusterAuditQueryTests : IDisposable
|
||||
var t0 = new DateTime(2026, 6, 2, 10, 0, 0, DateTimeKind.Utc);
|
||||
for (var i = 0; i < 5; i++)
|
||||
SeedAudit("DraftEdited", clusterId: null, nodeId: "LINE3-OPCUA-A", ts: t0.AddMinutes(i));
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 3);
|
||||
var rows = await ClusterAuditQuery.ForClusterAsync(_db, "LINE3-OPCUA", pageSize: 3, ct: TestContext.Current.CancellationToken);
|
||||
|
||||
rows.Count.ShouldBe(3);
|
||||
rows.First().Timestamp.ShouldBe(t0.AddMinutes(4)); // newest
|
||||
|
||||
@@ -35,16 +35,16 @@ public sealed class ClusterNodeTransportPortsTests(SchemaComplianceFixture fixtu
|
||||
// GrpcPort is deliberately nullable: nothing listens on it until Phase 5, and a non-null
|
||||
// default would assert a port that does not exist.
|
||||
ctx.ClusterNodes.Add(NewNode(clusterId, "defaulted"));
|
||||
await ctx.SaveChangesAsync();
|
||||
await ctx.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
await using var read = NewContext();
|
||||
var explicitNode = await read.ClusterNodes.AsNoTracking()
|
||||
.SingleAsync(n => n.NodeId == $"{clusterId}-explicit:4053");
|
||||
.SingleAsync(n => n.NodeId == $"{clusterId}-explicit:4053", cancellationToken: TestContext.Current.CancellationToken);
|
||||
explicitNode.AkkaPort.ShouldBe(4055);
|
||||
explicitNode.GrpcPort.ShouldBe(5223);
|
||||
|
||||
var defaulted = await read.ClusterNodes.AsNoTracking()
|
||||
.SingleAsync(n => n.NodeId == $"{clusterId}-defaulted:4053");
|
||||
.SingleAsync(n => n.NodeId == $"{clusterId}-defaulted:4053", cancellationToken: TestContext.Current.CancellationToken);
|
||||
defaulted.AkkaPort.ShouldBe(4053);
|
||||
defaulted.GrpcPort.ShouldBeNull();
|
||||
}
|
||||
@@ -74,12 +74,12 @@ public sealed class ClusterNodeTransportPortsTests(SchemaComplianceFixture fixtu
|
||||
cmd.Parameters.AddWithValue("@clusterId", clusterId);
|
||||
cmd.Parameters.AddWithValue("@host", $"{clusterId}-raw");
|
||||
cmd.Parameters.AddWithValue("@uri", $"urn:OtOpcUa:{clusterId}-raw");
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
await using var read = NewContext();
|
||||
var row = await read.ClusterNodes.AsNoTracking()
|
||||
.SingleAsync(n => n.NodeId == $"{clusterId}-raw:4053");
|
||||
.SingleAsync(n => n.NodeId == $"{clusterId}-raw:4053", cancellationToken: TestContext.Current.CancellationToken);
|
||||
row.AkkaPort.ShouldBe(4053);
|
||||
row.GrpcPort.ShouldBeNull();
|
||||
}
|
||||
@@ -96,13 +96,13 @@ public sealed class ClusterNodeTransportPortsTests(SchemaComplianceFixture fixtu
|
||||
var clusterId = await SeedClusterAsync(ctx);
|
||||
ctx.ClusterNodes.Add(NewNode(clusterId, "a", n => n.GrpcPort = 5223));
|
||||
ctx.ClusterNodes.Add(NewNode(clusterId, "b"));
|
||||
await ctx.SaveChangesAsync();
|
||||
await ctx.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
await using var read = NewContext();
|
||||
var contacts = await read.ClusterNodes.AsNoTracking()
|
||||
.Where(n => n.ClusterId == clusterId && n.GrpcPort != null)
|
||||
.Select(n => new { n.Host, n.AkkaPort, n.GrpcPort })
|
||||
.ToListAsync();
|
||||
.ToListAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
contacts.Count.ShouldBe(1);
|
||||
contacts[0].AkkaPort.ShouldBe(4053);
|
||||
|
||||
+6
-6
@@ -52,7 +52,7 @@ public sealed class DraftSnapshotFactorySqlServerTests
|
||||
ClusterId = clusterId, Name = "snapshot-read", Enterprise = "zb", Site = "test",
|
||||
NodeCount = 1, RedundancyMode = RedundancyMode.None, CreatedBy = "test",
|
||||
});
|
||||
await seed.SaveChangesAsync();
|
||||
await seed.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -64,7 +64,7 @@ public sealed class DraftSnapshotFactorySqlServerTests
|
||||
|
||||
await using (var db = new OtOpcUaConfigDbContext(options))
|
||||
{
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(db);
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(db, TestContext.Current.CancellationToken);
|
||||
// The interceptor committed a Script while ServerClusters (the FIRST table read) was being
|
||||
// materialised. Scripts is read later in the same transaction and must not see it.
|
||||
snapshot.ServerClusters.ShouldContain(c => c.ClusterId == clusterId);
|
||||
@@ -74,16 +74,16 @@ public sealed class DraftSnapshotFactorySqlServerTests
|
||||
await using (var verify = NewContext())
|
||||
{
|
||||
// Positive control: the edit really landed, and a read started afterwards sees it.
|
||||
(await verify.Scripts.AnyAsync(s => s.ScriptId == scriptId)).ShouldBeTrue();
|
||||
var later = await DraftSnapshotFactory.FromConfigDbAsync(verify);
|
||||
(await verify.Scripts.AnyAsync(s => s.ScriptId == scriptId, cancellationToken: TestContext.Current.CancellationToken)).ShouldBeTrue();
|
||||
var later = await DraftSnapshotFactory.FromConfigDbAsync(verify, TestContext.Current.CancellationToken);
|
||||
later.Scripts.ShouldContain(s => s.ScriptId == scriptId);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await using var cleanup = NewContext();
|
||||
await cleanup.Scripts.Where(s => s.ScriptId == scriptId).ExecuteDeleteAsync();
|
||||
await cleanup.ServerClusters.Where(c => c.ClusterId == clusterId).ExecuteDeleteAsync();
|
||||
await cleanup.Scripts.Where(s => s.ScriptId == scriptId).ExecuteDeleteAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
await cleanup.ServerClusters.Where(c => c.ClusterId == clusterId).ExecuteDeleteAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ public sealed class DraftSnapshotFactoryTests : IDisposable
|
||||
_db.Tags.Add(BuildTag(name: "speed"));
|
||||
_db.VirtualTags.Add(BuildVirtualTag(equipmentId: "eq-1", name: "speed", suffix: "a"));
|
||||
_db.VirtualTags.Add(BuildVirtualTag(equipmentId: "eq-1", name: "speed", suffix: "b"));
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db, TestContext.Current.CancellationToken);
|
||||
|
||||
snapshot.Tags.Count.ShouldBe(1);
|
||||
snapshot.VirtualTags.Count.ShouldBe(2);
|
||||
@@ -56,9 +56,9 @@ public sealed class DraftSnapshotFactoryTests : IDisposable
|
||||
SeedEquipment("eq-1");
|
||||
_db.Tags.Add(BuildTag(name: "speed"));
|
||||
_db.VirtualTags.Add(BuildVirtualTag(equipmentId: "eq-1", name: "temperature"));
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db, TestContext.Current.CancellationToken);
|
||||
|
||||
snapshot.Tags.Count.ShouldBe(1);
|
||||
snapshot.VirtualTags.Count.ShouldBe(1);
|
||||
@@ -94,9 +94,9 @@ public sealed class DraftSnapshotFactoryTests : IDisposable
|
||||
ReleasedBy = "test",
|
||||
ReleaseReason = "retired",
|
||||
});
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db, TestContext.Current.CancellationToken);
|
||||
|
||||
snapshot.ActiveReservations.Count.ShouldBe(1);
|
||||
snapshot.ActiveReservations[0].Value.ShouldBe("ZT-001");
|
||||
@@ -124,9 +124,9 @@ public sealed class DraftSnapshotFactoryTests : IDisposable
|
||||
{
|
||||
ApiKeyId = "k1", Name = "scada", SecretHash = "hash", Principal = "scadabridge", Enabled = true,
|
||||
});
|
||||
await _db.SaveChangesAsync();
|
||||
await _db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db);
|
||||
var snapshot = await DraftSnapshotFactory.FromConfigDbAsync(_db, TestContext.Current.CancellationToken);
|
||||
|
||||
snapshot.ServerClusters.ShouldHaveSingleItem().ClusterId.ShouldBe("SITE-A");
|
||||
snapshot.ClusterNodes.ShouldHaveSingleItem().NodeId.ShouldBe("site-a-1:4053");
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ public sealed class ScriptedAlarmEngineRunGuardTests
|
||||
await WaitForAsync(() => eng.RunGuard.StartedRunCount > before);
|
||||
}
|
||||
eng.RunGuard.StartedRunCount.ShouldBe(1 + 1 + pushes, "one runaway dispatch, then healthy-only dispatches");
|
||||
await Task.Delay(300); // absence window for trailing emissions
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken); // absence window for trailing emissions
|
||||
|
||||
ScriptedAlarmEvent[] snapshot;
|
||||
lock (events) snapshot = events.ToArray();
|
||||
|
||||
+11
-11
@@ -215,7 +215,7 @@ public sealed class ScriptedAlarmEngineTests
|
||||
eng.OnEvent += (_, e) => events.Add(e);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
events.Any(e => e.Emission == EmissionKind.Activated).ShouldBeFalse(
|
||||
"OneShot shelve suppresses activation emission");
|
||||
@@ -252,7 +252,7 @@ public sealed class ScriptedAlarmEngineTests
|
||||
|
||||
await eng.DisableAsync("HighTemp", "alice", TestContext.Current.CancellationToken);
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(100);
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken);
|
||||
eng.GetState("HighTemp")!.Active.ShouldBe(AlarmActiveState.Inactive,
|
||||
"disabled alarm ignores predicate");
|
||||
|
||||
@@ -353,7 +353,7 @@ public sealed class ScriptedAlarmEngineTests
|
||||
up.Push($"Temp{round % alarmCount}", round % 2 == 0 ? 150 : 50);
|
||||
round++;
|
||||
}
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
// Readers: continuously touch the three unguarded read paths.
|
||||
var readers = Enumerable.Range(0, 4).Select(_reader => Task.Run(() =>
|
||||
@@ -839,7 +839,7 @@ public sealed class ScriptedAlarmEngineTests
|
||||
|
||||
// Dispose must wait for the in-flight reevaluation to complete rather
|
||||
// than returning while a background task still runs.
|
||||
var disposeTask = Task.Run(() => eng.Dispose());
|
||||
var disposeTask = Task.Run(() => eng.Dispose(), TestContext.Current.CancellationToken);
|
||||
|
||||
// Verify Dispose does NOT complete immediately — it should block waiting
|
||||
// for the in-flight task. Without the -006 fix Dispose returns straight
|
||||
@@ -1003,7 +1003,7 @@ public sealed class ScriptedAlarmEngineTests
|
||||
// load could keep firing between the failed second load and Dispose.
|
||||
// After the fix, it is stopped at the START of every LoadAsync attempt
|
||||
// (successful or not), so Dispose always finds a clean timer state.
|
||||
var disposeTask = Task.Run(() => eng.Dispose());
|
||||
var disposeTask = Task.Run(() => eng.Dispose(), TestContext.Current.CancellationToken);
|
||||
await disposeTask.WaitAsync(TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken);
|
||||
// If Dispose threw or hung, the WaitAsync would surface it.
|
||||
}
|
||||
@@ -1748,12 +1748,12 @@ public sealed class ScriptedAlarmEngineTests
|
||||
eng.TrackBackgroundTaskForTest(() => gate.Task);
|
||||
eng.InFlightCountForTest.ShouldBe(1);
|
||||
|
||||
var disposeTask = Task.Run(() => eng.Dispose());
|
||||
var disposeTask = Task.Run(() => eng.Dispose(), TestContext.Current.CancellationToken);
|
||||
var raced = await Task.WhenAny(disposeTask, Task.Delay(300));
|
||||
raced.ShouldNotBe(disposeTask, "Dispose must not return while registered background work is still running");
|
||||
|
||||
gate.SetResult();
|
||||
await disposeTask.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await disposeTask.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
eng.InFlightCountForTest.ShouldBe(0, "completed work must unregister itself");
|
||||
}
|
||||
|
||||
@@ -1785,7 +1785,7 @@ public sealed class ScriptedAlarmEngineTests
|
||||
() => throw new InvalidOperationException("factory failed (test)")));
|
||||
|
||||
eng.InFlightCountForTest.ShouldBe(0, "work that never started must not be left registered");
|
||||
await Task.Run(() => eng.Dispose()).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await Task.Run(() => eng.Dispose(), TestContext.Current.CancellationToken).WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Verifies a faulted background task is still drained by Dispose and its failure is
|
||||
@@ -1801,11 +1801,11 @@ public sealed class ScriptedAlarmEngineTests
|
||||
|
||||
// Fault it while Dispose is actually blocked on the drain, so the WhenAll observes
|
||||
// the failure rather than finding an already-unregistered task.
|
||||
var disposeTask = Task.Run(() => Should.NotThrow(() => eng.Dispose()));
|
||||
await Task.Delay(200);
|
||||
var disposeTask = Task.Run(() => Should.NotThrow(() => eng.Dispose()), TestContext.Current.CancellationToken);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
gate.SetException(new InvalidOperationException("background failed (test)"));
|
||||
|
||||
await disposeTask.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await disposeTask.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
eng.InFlightCountForTest.ShouldBe(0);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public sealed class ScriptedAlarmSourceLoggingTests
|
||||
if (sinkCapture.Events.Any(e => e.Level == LogEventLevel.Warning && e.Exception?.Message == "subscriber broke"))
|
||||
break;
|
||||
}
|
||||
await Task.Delay(25);
|
||||
await Task.Delay(25, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
LogEvent warning;
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class ScriptedAlarmSourceTests
|
||||
var handle = await source.SubscribeAlarmsAsync([], TestContext.Current.CancellationToken);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
events.Count.ShouldBe(1);
|
||||
events[0].ConditionId.ShouldBe("Plant/Line1::HighTemp");
|
||||
@@ -80,7 +80,7 @@ public sealed class ScriptedAlarmSourceTests
|
||||
var handle = await source.SubscribeAlarmsAsync(["Plant/Line1"], TestContext.Current.CancellationToken);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
events.Count.ShouldBe(1);
|
||||
events[0].SourceNodeId.ShouldBe("Plant/Line1");
|
||||
@@ -102,7 +102,7 @@ public sealed class ScriptedAlarmSourceTests
|
||||
await source.UnsubscribeAlarmsAsync(handle, TestContext.Current.CancellationToken);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
events.Count.ShouldBe(0);
|
||||
}
|
||||
@@ -124,7 +124,7 @@ public sealed class ScriptedAlarmSourceTests
|
||||
// Drive HighTemp's input Bad: the predicate freezes (no transition), but the worst-quality bucket
|
||||
// moves Good→Bad → the engine emits QualityChanged. The source must swallow it.
|
||||
up.Push("Temp", 50, 0x80000000u);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
events.ShouldBeEmpty();
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public sealed class ScriptedAlarmSourceTests
|
||||
using var _s = source;
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
engine.GetState("Plant/Line1::HighTemp")!.Acked.ShouldBe(AlarmAckedState.Unacknowledged);
|
||||
|
||||
await source.AcknowledgeAsync([new AlarmAcknowledgeRequest(
|
||||
@@ -165,7 +165,7 @@ public sealed class ScriptedAlarmSourceTests
|
||||
|
||||
// Supplied principal -> honored.
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
await source.AcknowledgeAsync([new AlarmAcknowledgeRequest(
|
||||
"Plant/Line1", "Plant/Line1::HighTemp", "ack as bob", "bob")],
|
||||
TestContext.Current.CancellationToken);
|
||||
@@ -173,9 +173,9 @@ public sealed class ScriptedAlarmSourceTests
|
||||
|
||||
// No principal -> default audit identity.
|
||||
up.Push("Temp", 50);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
await source.AcknowledgeAsync([new AlarmAcknowledgeRequest(
|
||||
"Plant/Line1", "Plant/Line1::HighTemp", "ack anon", null)],
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class ScriptCancellationTokenTests
|
||||
{
|
||||
using var eval = ScriptEvaluator<FakeScriptContext, int>.Compile("return 1;");
|
||||
var ctx = new FakeScriptContext();
|
||||
(await eval.RunAsync(ctx)).ShouldBe(1);
|
||||
(await eval.RunAsync(ctx, TestContext.Current.CancellationToken)).ShouldBe(1);
|
||||
|
||||
ctx.CancellationToken.ShouldBe(CancellationToken.None, "the token is scoped to the run, not left on the context");
|
||||
}
|
||||
|
||||
+6
-6
@@ -26,7 +26,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
attempts++;
|
||||
if (attempts < 3) throw new InvalidOperationException("transient");
|
||||
await Task.Yield();
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
attempts.ShouldBe(3);
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
{
|
||||
liveAttempts++;
|
||||
await Task.Yield();
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
liveAttempts.ShouldBe(1, "healthy sibling host must not be affected by dead peer");
|
||||
}
|
||||
@@ -200,7 +200,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
{
|
||||
await Task.Yield();
|
||||
ran = true;
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
ran.ShouldBeTrue("the breaker must stay closed after refusals");
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
|
||||
// Elapse the break duration (15 s) so the breaker half-opens, then let a probe succeed → OnClosed.
|
||||
clock.Advance(TimeSpan.FromSeconds(16));
|
||||
await pipeline.ExecuteAsync(async _ => await Task.Yield());
|
||||
await pipeline.ExecuteAsync(async _ => await Task.Yield(), TestContext.Current.CancellationToken);
|
||||
|
||||
var snap = tracker.TryGet("drv-trk", "host-c")!;
|
||||
snap.LastBreakerClosedUtc.ShouldNotBeNull("OnClosed must stamp the close time on recovery");
|
||||
@@ -404,7 +404,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("dead");
|
||||
}));
|
||||
await live.ExecuteAsync(async _ => await Task.Yield());
|
||||
await live.ExecuteAsync(async _ => await Task.Yield(), TestContext.Current.CancellationToken);
|
||||
|
||||
tracker.TryGet("drv-trk", "dead")!.ConsecutiveFailures.ShouldBeGreaterThan(0);
|
||||
tracker.TryGet("drv-trk", "live").ShouldBeNull();
|
||||
@@ -428,7 +428,7 @@ public sealed class DriverResiliencePipelineBuilderTests
|
||||
attempts++;
|
||||
if (attempts < 3) throw new InvalidOperationException("transient");
|
||||
await Task.Yield();
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
attempts.ShouldBe(3);
|
||||
logger.Entries.ShouldContain(e =>
|
||||
|
||||
@@ -392,7 +392,7 @@ public sealed class AbCipAlarmProjectionTests
|
||||
|
||||
int Reads() => factory.Tags.Values.Sum(t => t.ReadCount); // the loop may re-create its runtime on the new device state
|
||||
var frozen = Reads();
|
||||
await Task.Delay(300); // ~15 poll intervals; a live loop would advance the count
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken); // ~15 poll intervals; a live loop would advance the count
|
||||
Reads().ShouldBe(frozen);
|
||||
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
@@ -357,7 +357,7 @@ public sealed class AbCipAlmaProjectionTests
|
||||
await Next(r, x => x.Kind == AlarmTransitionKind.Raise);
|
||||
r.Factory.Customise = p => new FakeAbCipTag(p) { Status = (int)AbStatus.NotFound };
|
||||
r.Factory.Tags[$"{Tag}.HHSeverity"].Status = (int)AbStatus.NotFound;
|
||||
await Task.Delay(150);
|
||||
await Task.Delay(150, TestContext.Current.CancellationToken);
|
||||
await Settled(r);
|
||||
await r.StopAsync();
|
||||
Snapshot(r).Count.ShouldBe(1, "no Retrigger/update from a Bad severity read");
|
||||
@@ -394,7 +394,7 @@ public sealed class AbCipAlmaProjectionTests
|
||||
factory.Tags[$"{Tag}.HInAlarm"].Value = true;
|
||||
|
||||
await WaitFor(() => { lock (events) return events.Count >= 2; });
|
||||
await Task.Delay(100);
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken);
|
||||
await drv.UnsubscribeAlarmsAsync(handle, CancellationToken.None);
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
List<AlarmEventArgs> evs; lock (events) evs = [.. events];
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class AbCipDeviceRefusalTests
|
||||
var (drv, factory) = Build(p => new RefusingTag(p) { ReadThrows = p.TagName == "Arr[999]" ? status : null, Value = 1 });
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
var before = drv.GetHealth().LastSuccessfulRead!.Value;
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
@@ -239,7 +239,7 @@ public sealed class AbCipDeviceRefusalTests
|
||||
: new RefusingTag(p) { ReadThrows = AbStatus.Unsupported });
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
var before = drv.GetHealth().LastSuccessfulRead!.Value;
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
|
||||
(await drv.ReadAsync(["Bad"], CancellationToken.None))[0].StatusCode.ShouldBe(AbCipStatusMapper.BadNotSupported);
|
||||
drv.GetHealth().LastSuccessfulRead.ShouldBe(before);
|
||||
@@ -278,7 +278,7 @@ public sealed class AbCipDeviceRefusalTests
|
||||
}, "abcip-refusal", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
var before = drv.GetHealth().LastSuccessfulRead!.Value;
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
|
||||
(await drv.WriteAsync([new WriteRequest("Ro", 5)], CancellationToken.None))[0].StatusCode
|
||||
.ShouldBe(AbCipStatusMapper.BadNotWritable);
|
||||
|
||||
+6
-6
@@ -104,14 +104,14 @@ public sealed class AbCipLifecycleConcurrencyTests
|
||||
|
||||
// The second init's own release waits on the slow first probe loop; the shutdown overtakes it there.
|
||||
var init = drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
var superseded = await Should.ThrowAsync<AbCipInitializationSupersededException>(() => init);
|
||||
superseded.ShouldNotBeAssignableTo<OperationCanceledException>(); // M4: distinct from a caller cancellation
|
||||
|
||||
drv.GetDeviceState(Host).ShouldBeNull();
|
||||
drv.GetHostStatuses().ShouldBeEmpty();
|
||||
await Task.Delay(100);
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken);
|
||||
factory.Tags["ProbeTag"].ShouldBeSameAs(firstProbe); // no second probe loop ever created a runtime
|
||||
drv.GetHealth().State.ShouldNotBe(DriverState.Healthy);
|
||||
|
||||
@@ -164,7 +164,7 @@ public sealed class AbCipLifecycleConcurrencyTests
|
||||
}
|
||||
return reads;
|
||||
});
|
||||
await started.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await started.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
for (var i = 0; i < 200; i++)
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
@@ -204,7 +204,7 @@ public sealed class AbCipLifecycleConcurrencyTests
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await WaitUntilAsync(() => factory.Tags.ContainsKey("ProbeTag"), "probe never started");
|
||||
var loop = drv.GetDeviceState(Host)!.ProbeTask!;
|
||||
await Task.Delay(50); // the probe is inside its slow, cancellation-deaf read
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken); // the probe is inside its slow, cancellation-deaf read
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
((IDisposable)drv).Dispose();
|
||||
@@ -225,10 +225,10 @@ public sealed class AbCipLifecycleConcurrencyTests
|
||||
var (drv, factory) = Build(probeReadDelay: TimeSpan.FromMilliseconds(400));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await WaitUntilAsync(() => factory.Tags.ContainsKey("ProbeTag"), "probe never started");
|
||||
await Task.Delay(30); // inside the slow probe read
|
||||
await Task.Delay(30, TestContext.Current.CancellationToken); // inside the slow probe read
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None);
|
||||
await Task.Delay(30);
|
||||
await Task.Delay(30, TestContext.Current.CancellationToken);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None); // newer: publishes while the shutdown still joins
|
||||
await shutdown;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class AbCipProbeRefusalTests
|
||||
var (drv, logger) = Build(new AbTransportException(AbStatus.NotFound, "NotFound"));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await WaitUntilAsync(() => drv.GetHostStatuses().Single().State == HostState.Running);
|
||||
await Task.Delay(100); // many probe ticks
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken); // many probe ticks
|
||||
|
||||
var r = await drv.ReadAsync(["Speed"], CancellationToken.None);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class AbCipReadWindowTests
|
||||
await read;
|
||||
throw new ShouldAssertException("device A's read waited behind device B's blocked cold create");
|
||||
}
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
}
|
||||
read.IsCompleted.ShouldBeFalse(); // B is still blocked — A was read regardless
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class AbCipWriteHealthTests
|
||||
await drv.ReadAsync(["Broken"], CancellationToken.None);
|
||||
var degraded = drv.GetHealth();
|
||||
degraded.State.ShouldBe(DriverState.Degraded);
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
|
||||
var r = await drv.WriteAsync([new WriteRequest("D", 5)], CancellationToken.None);
|
||||
|
||||
|
||||
+3
-3
@@ -173,7 +173,7 @@ public sealed class AbLegacyAnsweredStatusTests
|
||||
var healthyAt = drv.GetHealth().LastSuccessfulRead; // positive control: Good reads DID advance it
|
||||
healthyAt.ShouldNotBeNull();
|
||||
foreach (var t in factory.Tags.Values) t.Status = RemoteErr;
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
|
||||
for (var poll = 0; poll < 3; poll++)
|
||||
(await drv.ReadAsync(["X", "Y"], CancellationToken.None)).ShouldAllBe(s => s.StatusCode == AbLegacyStatusMapper.BadCommunicationError);
|
||||
@@ -203,7 +203,7 @@ public sealed class AbLegacyAnsweredStatusTests
|
||||
var healthyAt = drv.GetHealth().LastSuccessfulRead;
|
||||
drv.GetDeviceState(Host)!.Runtimes.TryRemove("X", out _);
|
||||
factory.Customise = p => new ThrowingTag(p, status);
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
|
||||
var r = await drv.ReadAsync(["X"], CancellationToken.None);
|
||||
|
||||
@@ -220,7 +220,7 @@ public sealed class AbLegacyAnsweredStatusTests
|
||||
var drv = NewDriver(factory, coalesce: false, new AbLegacyTagDefinition("X", Host, "N7:0", AbLegacyDataType.Int));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
var before = drv.GetHealth().LastSuccessfulRead;
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
|
||||
(await drv.ReadAsync(["X"], CancellationToken.None))[0].StatusCode.ShouldBe(AbLegacyStatusMapper.BadNodeIdUnknown);
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ public sealed class AbLegacyBatchSubscribeTests
|
||||
|
||||
var handle = await drv.SubscribeAsync(["A", "B"], TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
var deadline = DateTime.UtcNow.AddSeconds(5);
|
||||
while (batches.IsEmpty && DateTime.UtcNow < deadline) await Task.Delay(10);
|
||||
while (batches.IsEmpty && DateTime.UtcNow < deadline) await Task.Delay(10, TestContext.Current.CancellationToken);
|
||||
|
||||
batches.TryPeek(out var first).ShouldBeTrue();
|
||||
first!.SubscriptionHandle.ShouldBe(handle);
|
||||
|
||||
+2
-2
@@ -101,7 +101,7 @@ public sealed class AbLegacyDeviceIsolationTests
|
||||
new AbLegacyTagDefinition("B", HostB, "N7:0", AbLegacyDataType.Int));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var r = await drv.ReadAsync(["A", "B"], CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var r = await drv.ReadAsync(["A", "B"], CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
r[0].Value.ShouldBe(1);
|
||||
r[1].Value.ShouldBe(2);
|
||||
@@ -157,7 +157,7 @@ public sealed class AbLegacyDeviceIsolationTests
|
||||
}, "drv-p7", factory, logger);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
var initHealth = drv.GetHealth();
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
|
||||
var r1 = await drv.ReadAsync(["X"], CancellationToken.None);
|
||||
var r2 = await drv.ReadAsync(["X"], CancellationToken.None);
|
||||
|
||||
+1
-1
@@ -289,7 +289,7 @@ public sealed class AbLegacyFamilyAddressingTests
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var deadline = DateTime.UtcNow.AddSeconds(5);
|
||||
while (!factory.Tags.ContainsKey("I:15") && DateTime.UtcNow < deadline) await Task.Delay(20);
|
||||
while (!factory.Tags.ContainsKey("I:15") && DateTime.UtcNow < deadline) await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
factory.Tags.ContainsKey("I:15").ShouldBeTrue();
|
||||
|
||||
+8
-8
@@ -53,7 +53,7 @@ public sealed class AbLegacyHealthEvidenceTests
|
||||
var drv = await DegradedDriverAsync(f, new AbLegacyTagDefinition("X", Host, "N7:0", AbLegacyDataType.Int));
|
||||
var before = drv.GetHealth().LastSuccessfulRead;
|
||||
f.Make = p => new FakeAbLegacyTag(p) { Status = (int)AbStatus.NotFound };
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
|
||||
var r = await drv.ReadAsync(["X"], CancellationToken.None);
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class AbLegacyHealthEvidenceTests
|
||||
new AbLegacyTagDefinition("W", Host, "N7:5", AbLegacyDataType.Int));
|
||||
var before = drv.GetHealth().LastSuccessfulRead;
|
||||
f.Make = p => new FakeAbLegacyTag(p) { WriteStatusOverride = writeStatus };
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
|
||||
var w = await drv.WriteAsync([new WriteRequest("W", 3)], CancellationToken.None);
|
||||
|
||||
@@ -113,7 +113,7 @@ public sealed class AbLegacyHealthEvidenceTests
|
||||
new AbLegacyTagDefinition("Bad", Host, "N7:6", AbLegacyDataType.Int));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
var before = drv.GetHealth().LastSuccessfulRead;
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
|
||||
var w = await drv.WriteAsync([new WriteRequest("Good", 1), new WriteRequest("Bad", 2)], CancellationToken.None);
|
||||
|
||||
@@ -207,7 +207,7 @@ public sealed class AbLegacyHealthEvidenceTests
|
||||
await entered.Task;
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None); // parks joining the wedged probe loop
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
shutdown.IsCompleted.ShouldBeFalse();
|
||||
await drv.InitializeAsync("{}", CancellationToken.None); // a newer initialise wins
|
||||
drv.GetHealth().State.ShouldBe(DriverState.Healthy);
|
||||
@@ -242,13 +242,13 @@ public sealed class AbLegacyHealthEvidenceTests
|
||||
var first = f.Tags["N7:0"];
|
||||
|
||||
var gate = state.GetRuntimeLock("X");
|
||||
await gate.WaitAsync();
|
||||
await gate.WaitAsync(TestContext.Current.CancellationToken);
|
||||
// Signalled at exactly "handle fetched, about to wait on the gate" — no sleep, so the eviction below always
|
||||
// lands between the reader's fetch and its use (review 07 r1 test nit: the sleep could pass vacuously).
|
||||
var atGate = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
state.BeforeOperationGate = key => { if (key == "X") atGate.TrySetResult(); };
|
||||
var read = drv.ReadAsync(["X"], CancellationToken.None);
|
||||
await atGate.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await atGate.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
state.Runtimes.TryRemove("X", out var evicted).ShouldBeTrue();
|
||||
evicted!.Dispose();
|
||||
gate.Release();
|
||||
@@ -285,11 +285,11 @@ public sealed class AbLegacyHealthEvidenceTests
|
||||
state.Runtimes.ContainsKey(key).ShouldBeTrue();
|
||||
|
||||
var gate = state.GetRuntimeLock(key);
|
||||
await gate.WaitAsync();
|
||||
await gate.WaitAsync(TestContext.Current.CancellationToken);
|
||||
var atGate = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
state.BeforeOperationGate = k => { if (k == key) atGate.TrySetResult(); };
|
||||
var read = drv.ReadAsync(["Ind"], CancellationToken.None);
|
||||
await atGate.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await atGate.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
// What the sweep does when the index moves: evict the resolution, then forget its gate.
|
||||
state.Runtimes.TryRemove(key, out var swept).ShouldBeTrue();
|
||||
swept!.Dispose();
|
||||
|
||||
+3
-3
@@ -189,7 +189,7 @@ public sealed class AbLegacyLifecycleConcurrencyTests
|
||||
await probe!.Entered.Task;
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None); // parks joining the wedged probe loop
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
shutdown.IsCompleted.ShouldBeFalse();
|
||||
|
||||
Should.NotThrow(() => drv.Dispose());
|
||||
@@ -232,14 +232,14 @@ public sealed class AbLegacyLifecycleConcurrencyTests
|
||||
await first!.Entered.Task;
|
||||
|
||||
var abandoned = drv.InitializeAsync("{}", CancellationToken.None); // parks joining the wedged loop
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
abandoned.IsCompleted.ShouldBeFalse();
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
release.SetResult();
|
||||
await Should.ThrowAsync<OperationCanceledException>(() => abandoned);
|
||||
var createdAfter = probes.Count;
|
||||
await Task.Delay(150); // several probe intervals
|
||||
await Task.Delay(150, TestContext.Current.CancellationToken); // several probe intervals
|
||||
|
||||
drv.DeviceCount.ShouldBe(0);
|
||||
drv.GetHealth().State.ShouldBe(DriverState.Unknown);
|
||||
|
||||
@@ -38,16 +38,16 @@ public sealed class AbLegacyShutdownJoinTests
|
||||
Probe = new AbLegacyProbeOptions { Enabled = true, Interval = TimeSpan.FromMilliseconds(50), ProbeAddress = "S:0" },
|
||||
}, "drv-join", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
shutdown.IsCompleted.ShouldBeFalse("shutdown must join the probe loop parked in runtime init");
|
||||
|
||||
// Positive control: releasing the parked call lets shutdown finish, with the probe runtime released.
|
||||
release.SetResult();
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Tags["S:0"].Disposed.ShouldBeTrue();
|
||||
}
|
||||
@@ -68,16 +68,16 @@ public sealed class AbLegacyShutdownJoinTests
|
||||
Probe = new AbLegacyProbeOptions { Enabled = true, Interval = TimeSpan.FromMilliseconds(50), ProbeAddress = "S:0" },
|
||||
}, "drv-join-dispose", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
var dispose = Task.Run(drv.Dispose);
|
||||
var dispose = Task.Run(drv.Dispose, TestContext.Current.CancellationToken);
|
||||
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
dispose.IsCompleted.ShouldBeFalse("Dispose must join the probe loop parked in runtime init");
|
||||
|
||||
// Positive control: releasing the parked call lets Dispose finish, with the probe runtime released.
|
||||
release.SetResult();
|
||||
await dispose.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await dispose.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Tags["S:0"].Disposed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ public sealed class CalculationDriverRemediationTests
|
||||
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
driver.OnDependencyValue("src/a", 1, StatusGood, DateTime.UtcNow);
|
||||
var deadline = DateTime.UtcNow.AddSeconds(10);
|
||||
while (driver.IsEvaluationIdle && DateTime.UtcNow < deadline) await Task.Delay(5);
|
||||
while (driver.IsEvaluationIdle && DateTime.UtcNow < deadline) await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
driver.IsEvaluationIdle.ShouldBeFalse("positive control: a run is in flight when Dispose lands");
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class CalculationDriverTests
|
||||
{
|
||||
var (driver, published, _) = Build(
|
||||
CalcTag("calc/e/sum", "return (double)ctx.GetTag(\"src/a\").Value + (double)ctx.GetTag(\"src/b\").Value;"));
|
||||
await driver.InitializeAsync("{}", default);
|
||||
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
|
||||
// Only one of two deps arrived → the change-gate holds.
|
||||
driver.OnDependencyValue("src/a", 10.0, StatusGood, DateTime.UtcNow);
|
||||
@@ -197,7 +197,7 @@ public sealed class CalculationDriverTests
|
||||
{
|
||||
var (driver, published, _) = Build(
|
||||
CalcTag("calc/e/dbl", "return (double)ctx.GetTag(\"src/a\").Value * 2.0;"));
|
||||
await driver.InitializeAsync("{}", default);
|
||||
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
|
||||
// Each arrival is queued with the dependency snapshot that triggered it, so the three evaluations
|
||||
// see 5 / 5 / 7 in that order however far the pump lags behind them.
|
||||
@@ -216,7 +216,7 @@ public sealed class CalculationDriverTests
|
||||
{
|
||||
var (driver, published, logs) = Build(
|
||||
CalcTag("calc/e/div", "return 100 / (int)ctx.GetTag(\"src/a\").Value;"));
|
||||
await driver.InitializeAsync("{}", default);
|
||||
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
|
||||
// a = 0 → DivideByZero → Failure → Bad (carrying last-known = null) + one script-log.
|
||||
driver.OnDependencyValue("src/a", 0, StatusGood, DateTime.UtcNow);
|
||||
@@ -254,7 +254,7 @@ public sealed class CalculationDriverTests
|
||||
// No dependencies (constant script) so only the timer can drive it.
|
||||
var (driver, published, _) = Build(
|
||||
CalcTag("calc/e/const", "return 42.0;", changeTriggered: false, timerMs: 50));
|
||||
await driver.InitializeAsync("{}", default);
|
||||
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
|
||||
var fired = await WaitUntil(() => !published.IsEmpty, TimeSpan.FromSeconds(3));
|
||||
fired.ShouldBeTrue("the timer trigger should have computed the tag within 3s");
|
||||
@@ -262,7 +262,7 @@ public sealed class CalculationDriverTests
|
||||
e!.FullReference.ShouldBe("calc/e/const");
|
||||
e.Snapshot.Value.ShouldBe(42.0);
|
||||
|
||||
await driver.ShutdownAsync(default);
|
||||
await driver.ShutdownAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
// ── IReadable ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -271,14 +271,14 @@ public sealed class CalculationDriverTests
|
||||
public async Task Read_returns_BadWaitingForInitialData_before_first_eval_then_the_computed_value()
|
||||
{
|
||||
var (driver, _, _) = Build(CalcTag("calc/e/x", "return (double)ctx.GetTag(\"src/a\").Value * 2.0;"));
|
||||
await driver.InitializeAsync("{}", default);
|
||||
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||
|
||||
var before = await driver.ReadAsync(new[] { "calc/e/x" }, default);
|
||||
var before = await driver.ReadAsync(new[] { "calc/e/x" }, TestContext.Current.CancellationToken);
|
||||
before[0].StatusCode.ShouldBe(StatusBadWaitingForInitialData);
|
||||
|
||||
driver.OnDependencyValue("src/a", 3.0, StatusGood, DateTime.UtcNow);
|
||||
await Settle(driver);
|
||||
var after = await driver.ReadAsync(new[] { "calc/e/x" }, default);
|
||||
var after = await driver.ReadAsync(new[] { "calc/e/x" }, TestContext.Current.CancellationToken);
|
||||
after[0].StatusCode.ShouldBe(StatusGood);
|
||||
after[0].Value.ShouldBe(6.0);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public sealed class FocasCapabilityTests
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
var afterShutdown = events.Count;
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
events.Count.ShouldBe(afterShutdown);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed class FocasConcurrentConnectTests
|
||||
// Deterministic: the first caller is now parked INSIDE its connect (client created, not yet connected) —
|
||||
// the exact window the race needs. The second caller then runs synchronously up to its first real await:
|
||||
// pre-fix that is "dispose the first client, Create() a second"; with the gate it parks on ConnectGate.
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
var second = drv.ReadAsync(["B"], CancellationToken.None);
|
||||
release.SetResult();
|
||||
var results = await Task.WhenAll(first, second);
|
||||
@@ -89,9 +89,9 @@ public sealed class FocasConcurrentConnectTests
|
||||
(await drv.ReadAsync(["A"], CancellationToken.None)).Single().StatusCode
|
||||
.ShouldNotBe(FocasStatusMapper.Good);
|
||||
// Past the 1 s initial connect backoff window, the next caller must be able to connect.
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(1100));
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(1100), TestContext.Current.CancellationToken);
|
||||
var read = drv.ReadAsync(["A"], CancellationToken.None);
|
||||
(await read.WaitAsync(TimeSpan.FromSeconds(5))).Single().StatusCode.ShouldBe(FocasStatusMapper.Good);
|
||||
(await read.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken)).Single().StatusCode.ShouldBe(FocasStatusMapper.Good);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,12 +120,12 @@ public sealed class FocasConcurrentConnectTests
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
var cancelled = drv.ReadAsync(["A"], cts.Token);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
cts.Cancel();
|
||||
try { await cancelled; } catch (OperationCanceledException) { /* either surfaced or mapped — both fine */ }
|
||||
|
||||
// Immediately — well inside the 1 s initial backoff window a recorded failure would have opened.
|
||||
var next = await drv.ReadAsync(["A"], CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var next = await drv.ReadAsync(["A"], CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
next.Single().StatusCode.ShouldBe(FocasStatusMapper.Good, "a caller's own cancellation must not throttle the next connect");
|
||||
factory.Clients.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
+11
-11
@@ -67,11 +67,11 @@ public sealed class FocasDeviceTableLifecycleTests
|
||||
}
|
||||
catch (Exception ex) { lock (errors) errors.Add(ex); }
|
||||
}
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await Task.Delay(100);
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken);
|
||||
stop.Cancel();
|
||||
await reader;
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
@@ -105,13 +105,13 @@ public sealed class FocasDeviceTableLifecycleTests
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var read = drv.ReadAsync(["raw/R"], CancellationToken.None);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
release.SetResult();
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
var snaps = await read.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
var snaps = await read.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Clients.Count.ShouldBe(1);
|
||||
factory.Clients[0].IsConnected.ShouldBeFalse("a connect finishing on a retired device must dispose its client");
|
||||
@@ -137,13 +137,13 @@ public sealed class FocasDeviceTableLifecycleTests
|
||||
return new FocasDriverBinding(options, factory);
|
||||
});
|
||||
|
||||
var init = Task.Run(() => drv.InitializeAsync("{\"devices\":[]}", CancellationToken.None));
|
||||
await inRebind.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var init = Task.Run(() => drv.InitializeAsync("{\"devices\":[]}", CancellationToken.None), TestContext.Current.CancellationToken);
|
||||
await inRebind.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
gate.Set();
|
||||
await Should.ThrowAsync<OperationCanceledException>(() => init.WaitAsync(TimeSpan.FromSeconds(10)));
|
||||
|
||||
await Task.Delay(200); // several probe intervals — a started loop would have connected by now
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken); // several probe intervals — a started loop would have connected by now
|
||||
drv.DeviceCount.ShouldBe(0);
|
||||
factory.Clients.ShouldBeEmpty();
|
||||
drv.GetHealth().State.ShouldNotBe(DriverState.Healthy);
|
||||
@@ -180,11 +180,11 @@ public sealed class FocasDeviceTableLifecycleTests
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None);
|
||||
var dispose = drv.DisposeAsync().AsTask();
|
||||
await dispose.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await dispose.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Clients.ShouldAllBe(c => !c.IsConnected);
|
||||
factory.Clients.ShouldAllBe(c => c.DisposeCount >= 1);
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
drv.DeviceCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -85,11 +85,11 @@ public sealed class FocasFixedTreeConcurrencyTests
|
||||
|
||||
var tasks = new List<Task>
|
||||
{
|
||||
Task.Run(() => Writer(0)),
|
||||
Task.Run(() => Writer(1)),
|
||||
Task.Run(() => Writer(2)),
|
||||
Task.Run(Reader),
|
||||
Task.Run(Reader),
|
||||
Task.Run(() => Writer(0), TestContext.Current.CancellationToken),
|
||||
Task.Run(() => Writer(1), TestContext.Current.CancellationToken),
|
||||
Task.Run(() => Writer(2), TestContext.Current.CancellationToken),
|
||||
Task.Run(Reader, TestContext.Current.CancellationToken),
|
||||
Task.Run(Reader, TestContext.Current.CancellationToken),
|
||||
};
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class FocasHandleRecycleTests
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await drv.ReadAsync(["R"], CancellationToken.None);
|
||||
await Task.Delay(150);
|
||||
await Task.Delay(150, TestContext.Current.CancellationToken);
|
||||
|
||||
// With recycle off the same client stays live — no Dispose during the window.
|
||||
factory.Clients.Count.ShouldBe(1);
|
||||
@@ -119,7 +119,7 @@ public sealed class FocasHandleRecycleTests
|
||||
var addr = FocasAddress.TryParse("R100")!;
|
||||
|
||||
var read = sync.ReadAsync(addr, FocasDataType.Byte, CancellationToken.None);
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
sync.PendingOperations.ShouldBe(1);
|
||||
var dispose = sync.DisposeWhenIdleAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public sealed class FocasProgramInfoQualityTests
|
||||
|
||||
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5);
|
||||
while (DateTime.UtcNow < deadline && drv.GetDeviceState(Host)?.LastProgramInfo is null)
|
||||
await Task.Delay(15);
|
||||
await Task.Delay(15, TestContext.Current.CancellationToken);
|
||||
|
||||
var read = await drv.ReadAsync(
|
||||
[$"{Host}/Program/Name", $"{Host}/Program/BlockCount", $"{Host}/Program/ONumber",
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class FocasReadHealthContractTests
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(5, TestContext.Current.CancellationToken);
|
||||
var snap = (await drv.ReadAsync(["raw/R100"], CancellationToken.None))[0];
|
||||
snap.StatusCode.ShouldBe(refusal);
|
||||
}
|
||||
@@ -170,16 +170,16 @@ public sealed class FocasReadHealthContractTests
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
var stale = drv.ReadAsync(["raw/R100"], CancellationToken.None);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None); // a new generation, Healthy
|
||||
release.SetResult();
|
||||
(await stale.WaitAsync(TimeSpan.FromSeconds(5)))[0].StatusCode.ShouldBe(FocasStatusMapper.BadCommunicationError);
|
||||
(await stale.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken))[0].StatusCode.ShouldBe(FocasStatusMapper.BadCommunicationError);
|
||||
|
||||
drv.GetHealth().State.ShouldBe(DriverState.Healthy, "the old generation's failure must not land on the new one");
|
||||
|
||||
// Positive control: a failure under the live generation degrades.
|
||||
var live = drv.ReadAsync(["raw/R100"], CancellationToken.None);
|
||||
(await live.WaitAsync(TimeSpan.FromSeconds(5)))[0].StatusCode.ShouldBe(FocasStatusMapper.BadCommunicationError);
|
||||
(await live.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken))[0].StatusCode.ShouldBe(FocasStatusMapper.BadCommunicationError);
|
||||
drv.GetHealth().State.ShouldBe(DriverState.Degraded);
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -39,16 +39,16 @@ public sealed class FocasShutdownJoinTests
|
||||
};
|
||||
var drv = new FocasDriver(options, "focas-1", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
var shutdown = drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
shutdown.IsCompleted.ShouldBeFalse("shutdown must join the probe loop parked in connect");
|
||||
|
||||
// Positive control: releasing the parked connect lets shutdown finish.
|
||||
release.SetResult();
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await shutdown.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Clients.ShouldNotBeEmpty();
|
||||
factory.Clients.ShouldAllBe(c => c.DisposeCount >= 1);
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed class FocasWireProtocolTests
|
||||
var (client, server) = await ConnectedPairAsync();
|
||||
try
|
||||
{
|
||||
await server.GetStream().WriteAsync(pdu);
|
||||
await server.GetStream().WriteAsync(pdu, TestContext.Current.CancellationToken);
|
||||
var read = await FocasWireProtocol.ReadPduAsync(client.GetStream(), CancellationToken.None);
|
||||
|
||||
read.Type.ShouldBe(FocasWireProtocol.TypeData);
|
||||
@@ -84,7 +84,7 @@ public sealed class FocasWireProtocolTests
|
||||
var (client, server) = await ConnectedPairAsync();
|
||||
try
|
||||
{
|
||||
await server.GetStream().WriteAsync(bad);
|
||||
await server.GetStream().WriteAsync(bad, TestContext.Current.CancellationToken);
|
||||
await Should.ThrowAsync<FocasWireException>(async () =>
|
||||
await FocasWireProtocol.ReadPduAsync(client.GetStream(), CancellationToken.None));
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public sealed class FocasWireProtocolTests
|
||||
var (client, server) = await ConnectedPairAsync();
|
||||
try
|
||||
{
|
||||
await server.GetStream().WriteAsync(header);
|
||||
await server.GetStream().WriteAsync(header, TestContext.Current.CancellationToken);
|
||||
await Should.ThrowAsync<FocasWireException>(async () =>
|
||||
await FocasWireProtocol.ReadPduAsync(client.GetStream(), CancellationToken.None));
|
||||
}
|
||||
@@ -127,7 +127,7 @@ public sealed class FocasWireProtocolTests
|
||||
var (client, server) = await ConnectedPairAsync();
|
||||
try
|
||||
{
|
||||
await server.GetStream().WriteAsync(pdu);
|
||||
await server.GetStream().WriteAsync(pdu, TestContext.Current.CancellationToken);
|
||||
var read = await FocasWireProtocol.ReadPduAsync(client.GetStream(), CancellationToken.None);
|
||||
|
||||
read.Type.ShouldBe(FocasWireProtocol.TypeData);
|
||||
|
||||
@@ -147,7 +147,7 @@ public sealed class FocasWireV3Tests
|
||||
try
|
||||
{
|
||||
// Send only 5 of the 10 header bytes, then stall forever — the cnc_rdsvmeter hang shape.
|
||||
await server.GetStream().WriteAsync(new byte[] { 0xa0, 0xa0, 0xa0, 0xa0, 0x00 });
|
||||
await server.GetStream().WriteAsync(new byte[] { 0xa0, 0xa0, 0xa0, 0xa0, 0x00 }, TestContext.Current.CancellationToken);
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(300));
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
@@ -131,11 +131,11 @@ public sealed class DeployWatcherTests
|
||||
await watcher.StartAsync(CancellationToken.None);
|
||||
|
||||
// Push only the bootstrap event.
|
||||
await channel.Writer.WriteAsync(Event(0, DateTimeOffset.Parse("2026-01-01T00:00:00Z")));
|
||||
await channel.Writer.WriteAsync(Event(0, DateTimeOffset.Parse("2026-01-01T00:00:00Z")), TestContext.Current.CancellationToken);
|
||||
|
||||
// Give the loop a moment to consume + ack.
|
||||
await WaitUntilAsync(() => source.CallCount > 0 && channel.Reader.Count == 0, TimeSpan.FromSeconds(2));
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
|
||||
captured.ShouldBeEmpty();
|
||||
|
||||
@@ -156,8 +156,8 @@ public sealed class DeployWatcherTests
|
||||
|
||||
await watcher.StartAsync(CancellationToken.None);
|
||||
|
||||
await channel.Writer.WriteAsync(Event(0, t0)); // bootstrap
|
||||
await channel.Writer.WriteAsync(Event(1, t1)); // real change
|
||||
await channel.Writer.WriteAsync(Event(0, t0), TestContext.Current.CancellationToken); // bootstrap
|
||||
await channel.Writer.WriteAsync(Event(1, t1), TestContext.Current.CancellationToken); // real change
|
||||
|
||||
await WaitUntilAsync(() => captured.Count >= 1, TimeSpan.FromSeconds(2));
|
||||
|
||||
@@ -183,11 +183,11 @@ public sealed class DeployWatcherTests
|
||||
|
||||
await watcher.StartAsync(CancellationToken.None);
|
||||
|
||||
await channel.Writer.WriteAsync(Event(0, t0)); // bootstrap
|
||||
await channel.Writer.WriteAsync(Event(2, t0)); // duplicate state — gateway re-sent
|
||||
await channel.Writer.WriteAsync(Event(0, t0), TestContext.Current.CancellationToken); // bootstrap
|
||||
await channel.Writer.WriteAsync(Event(2, t0), TestContext.Current.CancellationToken); // duplicate state — gateway re-sent
|
||||
|
||||
await WaitUntilAsync(() => channel.Reader.Count == 0, TimeSpan.FromSeconds(2));
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
|
||||
captured.ShouldBeEmpty();
|
||||
|
||||
@@ -208,9 +208,9 @@ public sealed class DeployWatcherTests
|
||||
await watcher.StartAsync(CancellationToken.None);
|
||||
|
||||
// Bootstrap with absent deploy time (Galaxy never deployed).
|
||||
await channel.Writer.WriteAsync(Event(0, deployTime: null));
|
||||
await channel.Writer.WriteAsync(Event(0, deployTime: null), TestContext.Current.CancellationToken);
|
||||
// Now a deploy lands and the present flag flips.
|
||||
await channel.Writer.WriteAsync(Event(1, t1));
|
||||
await channel.Writer.WriteAsync(Event(1, t1), TestContext.Current.CancellationToken);
|
||||
|
||||
await WaitUntilAsync(() => captured.Count >= 1, TimeSpan.FromSeconds(2));
|
||||
|
||||
@@ -232,7 +232,7 @@ public sealed class DeployWatcherTests
|
||||
await watcher.StartAsync(CancellationToken.None);
|
||||
|
||||
// Push bootstrap so the loop enters its enumeration body before stop.
|
||||
await channel.Writer.WriteAsync(Event(0, DateTimeOffset.UtcNow));
|
||||
await channel.Writer.WriteAsync(Event(0, DateTimeOffset.UtcNow), TestContext.Current.CancellationToken);
|
||||
await WaitUntilAsync(() => source.CallCount > 0, TimeSpan.FromSeconds(2));
|
||||
|
||||
// StopAsync should complete without throwing and within a reasonable window.
|
||||
@@ -251,11 +251,11 @@ public sealed class DeployWatcherTests
|
||||
var watcher = new DeployWatcher(source);
|
||||
|
||||
await watcher.StartAsync(CancellationToken.None);
|
||||
await channel.Writer.WriteAsync(Event(0, DateTimeOffset.UtcNow));
|
||||
await channel.Writer.WriteAsync(Event(0, DateTimeOffset.UtcNow), TestContext.Current.CancellationToken);
|
||||
await WaitUntilAsync(() => source.CallCount > 0, TimeSpan.FromSeconds(2));
|
||||
|
||||
// Should not throw, should not hang.
|
||||
var disposeTask = Task.Run(watcher.Dispose);
|
||||
var disposeTask = Task.Run(watcher.Dispose, TestContext.Current.CancellationToken);
|
||||
var completed = await Task.WhenAny(disposeTask, Task.Delay(TimeSpan.FromSeconds(5)));
|
||||
completed.ShouldBe(disposeTask);
|
||||
await disposeTask;
|
||||
@@ -295,8 +295,8 @@ public sealed class DeployWatcherTests
|
||||
await WaitUntilAsync(() => source.CallCount >= 2, TimeSpan.FromSeconds(2));
|
||||
|
||||
// Now feed bootstrap + real event into the second channel.
|
||||
await secondChannel.Writer.WriteAsync(Event(0, t0));
|
||||
await secondChannel.Writer.WriteAsync(Event(1, t1));
|
||||
await secondChannel.Writer.WriteAsync(Event(0, t0), TestContext.Current.CancellationToken);
|
||||
await secondChannel.Writer.WriteAsync(Event(1, t1), TestContext.Current.CancellationToken);
|
||||
|
||||
await WaitUntilAsync(() => captured.Count >= 1, TimeSpan.FromSeconds(2));
|
||||
|
||||
|
||||
+8
-8
@@ -30,7 +30,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
[Fact]
|
||||
public async Task Literal_string_is_returned_unchanged()
|
||||
{
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync("plain-text-key", FakeResolver()))
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync("plain-text-key", FakeResolver(), ct: TestContext.Current.CancellationToken))
|
||||
.ShouldBe("plain-text-key");
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
Environment.SetEnvironmentVariable(name, "key-from-env");
|
||||
try
|
||||
{
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync($"env:{name}", FakeResolver()))
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync($"env:{name}", FakeResolver(), ct: TestContext.Current.CancellationToken))
|
||||
.ShouldBe("key-from-env");
|
||||
}
|
||||
finally
|
||||
@@ -72,7 +72,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
File.WriteAllText(path, " key-from-file \n");
|
||||
try
|
||||
{
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync($"file:{path}", FakeResolver()))
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync($"file:{path}", FakeResolver(), ct: TestContext.Current.CancellationToken))
|
||||
.ShouldBe("key-from-file");
|
||||
}
|
||||
finally
|
||||
@@ -98,7 +98,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
[Fact]
|
||||
public async Task Secret_prefix_resolves_through_secret_resolver()
|
||||
{
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync($"secret:{KnownSecretName}", FakeResolver()))
|
||||
(await GalaxySecretRef.ResolveApiKeyAsync($"secret:{KnownSecretName}", FakeResolver(), ct: TestContext.Current.CancellationToken))
|
||||
.ShouldBe(KnownSecretValue);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
public async Task Secret_prefix_does_not_emit_literal_warning()
|
||||
{
|
||||
var logger = new CaptureLogger();
|
||||
await GalaxySecretRef.ResolveApiKeyAsync($"secret:{KnownSecretName}", FakeResolver(), logger);
|
||||
await GalaxySecretRef.ResolveApiKeyAsync($"secret:{KnownSecretName}", FakeResolver(), logger, TestContext.Current.CancellationToken);
|
||||
logger.Entries.ShouldNotContain(e => e.Level == LogLevel.Warning);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
// in the DriverConfig JSON. The resolver must surface a warning so an
|
||||
// operator who committed one by accident sees it at startup.
|
||||
var logger = new CaptureLogger();
|
||||
var key = await GalaxySecretRef.ResolveApiKeyAsync("plain-text-key", FakeResolver(), logger);
|
||||
var key = await GalaxySecretRef.ResolveApiKeyAsync("plain-text-key", FakeResolver(), logger, TestContext.Current.CancellationToken);
|
||||
|
||||
key.ShouldBe("plain-text-key");
|
||||
logger.Entries.ShouldContain(e =>
|
||||
@@ -151,7 +151,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
// key (dev / parity rig). The resolver must accept it AND suppress the
|
||||
// warning so production logs aren't polluted on a deliberate dev choice.
|
||||
var logger = new CaptureLogger();
|
||||
var key = await GalaxySecretRef.ResolveApiKeyAsync("dev:plain-text-key", FakeResolver(), logger);
|
||||
var key = await GalaxySecretRef.ResolveApiKeyAsync("dev:plain-text-key", FakeResolver(), logger, TestContext.Current.CancellationToken);
|
||||
|
||||
key.ShouldBe("plain-text-key");
|
||||
logger.Entries.ShouldNotContain(e => e.Level == LogLevel.Warning);
|
||||
@@ -166,7 +166,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
try
|
||||
{
|
||||
var logger = new CaptureLogger();
|
||||
await GalaxySecretRef.ResolveApiKeyAsync($"env:{name}", FakeResolver(), logger);
|
||||
await GalaxySecretRef.ResolveApiKeyAsync($"env:{name}", FakeResolver(), logger, TestContext.Current.CancellationToken);
|
||||
logger.Entries.ShouldNotContain(e => e.Level == LogLevel.Warning);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -222,7 +222,7 @@ public sealed class GalaxyDriverFactoryTests
|
||||
var firstStamp = driver.GetHealth().LastSuccessfulRead!.Value;
|
||||
|
||||
// Force a measurable clock delta so the comparison is stable on fast machines.
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
await driver.ReinitializeAsync(equivalentConfig, CancellationToken.None);
|
||||
|
||||
driver.GetHealth().State.ShouldBe(DriverState.Healthy);
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ public sealed class HostStatusAggregatorTests
|
||||
}
|
||||
})).ToArray();
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
Task.WaitAll(tasks, TestContext.Current.CancellationToken);
|
||||
agg.Snapshot().Count.ShouldBeLessThanOrEqualTo(32);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ public sealed class EventPumpBoundedChannelTests
|
||||
// Stage 1 — one event, then wait for the dispatcher to take it and block. After this the
|
||||
// channel is provably empty and exactly one event is in flight inside the handler.
|
||||
await subscriber.EmitAsync(itemHandle: 7, value: 0);
|
||||
await handlerEntered.Task.WaitAsync(Budget);
|
||||
await handlerEntered.Task.WaitAsync(Budget, TestContext.Current.CancellationToken);
|
||||
|
||||
// Stage 2 — the rest. `channelCapacity` are accepted; the remainder must be dropped.
|
||||
for (var i = 1; i < totalEvents; i++)
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ public sealed class EventPumpStreamFaultTests
|
||||
oldPump.OnDataChange += (_, _) => staleObserved = true;
|
||||
oldPump.Start();
|
||||
faulted.FaultStream(new IOException("simulated gateway transport drop"));
|
||||
await Task.Delay(100);
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken);
|
||||
|
||||
// In-place Start() after a fault is a no-op — the loop task is non-null but done.
|
||||
oldPump.Start();
|
||||
|
||||
+3
-3
@@ -324,7 +324,7 @@ public sealed class GalaxyDriverFixRound1Tests
|
||||
// Never leave the teardown parked: it holds the lifecycle gate the await-using dispose needs.
|
||||
sub.ReleaseUnsubscribe();
|
||||
}
|
||||
await shutdown.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await shutdown.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
driver.HasEventPumpForTests.ShouldBeFalse("no pump may survive the teardown");
|
||||
driver.TrackedSubscriptionCountForTests.ShouldBe(0);
|
||||
|
||||
@@ -354,7 +354,7 @@ public sealed class GalaxyDriverFixRound1Tests
|
||||
};
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
sw.Stop();
|
||||
|
||||
closeEntered.Task.IsCompleted.ShouldBeTrue("positive control: the close was attempted");
|
||||
@@ -398,7 +398,7 @@ public sealed class GalaxyDriverFixRound1Tests
|
||||
driver.TrackedSubscriptionCountForTests.ShouldBe(0);
|
||||
|
||||
sub.ReleaseSubscribe();
|
||||
await replay.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await replay.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
|
||||
driver.TrackedSubscriptionCountForTests.ShouldBe(0, "a Rebind after the teardown would resurrect dead handles");
|
||||
}
|
||||
|
||||
+11
-11
@@ -84,14 +84,14 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
try
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget);
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget, TestContext.Current.CancellationToken);
|
||||
sw.Stop();
|
||||
|
||||
driver.GetHealth().State.ShouldBe(DriverState.Healthy);
|
||||
await Entered(platforms.Entered, "positive control: the seed fetch was started");
|
||||
sw.Elapsed.ShouldBeLessThan(InitBudget);
|
||||
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
var token = await platforms.ParkedToken.Task;
|
||||
token.IsCancellationRequested.ShouldBeTrue("the teardown must cancel a seed still in flight");
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
var platforms = new ThrowingPlatformSource(new OperationCanceledException("gateway deadline"));
|
||||
await using var driver = new GalaxyDriver("drv", Opts(), hierarchySource: null, subscriber: sub, platformSource: platforms);
|
||||
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget);
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget, TestContext.Current.CancellationToken);
|
||||
|
||||
driver.GetHealth().State.ShouldBe(DriverState.Healthy);
|
||||
await WaitUntilAsync(() => platforms.Calls == 1, "positive control: the seed fetch ran");
|
||||
@@ -136,7 +136,7 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
try
|
||||
{
|
||||
initCts.CancelAfter(TimeSpan.FromMilliseconds(300)); // the Polly Initialize timeout, shrunk
|
||||
await driver.InitializeAsync("{}", initCts.Token).WaitAsync(InitBudget);
|
||||
await driver.InitializeAsync("{}", initCts.Token).WaitAsync(InitBudget, TestContext.Current.CancellationToken);
|
||||
driver.GetHealth().State.ShouldBe(DriverState.Healthy);
|
||||
|
||||
await Entered(platforms.Entered, "the seed fetch must be in flight");
|
||||
@@ -162,7 +162,7 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
await using var driver = new GalaxyDriver("drv", Opts(), hierarchySource: null, subscriber: sub, platformSource: platforms);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget);
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget, TestContext.Current.CancellationToken);
|
||||
await Entered(platforms.Entered, "the seed must be parked");
|
||||
driver.ProbeWatcherForTests.ShouldNotBeNull().WatchedPlatforms.ShouldBeEmpty();
|
||||
}
|
||||
@@ -197,11 +197,11 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
await using var driver = new GalaxyDriver("drv", Opts(), hierarchySource: null, subscriber: sub, platformSource: platforms);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget);
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget, TestContext.Current.CancellationToken);
|
||||
await Entered(platforms.Entered, "runtime 1's seed must be parked");
|
||||
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget);
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
await driver.InitializeAsync("{}", CancellationToken.None).WaitAsync(InitBudget, TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -242,7 +242,7 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
await watcher.StopAsync(CancellationToken.None);
|
||||
|
||||
sub.ReleaseSubscribe();
|
||||
await sync.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await sync.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
|
||||
registry.Contains(SubscriptionRegistry.ProbeSubscriptionId).ShouldBeFalse("a stopped watcher must not re-register");
|
||||
watcher.WatchedPlatforms.ShouldBeEmpty();
|
||||
@@ -271,7 +271,7 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
try
|
||||
{
|
||||
await Entered(sub.SubscribeEntered, "the subscribe must be parked in SubscribeBulk");
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
driver.TrackedSubscriptionCountForTests.ShouldBe(0);
|
||||
}
|
||||
finally
|
||||
@@ -299,7 +299,7 @@ public sealed class GalaxyDriverFixRound2Tests
|
||||
try
|
||||
{
|
||||
await Entered(sub.SubscribeEntered, "the read must be parked in SubscribeBulk");
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
await driver.ShutdownAsync(CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
+2
-2
@@ -197,7 +197,7 @@ public sealed class GalaxyDriverProbeWiringTests
|
||||
};
|
||||
await driver.InitializeAsync("{}", CancellationToken.None);
|
||||
var initStamp = driver.GetHealth().LastSuccessfulRead.ShouldNotBeNull();
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
|
||||
var delivered = 0;
|
||||
driver.OnDataChange += (_, _) => Interlocked.Increment(ref delivered);
|
||||
@@ -209,7 +209,7 @@ public sealed class GalaxyDriverProbeWiringTests
|
||||
var first = driver.GetHealth().LastSuccessfulRead.ShouldNotBeNull();
|
||||
first.ShouldBeGreaterThan(initStamp, "data on the stream is liveness evidence");
|
||||
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
await sub.EmitDoubleAsync(handle, 2.0);
|
||||
await WaitUntilAsync(() => Volatile.Read(ref delivered) == 2, "second event must be delivered");
|
||||
driver.GetHealth().LastSuccessfulRead.ShouldBe(first, "inside the throttle window the stamp holds still");
|
||||
|
||||
+4
-4
@@ -109,7 +109,7 @@ public sealed class GalaxyDriverReadTests
|
||||
|
||||
var readTask = driver.ReadAsync(["Tank.Level"], CancellationToken.None);
|
||||
// Push the "initial value" event the gw would emit immediately after SubscribeBulk.
|
||||
await Task.Delay(50); // give SubscribeBulk a beat to register + handler to attach
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken); // give SubscribeBulk a beat to register + handler to attach
|
||||
var itemHandle = subscriber.Map["Tank.Level"];
|
||||
await subscriber.EmitOnDataChangeAsync(itemHandle, 42.0);
|
||||
|
||||
@@ -131,7 +131,7 @@ public sealed class GalaxyDriverReadTests
|
||||
"g", Opts(), hierarchySource: null, dataReader: null, dataWriter: null, subscriber: subscriber);
|
||||
|
||||
var readTask = driver.ReadAsync(["Good", "Bad"], CancellationToken.None);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
await subscriber.EmitOnDataChangeAsync(subscriber.Map["Good"], 1.0);
|
||||
|
||||
var result = await readTask;
|
||||
@@ -197,7 +197,7 @@ public sealed class GalaxyDriverReadTests
|
||||
};
|
||||
|
||||
var result = await driver.ReadAsync(["Slow.A", "Slow.B"], CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Count.ShouldBe(2);
|
||||
result.ShouldAllBe(r => r.StatusCode == StatusCodeMap.BadTimeout);
|
||||
@@ -205,7 +205,7 @@ public sealed class GalaxyDriverReadTests
|
||||
// The RPC finally completes after the read gave up; its handles must not leak.
|
||||
subscriber.Gate.SetResult();
|
||||
var deadline = DateTime.UtcNow.AddSeconds(10);
|
||||
while (subscriber.UnsubscribedHandles.Count < 2 && DateTime.UtcNow < deadline) await Task.Delay(20);
|
||||
while (subscriber.UnsubscribedHandles.Count < 2 && DateTime.UtcNow < deadline) await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
subscriber.UnsubscribedHandles.OrderBy(h => h).ShouldBe([101, 102]);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -257,7 +257,7 @@ public sealed class GalaxyDriverShutdownTests
|
||||
await source.PushAsync(1, new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
|
||||
await source.PushAsync(2, new DateTimeOffset(2026, 1, 2, 0, 0, 0, TimeSpan.Zero));
|
||||
await WaitUntilAsync(() => Volatile.Read(ref raised) >= 1, "positive control: a deploy change must raise rediscovery");
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
Volatile.Read(ref raised).ShouldBe(1, "stacked watchers would each raise for the same deploy");
|
||||
|
||||
await driver.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
+2
-2
@@ -195,7 +195,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
// Good has an itemHandle; Bad doesn't (item handle 0). An event with handle 0
|
||||
// must NOT be dispatched (no subscribers registered against it).
|
||||
await subscriber.EmitOnDataChangeAsync(itemHandle: 0, value: 999.0);
|
||||
await Task.Delay(50); // give the pump a chance
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken); // give the pump a chance
|
||||
|
||||
captured.ShouldBeEmpty();
|
||||
}
|
||||
@@ -219,7 +219,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
var captured = new List<DataChangeEventArgs>();
|
||||
driver.OnDataChange += (_, args) => captured.Add(args);
|
||||
await subscriber.EmitOnDataChangeAsync(itemHandle, 11.0);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
captured.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ public sealed class GalaxyMxSessionCloseTests
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
await session.CloseAsync(cts.Token).WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await session.CloseAsync(cts.Token).WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
sw.Stop();
|
||||
|
||||
sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(5));
|
||||
|
||||
+4
-4
@@ -88,7 +88,7 @@ public sealed class GatewayGalaxyDataWriterConcurrencyTests
|
||||
var writes = Enumerable.Range(0, 16).Select(i => new WriteRequest($"Tag{i}.PV", (double)i)).ToArray();
|
||||
|
||||
var results = await NewWriter().WriteBatchForTestAsync(ops, writes, Free, CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
|
||||
ops.Peak.ShouldBe(GatewayGalaxyDataWriter.MaxConcurrentWriteLanes,
|
||||
"first-touch lanes must overlap (serial = 1) and stay bounded (unbounded > 8)");
|
||||
@@ -112,7 +112,7 @@ public sealed class GatewayGalaxyDataWriterConcurrencyTests
|
||||
};
|
||||
|
||||
var results = await NewWriter().WriteBatchForTestAsync(ops, writes, Free, CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(4);
|
||||
results.ShouldAllBe(r => r.StatusCode == StatusCodeMap.Good);
|
||||
@@ -146,7 +146,7 @@ public sealed class GatewayGalaxyDataWriterConcurrencyTests
|
||||
};
|
||||
|
||||
var results = await NewWriter().WriteBatchForTestAsync(ops, writes, Free, CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
|
||||
results.Select(r => r.StatusCode).ShouldBe(
|
||||
[StatusCodeMap.Good, StatusCodeMap.BadCommunicationError, StatusCodeMap.Good],
|
||||
@@ -161,7 +161,7 @@ public sealed class GatewayGalaxyDataWriterConcurrencyTests
|
||||
var writes = new[] { new WriteRequest("X.PV", 1.0), new WriteRequest("X.PV", 2.0) };
|
||||
|
||||
var results = await NewWriter().WriteBatchForTestAsync(ops, writes, Free, CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs));
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WaitMs), TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(2);
|
||||
ops.Peak.ShouldBe(1);
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ public sealed class ReconnectSupervisorTests
|
||||
options: FastBackoff());
|
||||
|
||||
sup.ReportTransportFailure(new IOException("kick off"));
|
||||
await Task.Delay(50); // let the loop start the long reopen
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken); // let the loop start the long reopen
|
||||
Should.NotThrow(() => sup.Dispose());
|
||||
cancelled.ShouldBeTrue();
|
||||
}
|
||||
|
||||
+7
-7
@@ -33,11 +33,11 @@ public sealed class AlarmWriterTests(SqlHistorianFixture fx)
|
||||
(await w.WriteBatchAsync([Ev(kind: "Cleared")], CancellationToken.None)).ShouldBe([HistorianWriteOutcome.Ack]);
|
||||
|
||||
await using var c = new SqlConnection(fx.ConnectionString);
|
||||
await c.OpenAsync();
|
||||
await c.OpenAsync(TestContext.Current.CancellationToken);
|
||||
await using var cmd = c.CreateCommand();
|
||||
cmd.CommandText = $"SELECT SourceName, AlarmId, AlarmName, AlarmTypeName, Severity, EventKind, Message, UserName, Comment, EventTimeUtc, ReceivedTimeUtc FROM [{s}].Event WHERE EventKind='Activated'";
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
(await r.ReadAsync()).ShouldBeTrue();
|
||||
await using var r = await cmd.ExecuteReaderAsync(TestContext.Current.CancellationToken);
|
||||
(await r.ReadAsync(TestContext.Current.CancellationToken)).ShouldBeTrue();
|
||||
r.GetString(0).ShouldBe("Plant/Line/Eq");
|
||||
r.GetString(1).ShouldBe("alarm-1");
|
||||
r.GetString(2).ShouldBe("HiTemp");
|
||||
@@ -48,10 +48,10 @@ public sealed class AlarmWriterTests(SqlHistorianFixture fx)
|
||||
r.GetString(8).ShouldBe("c");
|
||||
SqlHistorianConnection.GetUtc(r, 9).Ticks.ShouldBe(T.Ticks);
|
||||
(DateTime.UtcNow - SqlHistorianConnection.GetUtc(r, 10)).ShouldBeLessThan(TimeSpan.FromMinutes(1));
|
||||
(await r.ReadAsync()).ShouldBeFalse();
|
||||
(await r.ReadAsync(TestContext.Current.CancellationToken)).ShouldBeFalse();
|
||||
await r.CloseAsync();
|
||||
cmd.CommandText = $"SELECT COUNT(*) FROM [{s}].Event";
|
||||
Convert.ToInt32(await cmd.ExecuteScalarAsync()).ShouldBe(2);
|
||||
Convert.ToInt32(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken)).ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,10 +72,10 @@ public sealed class AlarmWriterTests(SqlHistorianFixture fx)
|
||||
var rs = await Task.WhenAll(Enumerable.Range(0, 12).Select(_ => Task.Run(() => w.WriteBatchAsync([Ev()], CancellationToken.None))));
|
||||
rs.ShouldAllBe(x => x[0] == HistorianWriteOutcome.Ack);
|
||||
await using var c = new SqlConnection(fx.ConnectionString);
|
||||
await c.OpenAsync();
|
||||
await c.OpenAsync(TestContext.Current.CancellationToken);
|
||||
await using var cmd = c.CreateCommand();
|
||||
cmd.CommandText = $"SELECT COUNT(*) FROM [{s}].Event WHERE EventKind='Activated'";
|
||||
Convert.ToInt32(await cmd.ExecuteScalarAsync()).ShouldBe(1);
|
||||
Convert.ToInt32(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken)).ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+2
-2
@@ -90,10 +90,10 @@ public sealed class ProvisioningTests(SqlHistorianFixture fx)
|
||||
}
|
||||
|
||||
await using var c = new SqlConnection(fx.ConnectionString);
|
||||
await c.OpenAsync();
|
||||
await c.OpenAsync(TestContext.Current.CancellationToken);
|
||||
await using var cmd = c.CreateCommand();
|
||||
cmd.CommandText = $"SELECT COUNT(*) FROM [{s}].Tag";
|
||||
Convert.ToInt32(await cmd.ExecuteScalarAsync()).ShouldBe(20 * 6 + 1);
|
||||
Convert.ToInt32(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken)).ShouldBe(20 * 6 + 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+11
-11
@@ -117,11 +117,11 @@ public sealed class RetentionTests(SqlHistorianFixture fx)
|
||||
|
||||
// Hold the same session applock the purger takes, on a separate connection.
|
||||
await using var holder = new SqlConnection(fx.ConnectionString);
|
||||
await holder.OpenAsync();
|
||||
await holder.OpenAsync(TestContext.Current.CancellationToken);
|
||||
await using (var cmd = holder.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"EXEC sp_getapplock @Resource = N'OtOpcUa.Historian.Retention.{s}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = 0;";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var purger = new SqlServerHistorianRetention(c, b, TimeSpan.FromHours(1), 30, 4000, time: new FixedTime(Now));
|
||||
@@ -131,7 +131,7 @@ public sealed class RetentionTests(SqlHistorianFixture fx)
|
||||
await using (var cmd = holder.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"EXEC sp_releaseapplock @Resource = N'OtOpcUa.Historian.Retention.{s}', @LockOwner = 'Session';";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
(await purger.PurgeAsync(CancellationToken.None)).ShouldNotBeEmpty();
|
||||
(await CountAsync($"SELECT COUNT(*) FROM [{s}].Sample")).ShouldBe(6);
|
||||
@@ -187,20 +187,20 @@ public sealed class RetentionTests(SqlHistorianFixture fx)
|
||||
|
||||
// A second session holds an exclusive table lock, so the purger's first DELETE blocks server-side.
|
||||
await using var holder = new SqlConnection(fx.ConnectionString);
|
||||
await holder.OpenAsync();
|
||||
await using var tx = (SqlTransaction)await holder.BeginTransactionAsync();
|
||||
await holder.OpenAsync(TestContext.Current.CancellationToken);
|
||||
await using var tx = (SqlTransaction)await holder.BeginTransactionAsync(TestContext.Current.CancellationToken);
|
||||
await using (var cmd = holder.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tx;
|
||||
cmd.CommandText = $"SELECT COUNT(*) FROM [{s}].Sample WITH (TABLOCKX, HOLDLOCK)";
|
||||
await cmd.ExecuteScalarAsync();
|
||||
await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
short holderSpid;
|
||||
await using (var cmd = holder.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tx;
|
||||
cmd.CommandText = "SELECT CAST(@@SPID AS smallint)";
|
||||
holderSpid = (short)(await cmd.ExecuteScalarAsync())!;
|
||||
holderSpid = (short)(await cmd.ExecuteScalarAsync(TestContext.Current.CancellationToken))!;
|
||||
}
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
@@ -215,7 +215,7 @@ public sealed class RetentionTests(SqlHistorianFixture fx)
|
||||
{
|
||||
purge.IsCompleted.ShouldBeFalse("the purge finished without ever blocking on the holder's table lock");
|
||||
DateTime.UtcNow.ShouldBeLessThan(deadline, "the purge's DELETE never blocked behind the holder");
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
}
|
||||
cts.Cancel();
|
||||
|
||||
@@ -223,7 +223,7 @@ public sealed class RetentionTests(SqlHistorianFixture fx)
|
||||
var ex = await Should.ThrowAsync<Exception>(() => purge);
|
||||
(ex is OperationCanceledException or SqlException).ShouldBeTrue($"unexpected {ex.GetType().Name}: {ex.Message}");
|
||||
|
||||
await tx.RollbackAsync();
|
||||
await tx.RollbackAsync(TestContext.Current.CancellationToken);
|
||||
(await LockIsFreeAsync(s)).ShouldBeTrue();
|
||||
(await CountAsync($"SELECT COUNT(*) FROM [{s}].Sample")).ShouldBe(21);
|
||||
}
|
||||
@@ -301,11 +301,11 @@ public sealed class RetentionTests(SqlHistorianFixture fx)
|
||||
await SeedAsync(b, s);
|
||||
|
||||
await using var holder = new SqlConnection(fx.ConnectionString);
|
||||
await holder.OpenAsync();
|
||||
await holder.OpenAsync(TestContext.Current.CancellationToken);
|
||||
await using (var cmd = holder.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"EXEC sp_getapplock @Resource = N'OtOpcUa.Historian.Retention.{s}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = 0;";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var log = new CapturingLogger();
|
||||
|
||||
+9
-9
@@ -44,12 +44,12 @@ public sealed class SchemaBootstrapIntegrationTests(SqlHistorianFixture fx)
|
||||
await Boot(Opts(s)).EnsureAsync(CancellationToken.None);
|
||||
var script = new StreamReader(typeof(SqlServerSchemaBootstrapper).Assembly.GetManifestResourceStream("Schema.001_initial.sql")!).ReadToEnd();
|
||||
await using var c = new SqlConnection(fx.ConnectionString);
|
||||
await c.OpenAsync();
|
||||
await c.OpenAsync(TestContext.Current.CancellationToken);
|
||||
foreach (var b in SqlServerSchemaBootstrapper.SplitBatches(script.Replace("$(Schema)", s)))
|
||||
{
|
||||
await using var cmd = c.CreateCommand();
|
||||
cmd.CommandText = b;
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
(await ScalarAsync($"SELECT COUNT(*) FROM [{s}].SchemaVersion")).ShouldBe(SqlServerSchemaBootstrapper.ModifiedTrailVersion);
|
||||
}
|
||||
@@ -68,17 +68,17 @@ public sealed class SchemaBootstrapIntegrationTests(SqlHistorianFixture fx)
|
||||
var v1 = new StreamReader(typeof(SqlServerSchemaBootstrapper).Assembly.GetManifestResourceStream("Schema.001_initial.sql")!).ReadToEnd();
|
||||
await using (var c = new SqlConnection(fx.ConnectionString))
|
||||
{
|
||||
await c.OpenAsync();
|
||||
await c.OpenAsync(TestContext.Current.CancellationToken);
|
||||
foreach (var b in SqlServerSchemaBootstrapper.SplitBatches(v1.Replace("$(Schema)", s)))
|
||||
{
|
||||
await using var cmd = c.CreateCommand();
|
||||
cmd.CommandText = b;
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
await using var seed = c.CreateCommand();
|
||||
seed.CommandText = $"INSERT [{s}].Tag (Name, DataType, CreatedUtc, UpdatedUtc) VALUES (N'old', 1, SYSUTCDATETIME(), SYSUTCDATETIME()); " +
|
||||
$"INSERT [{s}].Sample (TagId, SourceTimeUtc, ValueKind, NumValue, StatusCode, ServerTimeUtc) SELECT TagId, '2026-01-01', 1, 4.0, 0, SYSUTCDATETIME() FROM [{s}].Tag";
|
||||
await seed.ExecuteNonQueryAsync();
|
||||
await seed.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
(await ScalarAsync($"SELECT COUNT(*) FROM sys.tables WHERE name = 'SampleModified' AND schema_id = SCHEMA_ID('{s}')")).ShouldBe(0);
|
||||
|
||||
@@ -178,7 +178,7 @@ public sealed class SchemaBootstrapIntegrationTests(SqlHistorianFixture fx)
|
||||
start.Wait();
|
||||
return await cat.GetOrCreateTagIdAsync(name, 1, CancellationToken.None);
|
||||
})).ToArray();
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
start.Set();
|
||||
(await Task.WhenAll(ids)).Distinct().Count().ShouldBe(1);
|
||||
(await ScalarAsync($"SELECT COUNT(*) FROM [{s}].Tag WHERE Name = N'{name}'")).ShouldBe(1);
|
||||
@@ -203,10 +203,10 @@ public sealed class SchemaBootstrapIntegrationTests(SqlHistorianFixture fx)
|
||||
cmd.Parameters.AddWithValue("@id", tagId);
|
||||
conn.AddStructured(cmd, "@rows", "SampleRow",
|
||||
SampleRowMapper.ToSampleRecords([new HistorianSampleRow(t, ZB.MOM.WW.OtOpcUa.Core.Abstractions.HistorianValue.FromDouble(1), 0u)]));
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
await cmd.ExecuteNonQueryAsync(TestContext.Current.CancellationToken);
|
||||
await using var q = conn.CreateCommand(c, $"SELECT SourceTimeUtc FROM {conn.Q("Sample")}");
|
||||
await using var r = await q.ExecuteReaderAsync();
|
||||
(await r.ReadAsync()).ShouldBeTrue();
|
||||
await using var r = await q.ExecuteReaderAsync(TestContext.Current.CancellationToken);
|
||||
(await r.ReadAsync(TestContext.Current.CancellationToken)).ShouldBeTrue();
|
||||
SqlHistorianConnection.GetUtc(r, 0).Ticks.ShouldBe(t.Ticks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,14 +74,14 @@ public sealed class MTConnectDriverProbeTests
|
||||
public async Task Probe_on_unreachable_agent_returns_not_ok_and_does_not_throw()
|
||||
{
|
||||
var probe = new MTConnectDriverProbe();
|
||||
var r = await probe.ProbeAsync("{\"agentUri\":\"http://127.0.0.1:1/\"}", TimeSpan.FromMilliseconds(300), default);
|
||||
var r = await probe.ProbeAsync("{\"agentUri\":\"http://127.0.0.1:1/\"}", TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
r.Ok.ShouldBeFalse();
|
||||
r.Message.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Probe_on_blank_config_returns_not_ok()
|
||||
=> (await new MTConnectDriverProbe().ProbeAsync("{}", TimeSpan.FromSeconds(1), default)).Ok.ShouldBeFalse();
|
||||
=> (await new MTConnectDriverProbe().ProbeAsync("{}", TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken)).Ok.ShouldBeFalse();
|
||||
|
||||
// ── unparseable JSON ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ public sealed class ModbusBatchSubscriptionTests
|
||||
// it as a batch item, but the driver's deadband suppresses the publish — so neither
|
||||
// event fires and no batch is raised for that cycle at all.
|
||||
fake.HoldingRegisters[0] = 102;
|
||||
await Task.Delay(600);
|
||||
await Task.Delay(600, TestContext.Current.CancellationToken);
|
||||
perTag.Count.ShouldBe(1, "under-threshold change is suppressed on the per-tag event");
|
||||
batches.Count.ShouldBe(1, "a cycle whose only change is deadband-suppressed raises no batch");
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ public sealed class ModbusDriverProbeTests
|
||||
var result = await _probe.ProbeAsync(Config(port), QuickTimeout, CancellationToken.None);
|
||||
|
||||
// The server task should have completed (or close enough).
|
||||
await serverTask.WaitAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
||||
await serverTask.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Message.ShouldNotBeNull();
|
||||
@@ -211,7 +211,7 @@ public sealed class ModbusDriverProbeTests
|
||||
|
||||
var result = await _probe.ProbeAsync(Config(port), QuickTimeout, CancellationToken.None);
|
||||
|
||||
await serverTask.WaitAsync(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
|
||||
await serverTask.WaitAsync(TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Message.ShouldBe("Modbus FC03 OK");
|
||||
@@ -241,7 +241,7 @@ public sealed class ModbusDriverProbeTests
|
||||
|
||||
var result = await _probe.ProbeAsync(Config(port), QuickTimeout, CancellationToken.None);
|
||||
|
||||
await serverTask.WaitAsync(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
|
||||
await serverTask.WaitAsync(TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Message.ShouldNotBeNull();
|
||||
|
||||
@@ -245,7 +245,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
// new sends. Tolerate at most one in-flight send straddling the cancel.
|
||||
fake.DisposeCount.ShouldBe(1);
|
||||
var sendsAfterDispose = Interlocked.CompareExchange(ref fake.SendCount, 0, 0);
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
var sendsAtRest = Interlocked.CompareExchange(ref fake.SendCount, 0, 0);
|
||||
(sendsAtRest - sendsAfterDispose).ShouldBeLessThanOrEqualTo(1, "background loops must stop after DisposeAsync");
|
||||
}
|
||||
@@ -267,7 +267,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
// Spin up a polled subscription; the PollGroupEngine schedules a background Task that
|
||||
// will keep issuing SendAsync until either Unsubscribe or DisposeAsync stops it.
|
||||
var handle = await drv.SubscribeAsync(["A"], TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
await Task.Delay(250);
|
||||
await Task.Delay(250, TestContext.Current.CancellationToken);
|
||||
var beforeDispose = Interlocked.CompareExchange(ref fake.SendCount, 0, 0);
|
||||
beforeDispose.ShouldBeGreaterThan(0);
|
||||
|
||||
@@ -275,7 +275,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
await drv.DisposeAsync();
|
||||
|
||||
var atDispose = Interlocked.CompareExchange(ref fake.SendCount, 0, 0);
|
||||
await Task.Delay(400);
|
||||
await Task.Delay(400, TestContext.Current.CancellationToken);
|
||||
var atRest = Interlocked.CompareExchange(ref fake.SendCount, 0, 0);
|
||||
|
||||
(atRest - atDispose).ShouldBeLessThanOrEqualTo(1,
|
||||
@@ -353,7 +353,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
var drv = new ModbusDriver(opts, "modbus-1", _ => Interlocked.Increment(ref handed) == 1 ? first : second);
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
Interlocked.CompareExchange(ref first.SendCount, 0, 0).ShouldBeGreaterThan(0,
|
||||
"precondition: the first initialize's probe loop must actually be sending");
|
||||
|
||||
@@ -361,7 +361,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
|
||||
// Tolerate one in-flight send straddling the cancel; a leaked loop produces ~16 in 400ms.
|
||||
var atReinit = Interlocked.CompareExchange(ref first.SendCount, 0, 0);
|
||||
await Task.Delay(400);
|
||||
await Task.Delay(400, TestContext.Current.CancellationToken);
|
||||
var atRest = Interlocked.CompareExchange(ref first.SendCount, 0, 0);
|
||||
(atRest - atReinit).ShouldBeLessThanOrEqualTo(1,
|
||||
"the previous initialize's probe loop must be cancelled by the next initialize");
|
||||
@@ -442,7 +442,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
Activator.CreateInstance(stateType, nonPublic: true)!);
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
Interlocked.CompareExchange(ref first.SendCount, 0, 0).ShouldBeGreaterThan(0,
|
||||
"precondition: the seeded prohibition must make the re-probe loop actually send");
|
||||
|
||||
@@ -464,7 +464,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
prohibitions.Count.ShouldBe(1, "a re-initialize must not clear the prohibition set");
|
||||
|
||||
var atReinit = Interlocked.CompareExchange(ref second.SendCount, 0, 0);
|
||||
await Task.Delay(400);
|
||||
await Task.Delay(400, TestContext.Current.CancellationToken);
|
||||
var atRest = Interlocked.CompareExchange(ref second.SendCount, 0, 0);
|
||||
(atRest - atReinit).ShouldBeLessThanOrEqualTo(1,
|
||||
"the previous initialize's re-probe loop must be cancelled by the next initialize");
|
||||
@@ -723,7 +723,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
{
|
||||
try { while (!cts.IsCancellationRequested) await drv.ReadAsync(["A"], CancellationToken.None); }
|
||||
catch (Exception ex) { faults.Enqueue(ex); }
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
var reader = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
@@ -737,7 +737,7 @@ public sealed class ModbusLifecycleHygieneTests(ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { faults.Enqueue(ex); }
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
await Task.WhenAll(writer, reader);
|
||||
faults.ShouldBeEmpty();
|
||||
|
||||
@@ -89,11 +89,11 @@ public sealed class ModbusProbeTests
|
||||
|
||||
// Wait for the first probe to complete.
|
||||
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
while (fake.ProbeCount == 0 && DateTime.UtcNow < deadline) await Task.Delay(25);
|
||||
while (fake.ProbeCount == 0 && DateTime.UtcNow < deadline) await Task.Delay(25, TestContext.Current.CancellationToken);
|
||||
|
||||
// Then wait for the event to actually arrive.
|
||||
deadline = DateTime.UtcNow + TimeSpan.FromSeconds(1);
|
||||
while (transitions.Count == 0 && DateTime.UtcNow < deadline) await Task.Delay(25);
|
||||
while (transitions.Count == 0 && DateTime.UtcNow < deadline) await Task.Delay(25, TestContext.Current.CancellationToken);
|
||||
|
||||
transitions.Count.ShouldBeGreaterThanOrEqualTo(1);
|
||||
transitions.TryDequeue(out var t).ShouldBeTrue();
|
||||
@@ -169,7 +169,7 @@ public sealed class ModbusProbeTests
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await WaitForStateAsync(drv, HostState.Running, TimeSpan.FromSeconds(2));
|
||||
await Task.Delay(500); // several more probe ticks, all successful — state shouldn't thrash
|
||||
await Task.Delay(500, TestContext.Current.CancellationToken); // several more probe ticks, all successful — state shouldn't thrash
|
||||
|
||||
transitions.Count.ShouldBe(1); // only the initial Unknown→Running
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
@@ -184,7 +184,7 @@ public sealed class ModbusProbeTests
|
||||
drv.OnHostStatusChanged += (_, e) => transitions.Enqueue(e);
|
||||
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
|
||||
transitions.Count.ShouldBe(0);
|
||||
drv.GetHostStatuses()[0].State.ShouldBe(HostState.Unknown);
|
||||
@@ -206,7 +206,7 @@ public sealed class ModbusProbeTests
|
||||
|
||||
var before = fake.ProbeCount;
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
await Task.Delay(400);
|
||||
await Task.Delay(400, TestContext.Current.CancellationToken);
|
||||
|
||||
// A handful of in-flight ticks may complete after shutdown in a narrow race; the
|
||||
// contract is that the loop stops scheduling new ones. Tolerate ≤1 extra.
|
||||
|
||||
@@ -86,7 +86,7 @@ public sealed class ModbusSubscriptionTests
|
||||
drv.OnDataChange += (_, e) => events.Enqueue(e);
|
||||
|
||||
var handle = await drv.SubscribeAsync(["Level"], TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
await Task.Delay(500); // ~5 poll cycles at 100ms, value stable the whole time
|
||||
await Task.Delay(500, TestContext.Current.CancellationToken); // ~5 poll cycles at 100ms, value stable the whole time
|
||||
await drv.UnsubscribeAsync(handle, CancellationToken.None);
|
||||
|
||||
events.Count.ShouldBe(1); // only the initial-data push, no change events after
|
||||
@@ -129,7 +129,7 @@ public sealed class ModbusSubscriptionTests
|
||||
|
||||
var countAfterUnsub = events.Count;
|
||||
fake.HoldingRegisters[0] = 999; // would trigger a change if still polling
|
||||
await Task.Delay(400);
|
||||
await Task.Delay(400, TestContext.Current.CancellationToken);
|
||||
events.Count.ShouldBe(countAfterUnsub);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public sealed class ModbusSubscriptionTests
|
||||
drv.OnDataChange += (_, e) => events.Enqueue(e);
|
||||
|
||||
var handle = await drv.SubscribeAsync(["Level"], TimeSpan.FromMilliseconds(10), CancellationToken.None);
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
await drv.UnsubscribeAsync(handle, CancellationToken.None);
|
||||
|
||||
events.Count.ShouldBe(1);
|
||||
@@ -227,7 +227,7 @@ public sealed class ModbusSubscriptionTests
|
||||
{
|
||||
for (var i = 0; i < tagCount; i++)
|
||||
fake.HoldingRegisters[i] = (ushort)(fake.HoldingRegisters[i] + 5);
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
foreach (var h in handles)
|
||||
|
||||
@@ -268,8 +268,8 @@ public sealed class MqttDriverDiscoveryTests
|
||||
await release.Task;
|
||||
};
|
||||
|
||||
var initialize = Task.Run(() => driver.InitializeAsync("", CancellationToken.None));
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var initialize = Task.Run(() => driver.InitializeAsync("", CancellationToken.None), TestContext.Current.CancellationToken);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
var dispose = driver.DisposeAsync().AsTask();
|
||||
|
||||
@@ -282,7 +282,7 @@ public sealed class MqttDriverDiscoveryTests
|
||||
release.SetResult();
|
||||
await Should.ThrowAsync<Exception>(() => initialize); // connect refused on the closed port
|
||||
|
||||
await dispose.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await dispose.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
dispose.IsCompletedSuccessfully.ShouldBeTrue();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -267,7 +267,7 @@ public sealed class MqttDriverSessionLifecycleTests
|
||||
await release.Task;
|
||||
};
|
||||
|
||||
var initialize = Task.Run(() => driver.InitializeAsync(Json(options), TestContext.Current.CancellationToken));
|
||||
var initialize = Task.Run(() => driver.InitializeAsync(Json(options), TestContext.Current.CancellationToken), TestContext.Current.CancellationToken);
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
// Times out on the gate (1 s) and tears down ungated.
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class MqttDriverWriteTests
|
||||
public async Task An_unknown_reference_is_refused_in_its_own_slot()
|
||||
{
|
||||
var results = await PlainDriver(PlainTag(Path, "oven/set"))
|
||||
.WriteAsync([new WriteRequest("Plant/Mqtt/dev1/NotATag", 1.0)], default);
|
||||
.WriteAsync([new WriteRequest("Plant/Mqtt/dev1/NotATag", 1.0)], TestContext.Current.CancellationToken);
|
||||
|
||||
results.Count.ShouldBe(1);
|
||||
results[0].StatusCode.ShouldBe(0x80340000u); // BadNodeIdUnknown
|
||||
@@ -54,7 +54,7 @@ public sealed class MqttDriverWriteTests
|
||||
// the operator simply never said where a write should go. The status has to say that, or the
|
||||
// only diagnosis available is "writes don't work".
|
||||
var results = await PlainDriver(PlainTag(ReadOnlyPath, writeTopic: null))
|
||||
.WriteAsync([new WriteRequest(ReadOnlyPath, 1.0)], default);
|
||||
.WriteAsync([new WriteRequest(ReadOnlyPath, 1.0)], TestContext.Current.CancellationToken);
|
||||
|
||||
results[0].StatusCode.ShouldBe(0x803B0000u); // BadNotWritable
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public sealed class MqttDriverWriteTests
|
||||
// replay hazard WriteIdempotent exists to prevent — and this would be the driver deciding it on
|
||||
// the operator's behalf.
|
||||
var results = await PlainDriver(PlainTag(Path, "oven/set"))
|
||||
.WriteAsync([new WriteRequest(Path, 42.5)], default);
|
||||
.WriteAsync([new WriteRequest(Path, 42.5)], TestContext.Current.CancellationToken);
|
||||
|
||||
results[0].StatusCode.ShouldBe(0x80050000u); // BadCommunicationError
|
||||
}
|
||||
@@ -78,13 +78,11 @@ public sealed class MqttDriverWriteTests
|
||||
// write in the same OPC UA request.
|
||||
var driver = PlainDriver(PlainTag(Path, "oven/set"), PlainTag(ReadOnlyPath, writeTopic: null));
|
||||
|
||||
var results = await driver.WriteAsync(
|
||||
[
|
||||
var results = await driver.WriteAsync([
|
||||
new WriteRequest("Plant/Mqtt/dev1/NotATag", 1.0),
|
||||
new WriteRequest(ReadOnlyPath, 1.0),
|
||||
new WriteRequest(Path, 42.5),
|
||||
],
|
||||
default);
|
||||
], TestContext.Current.CancellationToken);
|
||||
|
||||
// Order-preserving, one status per request, each naming its own distinct cause.
|
||||
results.Count.ShouldBe(3);
|
||||
@@ -95,7 +93,7 @@ public sealed class MqttDriverWriteTests
|
||||
|
||||
[Fact]
|
||||
public async Task An_empty_batch_is_answered_with_an_empty_result()
|
||||
=> (await PlainDriver().WriteAsync([], default)).ShouldBeEmpty();
|
||||
=> (await PlainDriver().WriteAsync([], TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAsync_rejects_a_null_batch()
|
||||
@@ -109,7 +107,7 @@ public sealed class MqttDriverWriteTests
|
||||
{
|
||||
var builder = new WriteCapturingBuilder();
|
||||
|
||||
await PlainDriver(PlainTag(Path, "oven/set")).DiscoverAsync(builder, default);
|
||||
await PlainDriver(PlainTag(Path, "oven/set")).DiscoverAsync(builder, TestContext.Current.CancellationToken);
|
||||
|
||||
builder.Variables.ShouldHaveSingleItem();
|
||||
builder.Variables[0].SecurityClass.ShouldBe(SecurityClassification.Operate);
|
||||
@@ -125,7 +123,7 @@ public sealed class MqttDriverWriteTests
|
||||
// refuses every write would be the silent-no-op failure in a new costume.
|
||||
var builder = new WriteCapturingBuilder();
|
||||
|
||||
await PlainDriver(PlainTag(ReadOnlyPath, writeTopic: null)).DiscoverAsync(builder, default);
|
||||
await PlainDriver(PlainTag(ReadOnlyPath, writeTopic: null)).DiscoverAsync(builder, TestContext.Current.CancellationToken);
|
||||
|
||||
builder.Variables[0].SecurityClass.ShouldBe(SecurityClassification.ViewOnly);
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ public sealed class S7DriverReconnectTests
|
||||
await WaitUntilAsync(() => factory.Created.Count >= 2, TimeSpan.FromSeconds(5));
|
||||
factory.Created.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||
var atFirstAttempt = factory.Created.Count;
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(900));
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(900), TestContext.Current.CancellationToken);
|
||||
|
||||
// Without the fix this is ~10+ (one 80 ms-cut attempt per tick); with it the window suppresses them.
|
||||
(factory.Created.Count - atFirstAttempt).ShouldBeLessThanOrEqualTo(1);
|
||||
|
||||
@@ -96,7 +96,7 @@ public sealed class TwinCATCapabilityTests
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
var afterShutdown = events.Count;
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
events.Count.ShouldBe(afterShutdown);
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ public sealed class TwinCATCapabilityTests
|
||||
Probe = new TwinCATProbeOptions { Enabled = false },
|
||||
}, "drv-1", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
|
||||
drv.GetHostStatuses().Single().State.ShouldBe(HostState.Unknown);
|
||||
await drv.ShutdownAsync(CancellationToken.None);
|
||||
|
||||
@@ -186,7 +186,7 @@ public sealed class TwinCATDiagnosticsTests
|
||||
IReadOnlyDictionary<string, string> bag = new Dictionary<string, string>();
|
||||
for (var i = 0; i < 100 && bag.Count == 0; i++)
|
||||
{
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
bag = ((IDriverDiagnostics)drv).GetDiagnostics();
|
||||
}
|
||||
bag["TwinCAT.OnlineChangeCnt"].ShouldBe("7");
|
||||
@@ -218,8 +218,8 @@ public sealed class TwinCATDiagnosticsTests
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < 100 && drv.GetHostStatuses().All(h => h.State != HostState.Running); i++)
|
||||
await Task.Delay(20);
|
||||
await Task.Delay(100); // several more probe ticks
|
||||
await Task.Delay(20, TestContext.Current.CancellationToken);
|
||||
await Task.Delay(100, TestContext.Current.CancellationToken); // several more probe ticks
|
||||
}
|
||||
finally { await drv.ShutdownAsync(CancellationToken.None); }
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ public sealed class TwinCATDriverProbeTests
|
||||
catch (OperationCanceledException) { /* accept cancelled — expected on teardown */ }
|
||||
catch (ObjectDisposedException) { /* listener stopped — expected on teardown */ }
|
||||
catch (SocketException) { /* listener stopped — expected on teardown */ }
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -314,7 +314,7 @@ public sealed class TwinCATReconnectReplayTests
|
||||
factory.Clients[0].SimulateWireDrop();
|
||||
var readTask = Task.Run(() => drv.ReadAsync(["X"], CancellationToken.None));
|
||||
await WaitForAsync(() => factory.Clients.Count >= 2, TimeSpan.FromSeconds(2));
|
||||
await Task.Delay(50); // let the replay reach the blocked AddNotification
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken); // let the replay reach the blocked AddNotification
|
||||
|
||||
// Unsubscribe mid-replay (gate-free) — removes the registration from the device registry.
|
||||
await drv.UnsubscribeAsync(handle, TestContext.Current.CancellationToken);
|
||||
|
||||
@@ -56,7 +56,7 @@ public sealed class TwinCATSumChunkWarningTests
|
||||
while (!factory.Clients.Any(c => c.IsConnected) && DateTime.UtcNow < deadline)
|
||||
{
|
||||
await drv.ReadAsync(["A", "B"], CancellationToken.None);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50, TestContext.Current.CancellationToken);
|
||||
}
|
||||
factory.Clients.Any(c => c.IsConnected).ShouldBeTrue("the device must reconnect for the second half to mean anything");
|
||||
await drv.ReadAsync(["A", "B"], CancellationToken.None);
|
||||
|
||||
Reference in New Issue
Block a user