aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-20 23:33:02 -0500
committerCraig Jennings <c@cjennings.net>2026-07-20 23:33:02 -0500
commitcf211cd8dd8dd0ff18673c88a3eef2c63549d6d5 (patch)
tree4051e651241dd4b13d1d57fc70ead7d4099b4ad1 /tests
parent5ce5d6903b8178e3502f52fcc2723e9cc44766d5 (diff)
downloadarchsetup-cf211cd8dd8dd0ff18673c88a3eef2c63549d6d5.tar.gz
archsetup-cf211cd8dd8dd0ff18673c88a3eef2c63549d6d5.zip
fix(test): count a diagnose miss as a failed net scenario
The scenario_diagnose_expect else-branch printed its FAIL line but never forced a non-zero subshell exit. A scenario whose only failure was the diagnose check counted as passed, so the run printed "all scenarios passed" and exited 0 over a real net-doctor diagnosis regression. The miss now sets a per-scenario rc that carries to the subshell exit. The fix and assert still run first, since the repair result is worth having either way. The new harness drives the real script with stubbed ssh/rsync/jq and a controllable fake scenario, pinning all four check outcomes.
Diffstat (limited to 'tests')
-rw-r--r--tests/net-scenarios/test_run_net_scenarios.py102
1 files changed, 102 insertions, 0 deletions
diff --git a/tests/net-scenarios/test_run_net_scenarios.py b/tests/net-scenarios/test_run_net_scenarios.py
new file mode 100644
index 0000000..1d92185
--- /dev/null
+++ b/tests/net-scenarios/test_run_net_scenarios.py
@@ -0,0 +1,102 @@
+"""Test run-net-scenarios.sh scenario accounting.
+
+Bug: the scenario_diagnose_expect else-branch printed a FAIL line but never
+forced a non-zero subshell exit, so a scenario whose only failure was the
+diagnose check counted as passed and the run printed "all scenarios passed"
+with exit 0. A net-doctor diagnosis regression would ship behind a green run.
+
+Method: run the real script against a temp scenario dir (NET_SCENARIO_DIR)
+with stub ssh/rsync/jq on PATH, driving one fake scenario whose check results
+are controlled per test. Assert on the script's exit code and summary line.
+
+Run from repo root:
+ python3 -m unittest tests.net-scenarios.test_run_net_scenarios
+"""
+
+import os
+import shutil
+import subprocess
+import tempfile
+import textwrap
+import unittest
+
+REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+SCRIPT = os.path.join(REPO_ROOT, "scripts", "testing", "run-net-scenarios.sh")
+
+STUB = "#!/bin/sh\ncat >/dev/null 2>&1 || true\nexit 0\n"
+
+
+class RunNetScenarios(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.mkdtemp(prefix="net-scen-test-")
+ self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
+ self.bindir = os.path.join(self.tmp, "bin")
+ os.mkdir(self.bindir)
+ for tool in ("ssh", "rsync", "jq"):
+ p = os.path.join(self.bindir, tool)
+ with open(p, "w") as f:
+ f.write(STUB)
+ os.chmod(p, 0o755)
+ self.scen_dir = os.path.join(self.tmp, "scenarios")
+ os.mkdir(self.scen_dir)
+
+ def write_scenario(self, *, brk=0, diagnose=0, fix=0, assert_rc=0,
+ with_diagnose=True):
+ diag_fn = (
+ f"scenario_diagnose_expect() {{ return {diagnose}; }}\n"
+ if with_diagnose else ""
+ )
+ body = textwrap.dedent(f"""\
+ SCENARIO_DESC="fake scenario for harness tests"
+ scenario_break() {{ return {brk}; }}
+ {diag_fn}scenario_fix() {{ return {fix}; }}
+ scenario_assert() {{ return {assert_rc}; }}
+ """)
+ with open(os.path.join(self.scen_dir, "fake-scenario.sh"), "w") as f:
+ f.write(body)
+
+ def run_script(self):
+ env = dict(os.environ)
+ env["PATH"] = self.bindir + os.pathsep + env["PATH"]
+ env["NET_SCENARIO_DIR"] = self.scen_dir
+ env["DOTFILES"] = self.tmp # rsync is stubbed; path just has to exist
+ return subprocess.run(
+ ["bash", SCRIPT, "--target", "root@fake-vm"],
+ capture_output=True, text=True, timeout=20, env=env,
+ )
+
+ def test_all_checks_pass_exits_zero(self):
+ self.write_scenario()
+ r = self.run_script()
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
+ self.assertIn("all scenarios passed", r.stdout)
+
+ def test_diagnose_failure_alone_fails_the_run(self):
+ # The bug: break/fix/assert all pass, only the diagnose check fails.
+ self.write_scenario(diagnose=1)
+ r = self.run_script()
+ self.assertIn("diagnose did NOT name it", r.stdout)
+ self.assertNotEqual(r.returncode, 0,
+ "a diagnose regression must not exit green")
+ self.assertIn("1 scenario(s) failed", r.stdout)
+
+ def test_assert_failure_fails_the_run(self):
+ self.write_scenario(assert_rc=1)
+ r = self.run_script()
+ self.assertNotEqual(r.returncode, 0)
+ self.assertIn("NOT repaired", r.stdout)
+
+ def test_break_failure_fails_the_run(self):
+ self.write_scenario(brk=1)
+ r = self.run_script()
+ self.assertNotEqual(r.returncode, 0)
+ self.assertIn("break failed", r.stdout)
+
+ def test_scenario_without_diagnose_hook_still_passes(self):
+ self.write_scenario(with_diagnose=False)
+ r = self.run_script()
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
+
+
+if __name__ == "__main__":
+ unittest.main()