base.scm 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016, 2017, 2018, 2019, 2020, 2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
  4. ;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  5. ;;; Copyright © 2022 Marius Bakke <marius@gnu.org>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (gnu tests base)
  22. #:use-module (gnu tests)
  23. #:use-module (gnu image)
  24. #:use-module (gnu system)
  25. #:autoload (gnu system image) (system-image)
  26. #:use-module (gnu system shadow)
  27. #:use-module (gnu system nss)
  28. #:use-module (gnu system vm)
  29. #:use-module (gnu services)
  30. #:use-module (gnu services base)
  31. #:use-module (gnu services dbus)
  32. #:use-module (gnu services avahi)
  33. #:use-module (gnu services mcron)
  34. #:use-module (gnu services shepherd)
  35. #:use-module (gnu services networking)
  36. #:use-module (gnu packages base)
  37. #:use-module (gnu packages bash)
  38. #:use-module (gnu packages imagemagick)
  39. #:use-module (gnu packages linux)
  40. #:use-module (gnu packages ocr)
  41. #:use-module (gnu packages package-management)
  42. #:use-module (gnu packages tmux)
  43. #:use-module (gnu packages virtualization)
  44. #:use-module (guix gexp)
  45. #:use-module (guix store)
  46. #:use-module (guix monads)
  47. #:use-module (guix modules)
  48. #:use-module (guix packages)
  49. #:use-module (guix utils)
  50. #:use-module ((srfi srfi-1) #:hide (partition))
  51. #:use-module (ice-9 match)
  52. #:export (run-basic-test
  53. %test-basic-os
  54. %test-linux-libre-5.15
  55. %test-linux-libre-5.10
  56. %test-linux-libre-5.4
  57. %test-linux-libre-4.19
  58. %test-linux-libre-4.14
  59. %test-halt
  60. %test-root-unmount
  61. %test-cleanup
  62. %test-mcron
  63. %test-nss-mdns))
  64. (define %simple-os
  65. (simple-operating-system))
  66. (define* (run-basic-test os command #:optional (name "basic")
  67. #:key
  68. initialization
  69. root-password
  70. desktop?)
  71. "Return a derivation called NAME that tests basic features of the OS started
  72. using COMMAND, a gexp that evaluates to a list of strings. Compare some
  73. properties of running system to what's declared in OS, an <operating-system>.
  74. When INITIALIZATION is true, it must be a one-argument procedure that is
  75. passed a gexp denoting the marionette, and it must return gexp that is
  76. inserted before the first test. This is used to introduce an extra
  77. initialization step, such as entering a LUKS passphrase.
  78. When ROOT-PASSWORD is true, enter it as the root password when logging in.
  79. Otherwise assume that there is no password for root."
  80. (define special-files
  81. (service-value
  82. (fold-services (operating-system-services os)
  83. #:target-type special-files-service-type)))
  84. (define guix&co
  85. (match (package-transitive-propagated-inputs guix)
  86. (((labels packages) ...)
  87. (cons guix packages))))
  88. (define test
  89. (with-imported-modules '((gnu build marionette)
  90. (guix build syscalls))
  91. #~(begin
  92. (use-modules (gnu build marionette)
  93. (guix build syscalls)
  94. (srfi srfi-1)
  95. (srfi srfi-19)
  96. (srfi srfi-26)
  97. (srfi srfi-64)
  98. (ice-9 match))
  99. (define marionette
  100. (make-marionette #$command))
  101. (test-runner-current (system-test-runner #$output))
  102. (test-begin "basic")
  103. #$(and initialization
  104. (initialization #~marionette))
  105. (test-assert "uname"
  106. (match (marionette-eval '(uname) marionette)
  107. (#("Linux" host-name version _ architecture)
  108. (and (string=? host-name
  109. #$(operating-system-host-name os))
  110. (string-prefix? #$(package-version
  111. (operating-system-kernel os))
  112. version)
  113. (string-prefix? architecture %host-type)))))
  114. ;; Shepherd reads the config file *before* binding its control
  115. ;; socket, so /var/run/shepherd/socket might not exist yet when the
  116. ;; 'marionette' service is started.
  117. (test-assert "shepherd socket ready"
  118. (marionette-eval
  119. `(begin
  120. (use-modules (gnu services herd))
  121. (let loop ((i 10))
  122. (cond ((file-exists? (%shepherd-socket-file))
  123. #t)
  124. ((> i 0)
  125. (sleep 1)
  126. (loop (- i 1)))
  127. (else
  128. #f))))
  129. marionette))
  130. (test-eq "stdin is /dev/null"
  131. 'eof
  132. ;; Make sure services can no longer read from stdin once the
  133. ;; system has booted.
  134. (marionette-eval
  135. `(begin
  136. (use-modules (gnu services herd))
  137. (start-service 'user-processes)
  138. ((@@ (gnu services herd) eval-there)
  139. '(let ((result (read (current-input-port))))
  140. (if (eof-object? result)
  141. 'eof
  142. result))))
  143. marionette))
  144. (test-assert "shell and user commands"
  145. ;; Is everything in $PATH?
  146. (zero? (marionette-eval '(system "
  147. . /etc/profile
  148. set -e -x
  149. guix --version
  150. ls --version
  151. grep --version
  152. info --version")
  153. marionette)))
  154. (test-equal "special files"
  155. '#$special-files
  156. (marionette-eval
  157. '(begin
  158. (use-modules (ice-9 match))
  159. (map (match-lambda
  160. ((file target)
  161. (list file (readlink file))))
  162. '#$special-files))
  163. marionette))
  164. (test-assert "accounts"
  165. (let ((users (marionette-eval '(begin
  166. (use-modules (ice-9 match))
  167. (let loop ((result '()))
  168. (match (getpw)
  169. (#f (reverse result))
  170. (x (loop (cons x result))))))
  171. marionette)))
  172. (lset= equal?
  173. (map (lambda (user)
  174. (list (passwd:name user)
  175. (passwd:dir user)))
  176. users)
  177. (list
  178. #$@(map (lambda (account)
  179. `(list ,(user-account-name account)
  180. ,(user-account-home-directory account)))
  181. (operating-system-user-accounts os))))))
  182. (test-assert "shepherd services"
  183. (let ((services (marionette-eval
  184. '(begin
  185. (use-modules (gnu services herd))
  186. (map (compose car live-service-provision)
  187. (current-services)))
  188. marionette)))
  189. (lset= eq?
  190. (pk 'services services)
  191. '(root #$@(operating-system-shepherd-service-names os)))))
  192. (test-equal "libc honors /etc/localtime"
  193. -7200 ;CEST = GMT+2
  194. ;; Assume OS is configured to have a CEST timezone.
  195. (let* ((sept-2021 (time-second
  196. (date->time-utc
  197. (make-date 0 0 00 12 01 09 2021 7200)))))
  198. (marionette-eval
  199. `(tm:gmtoff (localtime ,sept-2021))
  200. marionette)))
  201. (test-equal "/var/log/messages is not world-readable"
  202. #o640 ;<https://bugs.gnu.org/40405>
  203. (begin
  204. (wait-for-file "/var/log/messages" marionette
  205. #:read 'get-u8)
  206. (marionette-eval '(stat:perms (lstat "/var/log/messages"))
  207. marionette)))
  208. (test-assert "homes"
  209. (let ((homes
  210. '#$(map user-account-home-directory
  211. (filter user-account-create-home-directory?
  212. (operating-system-user-accounts os)))))
  213. (marionette-eval
  214. `(begin
  215. (use-modules (gnu services herd) (srfi srfi-1))
  216. ;; Home directories are supposed to exist once 'user-homes'
  217. ;; has been started.
  218. (start-service 'user-homes)
  219. (every (lambda (home)
  220. (and (file-exists? home)
  221. (file-is-directory? home)))
  222. ',homes))
  223. marionette)))
  224. (test-assert "skeletons in home directories"
  225. (let ((users+homes
  226. '#$(filter-map (lambda (account)
  227. (and (user-account-create-home-directory?
  228. account)
  229. (not (user-account-system? account))
  230. (list (user-account-name account)
  231. (user-account-home-directory
  232. account))))
  233. (operating-system-user-accounts os))))
  234. (marionette-eval
  235. `(begin
  236. (use-modules (guix build utils) (srfi srfi-1)
  237. (ice-9 ftw) (ice-9 match))
  238. (every (match-lambda
  239. ((user home)
  240. ;; Make sure HOME has all the skeletons...
  241. (and (null? (lset-difference string=?
  242. (scandir "/etc/skel/")
  243. (scandir home)))
  244. ;; ... and that everything is user-owned.
  245. (let* ((pw (getpwnam user))
  246. (uid (passwd:uid pw))
  247. (gid (passwd:gid pw))
  248. (st (lstat home)))
  249. (define (user-owned? file)
  250. (= uid (stat:uid (lstat file))))
  251. (and (= uid (stat:uid st))
  252. (eq? 'directory (stat:type st))
  253. (every user-owned?
  254. (find-files home
  255. #:directories? #t)))))))
  256. ',users+homes))
  257. marionette)))
  258. (test-equal "permissions on /root"
  259. #o700
  260. (let ((root-home #$(any (lambda (account)
  261. (and (zero? (user-account-uid account))
  262. (user-account-home-directory
  263. account)))
  264. (operating-system-user-accounts os))))
  265. (stat:perms (marionette-eval `(stat ,root-home) marionette))))
  266. (test-equal "ownership and permissions of /var/empty"
  267. '(0 0 #o555)
  268. (let ((st (marionette-eval `(stat "/var/empty") marionette)))
  269. (list (stat:uid st) (stat:gid st)
  270. (stat:perms st))))
  271. (test-equal "no extra home directories"
  272. '()
  273. ;; Make sure the home directories that are not supposed to be
  274. ;; created are indeed not created.
  275. (let ((nonexistent
  276. '#$(filter-map (lambda (user)
  277. (and (not
  278. (user-account-create-home-directory?
  279. user))
  280. (user-account-home-directory user)))
  281. (operating-system-user-accounts os))))
  282. (marionette-eval
  283. `(begin
  284. (use-modules (srfi srfi-1))
  285. ;; Note: Do not flag "/var/empty".
  286. (filter file-exists?
  287. ',(remove (cut string-prefix? "/var/" <>)
  288. nonexistent)))
  289. marionette)))
  290. (test-equal "login on tty1"
  291. "root\n"
  292. (begin
  293. ;; XXX: On desktop, GDM3 will switch to TTY7. If this happens
  294. ;; after we switched to TTY1, we won't be able to login. Make
  295. ;; sure to wait long enough before switching to TTY1.
  296. (when #$desktop?
  297. (sleep 30))
  298. (marionette-control "sendkey ctrl-alt-f1" marionette)
  299. ;; Wait for the 'term-tty1' service to be running (using
  300. ;; 'start-service' is the simplest and most reliable way to do
  301. ;; that.)
  302. (marionette-eval
  303. '(begin
  304. (use-modules (gnu services herd))
  305. (start-service 'term-tty1))
  306. marionette)
  307. ;; Now we can type.
  308. (let ((password #$root-password))
  309. (if password
  310. (begin
  311. (marionette-type "root\n" marionette)
  312. (wait-for-screen-text marionette
  313. (lambda (text)
  314. (string-contains text "Password"))
  315. #:ocr
  316. #$(file-append ocrad "/bin/ocrad"))
  317. (marionette-type (string-append password "\n\n")
  318. marionette))
  319. (marionette-type "root\n\n" marionette)))
  320. (marionette-type "id -un > logged-in\n" marionette)
  321. ;; It can take a while before the shell commands are executed.
  322. (marionette-eval '(use-modules (rnrs io ports)) marionette)
  323. (wait-for-file "/root/logged-in" marionette
  324. #:read 'get-string-all
  325. #:timeout 30)))
  326. (test-equal "getlogin on tty1"
  327. "\"root\""
  328. (begin
  329. ;; Assume we logged in in the previous test and type.
  330. (marionette-type "guile -c '(write (getlogin))' > /root/login-id.tmp\n"
  331. marionette)
  332. (marionette-type "mv /root/login-id{.tmp,}\n"
  333. marionette)
  334. ;; It can take a while before the shell commands are executed.
  335. (marionette-eval '(use-modules (rnrs io ports)) marionette)
  336. (wait-for-file "/root/login-id" marionette
  337. #:read 'get-string-all
  338. #:timeout 30)))
  339. ;; There should be one utmpx entry for the user logged in on tty1.
  340. (test-equal "utmpx entry"
  341. '(("root" "tty1" #f))
  342. (marionette-eval
  343. '(begin
  344. (use-modules (guix build syscalls)
  345. (srfi srfi-1))
  346. (filter-map (lambda (entry)
  347. (and (equal? (login-type USER_PROCESS)
  348. (utmpx-login-type entry))
  349. (list (utmpx-user entry) (utmpx-line entry)
  350. (utmpx-host entry))))
  351. (utmpx-entries)))
  352. marionette))
  353. ;; Likewise for /var/log/wtmp (used by 'last').
  354. (test-assert "wtmp entry"
  355. (match (marionette-eval
  356. '(begin
  357. (use-modules (guix build syscalls)
  358. (srfi srfi-1))
  359. (define (entry->list entry)
  360. (list (utmpx-user entry) (utmpx-line entry)
  361. (utmpx-host entry) (utmpx-login-type entry)))
  362. (call-with-input-file "/var/log/wtmp"
  363. (lambda (port)
  364. (let loop ((result '()))
  365. (if (eof-object? (peek-char port))
  366. (map entry->list (reverse result))
  367. (loop (cons (read-utmpx port) result)))))))
  368. marionette)
  369. (((users lines hosts types) ..1)
  370. (every (lambda (type)
  371. (eqv? type (login-type LOGIN_PROCESS)))
  372. types))))
  373. (test-assert "host name resolution"
  374. (match (marionette-eval
  375. '(begin
  376. ;; Wait for nscd or our requests go through it.
  377. (use-modules (gnu services herd))
  378. (start-service 'nscd)
  379. (list (getaddrinfo "localhost")
  380. (getaddrinfo #$(operating-system-host-name os))))
  381. marionette)
  382. ((((? vector?) ..1) ((? vector?) ..1))
  383. #t)
  384. (x
  385. (pk 'failure x #f))))
  386. (test-assert "nscd configuration action"
  387. (marionette-eval '(with-shepherd-action 'nscd ('configuration)
  388. results
  389. (file-exists? (car results)))
  390. marionette))
  391. (test-equal "nscd invalidate action"
  392. '(#t) ;one value, #t
  393. (marionette-eval '(with-shepherd-action 'nscd ('invalidate "hosts")
  394. result
  395. result)
  396. marionette))
  397. ;; FIXME: The 'invalidate' action can't reliably obtain the exit
  398. ;; code of 'nscd' so skip this test.
  399. (test-skip 1)
  400. (test-equal "nscd invalidate action, wrong table"
  401. '(#f) ;one value, #f
  402. (marionette-eval '(with-shepherd-action 'nscd ('invalidate "xyz")
  403. result
  404. result)
  405. marionette))
  406. (test-equal "host not found"
  407. #f
  408. (marionette-eval
  409. '(false-if-exception (getaddrinfo "does-not-exist"))
  410. marionette))
  411. (test-equal "locale"
  412. "en_US.utf8"
  413. (marionette-eval '(let ((before (setlocale LC_ALL "en_US.utf8")))
  414. (setlocale LC_ALL before))
  415. marionette))
  416. (test-eq "/run/current-system is a GC root"
  417. 'success!
  418. (marionette-eval '(begin
  419. ;; Make sure the (guix …) modules are found.
  420. (eval-when (expand load eval)
  421. (set! %load-path
  422. (append (map (lambda (package)
  423. (string-append package
  424. "/share/guile/site/"
  425. (effective-version)))
  426. '#$guix&co)
  427. %load-path)))
  428. (use-modules (srfi srfi-34) (guix store))
  429. (let ((system (readlink "/run/current-system")))
  430. (guard (c ((store-protocol-error? c)
  431. (and (file-exists? system)
  432. 'success!)))
  433. (with-store store
  434. (delete-paths store (list system))
  435. #f))))
  436. marionette))
  437. ;; This symlink is currently unused, but better have it point to the
  438. ;; right place. See
  439. ;; <https://lists.gnu.org/archive/html/guix-devel/2016-08/msg01641.html>.
  440. (test-equal "/var/guix/gcroots/profiles is a valid symlink"
  441. "/var/guix/profiles"
  442. (marionette-eval '(readlink "/var/guix/gcroots/profiles")
  443. marionette))
  444. (test-equal "guix-daemon set-http-proxy action"
  445. '(#t) ;one value, #t
  446. (marionette-eval '(with-shepherd-action 'guix-daemon
  447. ('set-http-proxy "http://localhost:8118")
  448. result
  449. result)
  450. marionette))
  451. (test-equal "guix-daemon set-http-proxy action, clear"
  452. '(#t) ;one value, #t
  453. (marionette-eval '(with-shepherd-action 'guix-daemon
  454. ('set-http-proxy)
  455. result
  456. result)
  457. marionette))
  458. (test-assert "screendump"
  459. (begin
  460. (let ((capture
  461. (string-append #$output "/tty1.ppm")))
  462. (marionette-control
  463. (string-append "screendump " capture) marionette)
  464. (file-exists? capture))))
  465. (test-assert "screen text"
  466. (wait-for-screen-text
  467. marionette
  468. (lambda (text)
  469. ;; Check whether the welcome message and shell prompt are
  470. ;; displayed. Note: OCR confuses "y" and "V" for instance, so
  471. ;; we cannot reliably match the whole text.
  472. (and (string-contains text "This is the GNU")
  473. (string-contains text
  474. (string-append
  475. "root@"
  476. #$(operating-system-host-name os)))))
  477. #:ocr #$(file-append ocrad "/bin/ocrad")))
  478. (test-end))))
  479. (gexp->derivation name test))
  480. (define* (test-basic-os #:optional (kernel linux-libre))
  481. (system-test
  482. (name (if (eq? kernel linux-libre)
  483. "basic"
  484. (string-append (package-name kernel) "-"
  485. (version-major+minor (package-version kernel)))))
  486. (description
  487. "Instrument %SIMPLE-OS, run it in a VM, and run a series of basic
  488. functionality tests, using the given KERNEL.")
  489. (value
  490. (let* ((os (marionette-operating-system
  491. (operating-system
  492. (inherit %simple-os)
  493. (kernel kernel))
  494. #:imported-modules '((gnu services herd)
  495. (guix combinators))))
  496. (vm (virtual-machine os)))
  497. ;; XXX: Add call to 'virtualized-operating-system' to get the exact same
  498. ;; set of services as the OS produced by
  499. ;; 'system-qemu-image/shared-store-script'.
  500. (run-basic-test (virtualized-operating-system os '())
  501. #~(list #$vm)
  502. name)))))
  503. (define %test-basic-os
  504. (test-basic-os))
  505. ;; Ensure the LTS kernels are up to snuff, too.
  506. (define %test-linux-libre-5.15
  507. (test-basic-os linux-libre-5.15))
  508. (define %test-linux-libre-5.10
  509. (test-basic-os linux-libre-5.10))
  510. (define %test-linux-libre-5.4
  511. (test-basic-os linux-libre-5.4))
  512. (define %test-linux-libre-4.19
  513. (test-basic-os linux-libre-4.19))
  514. (define %test-linux-libre-4.14
  515. (test-basic-os linux-libre-4.14))
  516. ;;;
  517. ;;; Halt.
  518. ;;;
  519. (define (run-halt-test vm)
  520. ;; As reported in <http://bugs.gnu.org/26931>, running tmux would previously
  521. ;; lead the 'stop' method of 'user-processes' to an infinite loop, with the
  522. ;; tmux server process as a zombie that remains in the list of processes.
  523. ;; This test reproduces this scenario.
  524. (define test
  525. (with-imported-modules '((gnu build marionette))
  526. #~(begin
  527. (use-modules (gnu build marionette))
  528. (define marionette
  529. (make-marionette '(#$vm)))
  530. (define ocrad
  531. #$(file-append ocrad "/bin/ocrad"))
  532. ;; Wait for tty1 and log in.
  533. (marionette-eval '(begin
  534. (use-modules (gnu services herd))
  535. (start-service 'term-tty1))
  536. marionette)
  537. (marionette-type "root\n" marionette)
  538. ;; Start tmux and wait for it to be ready.
  539. (marionette-type "tmux new-session 'echo 1 > /ready; bash'\n"
  540. marionette)
  541. (wait-for-file "/ready" marionette)
  542. ;; Make sure to stop the test after a while.
  543. (sigaction SIGALRM (lambda _
  544. (format (current-error-port)
  545. "FAIL: Time is up, but VM still running.\n")
  546. (primitive-exit 1)))
  547. (alarm 10)
  548. ;; Get debugging info.
  549. (marionette-eval '(current-output-port
  550. (open-file "/dev/console" "w0"))
  551. marionette)
  552. (marionette-eval '(system* #$(file-append procps "/bin/ps")
  553. "-eo" "pid,ppid,stat,comm")
  554. marionette)
  555. ;; See if 'halt' actually works.
  556. (marionette-eval '(system* "/run/current-system/profile/sbin/halt")
  557. marionette)
  558. ;; If we reach this line, that means the VM was properly stopped in
  559. ;; a timely fashion.
  560. (alarm 0)
  561. (call-with-output-file #$output
  562. (lambda (port)
  563. (display "success!" port))))))
  564. (gexp->derivation "halt" test))
  565. (define %test-halt
  566. (system-test
  567. (name "halt")
  568. (description
  569. "Use the 'halt' command and make sure it succeeds and does not get stuck
  570. in a loop. See <http://bugs.gnu.org/26931>.")
  571. (value
  572. (let ((os (marionette-operating-system
  573. (operating-system
  574. (inherit %simple-os)
  575. (packages (cons tmux %base-packages)))
  576. #:imported-modules '((gnu services herd)
  577. (guix combinators)))))
  578. (run-halt-test (virtual-machine os))))))
  579. ;;;
  580. ;;; Root cleanly unmounted.
  581. ;;;
  582. (define (run-root-unmount-test os)
  583. (define test-image
  584. (image (operating-system os)
  585. (format 'compressed-qcow2)
  586. (volatile-root? #f)
  587. (shared-store? #f)
  588. (partition-table-type 'mbr)
  589. (partitions
  590. (list (partition
  591. (size 'guess)
  592. (offset (* 512 2048)) ;leave room for GRUB
  593. (flags '(boot))
  594. (label "root-under-test")))))) ;max 16 characters!
  595. (define observer-os
  596. (marionette-operating-system
  597. %simple-os
  598. #:imported-modules
  599. (source-module-closure '((guix build syscalls)
  600. (gnu build file-systems)))))
  601. (define test
  602. (with-imported-modules (source-module-closure
  603. '((gnu build marionette)
  604. (guix build utils)))
  605. #~(begin
  606. (use-modules (gnu build marionette)
  607. (guix build utils)
  608. (srfi srfi-64)
  609. (ice-9 ftw))
  610. (define image
  611. "/tmp/writable-image.qcow2")
  612. (define (test-system-marionette)
  613. ;; Return a marionette on a system where we'll run 'halt'.
  614. (invoke #$(file-append qemu-minimal "/bin/qemu-img")
  615. "create" "-f" "qcow2" image "3G"
  616. "-b" #$(system-image test-image) "-F" "qcow2")
  617. (make-marionette
  618. `(,(string-append #$qemu-minimal "/bin/" (qemu-command))
  619. ,@(if (file-exists? "/dev/kvm")
  620. '("-enable-kvm")
  621. '())
  622. "-no-reboot"
  623. "-m" "1024" ;memory size, in MiB
  624. "-drive" ,(format #f "file=~a,if=virtio" image))))
  625. (define witness-size
  626. ;; Size of the /witness file.
  627. (* 20 (expt 2 20)))
  628. (test-runner-current (system-test-runner #$output))
  629. (test-begin "root-unmount")
  630. (let ((marionette (test-system-marionette)))
  631. (test-assert "file created"
  632. (marionette-eval `(begin
  633. (use-modules (guix build utils))
  634. (call-with-output-file "/witness"
  635. (lambda (port)
  636. (call-with-input-file "/dev/random"
  637. (lambda (input)
  638. (dump-port input port
  639. ,witness-size))))))
  640. marionette))
  641. ;; Halt the system.
  642. (marionette-eval '(system* "/run/current-system/profile/sbin/halt")
  643. marionette)
  644. (display "waiting for marionette to complete...")
  645. (force-output)
  646. (false-if-exception (waitpid (marionette-pid marionette)))
  647. (display " done\n")
  648. (force-output))
  649. ;; Remove the sockets used by the marionette above to avoid
  650. ;; EADDRINUSE.
  651. (for-each delete-file
  652. (find-files "/tmp" (lambda (file stat)
  653. (eq? (stat:type stat) 'socket))))
  654. ;; Now boot another system and check whether the root file system of
  655. ;; the first one was cleanly unmounted.
  656. (let ((observer
  657. (make-marionette (list #$(virtual-machine observer-os)
  658. "-drive"
  659. (format #f "file=~a,if=virtio" image)))))
  660. (test-assert "partitions"
  661. (marionette-eval '(begin
  662. (use-modules (gnu build file-systems))
  663. (disk-partitions))
  664. observer))
  665. (test-assert "partition found"
  666. (marionette-eval '(find-partition-by-label "root-under-test")
  667. observer))
  668. (test-assert "root file system is clean"
  669. (marionette-eval '(cleanly-unmounted-ext2?
  670. (find-partition-by-label "root-under-test"))
  671. observer))
  672. (test-equal "root file system contains /witness"
  673. witness-size
  674. (let ((files (marionette-eval
  675. '(begin
  676. (use-modules (guix build syscalls)
  677. (ice-9 ftw))
  678. (mount (find-partition-by-label "root-under-test")
  679. "/mnt" "ext4" MS_RDONLY)
  680. (scandir "/mnt"))
  681. observer)))
  682. (if (member "witness" files)
  683. (marionette-eval '(stat:size (stat "/mnt/witness"))
  684. observer)
  685. files))))
  686. (test-end))))
  687. (gexp->derivation "root-unmount" test))
  688. (define %test-root-unmount
  689. (system-test
  690. (name "root-unmount")
  691. (description
  692. "Make sure the root file system is cleanly unmounted when the system is
  693. halted.")
  694. (value
  695. (let ((os (marionette-operating-system %simple-os)))
  696. (run-root-unmount-test os)))))
  697. ;;;
  698. ;;; Cleanup of /tmp, /var/run, etc.
  699. ;;;
  700. (define %cleanup-os
  701. (simple-operating-system
  702. (simple-service 'dirty-things
  703. boot-service-type
  704. (let ((script (plain-file
  705. "create-utf8-file.sh"
  706. (string-append
  707. "echo $0: dirtying /tmp...\n"
  708. "set -e; set -x\n"
  709. "touch /witness\n"
  710. "exec touch /tmp/λαμβδα"))))
  711. (with-imported-modules '((guix build utils))
  712. #~(begin
  713. (setenv "PATH"
  714. #$(file-append coreutils "/bin"))
  715. (invoke #$(file-append bash "/bin/sh")
  716. #$script)))))))
  717. (define (run-cleanup-test name)
  718. (define os
  719. (marionette-operating-system %cleanup-os
  720. #:imported-modules '((gnu services herd)
  721. (guix combinators))))
  722. (define test
  723. (with-imported-modules '((gnu build marionette))
  724. #~(begin
  725. (use-modules (gnu build marionette)
  726. (srfi srfi-64)
  727. (ice-9 match))
  728. (define marionette
  729. (make-marionette (list #$(virtual-machine os))))
  730. (test-runner-current (system-test-runner #$output))
  731. (test-begin "cleanup")
  732. (test-assert "dirty service worked"
  733. (marionette-eval '(file-exists? "/witness") marionette))
  734. (test-equal "/tmp cleaned up"
  735. '("." "..")
  736. (marionette-eval '(begin
  737. (use-modules (ice-9 ftw))
  738. (scandir "/tmp"))
  739. marionette))
  740. (test-end))))
  741. (gexp->derivation "cleanup" test))
  742. (define %test-cleanup
  743. ;; See <https://bugs.gnu.org/26353>.
  744. (system-test
  745. (name "cleanup")
  746. (description "Make sure the 'cleanup' service can remove files with
  747. non-ASCII names from /tmp.")
  748. (value (run-cleanup-test name))))
  749. ;;;
  750. ;;; Mcron.
  751. ;;;
  752. (define %mcron-os
  753. ;; System with an mcron service, with one mcron job for "root" and one mcron
  754. ;; job for an unprivileged user.
  755. (let ((job1 #~(job '(next-second '(0 5 10 15 20 25 30 35 40 45 50 55))
  756. (lambda ()
  757. (unless (file-exists? "witness")
  758. (call-with-output-file "witness"
  759. (lambda (port)
  760. (display (list (getuid) (getgid)) port)))))))
  761. (job2 #~(job next-second-from
  762. (lambda ()
  763. (call-with-output-file "witness"
  764. (lambda (port)
  765. (display (list (getuid) (getgid)) port))))
  766. #:user "alice"))
  767. (job3 #~(job next-second-from ;to test $PATH
  768. "touch witness-touch")))
  769. (simple-operating-system
  770. (service mcron-service-type
  771. (mcron-configuration (jobs (list job1 job2 job3)))))))
  772. (define (run-mcron-test name)
  773. (define os
  774. (marionette-operating-system
  775. %mcron-os
  776. #:imported-modules '((gnu services herd)
  777. (guix combinators))))
  778. (define test
  779. (with-imported-modules '((gnu build marionette))
  780. #~(begin
  781. (use-modules (gnu build marionette)
  782. (srfi srfi-64)
  783. (ice-9 match))
  784. (define marionette
  785. (make-marionette (list #$(virtual-machine os))))
  786. (test-runner-current (system-test-runner #$output))
  787. (test-begin "mcron")
  788. (test-assert "service running"
  789. (marionette-eval
  790. '(begin
  791. (use-modules (gnu services herd))
  792. (start-service 'mcron))
  793. marionette))
  794. ;; Make sure root's mcron job runs, has its cwd set to "/root", and
  795. ;; runs with the right UID/GID.
  796. (test-equal "root's job"
  797. '(0 0)
  798. (wait-for-file "/root/witness" marionette))
  799. ;; Likewise for Alice's job. We cannot know what its GID is since
  800. ;; it's chosen by 'groupadd', but it's strictly positive.
  801. (test-assert "alice's job"
  802. (match (wait-for-file "/home/alice/witness" marionette)
  803. ((1000 gid)
  804. (>= gid 100))))
  805. ;; Last, the job that uses a command; allows us to test whether
  806. ;; $PATH is sane.
  807. (test-equal "root's job with command"
  808. ""
  809. (wait-for-file "/root/witness-touch" marionette
  810. #:read '(@ (ice-9 rdelim) read-string)))
  811. ;; Make sure the 'schedule' action is accepted.
  812. (test-equal "schedule action"
  813. '(#t) ;one value, #t
  814. (marionette-eval '(with-shepherd-action 'mcron ('schedule) result
  815. result)
  816. marionette))
  817. (test-end))))
  818. (gexp->derivation name test))
  819. (define %test-mcron
  820. (system-test
  821. (name "mcron")
  822. (description "Make sure the mcron service works as advertised.")
  823. (value (run-mcron-test name))))
  824. ;;;
  825. ;;; Avahi and NSS-mDNS.
  826. ;;;
  827. (define %avahi-os
  828. (operating-system
  829. (inherit %simple-os)
  830. (name-service-switch %mdns-host-lookup-nss)
  831. (services (cons* (service avahi-service-type
  832. (avahi-configuration (debug? #t)))
  833. (service dbus-root-service-type)
  834. (service dhcp-client-service-type) ;needed for multicast
  835. ;; Enable heavyweight debugging output.
  836. (modify-services (operating-system-user-services
  837. %simple-os)
  838. (nscd-service-type config
  839. => (nscd-configuration
  840. (inherit config)
  841. (debug-level 3)
  842. (log-file "/dev/console")))
  843. (syslog-service-type config
  844. =>
  845. (syslog-configuration
  846. (inherit config)
  847. (config-file
  848. (plain-file
  849. "syslog.conf"
  850. "*.* /dev/console\n")))))))))
  851. (define (run-nss-mdns-test)
  852. ;; Test resolution of '.local' names via libc. Start the marionette service
  853. ;; *after* nscd. Failing to do that, libc will try to connect to nscd,
  854. ;; fail, then never try again (see '__nss_not_use_nscd_hosts' in libc),
  855. ;; leading to '.local' resolution failures.
  856. (define os
  857. (marionette-operating-system
  858. %avahi-os
  859. #:requirements '(nscd)
  860. #:imported-modules '((gnu services herd)
  861. (guix combinators))))
  862. (define mdns-host-name
  863. (string-append (operating-system-host-name os)
  864. ".local"))
  865. (define test
  866. (with-imported-modules '((gnu build marionette))
  867. #~(begin
  868. (use-modules (gnu build marionette)
  869. (srfi srfi-1)
  870. (srfi srfi-64)
  871. (ice-9 match))
  872. (define marionette
  873. (make-marionette (list #$(virtual-machine os))))
  874. (mkdir #$output)
  875. (chdir #$output)
  876. (test-runner-current (system-test-runner))
  877. (test-begin "avahi")
  878. (test-assert "nscd PID file is created"
  879. (marionette-eval
  880. '(begin
  881. (use-modules (gnu services herd))
  882. (start-service 'nscd))
  883. marionette))
  884. (test-assert "nscd is listening on its socket"
  885. (marionette-eval
  886. ;; XXX: Work around a race condition in nscd: nscd creates its
  887. ;; PID file before it is listening on its socket.
  888. '(let ((sock (socket PF_UNIX SOCK_STREAM 0)))
  889. (let try ()
  890. (catch 'system-error
  891. (lambda ()
  892. (connect sock AF_UNIX "/var/run/nscd/socket")
  893. (close-port sock)
  894. (format #t "nscd is ready~%")
  895. #t)
  896. (lambda args
  897. (format #t "waiting for nscd...~%")
  898. (usleep 500000)
  899. (try)))))
  900. marionette))
  901. (test-assert "avahi is running"
  902. (marionette-eval
  903. '(begin
  904. (use-modules (gnu services herd))
  905. (start-service 'avahi-daemon))
  906. marionette))
  907. (test-assert "network is up"
  908. (marionette-eval
  909. '(begin
  910. (use-modules (gnu services herd))
  911. (start-service 'networking))
  912. marionette))
  913. (test-equal "avahi-resolve-host-name"
  914. 0
  915. (marionette-eval
  916. '(system*
  917. "/run/current-system/profile/bin/avahi-resolve-host-name"
  918. "-v" #$mdns-host-name)
  919. marionette))
  920. (test-equal "avahi-browse"
  921. 0
  922. (marionette-eval
  923. '(system* "/run/current-system/profile/bin/avahi-browse" "-avt")
  924. marionette))
  925. (test-assert "getaddrinfo .local"
  926. ;; Wait for the 'avahi-daemon' service and perform a resolution.
  927. (match (marionette-eval
  928. '(getaddrinfo #$mdns-host-name)
  929. marionette)
  930. (((? vector? addrinfos) ..1)
  931. (pk 'getaddrinfo addrinfos)
  932. (and (any (lambda (ai)
  933. (= AF_INET (addrinfo:fam ai)))
  934. addrinfos)
  935. (any (lambda (ai)
  936. (= AF_INET6 (addrinfo:fam ai)))
  937. addrinfos)))))
  938. (test-assert "gethostbyname .local"
  939. (match (pk 'gethostbyname
  940. (marionette-eval '(gethostbyname #$mdns-host-name)
  941. marionette))
  942. ((? vector? result)
  943. (and (string=? (hostent:name result) #$mdns-host-name)
  944. (= (hostent:addrtype result) AF_INET)))))
  945. (test-end))))
  946. (gexp->derivation "nss-mdns" test))
  947. (define %test-nss-mdns
  948. (system-test
  949. (name "nss-mdns")
  950. (description
  951. "Test Avahi's multicast-DNS implementation, and in particular, test its
  952. glibc name service switch (NSS) module.")
  953. (value (run-nss-mdns-test))))