aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2015-2019, 2022, 2023 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (test-services)
  #:use-module (gnu services)
  #:use-module (gnu services herd)
  #:use-module (gnu services shepherd)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-64)
  #:use-module (ice-9 match))

(test-begin "services")

(test-equal "services, default value"
  '(42 123 234 error)
  (let* ((t1 (service-type (name 't1) (extensions '())
                           (description "")))
         (t2 (service-type (name 't2) (extensions '())
                           (description "")
                           (default-value 42))))
    (list (service-value (service t2))
          (service-value (service t2 123))
          (service-value (service t1 234))
          (guard (c ((missing-value-service-error? c) 'error))
            (service t1)))))

(test-assert "service-back-edges"
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")
                           (compose +) (extend *)))
         (t2 (service-type (name 't2) (description "")
                           (extensions
                            (list (service-extension t1 (const '()))))
                           (compose +) (extend *)))
         (t3 (service-type (name 't3) (description "")
                           (extensions
                            (list (service-extension t2 identity)
                                  (service-extension t1 list)))))
         (s1 (service t1 #t))
         (s2 (service t2 #t))
         (s3 (service t3 #t))
         (e  (service-back-edges (list s1 s2 s3))))
    (and (lset= eq? (e s1) (list s2 s3))
         (lset= eq? (e s2) (list s3))
         (null? (e s3)))))

(test-equal "fold-services"
  ;; Make sure 'fold-services' returns the right result.  The numbers come
  ;; from services of type T3; 'xyz 60' comes from the service of type T2,
  ;; where 60 = 15 × 4 = (1 + 2 + 3 + 4 + 5) × 4.
  '(initial-value 5 4 3 2 1 xyz 60)
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")
                           (compose concatenate)
                           (extend cons)))
         (t2 (service-type (name 't2) (description "")
                           (extensions
                            (list (service-extension t1
                                                     (cut list 'xyz <>))))
                           (compose (cut reduce + 0 <>))
                           (extend *)))
         (t3 (service-type (name 't3) (description "")
                           (extensions
                            (list (service-extension t2 identity)
                                  (service-extension t1 list)))))
         (r  (fold-services (cons* (service t1 'initial-value)
                                   (service t2 4)
                                   (map (lambda (x)
                                          (service t3 x))
                                        (iota 5 1)))
                            #:target-type t1)))
    (and (eq? (service-kind r) t1)
         (service-value r))))

(test-assert "fold-services, ambiguity"
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")
                           (compose concatenate)
                           (extend cons)))
         (t2 (service-type (name 't2) (description "")
                           (extensions
                            (list (service-extension t1 list)))))
         (s  (service t2 42)))
    (guard (c ((ambiguous-target-service-error? c)
               (and (eq? (ambiguous-target-service-error-target-type c)
                         t1)
                    (eq? (ambiguous-target-service-error-service c)
                         s))))
      (fold-services (list (service t1 'first)
                           (service t1 'second)
                           s)
                     #:target-type t1)
      #f)))

(test-assert "fold-services, missing target"
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")))
         (t2 (service-type (name 't2) (description "")
                           (extensions
                            (list (service-extension t1 list)))))
         (s  (service t2 42)))
    (guard (c ((missing-target-service-error? c)
               (and (eq? (missing-target-service-error-target-type c)
                         t1)
                    (eq? (missing-target-service-error-service c)
                         s))))
      (fold-services (list s) #:target-type t1)
      #f)))

(test-assert "instantiate-missing-services"
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")
                           (default-value 'dflt)
                           (compose concatenate)
                           (extend cons)))
         (t2 (service-type (name 't2) (description "")
                           (extensions
                            (list (service-extension t1 list)))))
         (s1 (service t1 'hey!))
         (s2 (service t2 42)))
    (and (lset= equal?
                (list (service t1) s2)
                (instantiate-missing-services (list s2)))
         (equal? (list s1 s2)
                 (instantiate-missing-services (list s1 s2))))))

(test-assert "instantiate-missing-services, indirect"
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")
                           (default-value 'dflt)
                           (compose concatenate)
                           (extend cons)))
         (t2 (service-type (name 't2) (description "")
                           (default-value 'dflt2)
                           (compose concatenate)
                           (extend cons)
                           (extensions
                            (list (service-extension t1 list)))))
         (t3 (service-type (name 't3) (description "")
                           (extensions
                            (list (service-extension t2 list)))))
         (s1 (service t1))
         (s2 (service t2))
         (s3 (service t3 42))
         (== (cut lset= equal? <...>)))
    (and (== (list s1 s2 s3)
             (instantiate-missing-services (list s3)))
         (== (list s1 s2 s3)
             (instantiate-missing-services (list s1 s3)))
         (== (list s1 s2 s3)
             (instantiate-missing-services (list s2 s3))))))

(test-assert "instantiate-missing-services, no default value"
  (let* ((t1 (service-type (name 't1) (extensions '()) (description "")))
         (t2 (service-type (name 't2) (description "")
                           (extensions
                            (list (service-extension t1 list)))))
         (s  (service t2 42)))
    (guard (c ((missing-target-service-error? c)
               (and (eq? (missing-target-service-error-target-type c)
                         t1)
                    (eq? (missing-target-service-error-service c)
                         s))))
      (instantiate-missing-services (list s))
      #f)))

(test-assert "shepherd-service-lookup-procedure"
  (let* ((s1 (shepherd-service (provision '(s1 s1b)) (start #f)))
         (s2 (shepherd-service (provision '(s2 s2b)) (start #f)))
         (s3 (shepherd-service (provision '(s3 s3b s3c)) (start #f)))
         (lookup (shepherd-service-lookup-procedure (list s1 s2 s3))))
    (and (eq? (lookup 's1) (lookup 's1b) s1)
         (eq? (lookup 's2) (lookup 's2b) s2)
         (eq? (lookup 's3) (lookup 's3b) s3))))

(test-assert "shepherd-service-back-edges"
  (let* ((s1 (shepherd-service (provision '(s1)) (start #f)))
         (s2 (shepherd-service (provision '(s2))
                               (requirement '(s1))
                               (start #f)))
         (s3 (shepherd-service (provision '(s3))
                               (requirement '(s1 s2))
                               (start #f)))
         (e  (shepherd-service-back-edges (list s1 s2 s3))))
    (and (lset= eq? (e s1) (list s2 s3))
         (lset= eq? (e s2) (list s3))
         (null? (e s3)))))

(test-equal "shepherd-service-upgrade: nothing to do"
  '(() ())
  (call-with-values
      (lambda ()
        (shepherd-service-upgrade '() '()))
    list))

(test-equal "shepherd-service-upgrade: one unchanged, one upgraded, one new"
  '(()                                            ;unload
    ((foo)))                                      ;restart
  (call-with-values
      (lambda ()
        ;; Here 'foo' is replaced and must be explicitly restarted later
        ;; because it is still running, whereas 'bar' is upgraded right away
        ;; because it is not currently running.  'baz' is loaded because it's
        ;; a new service.
        (shepherd-service-upgrade
         (list (live-service '(foo) '() #f #t)
               (live-service '(bar) '() #f #f)
               (live-service '(root) '() #f #t))     ;essential!
         (list (shepherd-service (provision '(foo))
                                 (start #t))
               (shepherd-service (provision '(bar))
                                 (start #t))
               (shepherd-service (provision '(baz))
                                 (start #t)))))
    (lambda (unload restart)
      (list (map live-service-provision unload)
            (map shepherd-service-provision restart)))))

(test-equal "shepherd-service-upgrade: service depended on is not unloaded"
  '(((baz))                                       ;unload
    ((foo)))                                      ;restart
  (call-with-values
      (lambda ()
        ;; Service 'bar' is not among the target services; yet, it must not be
        ;; unloaded because 'foo' depends on it.  'foo' gets replaced but it
        ;; must be restarted manually.
        (shepherd-service-upgrade
         (list (live-service '(foo) '(bar) #f #t)
               (live-service '(bar) '() #f #t)    ;still used!
               (live-service '(baz) '() #f #t))
         (list (shepherd-service (provision '(foo))
                                 (start #t)))))
    (lambda (unload restart)
      (list (map live-service-provision unload)
            (map shepherd-service-provision restart)))))

(test-equal "shepherd-service-upgrade: obsolete services that depend on each other"
  '(((foo) (bar) (baz))                           ;unload
    ())                                           ;restart
  (call-with-values
      (lambda ()
        ;; 'foo', 'bar', and 'baz' depend on each other, but all of them are
        ;; obsolete, and thus should be unloaded.
        (shepherd-service-upgrade
         (list (live-service '(foo) '(bar) #f #t) ;obsolete
               (live-service '(bar) '(baz) #f #t) ;obsolete
               (live-service '(baz) '() #f #t))   ;obsolete
         (list (shepherd-service (provision '(qux))
                                 (start #t)))))
    (lambda (unload restart)
      (list (map live-service-provision unload)
            (map shepherd-service-provision restart)))))

(test-equal "shepherd-service-upgrade: transient service"
  ;; Transient service must not be unloaded:
  ;; <https://issues.guix.gnu.org/54812>.
  '(((foo))                                       ;unload
    ((qux)))                                      ;restart
  (call-with-values
      (lambda ()
        (shepherd-service-upgrade
         (list (live-service '(sshd-42) '() #t 42) ;transient
               (live-service '(foo) '() #f #t)     ;obsolete
               (live-service '(qux) '() #f #t))    ;running
         (list (shepherd-service (provision '(qux))
                                 (start #t)))))
    (lambda (unload restart)
      (list (map live-service-provision unload)
            (map shepherd-service-provision restart)))))

(test-eq "lookup-service-types"
  system-service-type
  (and (null? (lookup-service-types 'does-not-exist-at-all))
       (match (lookup-service-types 'system)
         ((one) one)
         (x x))))

(test-equal "modify-services: do nothing"
  '(1 2 3)                              ;note: service order must be preserved
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2) (service t3 3))))
    (map service-value
         (modify-services services))))

(test-equal "modify-services: delete service"
  '(1 4)                                ;note: service order must be preserved
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (t4 (service-type (name 't4)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2)
                         (service t3 3) (service t4 4))))
    (map service-value
         (modify-services services
           (delete t3)
           (delete t2)))))

(test-error "modify-services: delete non-existing service"
  #t
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2))))
    (modify-services services
      (delete t3))))

(test-equal "modify-services: change value"
  '(11 2 33)                            ;note: service order must be preserved
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2) (service t3 3))))
    (map service-value
         (modify-services services
           (t1 value => 11)
           (t3 value => 33)))))

(test-error "modify-services: change value for non-existing service"
  #t
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t3 3))))
    (map service-value
         (modify-services services
           (t2 value => 22)))))

(test-error "modify-services: delete then modify"
  #t
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2) (service t3 3))))
    (map service-value
         (modify-services services
           (delete t2)
           (t2 value => 22)))))

(test-equal "modify-services: modify then delete"
  '(2 3)
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2) (service t3 3))))
    (map service-value
         (modify-services services
           (t1 value => 11)
           (delete t1)))))

(test-equal "modify-services: delete multiple services of the same type"
  '(1 3)
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2)
                         (service t2 2) (service t3 3))))
    (map service-value
         (modify-services services
           (delete t2)))))

(test-equal "modify-services: modify multiple services of the same type"
  '(1 12 13 4)
  (let* ((t1 (service-type (name 't1)
                           (extensions '())
                           (description "")))
         (t2 (service-type (name 't2)
                           (extensions '())
                           (description "")))
         (t3 (service-type (name 't3)
                           (extensions '())
                           (description "")))
         (services (list (service t1 1) (service t2 2)
                         (service t2 3) (service t3 4))))
    (map service-value
         (modify-services services
           (t2 value => (+ value 10))))))

(test-end)
ic Courtès 2020-11-29daemon: Remove pre-Guix hack....* nix/libstore/build.cc (DerivationGoal::startBuilder): Remove "NIX_OUTPUT_CHECKED" hack. Ludovic Courtès 2020-10-09nix: Honor '--rounds' when also using '--check'....Fixes <https://issues.guix.gnu.org/40144>. Until now, the '--rounds' option, when also using '--check', was ignored. This change makes it possible to use both, so that an item that has already been built once can be rebuilt as many times as desired. * nix/libstore/build.cc: Remove the conditionals causing the daemon to complete a build task early when 'buildMode' is equal to 'nix::bmCheck'. Reported-by: Brice Waegeneire <brice@waegenei.re> Maxim Cournoyer 2020-10-01daemon: Try to execute derivation builders only for matching OS kernels....Fixes <https://bugs.gnu.org/43668>. Previously, guix-daemon would try to run GNU/Hurd executables on GNU/Linux. execve(2) would succeed, but the executable would immediately crash. This change prevents it from attempting to execute "i586-gnu" code on "*-linux", while preserving the binfmt_misc-friendly behavior implemented in commit 7bf2a70a4ffd976d50638d3b9f2ec409763157df. * nix/libstore/build.cc (sameOperatingSystemKernel): New function. (DerivationGoal::runChild): Call 'execve' only when 'sameOperatingSystemKernel' returns true. Ludovic Courtès 2020-09-14daemon: Spawn 'guix authenticate' once for all....Previously, we'd spawn 'guix authenticate' once for each item that has to be signed (when exporting) or authenticated (when importing). Now, we spawn it once for all and then follow a request/reply protocol. This reduces the wall-clock time of: guix archive --export -r $(guix build coreutils -d) from 30s to 2s. * guix/scripts/authenticate.scm (sign-with-key): Return the signature instead of displaying it. Raise a &formatted-message instead of calling 'leave'. (validate-signature): Likewise. (read-command): New procedure. (define-enumerate-type, reply-code): New macros. (guix-authenticate)[send-reply]: New procedure. Change to read commands from current-input-port. * nix/libstore/local-store.cc (runAuthenticationProgram): Remove. (authenticationAgent, readInteger, readAuthenticateReply): New functions. (signHash, verifySignature): Rewrite in terms of the agent. * tests/store.scm ("import not signed"): Remove 'pk' call. ("import signed by unauthorized key"): Check the error message of C. * tests/guix-authenticate.sh: Rewrite using the new protocol. fixlet Ludovic Courtès 2020-09-14daemon: Move 'Agent' to libutil....* nix/libstore/build.cc (DerivationGoal::tryBuildHook): Add "offload" to 'args' and pass settings.guixProgram as the first argument to Agent::Agent. (pathNullDevice, commonChildInit, Agent, Agent::Agent) (Agent::~Agent): Move to... * nix/libutil/util.cc: ... here. * nix/libutil/util.hh (struct Agent, commonChildInit): New declarations. Ludovic Courtès 2020-09-14daemon: Isolate signing and signature verification functions....* nix/libstore/local-store.cc (signHash, verifySignature): New functions. (LocalStore::exportPath): Use 'signHash' instead of inline code. (LocalStore::importPath): Use 'verifySignature' instead of inline code. Ludovic Courtès 2020-09-14daemon: Generalize 'HookInstance' to 'Agent'....* nix/libstore/build.cc (HookInstance): Rename to... (Agent): ... this. Rename 'toHook' and 'fromHook' similarly and update users. Change constructor to require a command and an argument list. (DerivationGoal::tryBuildHook): Pass arguments to the 'Agent' constructor. Ludovic Courtès 2020-09-11daemon: Simplify interface with 'guix authenticate'....There's no reason at this point to mimic the calling convention of the 'openssl' command. * nix/libstore/local-store.cc (LocalStore::exportPath): Add only "sign" and HASH to ARGS. Remove 'tmpDir' and 'hashFile'. (LocalStore::importPath): Add only "verify" and SIGNATURE to * guix/scripts/authenticate.scm (guix-authenticate): Adjust accordingly; remove the OpenSSL-style clauses. (read-hash-data): Remove. (sign-with-key): Replace 'port' with 'sha256' and adjust accordingly. (validate-signature): Export SIGNATURE to be a canonical sexp. * tests/guix-authenticate.sh: Adjust tests accordingly. Ludovic Courtès 2020-06-25daemon: Correctly handle EMLINK corner case when deduplicating....Suggested by Caleb Ristvedt <caleb.ristvedt@cune.org>. * nix/libstore/optimise-store.cc (LocalStore::optimisePath_): Save errno from 'rename' before calling 'unlink'. Ludovic Courtès 2020-06-24nix: Tweak .gitignore files....Remove .gitignore entries where they match source files that are tracked in Git. This is relevant to me at least, as some code searching tools use .gitignore files and will ignore matched files. Christopher Baines 2020-06-06daemon: Handle EXDEV when moving to trash directory....Fixes <https://bugs.gnu.org/41607>. Reported by Stephen Scheck <singularsyntax@gmail.com>. * nix/libstore/gc.cc (LocalStore::deletePathRecursive): When we try to move a dead directory into the trashDir using rename(2) but it returns an EXDEV error, just delete the directory instead. This can happen in a Docker container when the directory is not on the "top layer". Chris Marusich