pull: Use ~/.cache/guix/checkouts instead of ~/.cache/guix/pull.
[jackhill/guix/guix.git] / guix / tests.scm
index 16b8cc7..66d60e9 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 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 (gcrypt hash)
   #:use-module (guix build-system gnu)
   #:use-module (gnu packages bootstrap)
   #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-64)
   #:use-module (rnrs bytevectors)
-  #:use-module (rnrs io ports)
+  #: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
+
             mock
+            %test-substitute-urls
+            test-assertm
+            test-equalm
             %substitute-directory
             with-derivation-narinfo
             with-derivation-substitute
 ;;;
 ;;; 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 (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)
          (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)))
@@ -117,27 +176,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
@@ -148,7 +244,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."
@@ -165,27 +263,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
@@ -205,7 +312,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")))))
@@ -222,13 +330,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 ()
@@ -237,18 +350,19 @@ 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."
-  (origin extra-fields ...
-          (method #f) (uri "http://www.example.com")
-          (sha256 (base32 (make-string 52 #\x)))))
+  (let ((o (origin (method #f) (uri "http://www.example.com")
+                   (sha256 (base32 (make-string 52 #\x))))))
+    (origin (inherit o) extra-fields ...)))
 
 ;; Local Variables:
 ;; eval: (put 'call-with-derivation-narinfo 'scheme-indent-function 1)