diff options
Diffstat (limited to 'tests/installer-steps/test_idempotency_cluster.py')
| -rw-r--r-- | tests/installer-steps/test_idempotency_cluster.py | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/tests/installer-steps/test_idempotency_cluster.py b/tests/installer-steps/test_idempotency_cluster.py new file mode 100644 index 0000000..ecb279d --- /dev/null +++ b/tests/installer-steps/test_idempotency_cluster.py @@ -0,0 +1,119 @@ +"""Test the resume-idempotency cluster: three extracted installer helpers. + +Each fix targets a step that misbehaves on a resume re-run or with unexpected +system state: + +- crontab_append_once: the log-cleanup cron line was appended unconditionally, + so a resume re-run of essential_services stacked a duplicate line each time. + The helper appends only when no existing line matches. +- zfs_scrub_timer_units: the scrub timer used `zpool list | head -1`, which + picks an arbitrary pool when several exist and yields the malformed unit + `zfs-scrub-weekly@.timer` when none do. The helper emits one unit per pool + and nothing for no pools. +- enable_user_service: gamemode was enabled via `systemctl --user enable`, + which the script itself documents fails at install time (no user bus). The + helper creates the wants symlink directly, the pattern syncthing already uses. + +Method: sed-extract each helper from the real `archsetup` and drive it with +plain stdin/args (the first two are pure) or a temp HOME (the third). + +Run from repo root: + python3 -m unittest tests.installer-steps.test_idempotency_cluster +""" + +import os +import subprocess +import tempfile +import unittest + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +ARCHSETUP = os.path.join(REPO_ROOT, "archsetup") +ME = os.environ.get("USER") or __import__("getpass").getuser() + + +def extract(func): + return ( + "source <(sed -n '/^{f}() {{/,/^}}/p' \"{a}\")".format(f=func, a=ARCHSETUP) + ) + + +def run(func, body, stdin=None): + script = f"{extract(func)}\n{body}\n" + return subprocess.run( + ["bash", "-c", script], capture_output=True, text=True, timeout=10, + input=stdin, + ) + + +class CrontabAppendOnce(unittest.TestCase): + def call(self, existing, match, line): + body = f'crontab_append_once {match!r} {line!r}' + return run("crontab_append_once", body, stdin=existing).stdout + + def test_appends_when_absent(self): + out = self.call("", "log-cleanup", "0 12 * * * run-cleanup") + self.assertIn("0 12 * * * run-cleanup", out) + + def test_preserves_existing_and_appends(self): + out = self.call("0 0 * * * other-job\n", "log-cleanup", "0 12 * * * cleanup") + self.assertIn("other-job", out) + self.assertIn("0 12 * * * cleanup", out) + + def test_does_not_duplicate_when_present(self): + existing = "0 12 * * * $HOME/.local/bin/cron/log-cleanup\n" + out = self.call(existing, "cron/log-cleanup", "0 12 * * * dup") + self.assertNotIn("0 12 * * * dup", out) + self.assertEqual(out.count("log-cleanup"), 1) + + +class ZfsScrubTimerUnits(unittest.TestCase): + def units(self, pools_text): + out = run("zfs_scrub_timer_units", "zfs_scrub_timer_units", stdin=pools_text).stdout + return [ln for ln in out.splitlines() if ln] + + def test_single_pool(self): + self.assertEqual(self.units("zroot\n"), + ["zfs-scrub-weekly@zroot.timer"]) + + def test_no_pool_yields_no_unit(self): + self.assertEqual(self.units(""), []) + + def test_multiple_pools_each_get_a_unit(self): + self.assertEqual( + self.units("zroot\ntank\n"), + ["zfs-scrub-weekly@zroot.timer", "zfs-scrub-weekly@tank.timer"], + ) + + def test_blank_lines_are_skipped(self): + self.assertEqual(self.units("zroot\n\n"), + ["zfs-scrub-weekly@zroot.timer"]) + + +class EnableUserService(unittest.TestCase): + def test_creates_wants_symlink_in_user_config(self): + with tempfile.TemporaryDirectory() as home: + body = ( + f'enable_user_service {ME!r} gamemoded.service ' + f'/usr/lib/systemd/user/gamemoded.service {home!r}\n' + f'link="{home}/.config/systemd/user/default.target.wants/gamemoded.service"\n' + f'[ -L "$link" ] && echo "LINK=yes" || echo "LINK=no"\n' + f'echo "TARGET=$(readlink "$link")"' + ) + out = run("enable_user_service", body).stdout + self.assertIn("LINK=yes", out) + self.assertIn("TARGET=/usr/lib/systemd/user/gamemoded.service", out) + + def test_idempotent_on_rerun(self): + with tempfile.TemporaryDirectory() as home: + svc = "/usr/lib/systemd/user/gamemoded.service" + one = ( + f'enable_user_service {ME!r} gamemoded.service {svc!r} {home!r}\n' + f'enable_user_service {ME!r} gamemoded.service {svc!r} {home!r}\n' + f'echo "RC=$?"' + ) + out = run("enable_user_service", one).stdout + self.assertIn("RC=0", out) + + +if __name__ == "__main__": + unittest.main() |
