gnu: emacs-org: Update to 9.4.
[jackhill/guix/guix.git] / guix / tests.scm
index 080ee9c..3ccf049 100644 (file)
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (guix tests)
+  #:use-module ((guix config) #:select (%storedir %localstatedir))
   #:use-module (guix store)
   #:use-module (guix derivations)
   #:use-module (guix packages)
   #:use-module (guix base32)
   #:use-module (guix serialization)
-  #:use-module (guix hash)
+  #:use-module (guix monads)
+  #:use-module ((guix utils) #:select (substitute-keyword-arguments))
+  #:use-module ((guix build utils) #:select (mkdir-p))
+  #:use-module ((gcrypt hash) #:hide (sha256))
   #:use-module (guix build-system gnu)
+  #:use-module (gnu packages base)
   #:use-module (gnu packages bootstrap)
+  #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-64)
   #:use-module (rnrs bytevectors)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 binary-ports)
   #:use-module (web uri)
   #:export (open-connection-for-tests
+            with-external-store
+            %seed
             random-text
             random-bytevector
+            file=?
+            canonical-file?
             network-reachable?
             shebang-too-long?
+            with-environment-variable
+
+            search-bootstrap-binary
+
             mock
+            %test-substitute-urls
+            test-assertm
+            test-equalm
             %substitute-directory
             with-derivation-narinfo
             with-derivation-substitute
-            dummy-package))
+            dummy-package
+            dummy-origin
+
+            gnu-make-for-tests))
 
 ;;; Commentary:
 ;;;
 ;;;
 ;;; Code:
 
