Add SCM-launched real-binary E2E test (P0 follow-up to PR #274)
Summary
- New high-fidelity E2E
R3hScm_RealBinary_LaunchedThroughSCM_ScriptDispatchRoundTripsAndStopsCleanly— verifies PR #274'sUseWindowsService()production fix actually works under real SCM - Pre-#274:
sc startwould time out at 30s (ERROR_SERVICE_REQUEST_TIMEOUT) because the binary never calledStartServiceCtrlDispatcher - Post-#274 + this test: SCM transitions
START_PENDING → RUNNING → STOPPEDcorrectly, polling channel comes up, scripts dispatch + round-trip, Stop signal cleanly drains - Closes the residual fidelity gap with Linux R1h (which uses systemd-launched start)
Why this is P0
PR #274 added UseWindowsService() integration to Squid.Tentacle.exe, but only had 16 unit tests covering the detection seam. The end-to-end SCM-launched path was never exercised on a real Windows runner. If PR #274's production code has a subtle bug (e.g. WindowsServiceLifetime not propagating Stop signal correctly), it would ship undetected.
This test IS the contract verification — it would have failed pre-#274 with an obvious timeout error message naming the exact production gap.
Coverage delta vs prior tests
| Test | SCM | Polling | Dispatch | Stop signal |
|---|---|---|---|---|
R1h-Windows (PR #271) |
|
|
||
R2h-Windows soak (PR #272) |
|
|
|
|
R3hScm-Windows (this PR) |
sc start
|
sc stop
|
Mechanism (12 steps)
StubSquidServer.StartAsync- Pre-create instance via
InstanceRegistry - Real binary
register --comms-url=stub.PollingUri4-5. Extract agent thumbprint + subscriptionId,stub.TrustAgent - Real binary
service install—WindowsServiceHost.Installcallssc.exe create + sc.exe start - Wait for SCM state == RUNNING (45s timeout — would fail fast pre-#274)
- Wait for polling channel via
stub.ProbeCapabilitiesPollingAsync -
THE MAIN PIN:
stub.DispatchAndObservePollingAsyncPowerShell round-trip - Real binary
service stop— sends SCM Stop signal - Wait for SCM state == STOPPED (drain + transition, ~3-15s)
- Cleanup:
service uninstall --purge
What this catches
-
WindowsServiceLifetimeintegration regression — Step 7's RUNNING-state wait fails fast if the lifetime isn't wired correctly -
TentacleEntry.ShouldRunUnderScmdetection regression — if the seam returns false for SCM-launched, host bypasses the lifetime, SCM times out -
SCM Stop signal propagation — Step 11's STOPPED-state wait fails if host CT cancellation doesn't reach the polling drain logic (operator's
sc stopwould hang) -
TentacleScmHostedService.LastExitCodepropagation — SCM records exit codes; if the hosted service swallows them, ops lose post-mortem signal
Test plan
-
dotnet build tests/Squid.WindowsTentacleE2ETests— 0 errors -
CI on windows-latestrunsCategory=WindowsTentacleBinaryE2E:-
R3hScm_RealBinary_LaunchedThroughSCM_ScriptDispatchRoundTripsAndStopsCleanlypasses -
All other R1h/R2h/smoke tests still green
-
-
Re-run the workflow 2-3 times to confirm SCM transitions are deterministic
Phase status
This is P0-#1 of the post-1.6.4-release P0/P1 hardening sweep. Sister PRs:
-
P0-#3 (#275):
LocalScriptServiceasync-flush race fix — eliminates timing flakes at the root - P0-#2 (next): Server-restart polling reconnect E2E