Commit graph

6 commits

Author SHA1 Message Date
ethernet
3e89edf830 perf(docker-tests): share containers across read-only tests
Add a module-scoped `shared_container` fixture to tests/docker/conftest.py
that boots one `sleep infinity` container per test module and tears it
down at module exit. Convert read-only tests that previously used
`docker run --rm --entrypoint sh/cat/test/su` (bypassing s6 to check
static image properties) or `docker run -d` + `docker exec` (starting
identical containers per test) to use `docker exec` on the shared
container instead.

Converted files:
  test_immutable_install_permissions.py — 2 throwaway runs → 2 execs
  test_license_file_present.py          — 1 throwaway run → 1 exec
  test_tini_compat_shim.py              — 1 throwaway run → 1 exec
  test_tui_prebuilt_bundle.py           — 2 throwaway runs → 2 execs
  test_dump_build_sha.py                — 2 throwaway runs → 2 execs
  test_immutable_install.py             — 3 detached runs → 1 shared + 1 isolated
  test_dashboard.py                     — 2 detached runs → 0 (use shared)

Local profiling shows docker run calls in these 7 files dropped from
~25 to 7 (the 7 are shared_container boots per module + the one test
that needs a restart). Each eliminated `docker run` was paying 1-9s
of s6 cont-init startup; the replacement `docker exec` calls average
0.10s — an ~50x speedup per operation.

Tests that mutate state (restarts, config changes, gateway starts)
still use their own containers via `container_name` + `start_container`.
2026-07-14 02:10:00 -04:00
ethernet
9f9ab13a64 feat(ci): profile docker integration test operations
Add a pytest profiling plugin (tests/docker/profiling.py) that
instruments every subprocess.run call targeting docker, collecting
per-test and per-subcommand wall-clock timings. Activated via
HERMES_DOCKER_TEST_PROFILE=1 — zero overhead when unset.

The CI docker.yml workflow now enables profiling and uploads the
JSON report as an artifact (docker-test-profile-<arch>), so we can
see exactly which docker operations (run, exec, restart, polling)
dominate the slow CI runs vs. fast local runs.

Output includes:
- Per-subcommand summary (call count, total/avg time, %)
- Top 10 slowest tests by docker operation time
- Top 10 slowest individual docker calls
- Full JSON report for offline analysis
2026-07-13 18:16:14 -04:00
ethernet
c918d07b50 refactor(ci): rewrite docker tests to check built container 2026-06-26 19:15:18 -07:00
Ben
fc39296e1f
fix(service_manager): s6 detection works for unprivileged hermes user
PR #30136 review surfaced two issues, both rooted in the same audit gap:
docker integration tests were running as root, not the unprivileged
`hermes` user (UID 10000) that the runtime actually uses via
`s6-setuidgid hermes`. Anything that probed PID-1 state or wrote to
the s6 control surface worked as root in the tests but was inert in
production.

Fixes:

1. `_s6_running()` previously called `Path("/proc/1/exe").resolve()`,
   which is root-only readable. For UID 10000 the symlink yields
   PermissionError, `resolve()` silently returns the unresolved path,
   and `exe.name == "exe"` — so detection always returned False, the
   service-manager runtime-registration path was inert, and every
   `hermes profile create` / `hermes -p X gateway start` silently
   skipped the s6 hook. Replace with `/proc/1/comm` (world-readable)
   + `/run/s6/basedir` (s6-overlay-specific) — both required, fail
   closed.

2. `02-reconcile-profiles` now also chowns `/run/service/.s6-svscan/`
   {control,lock} to hermes so `s6-svscanctl -a/-an` works without
   root. Previously the directory chown stopped at `/run/service`
   and the FIFO inside stayed root-owned, so `register_profile_gateway`
   from hermes failed at the rescan-trigger step with EACCES — the
   wrapper in profiles.py caught the exception and printed a swallowed
   warning, so profile creation appeared to succeed while the slot
   was rolled back.

Audit changes to flush this class of bug next time:

- Add `docker_exec` / `docker_exec_sh` helpers to `tests/docker/conftest.py`
  that default to `-u hermes`. The module docstring explains why and
  flags `user="root"` as opt-in only for tests that explicitly need
  root (none currently do).
- Refactor every `docker exec` call in tests/docker/ through the new
  helpers (test_dashboard.py, test_zombie_reaping.py, test_profile_gateway.py,
  test_container_restart.py, test_s6_profile_gateway_integration.py).
- Add 5 unit tests covering `_s6_running` under various probe states
  (both signals present; comm wrong; basedir missing; PermissionError
  on /proc/1/comm; missing /proc — non-Linux). The PermissionError
  test is the explicit regression guard for the original bug.

Known follow-up: the per-service `supervise/control` FIFO inside each
`/run/service/gateway-<profile>/supervise/` is created root-owned by
s6-supervise (which runs as root because s6-svscan is PID 1). `s6-svc
-u/-d/-t` from the hermes user will get EACCES on those. The audit
under `-u hermes` will reveal this in lifecycle tests — surfacing the
issue cleanly so it can be fixed in a focused follow-up (likely via a
small SUID helper or a polling chown loop in cont-init.d). The
detection + svscanctl fixes here are independent and complete on
their own.
2026-05-24 18:05:33 -07:00
Ben
a18f69eb55
test(docker): apply 180s timeout to docker harness tests
The agent-test suite default is 30s; docker test_no_args (the dashboard
spin-up, the container restart) routinely take 60-90s. Without this
they intermittently fail in CI with TimeoutError.
2026-05-24 18:05:14 -07:00
Ben
08302135b6
test(docker): add conftest fixtures for docker harness
Task 0.1 of the s6-overlay supervision plan. Establishes the test
infrastructure for tests/docker/: skip-on-missing-Docker collection
hook, session-scoped image-build fixture (overridable via the
HERMES_TEST_IMAGE env var for faster local iteration), and a
container_name fixture that ensures cleanup on test exit.

Refs: docs/plans/2026-05-07-s6-overlay-dynamic-subagent-gateways.md
2026-05-24 18:05:14 -07:00