base.scm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016, 2017 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. (define-module (gnu tests base)
  19. #:use-module (gnu tests)
  20. #:use-module (gnu system)
  21. #:use-module (gnu system shadow)
  22. #:use-module (gnu system nss)
  23. #:use-module (gnu system vm)
  24. #:use-module (gnu services)
  25. #:use-module (gnu services base)
  26. #:use-module (gnu services dbus)
  27. #:use-module (gnu services avahi)
  28. #:use-module (gnu services mcron)
  29. #:use-module (gnu services shepherd)
  30. #:use-module (gnu services networking)
  31. #:use-module (gnu packages imagemagick)
  32. #:use-module (gnu packages ocr)
  33. #:use-module (gnu packages package-management)
  34. #:use-module (gnu packages linux)
  35. #:use-module (gnu packages tmux)
  36. #:use-module (guix gexp)
  37. #:use-module (guix store)
  38. #:use-module (guix packages)
  39. #:use-module (srfi srfi-1)
  40. #:export (run-basic-test
  41. %test-basic-os
  42. %test-halt
  43. %test-mcron
  44. %test-nss-mdns))
  45. (define %simple-os
  46. (simple-operating-system))
  47. (define* (run-basic-test os command #:optional (name "basic")
  48. #:key initialization)
  49. "Return a derivation called NAME that tests basic features of the OS started
  50. using COMMAND, a gexp that evaluates to a list of strings. Compare some
  51. properties of running system to what's declared in OS, an <operating-system>.
  52. When INITIALIZATION is true, it must be a one-argument procedure that is
  53. passed a gexp denoting the marionette, and it must return gexp that is
  54. inserted before the first test. This is used to introduce an extra
  55. initialization step, such as entering a LUKS passphrase."
  56. (define special-files
  57. (service-value
  58. (fold-services (operating-system-services os)
  59. #:target-type special-files-service-type)))
  60. (define test
  61. (with-imported-modules '((gnu build marionette)
  62. (guix build syscalls))
  63. #~(begin
  64. (use-modules (gnu build marionette)
  65. (guix build syscalls)
  66. (srfi srfi-1)
  67. (srfi srfi-26)
  68. (srfi srfi-64)
  69. (ice-9 match))
  70. (define marionette
  71. (make-marionette #$command))
  72. (mkdir #$output)
  73. (chdir #$output)
  74. (test-begin "basic")
  75. #$(and initialization
  76. (initialization #~marionette))
  77. (test-assert "uname"
  78. (match (marionette-eval '(uname) marionette)
  79. (#("Linux" host-name version _ architecture)
  80. (and (string=? host-name
  81. #$(operating-system-host-name os))
  82. (string-prefix? #$(package-version
  83. (operating-system-kernel os))
  84. version)
  85. (string-prefix? architecture %host-type)))))
  86. (test-assert "shell and user commands"
  87. ;; Is everything in $PATH?
  88. (zero? (marionette-eval '(system "
  89. . /etc/profile
  90. set -e -x
  91. guix --version
  92. ls --version
  93. grep --version
  94. info --version")
  95. marionette)))
  96. (test-equal "special files"
  97. '#$special-files
  98. (marionette-eval
  99. '(begin
  100. (use-modules (ice-9 match))
  101. (map (match-lambda
  102. ((file target)
  103. (list file (readlink file))))
  104. '#$special-files))
  105. marionette))
  106. (test-assert "accounts"
  107. (let ((users (marionette-eval '(begin
  108. (use-modules (ice-9 match))
  109. (let loop ((result '()))
  110. (match (getpw)
  111. (#f (reverse result))
  112. (x (loop (cons x result))))))
  113. marionette)))
  114. (lset= string=?
  115. (map passwd:name users)
  116. (list
  117. #$@(map user-account-name
  118. (operating-system-user-accounts os))))))
  119. (test-assert "shepherd services"
  120. (let ((services (marionette-eval
  121. '(begin
  122. (use-modules (gnu services herd))
  123. (map (compose car live-service-provision)
  124. (current-services)))
  125. marionette)))
  126. (lset= eq?
  127. (pk 'services services)
  128. '(root #$@(operating-system-shepherd-service-names os)))))
  129. (test-assert "homes"
  130. (let ((homes
  131. '#$(map user-account-home-directory
  132. (filter user-account-create-home-directory?
  133. (operating-system-user-accounts os)))))
  134. (marionette-eval
  135. `(begin
  136. (use-modules (gnu services herd) (srfi srfi-1))
  137. ;; Home directories are supposed to exist once 'user-homes'
  138. ;; has been started.
  139. (start-service 'user-homes)
  140. (every (lambda (home)
  141. (and (file-exists? home)
  142. (file-is-directory? home)))
  143. ',homes))
  144. marionette)))
  145. (test-assert "skeletons in home directories"
  146. (let ((users+homes
  147. '#$(filter-map (lambda (account)
  148. (and (user-account-create-home-directory?
  149. account)
  150. (not (user-account-system? account))
  151. (list (user-account-name account)
  152. (user-account-home-directory
  153. account))))
  154. (operating-system-user-accounts os))))
  155. (marionette-eval
  156. `(begin
  157. (use-modules (srfi srfi-1) (ice-9 ftw)
  158. (ice-9 match))
  159. (every (match-lambda
  160. ((user home)
  161. ;; Make sure HOME has all the skeletons...
  162. (and (null? (lset-difference string=?
  163. (scandir "/etc/skel/")
  164. (scandir home)))
  165. ;; ... and that everything is user-owned.
  166. (let* ((pw (getpwnam user))
  167. (uid (passwd:uid pw))
  168. (gid (passwd:gid pw))
  169. (st (lstat home)))
  170. (define (user-owned? file)
  171. (= uid (stat:uid (lstat file))))
  172. (and (= uid (stat:uid st))
  173. (eq? 'directory (stat:type st))
  174. (every user-owned?
  175. (find-files home
  176. #:directories? #t)))))))
  177. ',users+homes))
  178. marionette)))
  179. (test-equal "permissions on /root"
  180. #o700
  181. (let ((root-home #$(any (lambda (account)
  182. (and (zero? (user-account-uid account))
  183. (user-account-home-directory
  184. account)))
  185. (operating-system-user-accounts os))))
  186. (stat:perms (marionette-eval `(stat ,root-home) marionette))))
  187. (test-equal "no extra home directories"
  188. '()
  189. ;; Make sure the home directories that are not supposed to be
  190. ;; created are indeed not created.
  191. (let ((nonexistent
  192. '#$(filter-map (lambda (user)
  193. (and (not
  194. (user-account-create-home-directory?
  195. user))
  196. (user-account-home-directory user)))
  197. (operating-system-user-accounts os))))
  198. (marionette-eval
  199. `(begin
  200. (use-modules (srfi srfi-1))
  201. ;; Note: Do not flag "/var/empty".
  202. (filter file-exists?
  203. ',(remove (cut string-prefix? "/var/" <>)
  204. nonexistent)))
  205. marionette)))
  206. (test-equal "login on tty1"
  207. "root\n"
  208. (begin
  209. (marionette-control "sendkey ctrl-alt-f1" marionette)
  210. ;; Wait for the 'term-tty1' service to be running (using
  211. ;; 'start-service' is the simplest and most reliable way to do
  212. ;; that.)
  213. (marionette-eval
  214. '(begin
  215. (use-modules (gnu services herd))
  216. (start-service 'term-tty1))
  217. marionette)
  218. ;; Now we can type.
  219. (marionette-type "root\n\nid -un > logged-in\n" marionette)
  220. ;; It can take a while before the shell commands are executed.
  221. (marionette-eval '(use-modules (rnrs io ports)) marionette)
  222. (wait-for-file "/root/logged-in" marionette
  223. #:read 'get-string-all)))
  224. ;; There should be one utmpx entry for the user logged in on tty1.
  225. (test-equal "utmpx entry"
  226. '(("root" "tty1" #f))
  227. (marionette-eval
  228. '(begin
  229. (use-modules (guix build syscalls)
  230. (srfi srfi-1))
  231. (filter-map (lambda (entry)
  232. (and (equal? (login-type USER_PROCESS)
  233. (utmpx-login-type entry))
  234. (list (utmpx-user entry) (utmpx-line entry)
  235. (utmpx-host entry))))
  236. (utmpx-entries)))
  237. marionette))
  238. ;; Likewise for /var/log/wtmp (used by 'last').
  239. (test-assert "wtmp entry"
  240. (match (marionette-eval
  241. '(begin
  242. (use-modules (guix build syscalls)
  243. (srfi srfi-1))
  244. (define (entry->list entry)
  245. (list (utmpx-user entry) (utmpx-line entry)
  246. (utmpx-host entry) (utmpx-login-type entry)))
  247. (call-with-input-file "/var/log/wtmp"
  248. (lambda (port)
  249. (let loop ((result '()))
  250. (if (eof-object? (peek-char port))
  251. (map entry->list (reverse result))
  252. (loop (cons (read-utmpx port) result)))))))
  253. marionette)
  254. (((users lines hosts types) ..1)
  255. (every (lambda (type)
  256. (eqv? type (login-type LOGIN_PROCESS)))
  257. types))))
  258. (test-assert "host name resolution"
  259. (match (marionette-eval
  260. '(begin
  261. ;; Wait for nscd or our requests go through it.
  262. (use-modules (gnu services herd))
  263. (start-service 'nscd)
  264. (list (getaddrinfo "localhost")
  265. (getaddrinfo #$(operating-system-host-name os))))
  266. marionette)
  267. ((((? vector?) ..1) ((? vector?) ..1))
  268. #t)
  269. (x
  270. (pk 'failure x #f))))
  271. (test-equal "host not found"
  272. #f
  273. (marionette-eval
  274. '(false-if-exception (getaddrinfo "does-not-exist"))
  275. marionette))
  276. (test-equal "locale"
  277. "en_US.utf8"
  278. (marionette-eval '(let ((before (setlocale LC_ALL "en_US.utf8")))
  279. (setlocale LC_ALL before))
  280. marionette))
  281. (test-eq "/run/current-system is a GC root"
  282. 'success!
  283. (marionette-eval '(begin
  284. ;; Make sure the (guix …) modules are found.
  285. ;;
  286. ;; XXX: Currently shepherd and marionette run
  287. ;; on Guile 2.0 whereas Guix is on 2.2. Yet
  288. ;; we should be able to load the 2.0 Scheme
  289. ;; files since it's pure Scheme.
  290. (add-to-load-path
  291. #+(file-append guix "/share/guile/site/2.2"))
  292. (use-modules (srfi srfi-34) (guix store))
  293. (let ((system (readlink "/run/current-system")))
  294. (guard (c ((nix-protocol-error? c)
  295. (and (file-exists? system)
  296. 'success!)))
  297. (with-store store
  298. (delete-paths store (list system))
  299. #f))))
  300. marionette))
  301. ;; This symlink is currently unused, but better have it point to the
  302. ;; right place. See
  303. ;; <https://lists.gnu.org/archive/html/guix-devel/2016-08/msg01641.html>.
  304. (test-equal "/var/guix/gcroots/profiles is a valid symlink"
  305. "/var/guix/profiles"
  306. (marionette-eval '(readlink "/var/guix/gcroots/profiles")
  307. marionette))
  308. (test-assert "screendump"
  309. (begin
  310. (marionette-control (string-append "screendump " #$output
  311. "/tty1.ppm")
  312. marionette)
  313. (file-exists? "tty1.ppm")))
  314. (test-assert "screen text"
  315. (let ((text (marionette-screen-text marionette
  316. #:ocrad
  317. #$(file-append ocrad
  318. "/bin/ocrad"))))
  319. ;; Check whether the welcome message and shell prompt are
  320. ;; displayed. Note: OCR confuses "y" and "V" for instance, so
  321. ;; we cannot reliably match the whole text.
  322. (and (string-contains text "This is the GNU")
  323. (string-contains text
  324. (string-append
  325. "root@"
  326. #$(operating-system-host-name os))))))
  327. (test-end)
  328. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  329. (gexp->derivation name test))
  330. (define %test-basic-os
  331. (system-test
  332. (name "basic")
  333. (description
  334. "Instrument %SIMPLE-OS, run it in a VM, and run a series of basic
  335. functionality tests.")
  336. (value
  337. (let* ((os (marionette-operating-system
  338. %simple-os
  339. #:imported-modules '((gnu services herd)
  340. (guix combinators))))
  341. (vm (virtual-machine os)))
  342. ;; XXX: Add call to 'virtualized-operating-system' to get the exact same
  343. ;; set of services as the OS produced by
  344. ;; 'system-qemu-image/shared-store-script'.
  345. (run-basic-test (virtualized-operating-system os '())
  346. #~(list #$vm))))))
  347. ;;;
  348. ;;; Halt.
  349. ;;;
  350. (define (run-halt-test vm)
  351. ;; As reported in <http://bugs.gnu.org/26931>, running tmux would previously
  352. ;; lead the 'stop' method of 'user-processes' to an infinite loop, with the
  353. ;; tmux server process as a zombie that remains in the list of processes.
  354. ;; This test reproduces this scenario.
  355. (define test
  356. (with-imported-modules '((gnu build marionette))
  357. #~(begin
  358. (use-modules (gnu build marionette))
  359. (define marionette
  360. (make-marionette '(#$vm)))
  361. (define ocrad
  362. #$(file-append ocrad "/bin/ocrad"))
  363. ;; Wait for tty1 and log in.
  364. (marionette-eval '(begin
  365. (use-modules (gnu services herd))
  366. (start-service 'term-tty1))
  367. marionette)
  368. (marionette-type "root\n" marionette)
  369. (wait-for-screen-text marionette
  370. (lambda (text)
  371. (string-contains text "root@komputilo"))
  372. #:ocrad ocrad)
  373. ;; Start tmux and wait for it to be ready.
  374. (marionette-type "tmux new-session 'echo 1 > /ready; bash'\n"
  375. marionette)
  376. (wait-for-file "/ready" marionette)
  377. ;; Make sure to stop the test after a while.
  378. (sigaction SIGALRM (lambda _
  379. (format (current-error-port)
  380. "FAIL: Time is up, but VM still running.\n")
  381. (primitive-exit 1)))
  382. (alarm 10)
  383. ;; Get debugging info.
  384. (marionette-eval '(current-output-port
  385. (open-file "/dev/console" "w0"))
  386. marionette)
  387. (marionette-eval '(system* #$(file-append procps "/bin/ps")
  388. "-eo" "pid,ppid,stat,comm")
  389. marionette)
  390. ;; See if 'halt' actually works.
  391. (marionette-eval '(system* "/run/current-system/profile/sbin/halt")
  392. marionette)
  393. ;; If we reach this line, that means the VM was properly stopped in
  394. ;; a timely fashion.
  395. (alarm 0)
  396. (call-with-output-file #$output
  397. (lambda (port)
  398. (display "success!" port))))))
  399. (gexp->derivation "halt" test))
  400. (define %test-halt
  401. (system-test
  402. (name "halt")
  403. (description
  404. "Use the 'halt' command and make sure it succeeds and does not get stuck
  405. in a loop. See <http://bugs.gnu.org/26931>.")
  406. (value
  407. (let ((os (marionette-operating-system
  408. (operating-system
  409. (inherit %simple-os)
  410. (packages (cons tmux %base-packages)))
  411. #:imported-modules '((gnu services herd)
  412. (guix combinators)))))
  413. (run-halt-test (virtual-machine os))))))
  414. ;;;
  415. ;;; Mcron.
  416. ;;;
  417. (define %mcron-os
  418. ;; System with an mcron service, with one mcron job for "root" and one mcron
  419. ;; job for an unprivileged user (note: #:user is an 'mcron2' thing.)
  420. (let ((job1 #~(job next-second-from
  421. (lambda ()
  422. (call-with-output-file "witness"
  423. (lambda (port)
  424. (display (list (getuid) (getgid)) port))))))
  425. (job2 #~(job next-second-from
  426. (lambda ()
  427. (call-with-output-file "witness"
  428. (lambda (port)
  429. (display (list (getuid) (getgid)) port))))
  430. #:user "alice"))
  431. (job3 #~(job next-second-from ;to test $PATH
  432. "touch witness-touch")))
  433. (simple-operating-system
  434. (mcron-service (list job1 job2 job3)))))
  435. (define (run-mcron-test name)
  436. (define os
  437. (marionette-operating-system
  438. %mcron-os
  439. #:imported-modules '((gnu services herd)
  440. (guix combinators))))
  441. (define test
  442. (with-imported-modules '((gnu build marionette))
  443. #~(begin
  444. (use-modules (gnu build marionette)
  445. (srfi srfi-64)
  446. (ice-9 match))
  447. (define marionette
  448. (make-marionette (list #$(virtual-machine os))))
  449. (mkdir #$output)
  450. (chdir #$output)
  451. (test-begin "mcron")
  452. (test-eq "service running"
  453. 'running!
  454. (marionette-eval
  455. '(begin
  456. (use-modules (gnu services herd))
  457. (start-service 'mcron)
  458. 'running!)
  459. marionette))
  460. ;; Make sure root's mcron job runs, has its cwd set to "/root", and
  461. ;; runs with the right UID/GID.
  462. (test-equal "root's job"
  463. '(0 0)
  464. (wait-for-file "/root/witness" marionette))
  465. ;; Likewise for Alice's job. We cannot know what its GID is since
  466. ;; it's chosen by 'groupadd', but it's strictly positive.
  467. (test-assert "alice's job"
  468. (match (wait-for-file "/home/alice/witness" marionette)
  469. ((1000 gid)
  470. (>= gid 100))))
  471. ;; Last, the job that uses a command; allows us to test whether
  472. ;; $PATH is sane.
  473. (test-equal "root's job with command"
  474. ""
  475. (wait-for-file "/root/witness-touch" marionette
  476. #:read '(@ (ice-9 rdelim) read-string)))
  477. (test-end)
  478. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  479. (gexp->derivation name test))
  480. (define %test-mcron
  481. (system-test
  482. (name "mcron")
  483. (description "Make sure the mcron service works as advertised.")
  484. (value (run-mcron-test name))))
  485. ;;;
  486. ;;; Avahi and NSS-mDNS.
  487. ;;;
  488. (define %avahi-os
  489. (operating-system
  490. (inherit %simple-os)
  491. (name-service-switch %mdns-host-lookup-nss)
  492. (services (cons* (avahi-service #:debug? #t)
  493. (dbus-service)
  494. (dhcp-client-service) ;needed for multicast
  495. ;; Enable heavyweight debugging output.
  496. (modify-services (operating-system-user-services
  497. %simple-os)
  498. (nscd-service-type config
  499. => (nscd-configuration
  500. (inherit config)
  501. (debug-level 3)
  502. (log-file "/dev/console")))
  503. (syslog-service-type config
  504. =>
  505. (syslog-configuration
  506. (inherit config)
  507. (config-file
  508. (plain-file
  509. "syslog.conf"
  510. "*.* /dev/console\n")))))))))
  511. (define (run-nss-mdns-test)
  512. ;; Test resolution of '.local' names via libc. Start the marionette service
  513. ;; *after* nscd. Failing to do that, libc will try to connect to nscd,
  514. ;; fail, then never try again (see '__nss_not_use_nscd_hosts' in libc),
  515. ;; leading to '.local' resolution failures.
  516. (define os
  517. (marionette-operating-system
  518. %avahi-os
  519. #:requirements '(nscd)
  520. #:imported-modules '((gnu services herd)
  521. (guix combinators))))
  522. (define mdns-host-name
  523. (string-append (operating-system-host-name os)
  524. ".local"))
  525. (define test
  526. (with-imported-modules '((gnu build marionette))
  527. #~(begin
  528. (use-modules (gnu build marionette)
  529. (srfi srfi-1)
  530. (srfi srfi-64)
  531. (ice-9 match))
  532. (define marionette
  533. (make-marionette (list #$(virtual-machine os))))
  534. (mkdir #$output)
  535. (chdir #$output)
  536. (test-begin "avahi")
  537. (test-assert "wait for services"
  538. (marionette-eval
  539. '(begin
  540. (use-modules (gnu services herd))
  541. (start-service 'nscd)
  542. ;; XXX: Work around a race condition in nscd: nscd creates its
  543. ;; PID file before it is listening on its socket.
  544. (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
  545. (let try ()
  546. (catch 'system-error
  547. (lambda ()
  548. (connect sock AF_UNIX "/var/run/nscd/socket")
  549. (close-port sock)
  550. (format #t "nscd is ready~%"))
  551. (lambda args
  552. (format #t "waiting for nscd...~%")
  553. (usleep 500000)
  554. (try)))))
  555. ;; Wait for the other useful things.
  556. (start-service 'avahi-daemon)
  557. (start-service 'networking)
  558. #t)
  559. marionette))
  560. (test-equal "avahi-resolve-host-name"
  561. 0
  562. (marionette-eval
  563. '(system*
  564. "/run/current-system/profile/bin/avahi-resolve-host-name"
  565. "-v" #$mdns-host-name)
  566. marionette))
  567. (test-equal "avahi-browse"
  568. 0
  569. (marionette-eval
  570. '(system* "avahi-browse" "-avt")
  571. marionette))
  572. (test-assert "getaddrinfo .local"
  573. ;; Wait for the 'avahi-daemon' service and perform a resolution.
  574. (match (marionette-eval
  575. '(getaddrinfo #$mdns-host-name)
  576. marionette)
  577. (((? vector? addrinfos) ..1)
  578. (pk 'getaddrinfo addrinfos)
  579. (and (any (lambda (ai)
  580. (= AF_INET (addrinfo:fam ai)))
  581. addrinfos)
  582. (any (lambda (ai)
  583. (= AF_INET6 (addrinfo:fam ai)))
  584. addrinfos)))))
  585. (test-assert "gethostbyname .local"
  586. (match (pk 'gethostbyname
  587. (marionette-eval '(gethostbyname #$mdns-host-name)
  588. marionette))
  589. ((? vector? result)
  590. (and (string=? (hostent:name result) #$mdns-host-name)
  591. (= (hostent:addrtype result) AF_INET)))))
  592. (test-end)
  593. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  594. (gexp->derivation "nss-mdns" test))
  595. (define %test-nss-mdns
  596. (system-test
  597. (name "nss-mdns")
  598. (description
  599. "Test Avahi's multicast-DNS implementation, and in particular, test its
  600. glibc name service switch (NSS) module.")
  601. (value (run-nss-mdns-test))))