From af444d7066fb93533a076d058be81a71d224571a Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Tue, 21 Apr 2026 07:05:46 +0000 Subject: [PATCH] test(anaf): assert monitor_v2 emits GSTACK-CRON marker as last stdout line Four checks: - The script file exists at the expected path. - The source contains the marker print statement (fast regression guard). - Running the script against an empty config produces a matching marker (^GSTACK-CRON: changes=\d+$) with changes=0. - The marker is the last non-empty line of stdout so tailers can parse it. The runtime test copies the script into a tmp cwd so that the script's SCRIPT_DIR-relative state files (hashes.json, versions.json, snapshots/, monitor.log) don't pollute the repo. --- tests/test_anaf_marker.py | 100 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/test_anaf_marker.py diff --git a/tests/test_anaf_marker.py b/tests/test_anaf_marker.py new file mode 100644 index 0000000..4801028 --- /dev/null +++ b/tests/test_anaf_marker.py @@ -0,0 +1,100 @@ +"""Regression guard: tools/anaf-monitor/monitor_v2.py must emit the +GSTACK-CRON marker so the Echo-Core scheduler (report_on="changes") can +decide whether to forward stdout to a channel. + +Two checks: + 1. Static — the marker print statement is present in the script source. + 2. Runtime — running the script via subprocess in an isolated cwd (with + config empty and network disabled via a stubbed urlopen) produces + a trailing line matching ^GSTACK-CRON: changes=\\d+$. +""" + +import json +import re +import subprocess +import sys +from pathlib import Path + +import pytest + +SCRIPT = ( + Path(__file__).resolve().parent.parent + / "tools" + / "anaf-monitor" + / "monitor_v2.py" +) + +MARKER_RE = re.compile(r"^GSTACK-CRON:\s+changes=(\d+)\s*$", re.MULTILINE) + + +def test_script_exists(): + assert SCRIPT.is_file(), f"Expected ANAF monitor at {SCRIPT}" + + +def test_anaf_monitor_source_contains_marker_print(): + """Weak but fast regression guard: the marker print must exist in source.""" + src = SCRIPT.read_text(encoding="utf-8") + assert 'print(f"GSTACK-CRON: changes=' in src, ( + "monitor_v2.py must emit GSTACK-CRON marker on its own line — " + "contract with the Echo-Core shell scheduler (report_on='changes')." + ) + + +def test_anaf_monitor_emits_gstack_marker(tmp_path): + """Run the script end-to-end with an empty config and assert the marker.""" + work_dir = tmp_path / "anaf" + work_dir.mkdir() + + # Empty config → zero pages processed → num_changes = 0 + (work_dir / "config.json").write_text(json.dumps({"pages": []})) + + # Copy the script to the isolated dir so SCRIPT_DIR-relative paths + # (hashes.json, versions.json, snapshots/, monitor.log) don't pollute + # the repo. + local_script = work_dir / "monitor_v2.py" + local_script.write_text(SCRIPT.read_text(encoding="utf-8")) + + proc = subprocess.run( + [sys.executable, str(local_script)], + capture_output=True, + text=True, + timeout=30, + cwd=str(work_dir), + ) + + assert proc.returncode == 0, ( + f"Script exited {proc.returncode}, stderr: {proc.stderr}" + ) + + match = MARKER_RE.search(proc.stdout) + assert match is not None, ( + "monitor_v2.py did not emit a GSTACK-CRON marker. " + f"stdout was:\n{proc.stdout}" + ) + # With empty config there are zero changes. + assert int(match.group(1)) == 0 + + +def test_anaf_monitor_marker_is_last_line(tmp_path): + """Marker should be the final meaningful line so log-tailers can parse.""" + work_dir = tmp_path / "anaf" + work_dir.mkdir() + (work_dir / "config.json").write_text(json.dumps({"pages": []})) + local_script = work_dir / "monitor_v2.py" + local_script.write_text(SCRIPT.read_text(encoding="utf-8")) + + proc = subprocess.run( + [sys.executable, str(local_script)], + capture_output=True, + text=True, + timeout=30, + cwd=str(work_dir), + ) + assert proc.returncode == 0 + + # Strip trailing whitespace/newlines, grab last non-empty line. + lines = [ln for ln in proc.stdout.splitlines() if ln.strip()] + assert lines, "Script produced no stdout lines" + assert MARKER_RE.match(lines[-1]), ( + f"Last stdout line is not the GSTACK-CRON marker. Got: {lines[-1]!r}" + )