maav / guix-mirror (public) (License: GPLv3+) (since 2019-11-02) (hash sha1)
Mirror of GNU Guix (https://git.savannah.gnu.org/git/guix.git) with personal branches integrated into master branch.

/guix/tests.scm (3ccf049a7dd358c8464bc3cdd15c71078609c0b3) (16153 bytes) (mode 100644) (type blob)

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 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 (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 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-origin

            gnu-make-for-tests))

;;; Commentary:
;;;
;;; This module provide shared infrastructure for the test suite.  For
;;; internal use only.
;;;
;;; Code:

(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 ((store-error? c)
             (format (current-error-port)
                     "warning: build daemon error: ~s~%" c)
             #f))
    (let ((store (open-connection uri)))
      ;; Make sure we build everything by ourselves.
      (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.
      (%guile-for-build (package-derivation store %bootstrap-guile))

      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
  (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."
  (number->string (random (expt 2 256) %seed) 16))

(define (random-bytevector n)
  "Return a random bytevector of N bytes."
  (let ((bv (make-bytevector n)))
    (let loop ((i 0))
      (if (< i n)
          (begin
            (bytevector-u8-set! bv i (random 256 %seed))
            (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)))

(define-syntax-rule (mock (module proc replacement) body ...)
  "Within BODY, replace the definition of PROC from MODULE with the definition
given by REPLACEMENT."
  (let* ((m (resolve-module 'module))
         (original (module-ref m 'proc)))
    (dynamic-wind
      (lambda () (module-set! m 'proc 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))))))


;;;
;;; Narinfo files, as used by the substituter.
;;;

(define* (derivation-narinfo drv #:key (nar "example.nar")
                             (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: ~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

(define %substitute-directory
  (make-parameter
   (and=> (getenv "GUIX_BINARY_SUBSTITUTE_URL")
          (compose uri-path string->uri))))

(define* (call-with-derivation-narinfo drv thunk
                                       #: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."
  (let* ((output  (derivation->output-path drv))
         (dir     (%substitute-directory))
         (info    (string-append dir "/nix-cache-info"))
         (narinfo (string-append dir "/" (store-path-hash-part output)
                                 ".narinfo")))
    (dynamic-wind
      (lambda ()
        (call-with-output-file info
          (lambda (p)
            (format p "StoreDir: ~a\nWantMassQuery: 0\n"
                    (%store-prefix))))
        (call-with-output-file narinfo
          (lambda (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 references =>)
    "Evaluate BODY in a context where DRV looks substitutable from the
substituter's viewpoint."
    ((_ drv (sha256 => hash) (references => refs) body ...)
     (call-with-derivation-narinfo drv
       (lambda () body ...)
       #: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
                                          (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
CONTENTS."
  (define dir (%substitute-directory))
  (dynamic-wind
    (lambda ()
      (call-with-output-file (string-append dir "/example.out")
        (lambda (port)
          (display contents port)))
      (call-with-output-file (string-append dir "/example.nar")
        (lambda (p)
          (write-file (string-append dir "/example.out") p))))
    (lambda ()
      (let ((hash (call-with-input-file (string-append dir "/example.nar")
                    port-sha256)))
        ;; Create fake substituter data, to be read by 'guix substitute'.
        (call-with-derivation-narinfo drv
          thunk
          #:sha256 (or sha256 hash)
          #:references references)))
    (lambda ()
      (delete-file (string-append dir "/example.out"))
      (delete-file (string-append dir "/example.nar")))))

(define (shebang-too-long?)
  "Return true if the typical shebang in the current store would exceed
Linux's static limit---the BINPRM_BUF_SIZE constant, normally 128 characters
all included."
  (define shebang
    (string-append "#!" (%store-prefix) "/"
                   (make-string 32 #\a)
                   "-bootstrap-binaries-0/bin/bash\0"))

  (> (string-length shebang) 128))

(define-syntax with-derivation-substitute
  (syntax-rules (sha256 references =>)
    "Evaluate BODY in a context where DRV is substitutable with the given
CONTENTS."
    ((_ drv contents (sha256 => hash) (references => refs) body ...)
     (call-with-derivation-substitute drv contents
       (lambda () body ...)
       #: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 ()
         body ...)))))

(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."
  (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)
;; eval: (put 'call-with-derivation-substitute 'scheme-indent-function 2)
;; End:

;;; tests.scm ends here


Mode Type Size Ref File
100644 blob 6139 7f310d2612983845551d0a641b730a269a180a3f .dir-locals.el
100644 blob 2533 e2f745b42a5a1591a93a3e9a7ccfc107a6178eff .gitignore
100644 blob 6179 ee164083c8379d90c6e7ab3b38576266f458f40c .guix-authorizations
100644 blob 182 b852180cf2563ec7b74b93c954de38d77237f23f .guix-channel
100644 blob 4499 146e65184a2f987b0307d0ec7300c93e84110625 .mailmap
100644 blob 472 1e30a74a64f51ec735dcc44ff4dfe5fa4fa13c6f AUTHORS
100644 blob 3273 ef90330cdacb9ecf7dbf38a03cdb490db131a4ad CODE-OF-CONDUCT
100644 blob 35147 94a9ed024d3859793618152ea559a168bbcbb5e2 COPYING
100644 blob 163 d6ea6943261fcae51c095ad39fe59140fc62de22 ChangeLog
100644 blob 749 aaa673fc93b0bb74feca4783ae427b9ea1b604ea HACKING
100644 blob 33109 a75d9c1ffc4237c0478b62a235b9b521fd840517 Makefile.am
100644 blob 359348 bb1de1e93802064ff44392db56d05cd0a11fcc15 NEWS
100644 blob 5260 5e9069f80f58d3946cdd588f30919a177eaccb55 README
100644 blob 3237 2475cb637ceb6eb43f54d080c56e5793041b76e5 ROADMAP
100644 blob 2381 af7afd3576f2e6aa5cbafc3c6354bbab1ae00774 THANKS
100644 blob 4360 f854f7fa98e09c7b512f3efb702c290b615186a0 TODO
100755 blob 906 a47269d87f1d6fd27bbaf634ac7439b38b32cca3 bootstrap
040000 tree - b15b9ede344760715e240528bc322c7b0194bbe7 build-aux
100644 blob 4808 50ead355a81edebf5c9419bd76a1dd69e85f5adf config-daemon.ac
100644 blob 8760 6861112eafaed85e107f8976f12e0ddb795571b7 configure.ac
100644 blob 339545 d234c4ec8668cead20279d903589d29c513b4cb6 d3.v3.js
040000 tree - ad0dbfb1b2956f33070d044c3cb173030dd3c5f1 doc
040000 tree - 8cc2bd0b87d4ef774820c72703a0b3ae914e8601 etc
100644 blob 5289 f139531ef3ecf56a790ae73934e2d91016c1aba4 gnu.scm
040000 tree - 34d1107337432fed7fa3f18c9affd932681e9b6a gnu
100644 blob 4207 ad8279395d8eb1fe5a836d54ec563a4577f4d135 graph.js
100644 blob 1357 8753c21e423f880e7a6d9f7f6f6ff1139f8b7254 guix.scm
040000 tree - 9c3a3e887916002138b68277e856e1f0e6416659 guix
040000 tree - 8df9aaabfb400159e2559fd4331fb861cb0a5adc m4
040000 tree - 84cedf0076fb8362febdffa59ba83b63e8c3f9e8 nix
040000 tree - 8dac6dd305591d733ef087c35eee3b3acb1daee2 po
040000 tree - 8c4db11917d51c4d71a841813cf8951000b76687 scripts
040000 tree - 867e6957c2eed1c47764e42093bb26ae37b221e3 tests
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/maav/guix-mirror

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/maav/guix-mirror

Clone this repository using git:
git clone git://git.rocketgit.com/user/maav/guix-mirror

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main