aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-25 15:00:44 -0500
committerCraig Jennings <c@cjennings.net>2026-07-25 15:00:44 -0500
commitd52da959bc416e09bc0f3e54994563bd80e19c64 (patch)
treeadaf2ce086e87c548c695e5b4a82eb63f7c926c8 /tests
parentd25711a61172ffb7b622a6a834ed17fdf52a54de (diff)
downloadarchsetup-d52da959bc416e09bc0f3e54994563bd80e19c64.tar.gz
archsetup-d52da959bc416e09bc0f3e54994563bd80e19c64.zip
refactor(test): share runner and scenario contracts
Diffstat (limited to 'tests')
-rw-r--r--tests/maint-scenarios/test_scenario_plan.py61
-rw-r--r--tests/vm-framework/test_run_common.py97
2 files changed, 158 insertions, 0 deletions
diff --git a/tests/maint-scenarios/test_scenario_plan.py b/tests/maint-scenarios/test_scenario_plan.py
index 9a72db2..d2dc8ea 100644
--- a/tests/maint-scenarios/test_scenario_plan.py
+++ b/tests/maint-scenarios/test_scenario_plan.py
@@ -213,6 +213,67 @@ class UsageTests(unittest.TestCase):
NSPAWN_RUNNER = os.path.join(REPO_ROOT, "scripts", "testing",
"run-maint-nspawn.sh")
+SCENARIO_LIB = os.path.join(REPO_ROOT, "scripts", "testing", "lib",
+ "maint-scenario.sh")
+
+
+class SharedScenarioLibraryTests(unittest.TestCase):
+ """Both transports share scenario parsing and execution semantics."""
+
+ def test_both_runners_source_the_shared_library(self):
+ for runner in (RUNNER, NSPAWN_RUNNER):
+ with open(runner) as f:
+ source = f.read()
+ self.assertIn('source "$SCRIPT_DIR/lib/maint-scenario.sh"', source)
+ self.assertNotRegex(source, r"(?m)^_scenario_var\(\)")
+ self.assertNotRegex(source, r"(?m)^_validate_scenario\(\)")
+ self.assertNotRegex(source, r"(?m)^run_scenario\(\)")
+
+ def test_shared_runner_records_pass_and_cleans_contract_symbols(self):
+ with tempfile.TemporaryDirectory() as d:
+ scenario = write_scenario(d, "10-pass.sh")
+ script = f"""
+source "{SCENARIO_LIB}"
+section() {{ :; }}; step() {{ :; }}; success() {{ :; }}; error() {{ :; }}
+mexec() {{ :; }}; mfix() {{ :; }}
+S_FILES=("{scenario}"); S_NAMES=("pass"); S_DESCS=("works")
+PASS=(); FAIL=()
+run_scenario 0
+printf 'pass=%s fail=%s desc=%s fn=%s\\n' \
+ "${{#PASS[@]}}" "${{#FAIL[@]}}" "${{SCENARIO_DESC-unset}}" \
+ "$(declare -F scenario_break || echo unset)"
+"""
+ proc = subprocess.run(["bash", "-c", script], capture_output=True,
+ text=True, cwd=REPO_ROOT)
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+ self.assertIn("pass=1 fail=0 desc=unset fn=unset", proc.stdout)
+
+ def test_shared_runner_stops_after_failed_fix(self):
+ with tempfile.TemporaryDirectory() as d:
+ scenario = write_scenario(
+ d, "10-fail.sh",
+ body=GOOD_SCENARIO.format(desc="fails", group="g",
+ profiles="any").replace(
+ "scenario_fix() { mfix some_remedy; }",
+ "scenario_fix() { return 1; }",
+ ).replace(
+ 'scenario_assert() { mexec "true"; }',
+ 'scenario_assert() { echo ASSERT_RAN; }',
+ ),
+ )
+ script = f"""
+source "{SCENARIO_LIB}"
+section() {{ :; }}; step() {{ :; }}; success() {{ :; }}; error() {{ :; }}
+S_FILES=("{scenario}"); S_NAMES=("fail"); S_DESCS=("fails")
+PASS=(); FAIL=()
+run_scenario 0
+printf 'pass=%s fail=%s\\n' "${{#PASS[@]}}" "${{#FAIL[@]}}"
+"""
+ proc = subprocess.run(["bash", "-c", script], capture_output=True,
+ text=True, cwd=REPO_ROOT)
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+ self.assertNotIn("ASSERT_RAN", proc.stdout)
+ self.assertIn("pass=0 fail=1", proc.stdout)
class NspawnPlanTests(unittest.TestCase):
diff --git a/tests/vm-framework/test_run_common.py b/tests/vm-framework/test_run_common.py
new file mode 100644
index 0000000..c5f22e5
--- /dev/null
+++ b/tests/vm-framework/test_run_common.py
@@ -0,0 +1,97 @@
+"""Tests for the run-test transport-independent polling/report core."""
+
+import os
+import subprocess
+import tempfile
+import textwrap
+import unittest
+
+
+REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+RUN_COMMON = os.path.join(
+ REPO_ROOT, "scripts", "testing", "lib", "run-common.sh")
+VM_RUNNER = os.path.join(REPO_ROOT, "scripts", "testing", "run-test.sh")
+BARE_RUNNER = os.path.join(
+ REPO_ROOT, "scripts", "testing", "run-test-baremetal.sh")
+
+
+def run_bash(body):
+ return subprocess.run(
+ ["bash", "-c", f'source "{RUN_COMMON}"\n{body}'],
+ capture_output=True, text=True, cwd=REPO_ROOT, timeout=10,
+ )
+
+
+class SharedCoreWiring(unittest.TestCase):
+ def test_both_runners_source_shared_core_and_avoid_ps_grep(self):
+ for runner in (VM_RUNNER, BARE_RUNNER):
+ with open(runner) as f:
+ source = f.read()
+ self.assertIn('source "$SCRIPT_DIR/lib/run-common.sh"', source)
+ self.assertNotIn("ps aux | grep '[b]ash archsetup'", source)
+
+ def test_vm_runner_adapts_the_password_taking_transport(self):
+ with open(VM_RUNNER) as f:
+ source = f.read()
+ self.assertIn('vm_exec "$ROOT_PASSWORD" "$@"', source)
+ self.assertIn("archsetup_process_running vm_transport", source)
+ self.assertIn("wait_for_archsetup vm_transport", source)
+
+
+class PollArchsetup(unittest.TestCase):
+ def test_uses_one_pgrep_liveness_probe_until_process_stops(self):
+ proc = run_bash(textwrap.dedent("""\
+ checks=0
+ transport() {
+ [ "$1" = "pgrep -f '[b]ash archsetup' > /dev/null" ] ||
+ { echo "bad-command:$1"; return 2; }
+ checks=$((checks + 1))
+ [ "$checks" -lt 3 ]
+ }
+ progress() { echo "progress:$1"; }
+ wait_for_archsetup transport 5 0 1 progress
+ echo "rc=$? checks=$checks polls=$ARCHSETUP_POLL_COUNT"
+ """))
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+ self.assertNotIn("bad-command", proc.stdout)
+ self.assertIn("progress:1", proc.stdout)
+ self.assertIn("progress:2", proc.stdout)
+ self.assertIn("rc=0 checks=3 polls=2", proc.stdout)
+
+ def test_returns_124_at_poll_limit(self):
+ proc = run_bash(textwrap.dedent("""\
+ transport() { return 0; }
+ set +e
+ wait_for_archsetup transport 2 0 10
+ rc=$?
+ set -e
+ echo "rc=$rc polls=$ARCHSETUP_POLL_COUNT"
+ """))
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+ self.assertIn("rc=124 polls=2", proc.stdout)
+
+
+class Report(unittest.TestCase):
+ def test_writes_common_report_with_transport_specific_details(self):
+ with tempfile.TemporaryDirectory() as d:
+ report = os.path.join(d, "report.txt")
+ proc = run_bash(textwrap.dedent(f"""\
+ write_archsetup_test_report "{report}" \
+ "Bare Metal ArchSetup Test Report" "stamp" \
+ "Bare Metal ZFS" "Target: ratio.local" \
+ yes true 12 1 2 "Results: {d}/"
+ """))
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+ with open(report) as f:
+ text = f.read()
+ self.assertIn("Bare Metal ArchSetup Test Report", text)
+ self.assertIn("Test Method: Bare Metal ZFS", text)
+ self.assertIn("Target: ratio.local", text)
+ self.assertIn("ArchSetup Completed: yes", text)
+ self.assertIn("Validation: PASSED", text)
+ self.assertIn("Passed: 12", text)
+ self.assertIn(f"Results: {d}/", text)
+
+
+if __name__ == "__main__":
+ unittest.main()