"""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]