test(windev): fix the two Windows-only gateway test failures dismissed as environmental
ci / java (push) Successful in 2m24s
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 3m33s
ci / portable (push) Successful in 8m13s

Both failures in the windev baseline were test bugs that reproduce on any Windows
host, not anything missing or misconfigured on windev.

SelfSignedCertificateProviderTests.GenerateCertificate_HasExpectedSansEkuAndValidity
asserted SAN content by substring-matching X509Extension.Format(false). That string
comes from the platform crypto library: Windows' CryptFormatObject renders the IPv6
loopback fully expanded (0000:0000:...:0001) where the managed formatter renders
"::1", so the loopback assertion could never hold on Windows. Decode the extension
with X509SubjectAlternativeNameExtension and compare parsed IPAddress values and DNS
names instead, which removes the platform-dependent formatting from the assertion.

SessionManagerTests.OpenSessionAsync_PipeNameIsShortAndUniquePerPidAndSession guards
the 104-byte macOS sun_path budget NEXT-01 shortened the pipe name to fit. It padded
the measured name up to a five-digit pid but never substituted that worst case
downward, so Windows' routine six-digit pids over-counted by a character against a
budget that does not constrain the host running the test. Substitute the five-digit
macOS worst case for the running pid's digit count so the check measures the name
format rather than the current pid.

EventStreamServiceTests.WaitUntilAsync now reports the unmet condition on timeout
instead of letting a bare TaskCanceledException escape. Its five-second real-clock
deadline is genuinely load-sensitive on windev (36 logical CPUs, maxParallelThreads
-1), and an opaque cancellation there is exactly what got the previous failures
filed as "environmental" and left unexplained.

Documents the windev run in docs/GatewayTesting.md: the two fixed bugs and their root
causes, the real-pipe suites whose failures are evidence of machine load rather than
of the change under test, and the full-suite testhost that completes every test and
then never exits (filtered runs exit normally; macOS exits cleanly). Corrects the
CLAUDE.md claim that the suite exits cleanly on the Windows dev box.
This commit is contained in:
Joseph Doherty
2026-08-10 08:52:46 -04:00
parent 347d59fc62
commit e2352d1666
5 changed files with 128 additions and 19 deletions
+82
View File
@@ -485,6 +485,88 @@ Run the gateway test project after shared gateway test infrastructure changes:
dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj
```
## Running the Gateway Suite on windev
The gateway suite (`ZB.MOM.WW.MxGateway.Tests`, net10.0/x64) is not part of the CI
Windows tier — `windows-x86` and `nightly-windev` run only the x86 Worker build and
`Worker.Tests`. It is still run on windev by hand when a change needs Windows
confirmation, and that run has three Windows-specific characteristics worth knowing
before results are interpreted.
Run it from an isolated clone under `C:\build` checked out to the SHA under test — never
the dirty Desktop checkout, and never the CI clone `C:\build\mxaccessgw-ci`, whose worktree
lock belongs to the Worker tier.
Baseline on an otherwise idle windev (2026-08-10): **855 passed, 0 failed, 29 s**. The
suite is smaller there than the 879 the macOS box runs because some cases are gated to
Unix. Any failure is therefore a real signal — but read the load caveat below before
acting on one.
### Two long-standing "windev-environmental" failures were test bugs, not the environment
Both were dismissed as environmental for months and are now fixed. Neither depended on
anything installed on windev; both failed on **any** Windows host:
- `SelfSignedCertificateProviderTests.GenerateCertificate_HasExpectedSansEkuAndValidity`
asserted SAN content by substring-matching `X509Extension.Format(false)`. That string is
produced by the platform crypto library: Windows' `CryptFormatObject` renders the IPv6
loopback fully expanded (`IP Address=0000:0000:0000:0000:0000:0000:0000:0001`) while the
managed formatter used on macOS/Linux renders `::1`, so the loopback assertion failed on
Windows only. The test now decodes the extension with `X509SubjectAlternativeNameExtension`
and compares parsed `IPAddress` values and DNS names (case-insensitively, as DNS names
are), which is platform-independent.
- `SessionManagerTests.OpenSessionAsync_PipeNameIsShortAndUniquePerPidAndSession` guards the
104-byte macOS `sun_path` budget that NEXT-01 shortened the pipe name to fit. It padded the
measured name up to a five-digit pid but never substituted that worst case *downward*, so a
six-digit pid — routine on Windows, impossible on macOS, where pids stop at 99999 — made the
name one character "too long" against a budget that does not apply to the host running the
test. The check now replaces the running pid's digit count with the five-digit macOS worst
case, so it measures the name *format* rather than the current process's pid.
### The real-pipe suites are load-sensitive
These suites drive real named pipes against a five-second worker startup timeout and start
failing when windev is busy — most often when the x86 Worker tier is building or testing at
the same time. All five passed in the idle baseline above and all five failed in a run taken
while an x86 build and `Worker.Tests` were in flight (that run also took 2 m 21 s against the
idle 29 s):
- `GatewayEndToEndFakeWorkerSmokeTests`, `GatewayEndToEndMultiSubscriberTests`,
`GatewayEndToEndReconnectReplayTests` — fail as
`RpcException Status(StatusCode="Unavailable", Detail="Failed to open session …")`.
- `SessionWorkerClientFactoryFakeWorkerTests.CreateAsync_WhenFakeWorkerStartupFails_ThrowsWorkerClientException`
— the startup timeout beats the protocol violation the test is asserting, so the observed
exception is `TimeoutException` instead of `WorkerClientException`.
- `WorkerClientTests.InvokeAsync_WhenCommandExceedsFrameMax_FailsOnlyThatCommandAndStaysReady`.
- `EventStreamServiceTests.StreamEventsAsync_WithConcurrentStreams_TracksAggregateQueueDepth`
— polls a metric against a five-second deadline. Its helper now reports the unmet condition
rather than letting a bare `TaskCanceledException` escape, so a load-induced timeout here
names what it was waiting for instead of looking like an unexplained cancellation.
A failure in that list is evidence about machine load, not about the change under test. Check
for a concurrent x86 build/test (`Get-Process dotnet, testhost, testhost.net48.x86,
MSBuild, VBCSCompiler`) and re-run the affected class on its own before treating it as real.
windev has 36 logical CPUs and `xunit.runner.json` sets `maxParallelThreads: -1`, so the
suite runs far wider there than on the macOS dev box — that width is what turns these
real-clock deadlines into failures.
### The full-suite testhost does not exit on windev
After the last test completes, the x64 `testhost` process stops doing work but never exits,
so `dotnet test` never returns and the run has to be killed. This does **not** happen on
filtered runs (`--filter …`), which exit normally, and does not happen on the macOS dev box —
it is specific to a full-suite run on windev. Run the full suite with a hang guard so the
wedged host is torn down and the pass/fail summary is still printed:
```powershell
dotnet test src\ZB.MOM.WW.MxGateway.Tests\ZB.MOM.WW.MxGateway.Tests.csproj `
--blame-hang --blame-hang-timeout 5m --blame-hang-dump-type none
```
The summary line ahead of the abort is the real result; the process exit code is nonzero
because of the abort even when every test passed, so read the summary rather than the exit
code. Prefer filtered runs on windev whenever the change under test allows it.
## Continuous Integration
CI runs on Gitea Actions (`.gitea/workflows/ci.yml`; origin is Gitea at