aboutsummaryrefslogtreecommitdiff
path: root/scripts/testing/tests/test_services.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-25 01:12:35 -0400
committerCraig Jennings <c@cjennings.net>2026-06-25 01:12:35 -0400
commit3cac3b3dfcd432395201a309920c2491ee9caf01 (patch)
tree45c3ea7f8b73f7375c484912ecadc8a65c4d88a5 /scripts/testing/tests/test_services.py
parent99a26d7de23bbfc757957c08e47606c3690df4cb (diff)
downloadarchsetup-3cac3b3dfcd432395201a309920c2491ee9caf01.tar.gz
archsetup-3cac3b3dfcd432395201a309920c2491ee9caf01.zip
test(archsetup): port full shell validation sweep to Testinfra (P2)
Port all ~26 post-install checks from validation.sh to pytest/Testinfra, reaching parity before the cutover. Adds test_users, test_packages, test_services, test_desktop, test_boot, test_keyring, and test_archsetup (88 tests after parametrizing groups, services, timers, tools, and configs), plus shared conftest fixtures for ZFS/NVMe/compositor/networking gating. The shell sweep's three outcomes map cleanly: hard failures become assertions, advisory warnings and unmet preconditions (headless compositor, slirp networking, optional services, non-ZFS/non-NVMe hosts) become skips. One correctness fix vs the shell sweep: check awww, not swww — archsetup installs awww (swww's successor) and `pacman -Q swww` no longer matches. Verified on the host: py_compile clean, pytest --collect-only green (88 tests). The sweep against a real VM is verified by the make test run that follows.
Diffstat (limited to 'scripts/testing/tests/test_services.py')
-rw-r--r--scripts/testing/tests/test_services.py98
1 files changed, 94 insertions, 4 deletions
diff --git a/scripts/testing/tests/test_services.py b/scripts/testing/tests/test_services.py
index dc89e74..0ca3970 100644
--- a/scripts/testing/tests/test_services.py
+++ b/scripts/testing/tests/test_services.py
@@ -1,13 +1,103 @@
# SPDX-License-Identifier: GPL-3.0-or-later
-"""Post-install checks: essential services archsetup enables.
+"""Post-install checks: services, timers, and their functional health.
-Parity port of validate_firewall from validation.sh (more to follow in P2).
+Parity port of validate_firewall, validate_dns_config, validate_avahi,
+validate_fail2ban, validate_networkmanager, and validate_all_services /
+validate_service_functions.
+
+Mapping of the shell sweep's three outcomes:
+ - validation_fail (hard) -> assert
+ - validation_warn (soft) -> pytest.skip with the reason (visible, never red)
+ - validation_skip (precond)-> pytest.skip gated on a fixture
"""
import pytest
+# Required services: (name, must_be_active). ufw can't activate in the VM (no
+# iptables kernel modules), so it's enabled-only; cronie/atd are enabled-only too.
+REQUIRED_ENABLED_ACTIVE = ["sshd", "systemd-resolved", "fail2ban", "NetworkManager", "rngd"]
+REQUIRED_ENABLED_ONLY = ["ufw", "cronie", "atd"]
+REQUIRED_TIMERS = ["reflector.timer", "paccache.timer"]
+OPTIONAL_SERVICES = ["avahi-daemon", "bluetooth", "cups", "docker", "tailscaled"]
+
+
+@pytest.mark.attribution("archsetup")
+@pytest.mark.parametrize("svc", REQUIRED_ENABLED_ACTIVE)
+def test_required_service_enabled_and_active(host, svc):
+ s = host.service(svc)
+ assert s.is_enabled, "%s should be enabled" % svc
+ assert s.is_running, "%s should be active" % svc
+
+
@pytest.mark.smoke
@pytest.mark.attribution("archsetup")
-def test_ufw_firewall_enabled(host):
- assert host.service("ufw").is_enabled
+@pytest.mark.parametrize("svc", REQUIRED_ENABLED_ONLY)
+def test_required_service_enabled(host, svc):
+ assert host.service(svc).is_enabled, "%s should be enabled" % svc
+
+
+@pytest.mark.attribution("archsetup")
+@pytest.mark.parametrize("timer", REQUIRED_TIMERS)
+def test_required_timer_enabled(host, timer):
+ assert host.service(timer).is_enabled, "%s should be enabled" % timer
+
+
+@pytest.mark.parametrize("svc", OPTIONAL_SERVICES)
+def test_optional_service(host, svc):
+ # Optional: warn-if-missing in the shell sweep -> skip here so it never reds.
+ if not host.service(svc).is_enabled:
+ pytest.skip("%s not enabled (optional)" % svc)
+
+
+@pytest.mark.attribution("archsetup")
+def test_dns_over_tls_dropin_present(host):
+ # archsetup ships /etc/systemd/resolved.conf.d/dns-over-tls.conf.
+ assert host.file("/etc/systemd/resolved.conf.d/dns-over-tls.conf").exists
+
+
+@pytest.mark.attribution("archsetup")
+def test_fail2ban_responds(host):
+ assert host.run("fail2ban-client status").rc == 0
+
+
+@pytest.mark.attribution("archsetup")
+def test_networkmanager_responds(host):
+ assert host.run("nmcli general status").rc == 0
+
+
+@pytest.mark.attribution("archsetup")
+def test_log_cleanup_cron_installed(host, target_user):
+ out = host.run("sudo -u %s crontab -l" % target_user).stdout
+ assert "log-cleanup" in out, "log-cleanup entry missing from user crontab"
+
+
+@pytest.mark.attribution("archsetup")
+def test_syncthing_user_lingering_enabled(host, target_user):
+ # syncthing runs as a user service; lingering must be on for autostart.
+ assert host.file("/var/lib/systemd/linger/%s" % target_user).exists
+
+
+def test_dns_resolution(host):
+ # Network-dependent; advisory in the shell sweep. Skip on failure.
+ if host.run("resolvectl query archlinux.org").rc != 0:
+ pytest.skip("DNS resolution query failed (network-dependent)")
+
+
+def test_mdns_resolves(host, on_slirp):
+ # mDNS needs multicast, which QEMU slirp doesn't pass.
+ if on_slirp:
+ pytest.skip("mDNS not possible on slirp networking (no multicast)")
+ if not host.service("avahi-daemon").is_enabled:
+ pytest.skip("avahi-daemon not enabled")
+ hostname = host.run("hostname").stdout.strip()
+ assert host.run("ping -c 1 -W 2 %s.local" % hostname).rc == 0
+
+
+def test_docker_functional(host):
+ if not host.service("docker").is_enabled:
+ pytest.skip("docker not enabled")
+ if not host.service("docker").is_running:
+ # archsetup enables docker for next boot, not --now; pre-reboot this is correct.
+ pytest.skip("docker enabled but not started (starts on boot by design)")
+ assert host.run("docker info").rc == 0