aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012-2015, 2018-2019, 2021, 2023 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
;;;
;;; 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 (tests builders)
  #:use-module (guix download)
  #:use-module (guix git-download)
  #:use-module (guix build-system)
  #:use-module (guix build-system gnu)
  #:use-module (guix build gnu-build-system)
  #:use-module (guix build utils)
  #:use-module (guix build-system python)
  #:use-module (guix store)
  #:use-module (guix monads)
  #:use-module (guix utils)
  #:use-module (guix base32)
  #:use-module (guix derivations)
  #:use-module (gcrypt hash)
  #:use-module ((guix hash) #:select (file-hash*))
  #:use-module (guix tests)
  #:use-module (guix tests git)
  #:use-module (guix packages)
  #:use-module (gnu packages bootstrap)
  #:use-module ((ice-9 ftw) #:select (scandir))
  #:use-module (ice-9 match)
  #:use-module (ice-9 textual-ports)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-64))

;; Test the higher-level builders.

(define %store
  (open-connection-for-tests))

(define url-fetch*
  (store-lower url-fetch))

;; Globally disable grafts because they can trigger early builds.
(%graft? #f)


(test-begin "builders")

(unless (network-reachable?) (test-skip 1))
(test-assert "url-fetch"
  (let* ((url      '("http://ftp.gnu.org/gnu/hello/hello-2.8.tar.gz"
                     "ftp://ftp.gnu.org/gnu/hello/hello-2.8.tar.gz"))
         (hash     (nix-base32-string->bytevector
                    "0wqd8sjmxfskrflaxywc7gqw7sfawrfvdxd9skxawzfgyy0pzdz6"))
         (drv      (url-fetch* %store url 'sha256 hash
                               #:guile %bootstrap-guile))
         (out-path (derivation->output-path drv)))
    (and (build-derivations %store (list drv))
         (file-exists? out-path)
         (valid-path? %store out-path))))

(test-assert "url-fetch, file"
  (let* ((file (search-path %load-path "guix.scm"))
         (hash (call-with-input-file file port-sha256))
         (out  (url-fetch* %store file 'sha256 hash)))
    (and (file-exists? out)
         (valid-path? %store out))))

(test-assert "url-fetch, file URI"
  (let* ((file (search-path %load-path "guix.scm"))
         (hash (call-with-input-file file port-sha256))
         (out  (url-fetch* %store
                           (string-append "file://" (canonicalize-path file))
                           'sha256 hash)))
    (and (file-exists? out)
         (valid-path? %store out))))

(test-equal "git-fetch, file URI"
  '("." ".." "a.txt" "b.scm")
  (let ((nonce (random-text)))
    (with-temporary-git-repository directory
        `((add "a.txt" ,nonce)
          (add "b.scm" "#t")
          (commit "Commit.")
          (tag "v1.0.0" "The tag."))
      (run-with-store %store
        (mlet* %store-monad ((hash
                              -> (file-hash* directory
                                             #:algorithm (hash-algorithm sha256)
                                             #:recursive? #t))
                             (drv (git-fetch
                                   (git-reference
                                    (url (string-append "file://" directory))
                                    (commit "v1.0.0"))
                                   'sha256 hash
                                   "git-fetch-test")))
          (mbegin %store-monad
            (built-derivations (list drv))
            (return (scandir (derivation->output-path drv)))))))))

(test-assert "gnu-build-system"
  (build-system? gnu-build-system))

(define unpack (assoc-ref %standard-phases 'unpack))

(define compressors '(("gzip"  . "gz")
                      ("xz"    . "xz")
                      ("bzip2" . "bz2")
                      (#f      . #f)))

(for-each
 (match-lambda
   ((comp . ext)

    (unless (network-reachable?) (test-skip 1)) ;for bootstrap binaries
    (test-equal (string-append "gnu-build-system unpack phase, "
                               "single file (compression: "
                               (if comp comp "None") ")")
      "expected text"
      (let*-values
          (((name) "test")
           ((compressed-name) (if ext
                                  (string-append name "." ext)
                                  name))
           ((file hash) (test-file %store compressed-name "expected text")))
        (call-with-temporary-directory
         (lambda (dir)
           (with-directory-excursion dir
             (unpack #:source file)
             (call-with-input-file name get-string-all))))))))
 compressors)


;;;
;;; Test the sanity-check phase of the Python build system.
;;;

(define* (make-python-dummy name #:key (setup-py-extra "")
                            (init-py "") (use-setuptools? #t))
  (dummy-package (string-append "python-dummy-" name)
    (version "0.1")
    (build-system python-build-system)
    (arguments
     `(#:tests? #f
       #:use-setuptools? ,use-setuptools?
       #:phases
       (modify-phases %standard-phases
         (replace 'unpack
           (lambda _
             (mkdir-p "dummy")
             (with-output-to-file "dummy/__init__.py"
               (lambda _
                 (display ,init-py)))
             (with-output-to-file "setup.py"
               (lambda _
                 (format #t "\
~a
setup(
     name='dummy-~a',
     version='0.1',
     packages=['dummy'],
     ~a
     )"
                         (if ,use-setuptools?
                             "from setuptools import setup"
                             "from distutils.core import setup")
                         ,name ,setup-py-extra))))))))))

(define python-dummy-ok
  (make-python-dummy "ok"))

;; distutil won't install any metadata, so make sure our script does not fail
;; on a otherwise fine package.
(define python-dummy-no-setuptools
  (make-python-dummy
   "no-setuptools" #:use-setuptools? #f))

(define python-dummy-fail-requirements
  (make-python-dummy "fail-requirements"
                     #:setup-py-extra "install_requires=['nonexistent'],"))

(define python-dummy-fail-import
  (make-python-dummy "fail-import" #:init-py "import nonexistent"))

(define python-dummy-fail-console-script
  (make-python-dummy "fail-console-script"
                     #:setup-py-extra (string-append "entry_points={'console_scripts': "
                                                     "['broken = dummy:nonexistent']},")))

(define (check-build-success store p)
  (unless store (test-skip 1))
  (test-assert (string-append "python-build-system: " (package-name p))
    (let* ((drv (package-derivation store p)))
      (build-derivations store (list drv)))))

(define (check-build-failure store p)
  (unless store (test-skip 1))
  (test-assert (string-append "python-build-system: " (package-name p))
    (let ((drv (package-derivation store p)))
      (guard (c ((store-protocol-error? c)
                 (pk 'failure c #t)))             ;good!
        (build-derivations store (list drv))
        #f))))                                    ;bad: it should have failed

(with-external-store store
  (for-each (lambda (p) (check-build-success store p))
            (list
             python-dummy-ok
             python-dummy-no-setuptools))
  (for-each (lambda (p) (check-build-failure store p))
            (list
             python-dummy-fail-requirements
             python-dummy-fail-import
             python-dummy-fail-console-script)))

(test-end "builders")
EXEC_PATH=" #$git "/libexec/git-core")) #:user #$user #:group #$group #:log-file #$main-log-file)) (stop #~(make-kill-destructor)) (actions (list (shepherd-configuration-action config-file)))) ,(shepherd-service (documentation "Run Cuirass web interface.") (provision '(cuirass-web)) (requirement '(user-processes cuirass)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "web" "--database" #$database "--listen" #$host "--port" #$(number->string port) #$@(if parameters (list (string-append "--parameters=" parameters)) '()) #$@web-extra-options) #:user #$user #:group #$group #:log-file #$web-log-file)) (stop #~(make-kill-destructor))) ,@(if remote-server (match-record remote-server <cuirass-remote-server-configuration> (backend-port publish-port log-file log-expiry cache publish? trigger-url public-key private-key) (list (shepherd-service (documentation "Run Cuirass remote build server.") (provision '(cuirass-remote-server)) (requirement '(user-processes avahi-daemon cuirass)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "remote-server" (string-append "--database=" #$database) (string-append "--cache=" #$cache) (string-append "--user=" #$user) (string-append "--log-expiry=" #$(number->string log-expiry) "s") #$@(if backend-port (list (string-append "--backend-port=" (number->string backend-port))) '()) #$@(if publish-port (list (string-append "--publish-port=" (number->string publish-port))) '()) #$@(if parameters (list (string-append "--parameters=" parameters)) '()) #$@(if trigger-url (list (string-append "--trigger-substitute-url=" trigger-url)) '()) #$@(if publish? '() (list "--no-publish")) #$@(if public-key (list (string-append "--public-key=" public-key)) '()) #$@(if private-key (list (string-append "--private-key=" private-key)) '())) #:log-file #$log-file)) (stop #~(make-kill-destructor))))) '())))) (define (cuirass-account config) "Return the user accounts and user groups for CONFIG." (let ((cuirass-user (cuirass-configuration-user config)) (cuirass-group (cuirass-configuration-group config))) (list (user-group (name cuirass-group) (system? #t)) (user-account (name cuirass-user) (group cuirass-group) (system? #t) (comment "Cuirass privilege separation user") (home-directory (string-append "/var/lib/" cuirass-user)) (shell (file-append shadow "/sbin/nologin")))))) (define (cuirass-postgresql-role config) (let ((user (cuirass-configuration-user config))) (list (postgresql-role (name user) (create-database? #t))))) (define (cuirass-activation config) "Return the activation code for CONFIG." (let* ((cache (cuirass-configuration-cache-directory config)) (remote-server (cuirass-configuration-remote-server config)) (remote-cache (and remote-server (cuirass-remote-server-configuration-cache remote-server))) (user (cuirass-configuration-user config)) ;; RUNSTATEDIR contains the "bridge" Unix-domain socket that 'cuirass ;; web' connects to to communicate with 'cuirass register'. (runstatedir "/var/run/cuirass") (log "/var/log/cuirass") (profile (string-append "/var/guix/profiles/per-user/" user)) (roots (string-append profile "/cuirass")) (group (cuirass-configuration-group config))) (with-imported-modules '((guix build utils)) #~(begin (use-modules (guix build utils)) (mkdir-p #$cache) (mkdir-p #$log) (mkdir-p #$roots) (mkdir-p #$runstatedir) (when #$remote-cache (mkdir-p #$remote-cache)) (let ((uid (passwd:uid (getpw #$user))) (gid (group:gid (getgr #$group)))) (chown #$cache uid gid) (chown #$log uid gid) (chown #$roots uid gid) (chown #$profile uid gid) (chown #$runstatedir uid gid) (chmod #$runstatedir #o700) (when #$remote-cache (chown #$remote-cache uid gid))))))) (define (cuirass-log-rotations config) "Return the list of log rotations that corresponds to CONFIG." (list (log-rotation (files (append (list (cuirass-configuration-log-file config) (cuirass-configuration-web-log-file config)) (let ((server (cuirass-configuration-remote-server config))) (if server (list (cuirass-remote-server-log-file server)) '())))) (frequency 'weekly) (options `("rotate 40" ;worth keeping ,@%default-log-rotation-options))))) (define cuirass-service-type (service-type (name 'cuirass) (extensions (list (service-extension profile-service-type ;for 'info cuirass' (compose list cuirass-configuration-cuirass)) (service-extension rottlog-service-type cuirass-log-rotations) (service-extension activation-service-type cuirass-activation) (service-extension shepherd-root-service-type cuirass-shepherd-service) (service-extension account-service-type cuirass-account) ;; Make sure postgresql and postgresql-role are instantiated. (service-extension postgresql-service-type (const #t)) (service-extension postgresql-role-service-type cuirass-postgresql-role))) (description "Run the Cuirass continuous integration service."))) (define-record-type* <cuirass-remote-worker-configuration> cuirass-remote-worker-configuration make-cuirass-remote-worker-configuration cuirass-remote-worker-configuration? (cuirass cuirass-remote-worker-configuration-cuirass ;file-like (default cuirass)) (workers cuirass-remote-worker-workers ;int (default 1)) (server cuirass-remote-worker-server ;string (default #f)) (systems cuirass-remote-worker-systems ;list (default (list (%current-system)))) (log-file cuirass-remote-worker-log-file ;string (default "/var/log/cuirass-remote-worker.log")) (publish-port cuirass-remote-worker-configuration-publish-port ;int (default 5558)) (substitute-urls cuirass-remote-worker-configuration-substitute-urls (default %default-substitute-urls)) ;list of strings (public-key cuirass-remote-worker-configuration-public-key ;string (default #f)) (private-key cuirass-remote-worker-configuration-private-key ;string (default #f))) (define (cuirass-remote-worker-shepherd-service config) "Return a <shepherd-service> for the Cuirass remote worker service with CONFIG." (match-record config <cuirass-remote-worker-configuration> (cuirass workers server systems log-file publish-port substitute-urls public-key private-key) (list (shepherd-service (documentation "Run Cuirass remote build worker.") (provision '(cuirass-remote-worker)) (requirement '(user-processes avahi-daemon guix-daemon networking)) (start #~(make-forkexec-constructor (list (string-append #$cuirass "/bin/cuirass") "remote-worker" (string-append "--workers=" #$(number->string workers)) #$@(if server (list (string-append "--server=" server)) '()) #$@(if systems (list (string-append "--systems=" (string-join systems ","))) '()) #$@(if publish-port (list (string-append "--publish-port=" (number->string publish-port))) '()) #$@(if substitute-urls (list (string-append "--substitute-urls=" (string-join substitute-urls))) '()) #$@(if public-key (list (string-append "--public-key=" public-key)) '()) #$@(if private-key (list (string-append "--private-key=" private-key)) '())) #:log-file #$log-file)) (stop #~(make-kill-destructor)))))) (define (cuirass-remote-worker-log-rotations config) "Return the list of log rotations that corresponds to CONFIG." (list (log-rotation (files (list (cuirass-remote-worker-log-file config))) (frequency 'weekly) (options `("rotate 4" ;don't keep too many of them ,@%default-log-rotation-options))))) (define cuirass-remote-worker-service-type (service-type (name 'cuirass-remote-worker) (extensions (list (service-extension shepherd-root-service-type cuirass-remote-worker-shepherd-service) (service-extension rottlog-service-type cuirass-remote-worker-log-rotations))) (description "Run the Cuirass remote build worker service.")))