]> git.ekhem.eu.org Git - guix.git/commitdiff
[tests] Add VPS test harness infrastructure.
authorJakub Czajka <jakub@ekhem.eu.org>
Wed, 15 Jul 2026 22:49:55 +0000 (22:49 +0000)
committerJakub Czajka <jakub@ekhem.eu.org>
Fri, 17 Jul 2026 13:47:16 +0000 (13:47 +0000)
Boot a marionette VM that inherits from vps-operating-system
with static networking and a throwaway SSH key.  No
individual test cases yet — the harness validates that the
VM boots and the marionette REPL is reachable.  Add
test-vps / test-vps-fresh Makefile targets with cache.

Co-Authored-By: Claude <noreply@anthropic.com>
.claude/hooks/block-builds
Makefile
tests/vps-base.scm [new file with mode: 0644]

index 924c1384640b9396c21cc8a1423acea8afd61307..ce3922d860ca1fc0c039c5d733a1a516dfa4408e 100755 (executable)
@@ -33,7 +33,7 @@ case "$command" in
             '}}'
         exit 0
         ;;
-    *'make build-'*)
+    *'make build-'*|*'make test-'*)
         $GUIX_BIN/echo \
             '{"hookSpecificOutput": {' \
             ' "hookEventName": "PreToolUse",' \
index a6ab11605421bd4d933b406f046ed86b9fad4d5a..896a0460107247a4fc3f02c9cb01f6cee3d8c354 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -60,6 +60,29 @@ build-vps-home:
 
 build-vps: build-vps-system build-vps-home
 
+# VPS test suite — validates configurations then boots a VM and
+# runs all test cases.  Uses cache by default; tests are
+# deterministic for given inputs.
+test-vps: build-vps-system build-vps-home
+       @out=$$(GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+           -e '(@ (tests vps-base) %test-vps)' 2>/dev/null); \
+       if [ -n "$$out" ] && [ -e "$$out" ]; then \
+           echo "📋 Cached: $$out"; \
+           echo "   Run 'make test-vps-fresh' to re-execute."; \
+       else \
+           GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+               -e '(@ (tests vps-base) %test-vps)'; \
+       fi
+
+# Force fresh tests — delete cached result and rebuild.
+test-vps-fresh: build-vps-system build-vps-home
+       @out=$$(GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+           -e '(@ (tests vps-base) %test-vps)' 2>/dev/null); \
+       [ -n "$$out" ] && [ -e "$$out" ] \
+           && guix gc --delete "$$out" 2>/dev/null; \
+       GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+           -e '(@ (tests vps-base) %test-vps)'
+
 tags:
        find $(guix-store) -wholename "*guix-$(guix-version)*.scm" -or \
                -wholename "*guix-module-union*.scm" -or \
