mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-11-03 01:44:17 +00:00
65cbd98607
- ci-pg-trace.py --expect now fails when a listed package never connected, and when no connection was seen at all, not only on an unexpected package. An empty trace (a renamed helper, a lost env gate) used to pass vacuously. Red first: three table cases. - ci-pg-packages.txt: the list was validated by the full traced run; its CANDIDATE label is replaced by what it is and what enforces it. - ci.yml: the non-server race steps in Go and Go (PostgreSQL) get the same STOP THE LINE step race.yml has (::error:: plus a job summary), keyed on the race step's own outcome. - race.yml: the stop-the-line step also fires on a cancelled run. Claude-Session: https://claude.ai/code/session_01Pa22hLbfHJSsjBT1mPFoPF
89 lines
4.4 KiB
Python
89 lines
4.4 KiB
Python
"""CLI regression cases for the Postgres connection evidence extractor."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SCRIPT = ROOT / "scripts" / "ci-pg-trace.py"
|
|
MODULE = "github.com/PerpetualSoftware/pad"
|
|
|
|
|
|
def event(package, output="PAD_PG_CONNECTED helper=probe\n", action="output"):
|
|
return {"Action": action, "Package": package, "Test": "TestProbe", "Output": output}
|
|
|
|
|
|
class TraceTest(unittest.TestCase):
|
|
def test_cli(self):
|
|
cases = [
|
|
("empty", [], None, 0, [], []),
|
|
("sort and deduplicate", [event(MODULE + "/internal/store"),
|
|
event(MODULE + "/cmd/pad"),
|
|
event(MODULE + "/internal/store")],
|
|
None, 0, [MODULE + "/cmd/pad", MODULE + "/internal/store"], []),
|
|
("only output events with packages",
|
|
[event("ignored", action="pass"), event("ignored", "ordinary log\n"),
|
|
{"Action": "output", "Output": "PAD_PG_CONNECTED helper=probe"}],
|
|
None, 0, [], []),
|
|
("relative and import paths",
|
|
[event(MODULE + "/cmd/pad"), event(MODULE + "/internal/store")],
|
|
"# candidate\n\n./cmd/pad/\n" + MODULE + "/internal/store\n",
|
|
0, [MODULE + "/cmd/pad", MODULE + "/internal/store"], []),
|
|
("unexpected and missing",
|
|
[event(MODULE + "/internal/server"), event(MODULE + "/cmd/pad")],
|
|
"./internal/store\n",
|
|
1, [MODULE + "/cmd/pad", MODULE + "/internal/server"],
|
|
["unexpected: " + MODULE + "/cmd/pad",
|
|
"unexpected: " + MODULE + "/internal/server",
|
|
"expected package never connected: " + MODULE + "/internal/store"]),
|
|
# A listed package that stopped connecting means the list (or the
|
|
# trace marker) drifted: fatal, not a warning (TASK-4224 r1 H1).
|
|
("listed package missing while others connect",
|
|
[event(MODULE + "/cmd/pad")], "./cmd/pad\n./internal/store\n",
|
|
1, [MODULE + "/cmd/pad"],
|
|
["expected package never connected: " + MODULE + "/internal/store"]),
|
|
# No marker at all means the trace is broken (renamed helper, env
|
|
# gate lost): it must not pass vacuously.
|
|
("empty trace fails", [], "./internal/store\n", 1, [],
|
|
["expected package never connected: " + MODULE + "/internal/store",
|
|
"no PAD_PG_CONNECTED markers: the trace saw no connection at all"]),
|
|
("empty trace fails even against an empty list", [], "# only comments\n", 1, [],
|
|
["no PAD_PG_CONNECTED markers: the trace saw no connection at all"]),
|
|
("package output without a test",
|
|
[{"Action": "output", "Package": MODULE + "/internal/store",
|
|
"Output": "PAD_PG_CONNECTED helper=probe\n"}], "./internal/store\n",
|
|
0, [MODULE + "/internal/store"], []),
|
|
]
|
|
for name, events, expected, code, packages, diagnostics in cases:
|
|
with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp:
|
|
args = [sys.executable, str(SCRIPT)]
|
|
if expected is not None:
|
|
expect = Path(tmp) / "expected.txt"
|
|
expect.write_text(expected)
|
|
args += ["--expect", str(expect)]
|
|
result = subprocess.run(
|
|
args, input="".join(json.dumps(row) + "\n" for row in events),
|
|
text=True, capture_output=True, cwd=tmp, check=False,
|
|
)
|
|
self.assertEqual(result.returncode, code, result.stderr)
|
|
self.assertEqual(result.stdout.splitlines(), packages)
|
|
self.assertEqual(result.stderr.splitlines(), diagnostics)
|
|
|
|
|
|
class HumanOutputTest(unittest.TestCase):
|
|
def test_output_can_be_mirrored_without_losing_trace(self):
|
|
row = event(MODULE + '/internal/store', 'ordinary test output\n')
|
|
result = subprocess.run([sys.executable, str(SCRIPT), '--show-output'],
|
|
input=json.dumps(row) + '\n', text=True,
|
|
capture_output=True, check=False)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout, 'ordinary test output\n')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|