Files
xas-test-library/tests/test_runner.py
T

103 lines
4.4 KiB
Python

"""Behavioral acceptance; Linux process tests also run on the target host."""
import json
import os
from pathlib import Path
import signal
import subprocess
import sys
import tempfile
import time
import unittest
ROOT = Path(__file__).resolve().parents[1]
RUNNER = ROOT / "scripts" / "run_smoke.py"
class RunnerTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.results = Path(self.temp.name)
def command(self, mode="success", run_id="test-1", timeout="3"):
self.assertTrue(RUNNER.is_file(), "run_smoke.py is not implemented")
return [sys.executable, "-u", str(RUNNER), "--mode", mode,
"--run-id", run_id, "--timeout", timeout,
"--results-root", str(self.results)]
def run_task(self, **kwargs):
return subprocess.run(self.command(**kwargs), capture_output=True,
text=True, timeout=12)
def summary(self, run_id="test-1"):
return json.loads((self.results / run_id / "summary.json").read_text())
def test_success_streams_progress_and_records_synthetic_result(self):
result = self.run_task()
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
summary = self.summary()
self.assertEqual(summary["status"], "succeeded")
self.assertEqual(summary["run_id"], "test-1")
self.assertTrue(summary["synthetic"])
self.assertEqual(summary["result"]["checks_passed"], 3)
self.assertIn("progress=1/3", result.stdout)
self.assertIn("progress=3/3", result.stdout)
self.assertEqual(len(summary["source_sha256"]["smoke_task.py"]), 64)
def test_nonzero_exit_is_recorded_as_failure(self):
result = self.run_task(mode="failure")
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.summary()["status"], "failed")
self.assertEqual(self.summary()["exit_code"], 23)
def test_zero_exit_with_invalid_report_is_failure(self):
result = self.run_task(mode="invalid-report")
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.summary()["status"], "invalid_result")
def test_reused_run_id_preserves_previous_result(self):
self.assertEqual(self.run_task().returncode, 0)
before = (self.results / "test-1" / "summary.json").read_bytes()
self.assertNotEqual(self.run_task(mode="failure").returncode, 0)
self.assertEqual((self.results / "test-1" / "summary.json").read_bytes(), before)
def test_invalid_inputs_do_not_create_run_directory(self):
for kwargs in ({"run_id": "../escape"}, {"mode": "arbitrary"},
{"timeout": "0"}, {"timeout": "31"}, {"timeout": "nan"}):
with self.subTest(kwargs=kwargs):
self.assertNotEqual(self.run_task(**kwargs).returncode, 0)
self.assertEqual(list(self.results.iterdir()), [])
@unittest.skipUnless(sys.platform == "linux", "requires Linux process groups")
def test_timeout_terminates_child_and_records_timeout(self):
result = self.run_task(mode="wait", timeout="2")
self.assertEqual(result.returncode, 124, result.stdout + result.stderr)
summary = self.summary()
self.assertEqual(summary["status"], "timed_out")
self.assertFalse(Path(f'/proc/{summary["child_pid"]}').exists())
@unittest.skipUnless(sys.platform == "linux", "requires Linux process groups")
def test_cancel_terminates_parent_and_child(self):
proc = subprocess.Popen(self.command(mode="wait", timeout="15"),
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
try:
deadline = time.monotonic() + 4
child_file = self.results / "test-1" / "child.pid"
while not child_file.exists() and time.monotonic() < deadline:
time.sleep(.05)
self.assertTrue(child_file.exists(), "worker did not start")
child_pid = int(child_file.read_text())
os.kill(proc.pid, signal.SIGTERM)
out, err = proc.communicate(timeout=4)
self.assertEqual(proc.returncode, 130, out + err)
self.assertEqual(self.summary()["status"], "cancelled")
self.assertFalse(Path(f"/proc/{child_pid}").exists())
finally:
if proc.poll() is None:
proc.kill()
proc.communicate()
if __name__ == "__main__":
unittest.main()