Files
Jyotisha/tests/test_run_quality_gate_output.py
T
jesse-ux 2c64b66423 fix(gate): keep quality-gate logs readable (BUG-1230)
Summarize successful steps, print the failing check and error text,
and split the validate job into seven steps. Local npm test stays TAP.
2026-10-04 22:58:17 +08:00

145 lines
5.0 KiB
Python

"""Output contract for the quality-gate runner (BUG-1230).
The gate used to stream every child report into one CI step. Compact mode
prints a single success line. A failing step keeps its exit code, the last
200 lines, and a full log under gate-logs/. --verbose streams the child
again and does not capture.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import run_quality_gate as gate # noqa: E402
def setup_function() -> None:
gate.set_verbose(False)
gate.reset_output_state()
def test_quick_banner_is_one_line() -> None:
line = gate.format_profile_banner("quick", gate.QUALITY_GATE_PROFILES["quick"], verbose=False)
assert "\n" not in line
assert "profile=quick" in line
assert "test_timeout_seconds=90" in line
assert "skip_frontend_runtime=false" in line
def test_verbose_banner_keeps_indented_profile_json() -> None:
text = gate.format_profile_banner("quick", gate.QUALITY_GATE_PROFILES["quick"], verbose=True)
assert "== Quality gate profile: quick ==" in text
assert '"test_timeout_seconds": 90' in text
assert text.count("\n") > 3
def test_last_output_lines_keeps_the_tail() -> None:
text = "\n".join(f"n{index}" for index in range(201))
tail = gate.last_output_lines(text).splitlines()
assert tail[0] == "n1"
assert tail[-1] == "n200"
assert len(tail) == 200
def test_compact_hides_child_stdout(capfd, tmp_path: Path) -> None:
script = tmp_path / "child_marker.py"
script.write_text("print('hidden-child-marker')\n", encoding="utf-8")
assert gate.run([sys.executable, str(script)]) is True
out, _err = capfd.readouterr()
assert "hidden-child-marker" not in out
assert "✓" in out
assert out.strip().endswith("s")
def test_verbose_streams_child_output(capfd) -> None:
gate.set_verbose(True)
assert gate.run([sys.executable, "-c", "print('verbose-child-marker')"]) is True
out, _err = capfd.readouterr()
assert "$" in out
assert "verbose-child-marker" in out
assert "✓" not in out
def test_capture_does_not_invent_a_timeout(monkeypatch, capfd) -> None:
seen: dict = {}
def fake_run(cmd, **kwargs):
seen.update(kwargs)
return subprocess.CompletedProcess(cmd, 0, stdout="ignored\n", stderr="")
monkeypatch.setattr(gate.subprocess, "run", fake_run)
assert gate.run([sys.executable, "scripts/audit_capabilities.py", "--mode", "validate"]) is True
assert seen.get("capture_output") is True
assert "timeout" not in seen
out, _err = capfd.readouterr()
assert "ignored" not in out
assert "scripts/audit_capabilities.py" in out
def test_failure_shows_mark_tail_and_log(monkeypatch, tmp_path: Path, capfd) -> None:
monkeypatch.setattr(gate, "ROOT", tmp_path)
stdout = "".join(f"line-{index:03d}\n" for index in range(250))
def fake_run(cmd, **kwargs):
assert kwargs.get("capture_output") is True
return subprocess.CompletedProcess(cmd, 3, stdout=stdout, stderr="err-tail\n")
monkeypatch.setattr(gate.subprocess, "run", fake_run)
with pytest.raises(SystemExit) as caught:
gate.run([sys.executable, "scripts/audit_capabilities.py"])
assert caught.value.code == 3
_out, err = capfd.readouterr()
assert "✗ scripts/audit_capabilities.py exit=3" in err
assert "line-249" in err
assert "line-000" not in err
assert "err-tail" in err
assert "full log: gate-logs/" in err
logs = list((tmp_path / "gate-logs").glob("*.log"))
assert len(logs) == 1
body = logs[0].read_text(encoding="utf-8")
assert "line-000" in body
assert "line-249" in body
assert "err-tail" in body
assert len(err.splitlines()) < 250
def test_real_failing_command_keeps_its_exit_code(monkeypatch, tmp_path: Path, capfd) -> None:
monkeypatch.setattr(gate, "ROOT", tmp_path)
with pytest.raises(SystemExit) as caught:
gate.run([sys.executable, "-c", "import sys; print('boom-line'); sys.exit(4)"])
assert caught.value.code == 4
_out, err = capfd.readouterr()
assert "exit=4" in err
assert "boom-line" in err
assert "full log: gate-logs/" in err
assert (tmp_path / "gate-logs").is_dir()
def test_optional_failure_continues(monkeypatch, tmp_path: Path, capfd) -> None:
monkeypatch.setattr(gate, "ROOT", tmp_path)
def fake_run(cmd, **kwargs):
return subprocess.CompletedProcess(cmd, 7, stdout="nope\n", stderr="")
monkeypatch.setattr(gate.subprocess, "run", fake_run)
assert gate.run([sys.executable, "scripts/validate_logic_v2.py"], optional=True) is False
out, err = capfd.readouterr()
assert "exit=7" in err
assert "continuing" in f"{out}\n{err}"
def test_compact_compile_is_one_line(capfd) -> None:
gate.compile_targets()
out, _err = capfd.readouterr()
lines = [line for line in out.splitlines() if line.strip()]
assert lines == [line for line in lines if line.startswith("✓ py_compile ")]
assert len(lines) == 1
assert "files" in lines[0]