diff --git a/tests/vps-base.scm b/tests/vps-base.scm
new file mode 100644 (file)
index 0000000..4d5a50a
--- /dev/null
@@ -0,0 +1,178 @@
+;;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+;;; License: GPL-3.0 or later.
+;;;
+;;; VPS test harness — one VM for all test sections.
+;;; Inherits from the production operating system so any change to
+;;; vps-system.scm or vps-home.scm is automatically tested.
+
+(define-module (tests vps-base)
+  #:use-module (gnu home)
+  #:use-module (gnu tests)
+  #:use-module (gnu system)
+  #:use-module (gnu system vm)
+  #:use-module (gnu services)
+  #:use-module (gnu services base)
+  #:use-module (gnu services ssh)
+  #:use-module (gnu services networking)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu packages base)
+  #:use-module (gnu packages bash)
+  #:use-module (gnu packages ssh)
+  #:use-module (vps-system)
+  #:use-module (vps-home)
+  #:use-module (guix gexp)
+  #:export (%test-vps %vps-test-os run-vps-test))
+
+;;;
+;;; Test SSH keypair — generated at build time.
+;;;
+
+(define %test-ssh-keypair
+  (computed-file "test-ssh-keypair"
+                 (with-imported-modules '((guix build utils))
+                                        #~(begin
+                                            (use-modules (guix build utils))
+                                            (mkdir #$output)
+                                            (invoke #$(file-append openssh
+                                                       "/bin/ssh-keygen")
+                                                    "-t"
+                                                    "ed25519"
+                                                    "-f"
+                                                    (string-append #$output
+                                                     "/id_ed25519")
+                                                    "-N"
+                                                    ""
+                                                    "-C"
+                                                    "guix-test@vps")
+                                            (copy-file (string-append #$output
+                                                        "/id_ed25519.pub")
+                                                       (string-append #$output
+                                                        "/authorized_key"))))
+                 #:local-build? #f))
+
+;;;
+;;; Static networking — QEMU user-mode DHCP is unreliable during
+;;; early boot, causing dhcpcd to hang and block the marionette REPL.
+;;;
+
+(define %static-network-config
+  (let ((addr (network-address (device "eth0")
+                               (value "10.0.2.15/24")))
+        (route (network-route (destination "default")
+                              (gateway "10.0.2.2"))))
+    (static-networking (addresses (list addr))
+                       (routes (list route)))))
+
+(define %static-networking
+  (service static-networking-service-type
+           (list %static-network-config)))
+
+;;;
+;;; Test operating system — inherits from production with only two
+;;; overrides: static networking and a throwaway SSH key for dak.
+;;;
+
+(define (test-os-services base-os)
+  "Return the service list for the test VM.  Removes dhcpcd (QEMU
+DHCP is unreliable), prepends static networking, and adds a throwaway
+ed25519 key to the production SSH config."
+  (define (add-test-key config)
+    (let ((key (file-append %test-ssh-keypair "/authorized_key")))
+      (openssh-configuration (inherit config)
+                             (authorized-keys `(("dak" ,key))))))
+  (cons %static-networking
+        (modify-services (operating-system-user-services base-os)
+          (delete dhcpcd-service-type)
+          (openssh-service-type config =>
+                                (add-test-key config)))))
+
+(define %vps-test-os
+  (marionette-operating-system (operating-system
+                                 (inherit vps-operating-system)
+                                 (packages (append (home-environment-packages
+                                                    vps-home-environment)
+                                                   (operating-system-packages
+                                                    vps-operating-system)))
+                                 (services
+                                  (test-os-services vps-operating-system)))
+                               #:imported-modules '((gnu services herd)
+                                                    (guix combinators))))
+
+;;;
+;;; SSH session helper — key-only auth as user dak via the
+;;; forwarded port (host:2222 -> guest:72).
+;;;
+
+(define (make-ssh-connect marionette-gexp)
+  "Return a gexp that connects over SSH using the system ssh
+command, runs a shell command, and verifies the witness file."
+  #~(lambda ()
+      (let* ((ssh #$(file-append openssh "/bin/ssh"))
+             (key (string-append #$%test-ssh-keypair "/id_ed25519"))
+             (exit (system* ssh
+                            "-i"
+                            key
+                            "-o"
+                            "StrictHostKeyChecking=no"
+                            "-o"
+                            "UserKnownHostsFile=/dev/null"
+                            "-o"
+                            "BatchMode=yes"
+                            "-o"
+                            "ConnectTimeout=10"
+                            "-p"
+                            "2222"
+                            "dak@localhost"
+                            "echo hello > /tmp/witness")))
+        (and (zero? exit)
+             (wait-for-file "/tmp/witness"
+                            #$marionette-gexp)))))
+
+;;;
+;;; Test harness.
+;;;
+
+(define (run-vps-test test-cases)
+  "Return a derivation that boots a VM with %vps-test-os and runs
+every function in TEST-CASES.  Each element is a function
+(marionette-gexp) -> gexp that returns test assertions."
+  (define os
+    %vps-test-os)
+  (define vm
+    (virtual-machine (operating-system
+                       os)
+                     (port-forwardings '((2222 . 72)))))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+                           #~(begin
+                               (use-modules (gnu build marionette)
+                                            (srfi srfi-64)
+                                            (ice-9 match))
+
+                               (define marionette
+                                 (make-marionette (list #$vm)))
+
+                               (define runner
+                                 (system-test-runner #$output))
+
+                               (test-runner-current runner)
+
+                               (test-begin "vps")
+
+                               #$@(map (lambda (tc)
+                                         (tc #~marionette)) test-cases)
+
+                               (test-end "vps"))))
+  (gexp->derivation "vps-test" test))
+
+;;;
+;;; System test record — test cases are added incrementally in
+;;; subsequent commits.
+;;;
+
+(define %test-vps
+  (system-test (name "vps")
+               (description "VPS test suite.")
+               (value (run-vps-test '()))))