summaryrefslogtreecommitdiff
path: root/gnu/tests
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2025-02-22 11:23:36 +0100
committerLudovic Courtès <ludo@gnu.org>2025-03-05 20:34:04 +0100
commitf57a660fc6c97d8324c7f36b84bec5234720fbb5 (patch)
tree8c0462b2a0bbe225f455902bbf5a841b28e04445 /gnu/tests
parent5d66d005bd2a8baf496a3c6466d36e6fb9c90caf (diff)
tests: Test installation on Debian.
* gnu/tests/foreign.scm: New file. * gnu/local.mk (GNU_SYSTEM_MODULES): Add it. Change-Id: I1f24d83bdc298acbef15db2e19775cc1d3fbd56c
Diffstat (limited to 'gnu/tests')
-rw-r--r--gnu/tests/foreign.scm379
1 files changed, 379 insertions, 0 deletions
diff --git a/gnu/tests/foreign.scm b/gnu/tests/foreign.scm
new file mode 100644
index 00000000000..a08622424a7
--- /dev/null
+++ b/gnu/tests/foreign.scm
@@ -0,0 +1,379 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2025 Ludovic Courtès <ludo@gnu.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu tests foreign)
20 #:use-module (guix download)
21 #:use-module (guix gexp)
22 #:use-module (guix modules)
23 #:use-module (guix monads)
24 #:use-module (guix packages)
25 #:use-module (guix profiles)
26 #:autoload (guix store) (%store-prefix %store-monad %graft?)
27 #:use-module (gnu compression)
28 #:use-module (gnu tests)
29 #:use-module (gnu packages base)
30 #:use-module (gnu packages bootstrap)
31 #:use-module (gnu packages guile)
32 #:use-module (gnu packages make-bootstrap)
33 #:use-module (gnu packages package-management)
34 #:use-module (gnu packages virtualization)
35 #:use-module (gnu system vm)
36 #:use-module ((guix scripts pack) #:prefix pack:)
37 #:use-module (srfi srfi-9)
38 #:export (%test-debian-install))
39
40(define marionette-systemd-service
41 ;; Definition of the marionette service for systemd.
42 (plain-file "marionette.service" "
43[Unit]
44Description=Guix marionette service
45
46[Install]
47WantedBy=multi-user.target
48
49[Service]
50ExecStart=/opt/guix/bin/guile --no-auto-compile \\
51 /opt/guix/share/guix/marionette-repl.scm\n"))
52
53(define* (qcow-image-with-marionette image
54 #:key
55 (name "image-with-marionette.qcow2")
56 (device "/dev/vdb1"))
57 "Instrument IMAGE, returning a new image that contains a statically-linked
58Guile under /opt/guix and a marionette systemd service. The relevant file
59system is expected to be on DEVICE."
60 (define vm
61 (virtual-machine
62 (marionette-operating-system %simple-os)))
63
64 (define build
65 (with-imported-modules (source-module-closure
66 '((guix build utils)
67 (gnu build marionette)))
68 #~(begin
69 (use-modules (guix build utils)
70 (gnu build marionette))
71
72 (define target-image
73 #$output)
74
75 (invoke #+(file-append qemu "/bin/qemu-img")
76 "create" "-b" #$image
77 "-F" "qcow2" "-f" "qcow2" target-image
78 "10G")
79
80 ;; Run a VM that will mount IMAGE and populate it. This is somewhat
81 ;; more convenient to set up than 'guestfish' from libguestfs.
82 (let ((marionette
83 (make-marionette
84 (list #$vm "-drive"
85 (string-append "file=" target-image
86 ",format=qcow2,if=virtio,"
87 "cache=writeback,werror=report,readonly=off")))))
88
89 (marionette-eval '(system* "mount" #$device "/mnt")
90 marionette)
91 (marionette-eval '(system* "ls" "-la" "/mnt")
92 marionette)
93 (marionette-eval '(begin
94 (use-modules (guix build utils))
95 (mkdir-p "/mnt/opt/guix")
96 (copy-recursively #$%guile-static-initrd
97 "/mnt/opt/guix"
98 #:log (%make-void-port "w")
99 #:keep-mtime? #t))
100 marionette)
101 (marionette-eval '(system* "/mnt/opt/guix/bin/guile" "--version")
102 marionette)
103 (unless (= 42 (status:exit-val
104 (marionette-eval '(system* "/mnt/opt/guix/bin/guile"
105 "-c" "(exit 42)")
106 marionette)))
107 (error "statically-linked Guile is broken"))
108
109 ;; Install the marionette systemd service and activate it.
110 (marionette-eval '(begin
111 (mkdir-p "/mnt/opt/guix/share/guix")
112 (copy-file #$(marionette-program)
113 "/mnt/opt/guix/share/guix/marionette-repl.scm")
114
115 (mkdir-p "/mnt/etc/systemd/system")
116 (copy-file #$marionette-systemd-service
117 "/mnt/etc/systemd/system/marionette.service")
118
119 ;; Activate the service, as per 'systemctl
120 ;; enable marionette.service'.
121 (symlink
122 "/etc/systemd/system/marionette.service"
123 "/mnt/etc/systemd/system/multi-user.target.wants/marionette.service"))
124 marionette)
125
126 (unless (zero? (marionette-eval '(system* "umount" "/mnt")
127 marionette))
128 (error "failed to unmount device"))))))
129
130 (computed-file name build))
131
132(define (manifest-entry-without-grafts entry)
133 "Return ENTRY with grafts disabled on its contents."
134 (manifest-entry
135 (inherit entry)
136 (item (with-parameters ((%graft? #f))
137 (manifest-entry-item entry)))))
138
139(define %installation-tarball-manifest
140 ;; Manifest of the Guix installation tarball.
141 (concatenate-manifests
142 (list (packages->manifest (list guix))
143
144 ;; Include the dependencies of 'hello' in addition to 'guix' so that
145 ;; we can test 'guix build hello'.
146 (map-manifest-entries
147 manifest-entry-without-grafts
148 (package->development-manifest hello))
149
150 ;; Add the source of 'hello'.
151 (manifest
152 (list (manifest-entry
153 (name "hello-source")
154 (version (package-version hello))
155 (item (let ((file (origin-actual-file-name
156 (package-source hello))))
157 (computed-file
158 "hello-source"
159 #~(begin
160 ;; Put the tarball in a subdirectory since
161 ;; profile union crashes otherwise.
162 (mkdir #$output)
163 (mkdir (in-vicinity #$output "src"))
164 (symlink #$(package-source hello)
165 (in-vicinity #$output
166 (string-append "src/"
167 #$file))))))))))
168
169 ;; Include 'guile-final', which is needed when building derivations
170 ;; such as that of 'hello' but missing from the development manifest.
171 ;; Add '%bootstrap-guile', used by 'guix install --bootstrap'.
172 (map-manifest-entries
173 manifest-entry-without-grafts
174 (packages->manifest (list (canonical-package guile-3.0)
175 %bootstrap-guile))))))
176
177(define %guix-install-script
178 ;; The 'guix-install.sh' script.
179 ;;
180 ;; To test local changes, replace the expression below with:
181 ;;
182 ;; (local-file "../../etc/guix-install.sh")
183 ;;
184 ;; This cannot be done unconditionally since that file does not exists in
185 ;; inferiors.
186 (file-append (package-source guix) "/etc/guix-install.sh"))
187
188(define (run-foreign-install-test image name)
189 "Run an installation of Guix in IMAGE, the QCOW2 image of a systemd-based
190GNU/Linux distro, and check that the installation is functional."
191 (define instrumented-image
192 (qcow-image-with-marionette image
193 #:name (string-append name ".qcow2")))
194
195 (define (test tarball)
196 (with-imported-modules (source-module-closure
197 '((gnu build marionette)
198 (gnu system file-systems)))
199 #~(begin
200 (use-modules (gnu build marionette)
201 (gnu system file-systems)
202 (srfi srfi-64)
203 (ice-9 match))
204
205 (define marionette
206 (make-marionette
207 (list (string-append #$qemu-minimal "/bin/" (qemu-command))
208 #$@(common-qemu-options instrumented-image
209 (list (%store-prefix))
210 #:image-format "qcow2"
211 #:rw-image? #t)
212 "-m" "512"
213 "-snapshot")))
214
215 (test-runner-current (system-test-runner #$output))
216 (test-begin "foreign-install")
217
218 (test-equal "marionette works"
219 "Linux"
220 (marionette-eval '(utsname:sysname (uname))
221 marionette))
222
223 (test-assert "/etc/os-release"
224 (marionette-eval '(begin
225 (use-modules (ice-9 textual-ports))
226 (call-with-input-file "/etc/os-release"
227 get-string-all))
228 marionette))
229
230 (test-equal "mount host file store"
231 0
232 (marionette-eval
233 '(begin
234 (mkdir "/host")
235 (system* "mount" "-t" "9p"
236 "-o" "trans=virtio,cache=loose,ro"
237 #$(file-system->mount-tag (%store-prefix))
238 "/host"))
239 marionette))
240
241 (test-assert "screenshot before"
242 (marionette-control (string-append "screendump " #$output
243 "/before-install.ppm")
244 marionette))
245
246 (test-assert "install fake dependencies"
247 ;; The installation script insists on checking for the
248 ;; availability of 'wget' and 'gpg' but does not actually use them
249 ;; when 'GUIX_BINARY_FILE_NAME' is set. Provide fake binaries.
250 (marionette-eval '(begin
251 (symlink "/bin/true" "/bin/wget")
252 (symlink "/bin/true" "/bin/gpg")
253 #t)
254 marionette))
255
256 (test-assert "run install script"
257 (marionette-eval '(system
258 (string-append
259 "yes '' | GUIX_BINARY_FILE_NAME="
260 (in-vicinity "/host"
261 (basename #$tarball))
262 " sh "
263 (in-vicinity
264 "/host"
265 (string-drop #$%guix-install-script
266 #$(string-length
267 (%store-prefix))))))
268 marionette))
269
270 (test-equal "hello not already built"
271 #f
272 ;; Check that the next test will really build 'hello'.
273 (marionette-eval '(file-exists?
274 #$(with-parameters ((%graft? #f))
275 hello))
276 marionette))
277
278 (test-equal "guix build hello"
279 0
280 ;; Check that guix-daemon is up and running and that the build
281 ;; environment is properly set up (build users, etc.).
282 (marionette-eval '(system* "guix" "build" "hello" "--no-grafts")
283 marionette))
284
285 (test-assert "hello indeed built"
286 (marionette-eval '(file-exists?
287 #$(with-parameters ((%graft? #f))
288 hello))
289 marionette))
290
291 (test-equal "guix install hello"
292 0
293 ;; Check that ~/.guix-profile & co. are properly created.
294 (marionette-eval '(let ((pw (getpwuid (getuid))))
295 (setenv "USER" (passwd:name pw))
296 (setenv "HOME" (pk 'home (passwd:dir pw)))
297 (system* "guix" "install" "hello"
298 "--no-grafts" "--bootstrap"))
299 marionette))
300
301 (test-equal "user profile created"
302 0
303 (marionette-eval '(system "ls -lad ~/.guix-profile")
304 marionette))
305
306 (test-equal "hello"
307 0
308 (marionette-eval '(system "~/.guix-profile/bin/hello")
309 marionette))
310
311 (test-equal "create user account"
312 0
313 (marionette-eval '(system* "useradd" "-d" "/home/user" "-m"
314 "user")
315 marionette))
316
317 (test-equal "guix install hello, unprivileged user"
318 0
319 ;; Check that 'guix' is in $PATH for new users and that
320 ;; ~user/.guix-profile also gets created.
321 (marionette-eval '(system "su - user -c \
322'guix install hello --no-grafts --bootstrap'")
323 marionette))
324
325 (test-equal "user hello"
326 0
327 (marionette-eval '(system "~user/.guix-profile/bin/hello")
328 marionette))
329
330 (test-equal "unprivileged user profile created"
331 0
332 (marionette-eval '(system "ls -lad ~user/.guix-profile")
333 marionette))
334
335 (test-equal "store is read-only"
336 EROFS
337 (marionette-eval '(catch 'system-error
338 (lambda ()
339 (mkdir (in-vicinity #$(%store-prefix)
340 "whatever"))
341 0)
342 (lambda args
343 (system-error-errno args)))
344 marionette))
345
346 (test-assert "screenshot after"
347 (marionette-control (string-append "screendump " #$output
348 "/after-install.ppm")
349 marionette))
350
351 (test-end))))
352
353 (mlet* %store-monad ((profile (profile-derivation
354 %installation-tarball-manifest))
355 (tarball (pack:self-contained-tarball
356 "guix-binary" profile
357 #:compressor (lookup-compressor "zstd")
358 #:profile-name "current-guix"
359 #:localstatedir? #t)))
360 (gexp->derivation name (test tarball))))
361
362(define debian-12-qcow2
363 ;; Image taken from <https://www.debian.org/distrib/>.
364 ;; XXX: Those images are periodically removed from debian.org.
365 (origin
366 (uri
367 "https://cloud.debian.org/images/cloud/bookworm/20250210-2019/debian-12-nocloud-amd64-20250210-2019.qcow2")
368 (method url-fetch)
369 (sha256
370 (base32
371 "06vlcq2dzgczlyp9lfkkdf3dgvfjp22lh5xz0mnl0bdgzq61sykb"))))
372
373(define %test-debian-install
374 (system-test
375 (name "debian-install")
376 (description
377 "Test installation of Guix on Debian using the @file{guix-install.sh}
378script.")
379 (value (run-foreign-install-test debian-12-qcow2 name))))