aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps
diff options
context:
space:
mode:
Diffstat (limited to 'tests/installer-steps')
-rw-r--r--tests/installer-steps/test_configure_hibernate_delay.py131
-rw-r--r--tests/installer-steps/test_orchestrators.py2
2 files changed, 132 insertions, 1 deletions
diff --git a/tests/installer-steps/test_configure_hibernate_delay.py b/tests/installer-steps/test_configure_hibernate_delay.py
new file mode 100644
index 0000000..95f2dcb
--- /dev/null
+++ b/tests/installer-steps/test_configure_hibernate_delay.py
@@ -0,0 +1,131 @@
+"""Test configure_hibernate_delay — the suspend-to-hibernate handoff.
+
+The desktop panel can upgrade its idle suspend stage to
+suspend-then-hibernate, but the delay between the two is systemd's
+HibernateDelaySec, which lives in a root-owned drop-in and so cannot be a
+panel setting. Left unset the delay depends on the hardware: a machine
+with a battery hibernates on a low-battery alarm at an hour nobody chose,
+and one without falls back to systemd's 2h default.
+
+90 minutes is the seeded value: long enough that stepping out of a meeting
+costs a screen unlock rather than a passphrase and a resume, short enough
+that a laptop in a bag is not holding its encryption keys in RAM all
+afternoon (a suspended machine is; a hibernated one is not).
+
+Method: sed-extract configure_hibernate_delay from the real `archsetup`,
+point it at a temp config dir, and assert on the file it writes.
+
+ python3 -m unittest tests.installer-steps.test_configure_hibernate_delay
+"""
+
+import os
+import re
+import stat
+import subprocess
+import tempfile
+import textwrap
+import unittest
+
+REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+ARCHSETUP = os.path.join(REPO_ROOT, "archsetup")
+
+
+def run(confdir):
+ script = textwrap.dedent(f"""\
+ logfile=/dev/null
+ action=""
+ display() {{ :; }}
+ error_warn() {{ echo "WARN: $1"; return 1; }}
+ source <(sed -n '/^configure_hibernate_delay() {{/,/^}}/p' "{ARCHSETUP}")
+ configure_hibernate_delay "{confdir}"
+ echo "RC=$?"
+ exit 0
+ """)
+ return subprocess.run(
+ ["bash", "-c", script], capture_output=True, text=True, timeout=10,
+ )
+
+
+def rc_of(r):
+ m = re.search(r"^RC=(\d+)$", r.stdout, re.M)
+ assert m, "no RC line in output: %r / %r" % (r.stdout, r.stderr)
+ return int(m.group(1))
+
+
+def body(confdir):
+ with open(os.path.join(confdir, "60-hibernate-delay.conf")) as f:
+ return f.read()
+
+
+class ConfigureHibernateDelay(unittest.TestCase):
+ # ------------------------------------------------------------ normal ----
+ def test_writes_a_sleep_drop_in_with_the_delay(self):
+ with tempfile.TemporaryDirectory() as d:
+ r = run(d)
+ self.assertEqual(rc_of(r), 0)
+ text = body(d)
+ self.assertIn("[Sleep]", text)
+ self.assertIn("HibernateDelaySec=90min", text)
+
+ def test_drop_in_is_world_readable_not_writable(self):
+ with tempfile.TemporaryDirectory() as d:
+ run(d)
+ mode = stat.S_IMODE(
+ os.stat(os.path.join(d, "60-hibernate-delay.conf")).st_mode)
+ self.assertEqual(mode, 0o644)
+
+ def test_explains_why_the_value_is_not_left_to_systemd(self):
+ # The number is a judgement call, so the file has to carry its
+ # reasoning: whoever changes it next needs the tradeoff, not just
+ # a number to overwrite. Assert on the tradeoff's own terms —
+ # "contains a hash" would pass on any comment at all.
+ with tempfile.TemporaryDirectory() as d:
+ run(d)
+ text = body(d).lower()
+ self.assertIn("2h default", text) # what unset would give
+ self.assertIn("encryption keys", text) # why not longer
+ self.assertIn("passphrase", text) # why not shorter
+
+ def test_drop_in_sorts_above_vendor_drop_ins(self):
+ # systemd reserves 10-40 for /usr and 60-90 for /etc. A 10- file
+ # in /etc sorts below a vendor 20- file and silently loses to it.
+ with tempfile.TemporaryDirectory() as d:
+ run(d)
+ name = os.listdir(d)[0]
+ prefix = int(name.split("-")[0])
+ self.assertGreaterEqual(prefix, 60)
+ self.assertLessEqual(prefix, 90)
+
+ # ---------------------------------------------------------- boundary ----
+ def test_running_twice_leaves_one_correct_drop_in(self):
+ with tempfile.TemporaryDirectory() as d:
+ run(d)
+ first = body(d)
+ r = run(d)
+ self.assertEqual(rc_of(r), 0)
+ self.assertEqual(first, body(d))
+
+ def test_absent_directory_is_created(self):
+ with tempfile.TemporaryDirectory() as d:
+ nested = os.path.join(d, "etc", "systemd", "sleep.conf.d")
+ r = run(nested)
+ self.assertEqual(rc_of(r), 0)
+ self.assertIn("HibernateDelaySec=90min", body(nested))
+
+ # ------------------------------------------------------------- error ----
+ @unittest.skipUnless(os.geteuid() != 0, "root ignores directory write bits")
+ def test_unwritable_directory_warns_and_does_not_crash(self):
+ with tempfile.TemporaryDirectory() as d:
+ confdir = os.path.join(d, "ro")
+ os.mkdir(confdir)
+ os.chmod(confdir, 0o500)
+ try:
+ r = run(confdir)
+ self.assertIn("WARN:", r.stdout)
+ self.assertNotEqual(rc_of(r), 0)
+ finally:
+ os.chmod(confdir, 0o700)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/installer-steps/test_orchestrators.py b/tests/installer-steps/test_orchestrators.py
index 34c46d1..c2eb710 100644
--- a/tests/installer-steps/test_orchestrators.py
+++ b/tests/installer-steps/test_orchestrators.py
@@ -28,7 +28,7 @@ ARCHSETUP = os.path.join(REPO_ROOT, "archsetup")
ORCHESTRATORS = {
"essential_services": [
"configure_randomness", "configure_networking", "configure_power",
- "configure_backlight_access",
+ "configure_hibernate_delay", "configure_backlight_access",
"configure_ssh_server", "configure_fail2ban", "configure_firewall",
"configure_service_discovery", "configure_job_scheduling",
"configure_package_cache", "configure_snapshots",