97 lines
4.4 KiB
Python
97 lines
4.4 KiB
Python
"""Execute maintscript branches with cleanup commands intercepted in a tempdir."""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "packaging"))
|
|
from build_expanded import maintainer_prerm, PRIVILEGED_HELPER_FLOOR
|
|
from build_profiles import CANDIDATE_VERSION
|
|
|
|
|
|
class MaintainerScriptTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.root = Path(self.tmp.name)
|
|
self.trace = self.root / "calls.jsonl"
|
|
self.stub = self.root / "cleanup-stub"
|
|
self.stub.write_text(
|
|
"#!" + sys.executable + "\n"
|
|
"import json,os,sys\n"
|
|
"with open(os.environ['TEST_CALLS'], 'a') as stream: stream.write(json.dumps(sys.argv[1:])+'\\n')\n"
|
|
"raise SystemExit(int(os.environ.get('TEST_CLEANUP_RC','0')))\n"
|
|
)
|
|
self.stub.chmod(0o755)
|
|
self.screen = self.root / "screen-access.json"
|
|
|
|
def run_script(self, args, *, fail=0, screen=False):
|
|
script = maintainer_prerm(PRIVILEGED_HELPER_FLOOR)
|
|
# Only side-effecting commands and config probes are redirected. The
|
|
# real dpkg comparisons are read-only and verify Debian version ordering.
|
|
script = script.replace("/usr/bin/python3", shlex.quote(str(self.stub)))
|
|
script = script.replace("/usr/lib/framebridge-expanded/bin/framebridge-screen-access", shlex.quote(str(self.stub)))
|
|
script = script.replace("/etc/framebridge-expanded/screen-access.json", shlex.quote(str(self.screen)))
|
|
if screen:
|
|
self.screen.write_text("{}")
|
|
env = dict(os.environ, TEST_CALLS=str(self.trace), TEST_CLEANUP_RC=str(fail))
|
|
result = subprocess.run(["/bin/sh", "-s", "--", *args], input=script, text=True,
|
|
capture_output=True, env=env, timeout=10)
|
|
calls = [json.loads(row) for row in self.trace.read_text().splitlines()] if self.trace.exists() else []
|
|
return result, calls
|
|
|
|
def test_downgrade_revokes_only_helper_before_replacement(self):
|
|
result, calls = self.run_script(["upgrade", "0.3.1"])
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(len(calls), 1)
|
|
self.assertEqual(calls[0][:3], ["-I", "-S", "-c"])
|
|
self.assertEqual(calls[0][3], 'import runpy; runpy.run_path("/usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py")["purge_credential_helpers"]()')
|
|
self.assertNotIn("--all-users", calls[0])
|
|
|
|
def test_forward_upgrade_retains_helper_state(self):
|
|
result, calls = self.run_script(["upgrade", "0.3.2"])
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_same_version_reinstall_retains_helper_state(self):
|
|
result, calls = self.run_script(["upgrade", CANDIDATE_VERSION])
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_remove_and_deconfigure_keep_existing_cleanup(self):
|
|
for operation in ("remove", "deconfigure"):
|
|
if self.trace.exists():
|
|
self.trace.unlink()
|
|
result, calls = self.run_script([operation], screen=True)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(calls, [["-I", "-S", "/usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py", "--all-users"], ["disable"]])
|
|
|
|
def test_failed_helper_cleanup_blocks_downgrade(self):
|
|
result, calls = self.run_script(["upgrade", "0.3.1"], fail=17)
|
|
self.assertEqual(result.returncode, 17)
|
|
self.assertEqual(len(calls), 1)
|
|
|
|
def test_invalid_target_does_not_execute_or_interpolate(self):
|
|
marker = self.root / "must-not-exist"
|
|
result, calls = self.run_script(["upgrade", "0.3.1;$(touch " + str(marker) + ")"])
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertEqual(calls, [])
|
|
self.assertFalse(marker.exists())
|
|
|
|
def test_missing_target_fails_closed(self):
|
|
result, calls = self.run_script(["upgrade"])
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_current_version_cannot_inject_script_paths(self):
|
|
with self.assertRaises(ValueError):
|
|
maintainer_prerm("0.3.2'; /tmp/untrusted-script; '")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|