Skip to content

Add Phase 13 PR-1: Linux real-binary polling agent E2E

Summary

  • New high-fidelity E2E R1h_RealBinary_PollingAgent_ScriptDispatchRoundTripsThroughHalibut that drives the real dotnet publish'd Squid.Tentacle binary as a polling agent against StubSquidServer (real Halibut listener + REST register), then dispatches a real script through the polling channel and asserts the output round-trips.
  • Closes the coverage gap between B3h-Linux (register → service install → systemd "active" but never exercises polling) and LD1h–LD14h (Halibut dispatch + bash spawn but uses the in-process StubAgent wrapper, not the real binary).
  • Tier 🟢 H per Rule 12.4 — zero mocks at the OS layer. Real binary, real systemd unit, real Halibut RPC over loopback TCP, real bash spawn.

Test plan

  • CI on ubuntu-latest runs Category=LinuxTentacleBinaryE2E and R1h_RealBinary_PollingAgent_ScriptDispatchRoundTripsThroughHalibut passes
  • Test exercises: register polling → service install → systemctl active → ProbeCapabilitiesPolling success → DispatchAndObservePolling round-trip
  • Cleanup via service uninstall --purge leaves /etc/squid-tentacle/instances/Default* removed
  • dotnet build tests/Squid.LinuxTentacleE2ETests/Squid.LinuxTentacleE2ETests.csproj (0 errors, 283 pre-existing warnings)

Why this matters

Prior to this test the Linux suite had two disjoint coverage tiers:

Existing test What it pins Gap
B3h-Linux (B3h_FullWorkflow_RegisterAndServiceStart_ReachesActiveState) register → service install → systemd is-active=active systemd "active" only confirms ExecStart launched. Polling channel connectivity + script-dispatch capability are not exercised. A regression where the real binary crashes silently after launch (Type=simple) would still pass.
LD1h–LD14h (TentacleLinuxDeployE2ETests) Halibut script dispatch + LocalScriptService bash spawn + ProcessOutput round-trip Uses StubAgent (in-process wrapper around LocalScriptService) — the real binary's systemd-started polling code path is never exercised.

R1h-Linux closes the gap: real binary, started by systemd, polling against the stub server, executing a real script that round-trips back. This is now the highest-fidelity E2E in the entire Linux suite.

What this catches that prior tests miss

  • register → run config round-trip regressions (e.g. Tentacle:ServerCommsUrl not persisted → run falls back to Listening mode and never polls)
  • TentacleHalibutHost.StartPolling regressions (e.g. polling URI resolution: empty subscriptionUri should fall back to poll://<sub>/, regression makes new Uri("") throw)
  • Halibut runtime trust-list propagation regression (server thumbprint loaded from config but not added to trust → TLS handshake fails)
  • LocalScriptService spawn regression on the systemd-started path (different env vars / cwd than register-time invocation)
  • Server→agent ProcessOutput streaming regression in the real agent (vs StubAgent which is the in-process wrapper)

Mechanism

  1. StubSquidServer.StartAsync — full Halibut listener on PollingUri + REST register on ServerUri, real self-signed cert
  2. Real binary register --comms-url=stub.PollingUri --server=stub.ServerUri — Polling mode, persists config with stub's ServerThumbprint
  3. Extract agent's thumbprint + subscriptionId from stub.ReceivedRegistrations[0]
  4. stub.TrustAgent(thumbprint) — must come before service install (TLS handshake on stub-side rejects unknown agent certs)
  5. Real binary service install --service-name <name> — systemd writes unit, starts service, real binary's run loads config, NoOpRegistrar reuses persisted ServerCertificate, StartPolling connects to stub
  6. Wait for systemctl is-active = active (binary launched)
  7. Wait for stub.ProbeCapabilitiesPollingAsync to succeed — proves agent's polling channel actually connected (Halibut handshake takes 1-3s after runtime starts)
  8. THE PIN: stub.DispatchAndObservePollingAsync round-trip with sleep 1; echo '<marker>' — assert exit 0 + marker in logs
  9. Cleanup: service uninstall --purge → /etc/squid-tentacle/instances/Default* removed; defensive cleanup in RealBinaryPollingContext.DisposeAsync covers failure paths

Phase 13 follow-ups (separate PRs)

  • PR-2: WindowsTentacleBinaryFixture (mirror Linux's dotnet publish cache pattern)
  • PR-3: Windows real-binary as polling agent (mirror this test)
  • PR-4: Keep-alive / soak — multi-second probe stability for both OSes (catch reconnection / drift bugs that single-shot tests miss)

🤖 Generated with Claude Code

Merge request reports

Loading