aboutsummaryrefslogtreecommitdiff
path: root/tests/audit-packages
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-11 11:29:41 -0500
committerCraig Jennings <c@cjennings.net>2026-06-11 11:29:41 -0500
commit1f89523739a575f5dec19616ec44df4143df4866 (patch)
tree0838b35025e415da76b61e62f6e714026c931db8 /tests/audit-packages
parente688d70ed400d8ea1e293493eddbf5e237854823 (diff)
downloadarchsetup-1f89523739a575f5dec19616ec44df4143df4866.tar.gz
archsetup-1f89523739a575f5dec19616ec44df4143df4866.zip
feat(scripts): package auditor + fix the four packages it caught
scripts/audit-packages.sh extracts every pacman_install/aur_install package (loop lists included) and verifies each against its declared source — sync dbs for official, one batched RPC query for AUR — flagging movers in both directions. Unit-tested against fixture installers with fake pacman/curl. First real run over 420 packages found four that vanished from both sources, each now fixed: libva-mesa-driver folded into mesa (line dropped), nvidia-dkms replaced by nvidia-open-dkms (Turing+; legacy cards are the preflight task's problem), swww replaced by awww (its successor, already what both machines run), and libappindicator-gtk3 replaced by libayatana-appindicator. Fifteen AUR entries that graduated to official repos still install fine via yay and are left as-is.
Diffstat (limited to 'tests/audit-packages')
-rw-r--r--tests/audit-packages/test_audit_packages.py127
1 files changed, 127 insertions, 0 deletions
diff --git a/tests/audit-packages/test_audit_packages.py b/tests/audit-packages/test_audit_packages.py
new file mode 100644
index 0000000..5adb26d
--- /dev/null
+++ b/tests/audit-packages/test_audit_packages.py
@@ -0,0 +1,127 @@
+"""Tests for scripts/audit-packages.sh.
+
+The auditor extracts every package archsetup installs — direct
+pacman_install/aur_install calls plus `for software in ...` loop lists —
+then verifies each against the right source: official packages must exist
+in the sync databases, AUR packages in the AUR. Packages that moved
+between sources are flagged in both directions. Tests run the real script
+against a fixture installer snippet, a fake pacman, and a fake curl that
+serves controlled AUR RPC JSON; no network, no real pacman db.
+
+Run from repo root:
+ make test-unit (or python3 -m unittest tests.audit-packages.test_audit_packages)
+"""
+
+import json
+import os
+import shutil
+import stat
+import subprocess
+import tempfile
+import unittest
+
+REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+SCRIPT = os.path.join(REPO_ROOT, "scripts/audit-packages.sh")
+
+FIXTURE = """
+pacman_install good-official # exists in repos
+pacman_install gone-official # vanished from repos, not in AUR
+pacman_install moved-to-aur # left repos, lives in AUR now
+aur_install good-aur # exists in AUR
+aur_install gone-aur # vanished from AUR
+aur_install now-official # promoted into the repos
+aur_install "$software" # variable — not a literal package
+for software in loop-pkg-a loop-pkg-b \\
+ loop-pkg-c; do
+ pacman_install "$software"
+done
+"""
+
+
+class AuditHarness(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.mkdtemp(prefix="audit-pkgs-test-")
+ self.fixture = os.path.join(self.tmp, "installer-snippet")
+ with open(self.fixture, "w") as f:
+ f.write(FIXTURE)
+
+ # Fake pacman: -Si exits 0 only for the packages "in the repos".
+ official = ["good-official", "now-official",
+ "loop-pkg-a", "loop-pkg-b", "loop-pkg-c"]
+ self.fake_pacman = os.path.join(self.tmp, "pacman")
+ self._stub(self.fake_pacman, (
+ 'case "$2" in\n'
+ + "".join(f" {p}) exit 0 ;;\n" for p in official)
+ + ' *) exit 1 ;;\nesac\n'
+ ))
+
+ # Fake curl: serves AUR RPC v5 info JSON for the packages "in the AUR".
+ aur = ["good-aur", "moved-to-aur"]
+ rpc = json.dumps({"results": [{"Name": p} for p in aur]})
+ self.fake_curl = os.path.join(self.tmp, "curl")
+ self._stub(self.fake_curl, f"printf '%s' '{rpc}'\nexit 0\n")
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp, ignore_errors=True)
+
+ def _stub(self, path, body):
+ with open(path, "w") as f:
+ f.write("#!/bin/sh\n" + body)
+ os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
+
+ def run_audit(self, *args):
+ env = os.environ.copy()
+ env["AUDIT_PACMAN"] = self.fake_pacman
+ env["AUDIT_CURL"] = self.fake_curl
+ return subprocess.run(
+ [SCRIPT, *args, self.fixture],
+ env=env, capture_output=True, text=True, timeout=20,
+ )
+
+
+class TestParser(AuditHarness):
+ def test_list_mode_extracts_direct_and_loop_packages(self):
+ r = self.run_audit("--list")
+ self.assertEqual(r.returncode, 0, msg=r.stderr)
+ for p in ("good-official", "gone-official", "moved-to-aur",
+ "loop-pkg-a", "loop-pkg-b", "loop-pkg-c"):
+ self.assertIn(p, r.stdout)
+ for p in ("good-aur", "gone-aur", "now-official"):
+ self.assertIn(p, r.stdout)
+
+ def test_variable_args_are_not_packages(self):
+ r = self.run_audit("--list")
+ self.assertNotIn("$software", r.stdout)
+
+
+class TestAudit(AuditHarness):
+ def test_clean_packages_pass_silently(self):
+ r = self.run_audit()
+ self.assertNotIn("good-official", r.stdout)
+ self.assertNotIn("good-aur", r.stdout)
+
+ def test_missing_everywhere_reported(self):
+ r = self.run_audit()
+ self.assertNotEqual(r.returncode, 0)
+ self.assertIn("gone-official", r.stdout)
+ self.assertIn("gone-aur", r.stdout)
+
+ def test_movers_flagged_in_both_directions(self):
+ r = self.run_audit()
+ self.assertIn("moved-to-aur", r.stdout)
+ self.assertIn("now-official", r.stdout)
+
+ def test_clean_fixture_exits_zero(self):
+ clean = os.path.join(self.tmp, "clean-snippet")
+ with open(clean, "w") as f:
+ f.write("pacman_install good-official\naur_install good-aur\n")
+ env = os.environ.copy()
+ env["AUDIT_PACMAN"] = self.fake_pacman
+ env["AUDIT_CURL"] = self.fake_curl
+ r = subprocess.run([SCRIPT, clean], env=env,
+ capture_output=True, text=True, timeout=20)
+ self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr)
+
+
+if __name__ == "__main__":
+ unittest.main()