aboutsummaryrefslogtreecommitdiff
path: root/tests/audit-packages/test_audit_packages.py
blob: 5adb26d69ed072631c0edee2c94b91fc395afdcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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()