Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
131 lines
4.3 KiB
C#
131 lines
4.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Resilience;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class InFlightCounterTests
|
|
{
|
|
[Fact]
|
|
public void StartThenComplete_NetsToZero()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
tracker.RecordCallStart("drv", "host-a");
|
|
tracker.RecordCallComplete("drv", "host-a");
|
|
|
|
tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void NestedStarts_SumDepth()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
tracker.RecordCallStart("drv", "host-a");
|
|
tracker.RecordCallStart("drv", "host-a");
|
|
tracker.RecordCallStart("drv", "host-a");
|
|
|
|
tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(3);
|
|
|
|
tracker.RecordCallComplete("drv", "host-a");
|
|
tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public void CompleteBeforeStart_ClampedToZero()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
tracker.RecordCallComplete("drv", "host-a");
|
|
|
|
// A stray Complete without a matching Start shouldn't drive the counter negative.
|
|
tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void DifferentHosts_TrackIndependently()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
tracker.RecordCallStart("drv", "host-a");
|
|
tracker.RecordCallStart("drv", "host-a");
|
|
tracker.RecordCallStart("drv", "host-b");
|
|
|
|
tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(2);
|
|
tracker.TryGet("drv", "host-b")!.CurrentInFlight.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConcurrentStarts_DoNotLose_Count()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
Parallel.For(0, 500, _ => tracker.RecordCallStart("drv", "host-a"));
|
|
|
|
tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(500);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CapabilityInvoker_IncrementsTracker_DuringExecution()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
var invoker = new CapabilityInvoker(
|
|
new DriverResiliencePipelineBuilder(),
|
|
"drv-live",
|
|
() => new DriverResilienceOptions { Tier = DriverTier.A },
|
|
driverType: "Modbus",
|
|
statusTracker: tracker);
|
|
|
|
var observedMidCall = -1;
|
|
await invoker.ExecuteAsync(
|
|
DriverCapability.Read,
|
|
"plc-1",
|
|
async _ =>
|
|
{
|
|
observedMidCall = tracker.TryGet("drv-live", "plc-1")?.CurrentInFlight ?? -1;
|
|
await Task.Yield();
|
|
return 42;
|
|
},
|
|
CancellationToken.None);
|
|
|
|
observedMidCall.ShouldBe(1, "during call, in-flight == 1");
|
|
tracker.TryGet("drv-live", "plc-1")!.CurrentInFlight.ShouldBe(0, "post-call, counter decremented");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CapabilityInvoker_ExceptionPath_DecrementsCounter()
|
|
{
|
|
var tracker = new DriverResilienceStatusTracker();
|
|
var invoker = new CapabilityInvoker(
|
|
new DriverResiliencePipelineBuilder(),
|
|
"drv-live",
|
|
() => new DriverResilienceOptions { Tier = DriverTier.A },
|
|
statusTracker: tracker);
|
|
|
|
await Should.ThrowAsync<InvalidOperationException>(async () =>
|
|
await invoker.ExecuteAsync<int>(
|
|
DriverCapability.Write,
|
|
"plc-1",
|
|
_ => throw new InvalidOperationException("boom"),
|
|
CancellationToken.None));
|
|
|
|
tracker.TryGet("drv-live", "plc-1")!.CurrentInFlight.ShouldBe(0,
|
|
"finally-block must decrement even when call-site throws");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CapabilityInvoker_WithoutTracker_DoesNotThrow()
|
|
{
|
|
var invoker = new CapabilityInvoker(
|
|
new DriverResiliencePipelineBuilder(),
|
|
"drv-live",
|
|
() => new DriverResilienceOptions { Tier = DriverTier.A },
|
|
statusTracker: null);
|
|
|
|
var result = await invoker.ExecuteAsync(
|
|
DriverCapability.Read, "host-1",
|
|
_ => ValueTask.FromResult(7),
|
|
CancellationToken.None);
|
|
|
|
result.ShouldBe(7);
|
|
}
|
|
}
|