#!/usr/bin/env python3 """Smoke tests for audit-patch-idempotency.py. Run with: python3 -m unittest local/scripts/tests/test_audit_patch_idempotency.py """ import re import sys import unittest from pathlib import Path SCRIPTS_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(SCRIPTS_DIR)) import importlib.util # noqa: E402 _spec = importlib.util.spec_from_file_location( "api", SCRIPTS_DIR / "audit-patch-idempotency.py" ) assert _spec is not None and _spec.loader is not None api = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(api) class TestCollectPatches(unittest.TestCase): """The patch collector walks local/patches//NN-*.patch.""" def test_collect_real_patches(self): # On the live tree, this should find at least 10 patches. patches = list(api.collect_patches()) self.assertGreater(len(patches), 0) # Every patch is a 2-tuple (component, Path). for comp, p in patches: self.assertIsInstance(comp, str) self.assertTrue(p.exists()) def test_collect_filter_by_component(self): # Should find the 3 libdrm patches. patches = list(api.collect_patches(component_filter="libdrm")) for _, name in patches: self.assertIn("libdrm", str(name)) def test_collect_nonexistent_component(self): patches = list(api.collect_patches(component_filter="does-not-exist-xyz")) self.assertEqual(patches, []) class TestPatchNameValidation(unittest.TestCase): """The regex accepts files matching NN-name.patch.""" def test_valid_patch_names(self): # The collector uses PATCH_NAME_RE — verify it accepts real names. names = [ "01-foo.patch", "02-bar.patch", "99-trailing-numbers.patch", "10-multi-word-name-with-dashes.patch", ] for n in names: self.assertTrue(api.PATCH_NAME_RE.match(n), f"should accept {n!r}") def test_invalid_patch_names(self): for n in ["foo.patch", "01-foo", "01-.patch", "foo-01-bar.patch"]: self.assertFalse(api.PATCH_NAME_RE.match(n), f"should reject {n!r}") class TestJSONSchemaHonesty(unittest.TestCase): """--no-fetch must produce JSON with skipped entries and a clear message.""" def test_no_fetch_json_shape(self): import json import subprocess proc = subprocess.run( ["python3", str(SCRIPTS_DIR / "audit-patch-idempotency.py"), "--no-fetch", "--json"], capture_output=True, text=True, ) # With --no-fetch, every entry is skipped -> exit 2 (CI-safe). self.assertEqual(proc.returncode, 2) data = json.loads(proc.stdout) self.assertIn("patches", data) self.assertIn("total", data) self.assertIn("errors", data) self.assertIn("skipped", data) # Every entry must be status=skipped. for entry in data["patches"]: self.assertEqual(entry["status"], "skipped") self.assertEqual(data["skipped"], data["total"]) def test_no_fetch_text_honest_about_skipping(self): import subprocess proc = subprocess.run( ["python3", str(SCRIPTS_DIR / "audit-patch-idempotency.py"), "--no-fetch"], capture_output=True, text=True, ) # Must NOT say "All N patches are idempotent" when none were # actually audited. self.assertIn("SKIPPED", proc.stdout) self.assertIn("No audit was performed", proc.stdout) if __name__ == "__main__": unittest.main()