`test_tty_passthrough_to_container` asserted `int(numeric_lines[0]) > 0` where `numeric_lines` was every `.isdigit()` token in the FULL PTY stream — but the container's s6 boot output (cont-init diagnostics, the preinit `uid=0 ... egid=0` line, skills-sync summaries like `Done: 90 new, 0 updated, 0 unchanged. 90 total bundled.`) is written to the same PTY before the `tput cols` probe runs. So the test was really asserting on "the first number anywhere in the boot log", which passed only by luck on whatever that first digit happened to be. Any PR that shifts boot output flips the first digit to a stray `0` and breaks the test with `assert 0 > 0` — even when TTY passthrough is working perfectly (`tput cols` returns the right value). This is a latent landmine for every Docker PR that changes boot output (e.g. adding a bundled dependency changes the skills-sync counts). Fix: emit the probe result behind a unique marker (`HERMES_TTY_COLS=<cols>` / `HERMES_TTY_COLS=NO_TTY`) and parse only the marked value, ignoring all boot-log noise. The test's real intent — verify `docker run -t` delivers a real TTY with a positive column count — is preserved (NO_TTY and non-numeric values still fail). Verified against a real build, adversarially: - Built an image with extra boot output (the markdown core-dep change from #38649, which is what surfaced this) so the OLD logic grabs a stray `0` -> reproduced `assert 0 > 0` locally. - The hardened test PASSES against that same image, and against a clean image. `tput cols` correctly returns 123 in both.
66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
"""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 re
|
|
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."""
|
|
# Emit the probe result behind a unique marker. The container's s6 boot
|
|
# output (cont-init diagnostics, skills-sync summaries like
|
|
# "Done: 90 new, 0 updated, ...", the preinit "uid=0 ... egid=0" line)
|
|
# is written to the SAME PTY stream before this runs, so we must NOT
|
|
# scan the whole stream for "the first number" — that picks up a stray
|
|
# 0 from the boot log and flips the assertion (assert 0 > 0) whenever
|
|
# boot output shifts (e.g. a new bundled dep changes the skills-sync
|
|
# counts). Parse only the value tagged with our marker.
|
|
marker = "HERMES_TTY_COLS"
|
|
probe = (
|
|
f'if [ -t 1 ]; then echo "{marker}=$(tput cols)"; else echo "{marker}=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
|
|
matches = re.findall(rf"{marker}=(\S+)", output)
|
|
assert matches, f"No {marker} marker in output: {output!r}"
|
|
value = matches[-1].strip()
|
|
assert value != "NO_TTY", f"TTY passthrough failed: {output!r}"
|
|
assert value.isdigit(), f"Non-numeric column width {value!r} in: {output!r}"
|
|
assert int(value) > 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
|