From c867aca36b4dd0aff5c20637f2c772f502af4354 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 9 Aug 2026 12:37:35 -0400 Subject: [PATCH] test(worker): deterministic pump-wait ordering, env hermeticity, ResolveWriteCompletionTimeout coverage --- .../MxAccess/MxAccessCommandExecutorTests.cs | 13 +++++- .../MxAccess/MxAccessStaSessionTests.cs | 42 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs index 16e53c9..34f9406 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs @@ -956,6 +956,9 @@ public sealed class MxAccessCommandExecutorTests sink.WriteCompletionCache.Record(82, 820, CreateCompletionRows(detail: 4321)); using StaRuntime runtime = CreateRuntime(); using MxAccessStaSession session = new(runtime, factory, sink); + // Hermetic: don't inherit MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS + // from the test runner's environment. + session.WriteCompletionTimeout = TimeSpan.FromSeconds(10); await session.StartAsync(workerProcessId: 1234); MxCommandReply reply = await session.DispatchAsync(CreateWriteSecuredCommand( @@ -981,6 +984,12 @@ public sealed class MxAccessCommandExecutorTests FakeMxAccessComObject fakeComObject = new(registerHandle: 83); FakeMxAccessComObjectFactory factory = new(fakeComObject); CompletionCacheEventSink sink = new(); + // Deterministic ordering: the executor captures its version baseline + // BEFORE the COM call, so once the fake's WriteSecured has run the + // baseline is committed and a Record from the test thread is + // guaranteed to be "newer" — no fixed sleep racing the STA thread. + using System.Threading.ManualResetEventSlim comCallReached = new(initialState: false); + fakeComObject.OnWriteSecuredCallback = () => comCallReached.Set(); using StaRuntime runtime = CreateRuntime(); using MxAccessStaSession session = new(runtime, factory, sink); session.WriteCompletionTimeout = TimeSpan.FromSeconds(10); @@ -988,7 +997,7 @@ public sealed class MxAccessCommandExecutorTests Task pending = session.DispatchAsync(CreateWriteSecuredCommand( "write-secured-waiting", serverHandle: 83, itemHandle: 830, value: 1, currentUserId: 11, verifierUserId: 22)); - await Task.Delay(50); + Assert.True(comCallReached.Wait(TimeSpan.FromSeconds(5))); sink.WriteCompletionCache.Record(83, 830, CreateCompletionRows(detail: 99)); MxCommandReply reply = await pending; @@ -1063,6 +1072,8 @@ public sealed class MxAccessCommandExecutorTests sink.WriteCompletionCache.Record(86, 860, CreateCompletionRows(detail: 2222)); using StaRuntime runtime = CreateRuntime(); using MxAccessStaSession session = new(runtime, factory, sink); + // Hermetic: same rationale as the WriteSecured fast-completion test. + session.WriteCompletionTimeout = TimeSpan.FromSeconds(10); await session.StartAsync(workerProcessId: 1234); MxCommandReply reply = await session.DispatchAsync(CreateWriteSecured2Command( diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs index a8002b3..44894ea 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs @@ -15,6 +15,48 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess; /// public sealed class MxAccessStaSessionTests { + /// + /// Verifies the launcher-env-var parse branches of + /// : + /// a valid non-negative value is honored (0 = disabled), while a + /// missing, malformed, or negative value falls back to the executor + /// default. Env mutation is restored in a finally so parallel tests + /// never observe the temporary value. + /// + /// Raw env-var value, or null for unset. + /// Expected resolved wait, or null for the executor default. + [Theory] + [InlineData(null, null)] + [InlineData("", null)] + [InlineData("junk", null)] + [InlineData("-5", null)] + [InlineData("0", 0)] + [InlineData("250", 250)] + public void ResolveWriteCompletionTimeout_ParsesEnvironmentValue(string? rawValue, int? expectedMilliseconds) + { + string? original = Environment.GetEnvironmentVariable( + MxAccessStaSession.WriteCompletionWaitEnvironmentVariableName); + try + { + Environment.SetEnvironmentVariable( + MxAccessStaSession.WriteCompletionWaitEnvironmentVariableName, + rawValue); + + TimeSpan resolved = MxAccessStaSession.ResolveWriteCompletionTimeout(); + + TimeSpan expected = expectedMilliseconds is null + ? MxAccessCommandExecutor.DefaultWriteCompletionTimeout + : TimeSpan.FromMilliseconds(expectedMilliseconds.Value); + Assert.Equal(expected, resolved); + } + finally + { + Environment.SetEnvironmentVariable( + MxAccessStaSession.WriteCompletionWaitEnvironmentVariableName, + original); + } + } + /// /// Verifies that StartAsync creates the MXAccess COM object and attaches the event sink on the STA thread. ///