"""Tests for the run_task / enable_service helpers in the archsetup installer. run_task is the installer's describe-run-warn primitive. It replaces the hand-written idiom that recurs ~100 times across the script: action="enabling rngd service" && display "task" "$action" systemctl enable rngd >> "$logfile" 2>&1 || error_warn "$action" "$?" as a single call: run_task "enabling rngd service" systemctl enable rngd It announces the task via display, runs the command with stdout+stderr appended to $logfile, and on failure calls error_warn with the command's real exit code (non-fatal). enable_service is a thin wrapper that enables one or more systemd units with the conventional "enabling service" wording. These tests exercise the REAL function bodies, extracted from the `archsetup` script at run time (not a copy), with recording stubs standing in for display, error_warn, and systemctl. The command run by run_task is genuinely executed. Run from repo root: python3 -m unittest tests.run-task.test_run_task """ import os import shutil 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") # A bash harness that sources the real run_task + enable_service out of the # installer, with recording stubs for their dependencies. Each stub appends a # tab-separated record to a file named by an env var, so the Python side can # assert what was called. The real command passed to run_task still runs. WRAPPER = r"""#!/bin/bash ARCHSETUP="$1"; shift logfile="$LOGFILE" display() { printf '%s\t%s\n' "$1" "$2" >> "$DISPLAY_LOG"; } error_warn() { printf '%s\t%s\n' "$1" "$2" >> "$ERRWARN_LOG"; return 1; } systemctl() { printf 'systemctl %s\n' "$*"; } source <(sed -n '/^run_task() {/,/^}/p' "$ARCHSETUP") source <(sed -n '/^enable_service() {/,/^}/p' "$ARCHSETUP") "$@" """ class RunTaskHarness(unittest.TestCase): def setUp(self): self.tmp = tempfile.mkdtemp(prefix="run-task-test-") self.wrapper = os.path.join(self.tmp, "run.sh") with open(self.wrapper, "w") as f: f.write(WRAPPER) os.chmod(self.wrapper, 0o755) self.logfile = os.path.join(self.tmp, "install.log") self.display_log = os.path.join(self.tmp, "display.log") self.errwarn_log = os.path.join(self.tmp, "errwarn.log") def tearDown(self): shutil.rmtree(self.tmp, ignore_errors=True) def call(self, *args): env = dict(os.environ) env["LOGFILE"] = self.logfile env["DISPLAY_LOG"] = self.display_log env["ERRWARN_LOG"] = self.errwarn_log return subprocess.run( ["bash", self.wrapper, ARCHSETUP, *args], capture_output=True, text=True, timeout=10, env=env, ) def read(self, path): if not os.path.exists(path): return "" with open(path) as f: return f.read() # --- Normal cases ----------------------------------------------------- def test_run_task_success_announces_and_runs(self): result = self.call("run_task", "doing a thing", "true") self.assertEqual(result.returncode, 0, result.stderr) # Announced as a "task" with the exact description. self.assertEqual(self.read(self.display_log), "task\tdoing a thing\n") # No warning on success. self.assertEqual(self.read(self.errwarn_log), "") def test_run_task_captures_command_output_to_logfile(self): result = self.call("run_task", "echo something", "echo", "hello-from-cmd") self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("hello-from-cmd", self.read(self.logfile)) # Command output is logged, not printed to the terminal. self.assertNotIn("hello-from-cmd", result.stdout) def test_run_task_captures_stderr_to_logfile(self): # `ls` of a missing path writes to stderr; it must land in the logfile. missing = os.path.join(self.tmp, "no-such-path") self.call("run_task", "listing", "ls", missing) self.assertIn("no-such-path", self.read(self.logfile)) def test_run_task_preserves_multiple_arguments(self): self.call("run_task", "multi-arg", "printf", "%s|%s|%s", "a", "b", "c") self.assertIn("a|b|c", self.read(self.logfile)) def test_run_task_preserves_arguments_with_spaces(self): self.call("run_task", "spacey", "printf", "[%s]", "two words") self.assertIn("[two words]", self.read(self.logfile)) # --- enable_service --------------------------------------------------- def test_enable_service_single_unit(self): self.call("enable_service", "rngd") self.assertEqual(self.read(self.display_log), "task\tenabling rngd service\n") self.assertIn("systemctl enable rngd", self.read(self.logfile)) def test_enable_service_multiple_units(self): self.call("enable_service", "foo", "bar", "baz") disp = self.read(self.display_log) self.assertIn("task\tenabling foo service\n", disp) self.assertIn("task\tenabling bar service\n", disp) self.assertIn("task\tenabling baz service\n", disp) log = self.read(self.logfile) self.assertIn("systemctl enable foo", log) self.assertIn("systemctl enable bar", log) self.assertIn("systemctl enable baz", log) # --- Error cases ------------------------------------------------------ def test_run_task_failure_warns_with_description(self): result = self.call("run_task", "failing thing", "false") self.assertNotEqual(result.returncode, 0) self.assertEqual(self.read(self.errwarn_log), "failing thing\t1\n") def test_run_task_failure_propagates_real_exit_code(self): # `bash -c 'exit 42'` must surface 42 to error_warn, not a clobbered 0. self.call("run_task", "exit-42", "bash", "-c", "exit 42") self.assertEqual(self.read(self.errwarn_log), "exit-42\t42\n") def test_enable_service_failure_warns_per_unit(self): # Override systemctl to fail; each unit should produce a warning. env = dict(os.environ) env["LOGFILE"] = self.logfile env["DISPLAY_LOG"] = self.display_log env["ERRWARN_LOG"] = self.errwarn_log # Re-create wrapper with a failing systemctl stub for this case. failing = os.path.join(self.tmp, "run-fail.sh") with open(failing, "w") as f: f.write(WRAPPER.replace( "systemctl() { printf 'systemctl %s\\n' \"$*\"; }", "systemctl() { printf 'systemctl %s\\n' \"$*\"; return 1; }", )) os.chmod(failing, 0o755) subprocess.run( ["bash", failing, ARCHSETUP, "enable_service", "alpha", "beta"], capture_output=True, text=True, timeout=10, env=env, ) warns = self.read(self.errwarn_log) self.assertIn("enabling alpha service\t1\n", warns) self.assertIn("enabling beta service\t1\n", warns) if __name__ == "__main__": unittest.main()