test(docker): lock baseline behavior for Phase 0 harness
Tasks 0.2-0.6 of the s6-overlay supervision plan. Locks the user-visible behavior we must preserve through the Phase 2 init- system swap: - test_main_invocation.py (Task 0.2): docker run <image> with no args, chat subcommand passthrough, bare executable passthrough, bash pattern, exit-code propagation - test_tui_passthrough.py (Task 0.3): TTY allocation via docker -t using the host's script(1) for a PTY - test_dashboard.py (Task 0.4): HERMES_DASHBOARD=1 opt-in, HERMES_DASHBOARD_PORT override - test_profile_gateway.py (Task 0.5): per-profile gateway start/stop and profile-delete-stops-gateway. Both marked xfail(strict=True) because the current tini image refuses gateway lifecycle commands inside the container; Phase 4 Task 4.3 flips them to passing. - test_zombie_reaping.py (Task 0.6): PID 1 reaps orphaned zombies. tini does this today; s6-overlay's /init must continue to. Refs: docs/plans/2026-05-07-s6-overlay-dynamic-subagent-gateways.md
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
"""Harness: interactive TUI TTY passthrough.
|
||||
|
||||
Uses ``script -qc`` on the host to allocate a PTY for the docker client,
|
||||
which then allocates a container-side PTY via ``-t``. The probe inside
|
||||
the container is ``tput cols``, which returns a real column count when
|
||||
stdout is a TTY and either prints ``80`` (the terminfo fallback) or
|
||||
nothing when it is not.
|
||||
|
||||
These tests MUST pass on the current tini-based image AND continue to
|
||||
pass after the Phase 2 s6 migration. Any drift is a regression.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
shutil.which("script") is None,
|
||||
reason="`script` command not available on this host",
|
||||
)
|
||||
|
||||
|
||||
def test_tty_passthrough_to_container(built_image: str) -> None:
|
||||
"""``docker run -t`` must deliver a real TTY to the container process."""
|
||||
probe = "if [ -t 1 ]; then tput cols; else echo NO_TTY; fi"
|
||||
cmd = (
|
||||
f"docker run --rm -t -e COLUMNS=123 {built_image} "
|
||||
f"sh -c {shlex.quote(probe)}"
|
||||
)
|
||||
r = subprocess.run(
|
||||
["script", "-qc", cmd, "/dev/null"],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
output = r.stdout.strip()
|
||||
assert "NO_TTY" not in output, f"TTY passthrough failed: {output!r}"
|
||||
numeric_lines = [s for s in output.split() if s.strip().isdigit()]
|
||||
assert numeric_lines, f"No numeric width in output: {output!r}"
|
||||
assert int(numeric_lines[0]) > 0
|
||||
|
||||
|
||||
def test_tui_flag_recognized(built_image: str) -> None:
|
||||
"""``docker run -it <image> --help`` should run without crashing."""
|
||||
cmd = f"docker run --rm -t {built_image} --help"
|
||||
r = subprocess.run(
|
||||
["script", "-qc", cmd, "/dev/null"],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
)
|
||||
assert r.returncode == 0
|
||||
Reference in New Issue
Block a user