-(define (open-connection-for-tests)
+(define %test-substitute-urls
+  ;; URLs where to look for substitutes during tests.
+  (make-parameter
+   (or (and=> (getenv "GUIX_BINARY_SUBSTITUTE_URL") list)
+       '())))
+
+(define* (open-connection-for-tests #:optional (uri (%daemon-socket-uri)))
   "Open a connection to the build daemon for tests purposes and return it."
-  (guard (c ((nix-error? c)
+  (guard (c ((store-error? c)
              (format (current-error-port)
                      "warning: build daemon error: ~s~%" c)
              #f))
-    (let ((store (open-connection)))
+    (let ((store (open-connection uri)))
       ;; Make sure we build everything by ourselves.
-      (set-build-options store #:use-substitutes? #f)
+      (set-build-options store
+                         #:use-substitutes? #f
+                         #:substitute-urls (%test-substitute-urls))
 
       ;; Use the bootstrap Guile when running tests, so we don't end up
       ;; building everything in the temporary test store.
 
       store)))
 
+(define (bootstrap-binary-file program system)
+  "Return the absolute file name where bootstrap binary PROGRAM for SYSTEM is
+stored."
+  (string-append (dirname (search-path %load-path
+                                       "gnu/packages/bootstrap.scm"))
+                 "/bootstrap/" system "/" program))
+
+(define (search-bootstrap-binary file-name system)
+  "Search the bootstrap binary FILE-NAME for SYSTEM.  Raise an error if not
+found."
+  ;; Note: Keep bootstrap binaries on the local file system so that the 'guix'
+  ;; package can provide them as inputs and copy them to the right place.
+  (let* ((system (match system
+                   ("x86_64-linux" "i686-linux")
+                   (_ system)))
+         (file   (bootstrap-binary-file file-name system)))
+    (if (file-exists? file)
+        file
+        (with-store store
+          (run-with-store store
+            (mlet %store-monad ((drv (origin->derivation
+                                      (bootstrap-executable file-name system))))
+              (mbegin %store-monad
+                (built-derivations (list drv))
+                (begin
+                  (mkdir-p (dirname file))
+                  (copy-file (derivation->output-path drv) file)
+                  (return file)))))))))
+
+(define (call-with-external-store proc)
+  "Call PROC with an open connection to the external store or #f it there is
+no external store to talk to."
+  (parameterize ((%daemon-socket-uri
+                  (string-append %localstatedir
+                                 "/guix/daemon-socket/socket"))
+                 (%store-prefix %storedir))
+    (define store
+      (catch #t
+        (lambda ()
+          (open-connection))
+        (const #f)))
+
+    (dynamic-wind
+      (const #t)
+      (lambda ()
+        ;; Since we're using a different store we must clear the
+        ;; package-derivation cache.
+        (hash-clear! (@@ (guix packages) %derivation-cache))
+
+        (proc store))
+      (lambda ()
+        (when store
+          (close-connection store))))))
+
+(define-syntax-rule (with-external-store store exp ...)
+  "Evaluate EXP with STORE bound to the external store rather than the
+temporary test store, or #f if there is no external store to talk to.
+
+This is meant to be used for tests that need to build packages that would be
+too expensive to build entirely in the test store."
+  (call-with-external-store (lambda (store) exp ...)))
+
+(define (random-seed)
+  (or (and=> (getenv "GUIX_TESTS_RANDOM_SEED")
+             number->string)
+      (logxor (getpid) (car (gettimeofday)))))
+
 (define %seed
-  (seed->random-state (logxor (getpid) (car (gettimeofday)))))
+  (let ((seed (random-seed)))
+    (format (current-error-port) "random seed for tests: ~a~%"
+            seed)
+    (seed->random-state seed)))
 
 (define (random-text)
   "Return the hexadecimal representation of a random number."
             (loop (1+ i)))
           bv))))
 
+(define (file=? a b)
+  "Return true if files A and B have the same type and same content."
+  (and (eq? (stat:type (lstat a)) (stat:type (lstat b)))
+       (case (stat:type (lstat a))
+         ((regular)
+          (equal?
+           (call-with-input-file a get-bytevector-all)
+           (call-with-input-file b get-bytevector-all)))
+         ((symlink)
+          (string=? (readlink a) (readlink b)))
+         (else
+          (error "what?" (lstat a))))))
+
+(define (canonical-file? file)
+  "Return #t if FILE is in the store, is read-only, and its mtime is 1."
+  (let ((st (lstat file)))
+    (or (not (string-prefix? (%store-prefix) file))
+        (eq? 'symlink (stat:type st))
+        (and (= 1 (stat:mtime st))
+             (zero? (logand #o222 (stat:mode st)))))))
+
 (define (network-reachable?)
   "Return true if we can reach the Internet."
   (false-if-exception (getaddrinfo "www.gnu.org" "80" AI_NUMERICSERV)))
@@ -93,27 +215,64 @@ given by REPLACEMENT."
       (lambda () body ...)
       (lambda () (module-set! m 'proc original)))))
 
+(define-syntax-rule (test-assertm name exp)
+  "Like 'test-assert', but EXP is a monadic value.  A new connection to the
+store is opened."
+  (test-assert name
+    (let ((store (open-connection-for-tests)))
+      (dynamic-wind
+        (const #t)
+        (lambda ()
+          (run-with-store store exp
+                          #:guile-for-build (%guile-for-build)))
+        (lambda ()
+          (close-connection store))))))
+
+(define-syntax-rule (test-equalm name value exp)
+  "Like 'test-equal', but EXP is a monadic value.  A new connection to the
+store is opened."
+  (test-equal name
+    value
+    (with-store store
+      (run-with-store store exp
+                      #:guile-for-build (%guile-for-build)))))
+
+(define-syntax-rule (with-environment-variable variable value body ...)
+  "Run BODY with VARIABLE set to VALUE."
+  (let ((orig (getenv variable)))
+    (dynamic-wind
+      (lambda ()
+        (setenv variable value))
+      (lambda ()
+        body ...)
+      (lambda ()
+        (if orig
+            (setenv variable orig)
+            (unsetenv variable))))))
+
 \f
 ;;;
 ;;; Narinfo files, as used by the substituter.
 ;;;
 
 (define* (derivation-narinfo drv #:key (nar "example.nar")
-                             (sha256 (make-bytevector 32 0)))
-  "Return the contents of the narinfo corresponding to DRV; NAR should be the
-file name of the archive containing the substitute for DRV, and SHA256 is the
-expected hash."
+                             (sha256 (make-bytevector 32 0))
+                             (references '()))
+  "Return the contents of the narinfo corresponding to DRV, with the specified
+REFERENCES (a list of store items); NAR should be the file name of the archive
+containing the substitute for DRV, and SHA256 is the expected hash."
   (format #f "StorePath: ~a
 URL: ~a
 Compression: none
 NarSize: 1234
 NarHash: sha256:~a
-References: 
+References: ~a
 System: ~a
 Deriver: ~a~%"
           (derivation->output-path drv)       ; StorePath
           nar                                 ; URL
           (bytevector->nix-base32-string sha256)  ; NarHash
+          (string-join (map basename references)) ; References
           (derivation-system drv)             ; System
           (basename
            (derivation-file-name drv))))      ; Deriver
@@ -124,7 +283,9 @@ Deriver: ~a~%"
           (compose uri-path string->uri))))
 
 (define* (call-with-derivation-narinfo drv thunk
-                                       #:key (sha256 (make-bytevector 32 0)))
+                                       #:key
+                                       (sha256 (make-bytevector 32 0))
+                                       (references '()))
   "Call THUNK in a context where fake substituter data, as read by 'guix
 substitute', has been installed for DRV.  SHA256 is the hash of the
 expected output of DRV."
@@ -141,27 +302,36 @@ expected output of DRV."
                     (%store-prefix))))
         (call-with-output-file narinfo
           (lambda (p)
-            (display (derivation-narinfo drv #:sha256 sha256) p))))
+            (display (derivation-narinfo drv #:sha256 sha256
+                                         #:references references)
+                     p))))
       thunk
       (lambda ()
         (delete-file narinfo)
         (delete-file info)))))
 
 (define-syntax with-derivation-narinfo
-  (syntax-rules (sha256 =>)
+  (syntax-rules (sha256 references =>)
     "Evaluate BODY in a context where DRV looks substitutable from the
 substituter's viewpoint."
-    ((_ drv (sha256 => hash) body ...)
+    ((_ drv (sha256 => hash) (references => refs) body ...)
      (call-with-derivation-narinfo drv
        (lambda () body ...)
-       #:sha256 hash))
+       #:sha256 hash
+       #:references refs))
+    ((_ drv (sha256 => hash) body ...)
+     (with-derivation-narinfo drv
+       (sha256 => hash) (references => '())
+       body ...))
     ((_ drv body ...)
      (call-with-derivation-narinfo drv
        (lambda ()
          body ...)))))
 
 (define* (call-with-derivation-substitute drv contents thunk
-                                          #:key sha256)
+                                          #:key
+                                          sha256
+                                          (references '()))
   "Call THUNK in a context where a substitute for DRV has been installed,
 using CONTENTS, a string, as its contents.  If SHA256 is true, use it as the
 expected hash of the substitute; otherwise use the hash of the nar containing
@@ -181,7 +351,8 @@ CONTENTS."
         ;; Create fake substituter data, to be read by 'guix substitute'.
         (call-with-derivation-narinfo drv
           thunk
-          #:sha256 (or sha256 hash))))
+          #:sha256 (or sha256 hash)
+          #:references references)))
     (lambda ()
       (delete-file (string-append dir "/example.out"))
       (delete-file (string-append dir "/example.nar")))))
@@ -198,13 +369,18 @@ all included."
   (> (string-length shebang) 128))
 
 (define-syntax with-derivation-substitute
-  (syntax-rules (sha256 =>)
+  (syntax-rules (sha256 references =>)
     "Evaluate BODY in a context where DRV is substitutable with the given
 CONTENTS."
-    ((_ drv contents (sha256 => hash) body ...)
+    ((_ drv contents (sha256 => hash) (references => refs) body ...)
      (call-with-derivation-substitute drv contents
        (lambda () body ...)
-       #:sha256 hash))
+       #:sha256 hash
+       #:references refs))
+    ((_ drv contents (sha256 => hash) body ...)
+     (with-derivation-substitute drv contents
+       (sha256 => hash) (references => '())
+       body ...))
     ((_ drv contents body ...)
      (call-with-derivation-substitute drv contents
        (lambda ()
@@ -213,11 +389,49 @@ CONTENTS."
 (define-syntax-rule (dummy-package name* extra-fields ...)
   "Return a \"dummy\" package called NAME*, with all its compulsory fields
 initialized with default values, and with EXTRA-FIELDS set as specified."
-  (package extra-fields ...
-           (name name*) (version "0") (source #f)
-           (build-system gnu-build-system)
-           (synopsis #f) (description #f)
-           (home-page #f) (license #f)))
+  (let ((p (package
+             (name name*) (version "0") (source #f)
+             (build-system gnu-build-system)
+             (synopsis #f) (description #f)
+             (home-page #f) (license #f))))
+    (package (inherit p) extra-fields ...)))
+
+(define-syntax-rule (dummy-origin extra-fields ...)
+  "Return a \"dummy\" origin, with all its compulsory fields initialized with
+default values, and with EXTRA-FIELDS set as specified."
+  (let ((o (origin (method #f) (uri "http://www.example.com")
+                   (sha256 (base32 (make-string 52 #\x))))))
+    (origin (inherit o) extra-fields ...)))
+
+(define gnu-make-for-tests
+  ;; This is a variant of 'gnu-make-boot0' that can be built with minimal
+  ;; resources.
+  (package-with-bootstrap-guile
+   (package
+     (inherit gnu-make)
+     (name "make-test-boot0")
+     (arguments
+      `(#:guile ,%bootstrap-guile
+        #:implicit-inputs? #f
+        #:tests? #f                               ;cannot run "make check"
+        ,@(substitute-keyword-arguments (package-arguments gnu-make)
+            ((#:configure-flags flags ''())
+             ;; As in 'gnu-make-boot0', work around a 'config.status' defect.
+             `(cons "--disable-dependency-tracking" ,flags))
+            ((#:phases phases)
+             `(modify-phases ,phases
+                (replace 'build
+                  (lambda _
+                    (invoke "./build.sh")
+                    #t))
+                (replace 'install
+                  (lambda* (#:key outputs #:allow-other-keys)
+                    (let* ((out (assoc-ref outputs "out"))
+                           (bin (string-append out "/bin")))
+                      (install-file "make" bin)
+                      #t))))))))
+     (native-inputs '())                          ;no need for 'pkg-config'
+     (inputs %bootstrap-inputs-for-tests))))
 
 ;; Local Variables:
 ;; eval: (put 'call-with-derivation-narinfo 'scheme-indent-function 1)