Files
pulse/scripts/tests/test_native_installer_contract_runner.py
rcourtman 266144de1f Share native installer execution controls with script smoke tests
Script smoke still asserted the removed grouped runner while native
verification used a separate suite. Share the canonical execution controls
so both entrypoints reject lost checks, false passes and changed deadlines.
2026-10-18 17:55:06 +01:00

150 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""Execution controls for the native installer runner, including real Go exits."""
import contextlib
import importlib.util
import io
from pathlib import Path
import subprocess
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[2]
spec = importlib.util.spec_from_file_location("native_runner", ROOT / "scripts/installtests/run_unix_contracts.py")
runner = importlib.util.module_from_spec(spec)
spec.loader.exec_module(runner)
class InventoryControls(unittest.TestCase):
def setUp(self):
self.names = [f"TestFixture{i:03d}" for i in range(500)]
self.names += ["ExampleInstaller", "FuzzInstaller", "TestFutureFixture",
"TestRootInstallResetSafety", "TestΣ", *runner.PREFLIGHT]
self.inventory = "\n".join(self.names + ["BenchmarkIgnored", "ok package 0.1s"])
def test_growing_inventory_is_exhaustive_disjoint_and_ordered(self):
early, remaining = runner.partition(self.inventory)
self.assertEqual(early, list(runner.PREFLIGHT))
self.assertEqual(remaining, [name for name in self.names if name not in runner.PREFLIGHT])
self.assertEqual(sorted(early + remaining), sorted(self.names))
for invalid in ["", "TestOnly", "TestOne\nTestTwo", self.inventory + "\nTestFixture000",
self.inventory + "\nTestUnsafe|TestInjected", self.inventory + "\nFuzzBad.*",
self.inventory + "\nTest(name)", self.inventory + "\nTestX --skip TestY",
self.inventory.replace("TestRootInstallResetSafety\n", ""),
"\n".join(self.names[:-1])]:
with self.subTest(invalid=invalid[-60:]), self.assertRaises(ValueError):
runner.partition(invalid)
def test_discovery_order_survives_reversed_and_odd_inventories(self):
for names in (list(reversed(self.names)), self.names + ["TestFutureOdd"]):
with self.subTest(last=names[-1]):
early, remaining = runner.partition("\n".join(names))
self.assertEqual(early, [name for name in names if name in runner.PREFLIGHT])
self.assertEqual(remaining, [name for name in names if name not in runner.PREFLIGHT])
def execute(self, phase, failure_at=None):
calls, checks = [], []
def command(argv, **kwargs):
calls.append(argv)
self.assertTrue(kwargs["check"])
self.assertEqual(kwargs["cwd"], runner.ROOT if len(calls) == 1 else runner.ROOT / "scripts/installtests")
if len(calls) == failure_at:
raise subprocess.CalledProcessError(23, argv)
return SimpleNamespace(stdout=self.inventory)
def check(binary, name, **kwargs):
checks.append(name)
self.assertEqual(kwargs["cwd"], runner.ROOT / "scripts/installtests")
if len(checks) + 2 == failure_at:
raise subprocess.CalledProcessError(23, [str(binary), name])
with patch.object(runner.subprocess, "run", side_effect=command), patch.object(runner, "run_check", side_effect=check), contextlib.redirect_stdout(io.StringIO()):
if failure_at:
with self.assertRaises(subprocess.CalledProcessError):
runner.main(phase)
else:
runner.main(phase)
if failure_at:
self.assertEqual(len(calls) + len(checks), failure_at)
self.assertEqual(calls[0][:4], ["go", "test", "-c", "-o"])
if len(calls) > 1:
self.assertEqual(Path(calls[0][4]).name, "installtests.test")
self.assertEqual(calls[1], [calls[0][4], "-test.list=."])
self.assertEqual(calls[0][-1], runner.PACKAGE)
return checks
def test_workflow_phases_execute_every_check_once_and_propagate_failure(self):
all_checks = self.execute("all")
self.assertEqual(self.execute("preflight") + self.execute("remaining"), all_checks)
self.assertEqual(len(all_checks), len(set(all_checks)))
self.assertEqual(set(all_checks), set(self.names))
# Exercise every possible failure position, including the final reset,
# without replaying 500 mocked checks for each injected failure.
self.names = ["TestOne", "ExampleInstaller", "FuzzInstaller", "TestRootInstallResetSafety", *runner.PREFLIGHT]
self.inventory = "\n".join(self.names)
for phase in ("all", "preflight", "remaining"):
expected = self.execute(phase)
for failure in range(1, len(expected) + 3):
with self.subTest(phase=phase, failure=failure):
attempted = self.execute(phase, failure_at=failure)
self.assertEqual(attempted, expected[:max(0, failure - 2)])
def test_unknown_phase_starts_no_go_process(self):
with patch.object(runner.subprocess, "run") as run:
with self.assertRaises(ValueError):
runner.main("skip")
run.assert_not_called()
result = subprocess.run(["python3", "-I", "-S", str(ROOT / "scripts/installtests/run_unix_contracts.py"),
"--phase", "skip"], capture_output=True, text=True)
self.assertEqual(result.returncode, 2)
self.assertIn("invalid choice", result.stderr)
class CompiledGoControls(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.directory = tempfile.TemporaryDirectory(prefix="native-runner-control-")
cls.cwd = Path(cls.directory.name)
cls.binary = cls.cwd / "controls.test"
(cls.cwd / "go.mod").write_text("module native.runner.controls\n\ngo 1.24\n")
(cls.cwd / "controls_test.go").write_text('''package controls
import ("fmt"; "os"; "testing"; "time")
func TestPass(t *testing.T) { t.Run("child", func(t *testing.T) {}) }
func TestPassExtra(t *testing.T) { t.Fatal("selector must stay anchored") }
func TestSkip(t *testing.T) { t.Skip("explicit platform skip") }
func TestFail(t *testing.T) { t.Fatal("real failure") }
func TestExitZero(t *testing.T) { os.Exit(0) }
func TestTimeout(t *testing.T) { time.Sleep(time.Hour) }
func FuzzSeeds(f *testing.F) { f.Add("seed"); f.Fuzz(func(t *testing.T, s string) {}) }
func Example_output() { fmt.Println("installer"); /* Output: installer */ }
''')
subprocess.run(["go", "test", "-c", "-o", str(cls.binary), "."], cwd=cls.cwd, check=True, timeout=90)
@classmethod
def tearDownClass(cls):
cls.directory.cleanup()
def test_real_test_skip_example_and_fuzz_seed_complete(self):
for name in ("TestPass", "TestSkip", "Example_output", "FuzzSeeds"):
with self.subTest(name=name), contextlib.redirect_stdout(io.StringIO()):
runner.run_check(self.binary, name, cwd=self.cwd)
def test_actual_command_retains_deadline_count_and_exact_selection(self):
with patch.object(runner.subprocess, "Popen", wraps=subprocess.Popen) as popen, contextlib.redirect_stdout(io.StringIO()):
runner.run_check(self.binary, "TestPass", cwd=self.cwd)
self.assertEqual(popen.call_args.args[0], [str(self.binary), "-test.count=1", "-test.timeout=10m",
"-test.paniconexit0", "-test.v", "-test.run=^TestPass$"])
self.assertEqual(popen.call_args.kwargs["cwd"], self.cwd)
def test_failure_exit_zero_and_timeout_cannot_pass(self):
for name in ("TestFail", "TestExitZero", "TestTimeout"):
with self.subTest(name=name), contextlib.redirect_stdout(io.StringIO()), self.assertRaises(subprocess.CalledProcessError):
runner.run_check(self.binary, name, cwd=self.cwd, timeout="100ms")
def test_no_matching_test_cannot_pass(self):
with contextlib.redirect_stdout(io.StringIO()), self.assertRaises(RuntimeError):
runner.run_check(self.binary, "TestMissing", cwd=self.cwd)
if __name__ == "__main__":
unittest.main(verbosity=2)