code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012, 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 (test-nar)
  #:use-module (guix tests)
  #:use-module (guix nar)
  #:use-module (guix serialization)
  #:use-module (guix store)
  #:use-module ((gcrypt hash)
                #:select (open-sha256-port open-sha256-input-port))
  #:use-module ((guix packages)
                #:select (base32))
  #:use-module ((guix build utils)
                #:select (find-files))
  #:use-module (rnrs bytevectors)
  #:use-module (rnrs io ports)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-35)
  #:use-module (srfi srfi-64)
  #:use-module (ice-9 ftw)
  #:use-module (ice-9 regex)
  #:use-module ((ice-9 control) #:select (let/ec))
  #:use-module (ice-9 match))

;; Test the (guix nar) module.

;;;
;;; File system testing tools, initially contributed to Guile, then libchop.
;;;

(define (random-file-size)
  (define %average (* 1024 512))                  ; 512 KiB
  (define %stddev  (* 1024 64))                   ; 64 KiB
  (inexact->exact
   (max 0 (round (+ %average (* %stddev (random:normal)))))))

(define (make-file-tree dir tree)
  "Make file system TREE at DIR."
  (let loop ((dir  dir)
             (tree tree))
    (define (scope file)
      (string-append dir "/" file))

    (match tree
      (('directory name (body ...))
       (mkdir (scope name))
       (for-each (cute loop (scope name) <>) body))
      (('directory name (? integer? mode) (body ...))
       (mkdir (scope name))
       (for-each (cute loop (scope name) <>) body)
       (chmod (scope name) mode))
      ((file)
       (populate-file (scope file) (random-file-size)))
      ((file (? integer? mode))
       (populate-file (scope file) (random-file-size))
       (chmod (scope file) mode))
      ((from '-> to)
       (symlink to (scope from))))))

(define (delete-file-tree dir tree)
  "Delete file TREE from DIR."
  (let loop ((dir  dir)
             (tree tree))
    (define (scope file)
      (string-append dir "/" file))

    (match tree
      (('directory name (body ...))
       (for-each (cute loop (scope name) <>) body)
       (rmdir (scope name)))
      (('directory name (? integer? mode) (body ...))
       (chmod (scope name) #o755)          ; make sure it can be entered
       (for-each (cute loop (scope name) <>) body)
       (rmdir (scope name)))
      ((from '-> _)
       (delete-file (scope from)))
      ((file _ ...)
       (delete-file (scope file))))))

(define-syntax-rule (with-file-tree dir tree body ...)
  (dynamic-wind
    (lambda ()
      (make-file-tree dir 'tree))
    (lambda ()
      body ...)
    (lambda ()
      (delete-file-tree dir 'tree))))

(define (file-tree-equal? input output)
  "Return #t if the file trees at INPUT and OUTPUT are equal."
  (define strip
    (cute string-drop <> (string-length input)))
  (define sibling
    (compose (cut string-append output <>) strip))

  (file-system-fold (const #t)
                    (lambda (name stat result)    ; leaf
                      (and result
                           (file=? name (sibling name))))
                    (lambda (name stat result)    ; down
                      result)
                    (lambda (name stat result)    ; up
                      result)
                    (const #f)                    ; skip
                    (lambda (name stat errno result)
                      (pk 'error name stat errno)
                      #f)
                    #t                            ; result
                    input
                    lstat))

(define (populate-file file size)
  (call-with-output-file file
    (lambda (p)
      (put-bytevector p (random-bytevector size)))))

(define (rm-rf dir)
  (file-system-fold (const #t)                    ; enter?
                    (lambda (file stat result)    ; leaf
                      (unless (eq? 'symlink (stat:type stat))
                        (chmod file #o644))
                      (delete-file file))
                    (lambda (dir stat result)     ; down
                      (chmod dir #o755))
                    (lambda (dir stat result)     ; up
                      (rmdir dir))
                    (const #t)                    ; skip
                    (const #t)                    ; error
                    #t
                    dir
                    lstat))

(define %test-dir
  ;; An output directory under $top_builddir.
  (string-append (dirname (search-path %load-path "pre-inst-env"))
                 "/test-nar-" (number->string (getpid))))

(test-begin "nar")

(test-assert "write-file-tree + restore-file"
  (let* ((file1  (search-path %load-path "guix.scm"))
         (file2  (search-path %load-path "guix/base32.scm"))
         (file3  "#!/bin/something")
         (output (string-append %test-dir "/output")))
    (dynamic-wind
      (lambda () #t)
      (lambda ()
        (define-values (port get-bytevector)
          (open-bytevector-output-port))
        (write-file-tree "root" port
                         #:file-type+size
                         (match-lambda
                           ("root"
                            (values 'directory 0))
                           ("root/foo"
                            (values 'regular (stat:size (stat file1))))
                           ("root/lnk"
                            (values 'symlink 0))
                           ("root/dir"
                            (values 'directory 0))
                           ("root/dir/bar"
                            (values 'regular (stat:size (stat file2))))
                           ("root/dir/exe"
                            (values 'executable (string-length file3))))
                         #:file-port
                         (match-lambda
                           ("root/foo" (open-input-file file1))
                           ("root/dir/bar" (open-input-file file2))
                           ("root/dir/exe" (open-input-string file3)))
                         #:symlink-target
                         (match-lambda
                           ("root/lnk" "foo"))
                         #:directory-entries
                         (match-lambda
                           ("root" '("foo" "dir" "lnk"))
                           ("root/dir" '("bar" "exe"))))
        (close-port port)

        (rm-rf %test-dir)
        (mkdir %test-dir)
        (restore-file (open-bytevector-input-port (get-bytevector))
                      output)
        (and (file=? (string-append output "/foo") file1)
             (string=? (readlink (string-append output "/lnk"))
                       "foo")
             (file=? (string-append output "/dir/bar") file2)
             (string=? (call-with-input-file (string-append output "/dir/exe")
                         get-string-all)
                       file3)
             (> (logand (stat:mode (lstat (string-append output "/dir/exe")))
                        #o100)
                0)
             (equal? '("." ".." "bar" "exe")
                     (scandir (string-append output "/dir")))
             (equal? '("." ".." "dir" "foo" "lnk")
                     (scandir output))))
      (lambda ()
        (false-if-exception (rm-rf %test-dir))))))

(test-equal "write-file-tree + fold-archive"
  '(("R" directory #f)
    ("R/dir" directory #f)
    ("R/dir/exe" executable "1234")
    ("R/dir" directory-complete #f)
    ("R/foo" regular "abcdefg")
    ("R/lnk" symlink "foo")
    ("R" directory-complete #f))

  (let ()
    (define-values (port get-bytevector)
      (open-bytevector-output-port))
    (write-file-tree "root" port
                     #:file-type+size
                     (match-lambda
                       ("root"
                        (values 'directory 0))
                       ("root/foo"
                        (values 'regular 7))
                       ("root/lnk"
                        (values 'symlink 0))
                       ("root/dir"
                        (values 'directory 0))
                       ("root/dir/exe"
                        (values 'executable 4)))
                     #:file-port
                     (match-lambda
                       ("root/foo" (open-input-string "abcdefg"))
                       ("root/dir/exe" (open-input-string "1234")))
                     #:symlink-target
                     (match-lambda
                       ("root/lnk" "foo"))
                     #:directory-entries
                     (match-lambda
                       ("root" '("foo" "dir" "lnk"))
                       ("root/dir" '("exe"))))
    (close-port port)

    (reverse
     (fold-archive (lambda (file type contents result)
                     (let ((contents (if (memq type '(regular executable))
                                         (utf8->string
                                          (get-bytevector-n (car contents)
                                                            (cdr contents)))
                                         contents)))
                       (cons `(,file ,type ,contents)
                             result)))
                   '()
                   (open-bytevector-input-port (get-bytevector))
                   "R"))))

(test-equal "write-file-tree + fold-archive, flat file"
  '(("R" regular "abcdefg"))

  (let ()
    (define-values (port get-bytevector)
      (open-bytevector-output-port))
    (write-file-tree "root" port
                     #:file-type+size
                     (match-lambda
                       ("root" (values 'regular 7)))
                     #:file-port
                     (match-lambda
                       ("root" (open-input-string "abcdefg"))))
    (close-port port)

    (reverse
     (fold-archive (lambda (file type contents result)
                     (let ((contents (utf8->string
                                      (get-bytevector-n (car contents)
                                                        (cdr contents)))))
                       (cons `(,file ,type ,contents) result)))
                   '()
                   (open-bytevector-input-port (get-bytevector))
                   "R"))))

(test-assert "write-file supports non-file output ports"
  (let ((input  (string-append (dirname (search-path %load-path "guix.scm"))
                               "/guix"))
        (output (%make-void-port "w")))
    (write-file input output)
    #t))

(test-equal "write-file puts file in C locale collation order"
  (base32 "0sfn5r63k88w9ls4hivnvscg82bqg8a0w7955l6xlk4g96jnb2z3")
  (let ((input (string-append %test-dir ".input")))
    (dynamic-wind
      (lambda ()
        (define (touch file)
          (call-with-output-file (string-append input "/" file)
            (const #t)))

        (mkdir input)
        (touch "B")
        (touch "Z")
        (touch "a")
        (symlink "B" (string-append input "/z")))
      (lambda ()
        (let-values (((port get-hash) (open-sha256-port)))
          (write-file input port)
          (close-port port)
          (get-hash)))
      (lambda ()
        (rm-rf input)))))

(test-equal "restore-file with incomplete input"
  (string-append %test-dir "/foo")
  (let ((port (open-bytevector-input-port #vu8(1 2 3))))
    (guard (c ((nar-error? c)
               (and (eq? port (nar-error-port c))
                    (nar-error-file c))))
      (restore-file port (string-append %test-dir "/foo"))
      #f)))

(test-assert "write-file + restore-file"
  (let* ((input  (string-append (dirname (search-path %load-path "guix.scm"))
                                "/guix"))
         (output %test-dir)
         (nar    (string-append output ".nar")))
    (dynamic-wind
      (lambda () #t)
      (lambda ()
        (call-with-output-file nar
          (cut write-file input <>))
        (call-with-input-file nar
          (cut restore-file <> output))
        (file-tree-equal? input output))
      (lambda ()
        (false-if-exception (delete-file nar))
        (false-if-exception (rm-rf output))))))

(test-assert "write-file + restore-file with symlinks"
  (let ((input (string-append %test-dir ".input")))
    (mkdir input)
    (dynamic-wind
      (const #t)
      (lambda ()
        (with-file-tree input
            (directory "root"
                       (("reg") ("exe" #o777) ("sym" -> "reg")))
          (let* ((output %test-dir)
                 (nar    (string-append output ".nar")))
            (dynamic-wind
              (lambda () #t)
              (lambda ()
                (call-with-output-file nar
                  (cut write-file input <>))
                (call-with-input-file nar
                  (cut restore-file <> output))

                (and (file-tree-equal? input output)
                     (every (lambda (file)
                              (canonical-file?
                               (string-append output "/" file)))
                            '("root" "root/reg" "root/exe"))))
              (lambda ()
                (false-if-exception (delete-file nar))
                (false-if-exception (rm-rf output)))))))
      (lambda ()
        (rmdir input)))))

(test-assert "write-file #:select? + restore-file"
  (let ((input (string-append %test-dir ".input")))
    (mkdir input)
    (dynamic-wind
      (const #t)
      (lambda ()
        (with-file-tree input
            (directory "root"
                       ((directory "a" (("x") ("y") ("z")))
                        ("b") ("c") ("d" -> "b")))
          (let* ((output %test-dir)
                 (nar    (string-append output ".nar")))
            (dynamic-wind
              (lambda () #t)
              (lambda ()
                (call-with-output-file nar
                  (lambda (port)
                    (write-file input port
                                #:select?
                                (lambda (file stat)
                                  (and (not (string=? (basename file)
                                                      "a"))
                                       (not (eq? (stat:type stat)
                                                 'symlink)))))))
                (call-with-input-file nar
                  (cut restore-file <> output))

                ;; Make sure "a" and "d" have been filtered out.
                (and (not (file-exists? (string-append output "/root/a")))
                     (file=? (string-append output "/root/b")
                             (string-append input "/root/b"))
                     (file=? (string-append output "/root/c")
                             (string-append input "/root/c"))
                     (not (file-exists? (string-append output "/root/d")))))
              (lambda ()
                (false-if-exception (delete-file nar))
                (false-if-exception (rm-rf output)))))))
      (lambda ()
        (rmdir input)))))

(test-eq "restore-file with non-UTF8 locale"     ;<https://bugs.gnu.org/33603>
  'encoding-error
  (let* ((file   (search-path %load-path "guix.scm"))
         (output (string-append %test-dir "/output"))
         (locale (setlocale LC_ALL "C")))
    (dynamic-wind
      (lambda () #t)
      (lambda ()
        (define-values (port get-bytevector)
          (open-bytevector-output-port))

        (write-file-tree "root" port
                         #:file-type+size
                         (match-lambda
                           ("root"   (values 'directory 0))
                           ("root/λ" (values 'regular 0)))
                         #:file-port (const (%make-void-port "r"))
                         #:symlink-target (const #f)
                         #:directory-entries (const '("λ")))
        (close-port port)

        (mkdir %test-dir)
        (catch 'encoding-error
          (lambda ()
            ;; This show throw to 'encoding-error.
            (restore-file (open-bytevector-input-port (get-bytevector))
                          output)
            (scandir output))
          (lambda args
            'encoding-error)))
      (lambda ()
        (false-if-exception (rm-rf %test-dir))
        (setlocale LC_ALL locale)))))

;; XXX: Tell the 'deduplicate' procedure what store we're actually using.
(setenv "NIX_STORE" (%store-prefix))

(test-assert "restore-file-set (signed, valid)"
  (with-store store
    (let* ((texts (unfold (cut >= <> 10)
                          (lambda _ (random-text))
                          1+
                          0))
           (files (map (cut add-text-to-store store "text" <>) texts))
           (dump  (call-with-bytevector-output-port
                   (cut export-paths store files <>))))
      (delete-paths store files)
      (and (every (negate file-exists?) files)
           (let* ((source   (open-bytevector-input-port dump))
                  (imported (restore-file-set source)))
             (and (equal? imported files)
                  (every (lambda (file)
                           (and (file-exists? file)
                                (valid-path? store file)))
                         files)
                  (equal? texts
                          (map (lambda (file)
                                 (call-with-input-file file
                                   get-string-all))
                               files))
                  (every canonical-file? files)))))))

(test-assert "restore-file-set with directories (signed, valid)"
  ;; <https://bugs.gnu.org/33361> describes a bug whereby directories
  ;; containing files subject to deduplication were not canonicalized--i.e.,
  ;; their mtime and permissions were not reset.  Ensure that this bug is
  ;; gone.
  (with-store store
    (let* ((text1 (random-text))
           (text2 (random-text))
           (tree  `("tree" directory
                    ("a" regular (data ,text1))
                    ("b" directory
                     ("c" regular (data ,text2))
                     ("d" regular (data ,text1))))) ;duplicate
           (file  (add-file-tree-to-store store tree))
           (dump  (call-with-bytevector-output-port
                   (cute export-paths store (list file) <>))))
      (delete-paths store (list file))
      (and (not (file-exists? file))
           (let* ((source   (open-bytevector-input-port dump))
                  (imported (restore-file-set source)))
             (and (equal? imported (list file))
                  (file-exists? file)
                  (valid-path? store file)
                  (string=? text1
                            (call-with-input-file (string-append file "/a")
                              get-string-all))
                  (string=? text2
                            (call-with-input-file
                                (string-append file "/b/c")
                              get-string-all))
                  (= (stat:ino (stat (string-append file "/a"))) ;deduplication
                     (stat:ino (stat (string-append file "/b/d"))))
                  (every canonical-file?
                         (find-files file #:directories? #t))))))))

(test-assert "restore-file-set (missing signature)"
  (let/ec return
    (with-store store
      (let* ((file  (add-text-to-store store "foo" (random-text)))
             (dump  (call-with-bytevector-output-port
                     (cute export-paths store (list file) <>
                           #:sign? #f))))
        (delete-paths store (list file))
        (and (not (file-exists? file))
             (let ((source (open-bytevector-input-port dump)))
               (guard (c ((nar-signature-error? c)
                          (let ((message (condition-message c))
                                (port    (nar-error-port c)))
                            (return
                             (and (string-match "lacks.*signature" message)
                                  (string=? file (nar-error-file c))
                                  (eq? source port))))))
                 (restore-file-set source))
               #f))))))

(test-assert "restore-file-set (corrupt)"
  (let/ec return
    (with-store store
      (let* ((file  (add-text-to-store store "foo"
                                       (random-text)))
             (dump  (call-with-bytevector-output-port
                     (cute export-paths store (list file) <>))))
        (delete-paths store (list file))

        ;; Flip a byte in the file contents.
        (let* ((index 120)
               (byte  (bytevector-u8-ref dump index)))
          (bytevector-u8-set! dump index (logxor #xff byte)))

        (and (not (file-exists? file))
             (let ((source (open-bytevector-input-port dump)))
               (guard (c ((nar-invalid-hash-error? c)
                          (let ((message (condition-message c))
                                (port    (nar-error-port c)))
                            (return
                             (and (string-contains message "hash")
                                  (string=? file (nar-error-file c))
                                  (eq? source port))))))
                 (restore-file-set source))
               #f))))))

(test-end "nar")

;;; Local Variables:
;;; eval: (put 'with-file-tree 'scheme-indent-function 2)
;;; End:


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; 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 networking)
  #:use-module (ice-9 regex)
  #:use-module (gnu services networking)
  #:use-module (srfi srfi-64))

;;; Tests for the (gnu services networking) module.

(test-begin "networking")

;;;
;;; NTP.
;;;

(define ntp-server->string (@@ (gnu services networking) ntp-server->string))

(define %ntp-server-sample
  (ntp-server
   (type 'server)
   (address "some.ntp.server.org")
   ;; Using either strings or symbols for option names is accepted.
   (options `("iburst" (version 3) (maxpoll 16) prefer))))

(test-equal "ntp-server->string"
  "server some.ntp.server.org iburst version 3 maxpoll 16 prefer"
  (ntp-server->string %ntp-server-sample))

(test-equal "ntp configuration servers deprecated form"
  (ntp-configuration-servers
   (ntp-configuration
    (servers (list "example.pool.ntp.org"))))
  (ntp-configuration-servers
   (ntp-configuration
    (servers (list (ntp-server
                    (type 'server)
                    (address "example.pool.ntp.org")
                    (options '())))))))

;;;
;;; OpenNTPD
;;;

(define openntpd-configuration->string (@@ (gnu services networking)
                                           openntpd-configuration->string))

(define %openntpd-conf-sample
  (openntpd-configuration
   (server '("0.guix.pool.ntp.org" "1.guix.pool.ntp.org"))
   (listen-on '("127.0.0.1" "::1"))
   (sensor '("udcf0 correction 70000"))
   (constraint-from '("www.gnu.org"))
   (constraints-from '("https://www.google.com/"))))

(test-assert "openntpd configuration generation sanity check"

  (begin
    (define (string-match/newline pattern text)
      (regexp-exec (make-regexp pattern regexp/newline) text))

    (define (match-count pattern text)
      (fold-matches (make-regexp pattern regexp/newline) text 0
                    (lambda (match count)
                      (1+ count))))

    (let ((config (openntpd-configuration->string %openntpd-conf-sample)))
      (if (not
           (and (string-match/newline "^listen on 127.0.0.1$" config)
                (string-match/newline "^listen on ::1$" config)
                (string-match/newline "^sensor udcf0 correction 70000$" config)
                (string-match/newline "^constraint from www.gnu.org$" config)
                (string-match/newline "^server 0.guix.pool.ntp.org$" config)
                (string-match/newline
                 "^constraints from \"https://www.google.com/\"$"
                 config)

                ;; Check for issue #3731 (see:
                ;; http://debbugs.gnu.org/cgi/bugreport.cgi?bug=37318).
                (= (match-count "^listen on " config) 2)
                (= (match-count "^sensor " config) 1)
                (= (match-count "^constraint from " config) 1)
                (= (match-count "^server " config) 2)
                (= (match-count "^constraints from " config) 1)))
          (begin
            (format #t "The configuration below failed \
the sanity check:\n~a~%" config)
            #f)
          #t))))

(test-equal "openntpd generated config string ends with a newline"
  "\n"
  (let ((config (openntpd-configuration->string %openntpd-conf-sample)))
    (string-take-right config 1)))

(test-end "networking")

repo_name: maximed/guix

license: mit


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; 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 offload)
  #:use-module (guix scripts offload)
  #:use-module (srfi srfi-64))

(test-begin "offload")

(define-syntax-rule (expose-internal-definitions s1 s2 ...)
  (begin
    (define s1 (@@ (guix scripts offload) s1))
    (define s2 (@@ (guix scripts offload) s2)) ...))

(expose-internal-definitions machine-matches?
                             build-requirements-system
                             build-requirements-features
                             build-machine-system
                             build-machine-systems
                             %build-machine-system
                             %build-machine-systems
                             build-machine-features)

(define (deprecated-build-machine system)
  (build-machine
   (name "m1")
   (user "dummy")
   (host-key "some-key")
   (system system)))

(define (new-build-machine systems)
  (build-machine
   (name "m1")
   (user "dummy")
   (host-key "some-key")
   (systems systems)))

;;; Test that deprecated build-machine definitions still work.
(test-assert (machine-matches? (deprecated-build-machine "i686-linux")
                               (build-requirements
                                (system "i686-linux"))))

(test-assert (machine-matches? (new-build-machine '("i686-linux"))
                               (build-requirements
                                (system "i686-linux"))))

;;; A build machine can act as more than one system type, thanks to QEMU
;;; emulation.
(test-assert (machine-matches? (new-build-machine '("armhf-linux"
                                                    "aarch64-linux"
                                                    "i686-linux"
                                                    "x86_64-linux"))
                               (build-requirements
                                (system "armhf-linux"))))


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018 Julien Lepiller <julien@lepiller.eu>
;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;;
;;; 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-opam)
  #:use-module (guix import opam)
  #:use-module (guix base32)
  #:use-module (gcrypt hash)
  #:use-module (guix tests)
  #:use-module ((guix build syscalls) #:select (mkdtemp!))
  #:use-module ((guix build utils) #:select (delete-file-recursively mkdir-p which))
  #:use-module ((guix utils) #:select (call-with-temporary-output-file))
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-64)
  #:use-module (web uri)
  #:use-module (ice-9 match)
  #:use-module (ice-9 peg))

(define test-opam-file
"opam-version: \"2.0\"
  version: \"1.0.0\"
maintainer: \"Alice Doe\"
authors: [
  \"Alice Doe\"
  \"John Doe\"
]
homepage: \"https://example.org/\"
bug-reports: \"https://example.org/bugs\"
dev-repo: \"https://example.org/git\"
build: [
  [\"ocaml\" \"pkg/pkg.ml\" \"build\" \"--pinned\" \"%{pinned}%\"]
]
build-test: [
  [\"ocaml\" \"pkg/pkg.ml\" \"build\" \"--pinned\" \"%{pinned}%\" \"--tests\" \"true\"]
]
depends: [
  \"alcotest\" {test & >= \"0.7.2\"}
  \"ocamlbuild\" {build & >= \"0.9.2\"}
  \"zarith\" {>= \"0.7\"}
]
synopsis: \"Some example package\"
description: \"\"\"
This package is just an example.\"\"\"
license: \"BSD-3-Clause\"
url {
  src: \"https://example.org/foo-1.0.0.tar.gz\"
  checksum: \"md5=74c6e897658e820006106f45f736381f\"
}")

(define test-source-hash
  "")

(define test-repo
  (mkdtemp! "/tmp/opam-repo.XXXXXX"))

(test-begin "opam")

(test-assert "opam->guix-package"
  (mock ((guix import utils) url-fetch
         (lambda (url file-name)
           (match url
             ("https://example.org/foo-1.0.0.tar.gz"
              (begin
                (mkdir-p "foo-1.0.0")
                (system* "tar" "czvf" file-name "foo-1.0.0/")
                (delete-file-recursively "foo-1.0.0")
                (set! test-source-hash
                  (call-with-input-file file-name port-sha256))))
             (_ (error "Unexpected URL: " url)))))
        (mock ((guix import opam) get-opam-repository
               (const test-repo))
              (let ((my-package (string-append test-repo
                                               "/packages/foo/foo.1.0.0")))
                (mkdir-p my-package)
                (with-output-to-file (string-append my-package "/opam")
                  (lambda _
                    (format #t "~a" test-opam-file))))
              (match (opam->guix-package "foo" #:repo test-repo)
                (('package
                   ('name "ocaml-foo")
                   ('version "1.0.0")
                   ('source ('origin
                              ('method 'url-fetch)
                              ('uri "https://example.org/foo-1.0.0.tar.gz")
                              ('sha256
                               ('base32
                                (? string? hash)))))
                   ('build-system 'ocaml-build-system)
                   ('propagated-inputs
                    ('quasiquote
                     (("ocaml-zarith" ('unquote 'ocaml-zarith)))))
                   ('native-inputs
                    ('quasiquote
                     (("ocaml-alcotest" ('unquote 'ocaml-alcotest))
                      ("ocamlbuild" ('unquote 'ocamlbuild)))))
                   ('home-page "https://example.org/")
                   ('synopsis "Some example package")
                   ('description "This package is just an example.")
                   ('license 'license:bsd-3))
                 (string=? (bytevector->nix-base32-string
                            test-source-hash)
                           hash))
                (x
                 (pk 'fail x #f))))))

;; Test the opam file parser
;; We fold over some test cases. Each case is a pair of the string to parse and the
;; expected result.
(define (test-opam-syntax name pattern test-cases)
  (test-assert name
    (fold (lambda (test acc)
            (display test) (newline)
            (match test
              ((str . expected)
               (and acc
                    (let ((result (peg:tree (match-pattern pattern str))))
                      (if (equal? result expected)
                          #t
                          (pk 'fail (list str result expected) #f)))))))
          #t test-cases)))

(test-opam-syntax
  "parse-strings" string-pat
  '(("" . #f)
    ("\"hello\"" . (string-pat "hello"))
    ("\"hello world\"" . (string-pat "hello world"))
    ("\"The dreaded \\\"é\\\"\"" . (string-pat "The dreaded \"é\""))
    ("\"Have some \\\\\\\\ :)\"" . (string-pat "Have some \\\\ :)"))
    ("\"今日は\"" . (string-pat "今日は"))))

(test-opam-syntax
  "parse-multiline-strings" multiline-string
  '(("" . #f)
    ("\"\"\"hello\"\"\"" . (multiline-string "hello"))
    ("\"\"\"hello \"world\"!\"\"\"" . (multiline-string "hello \"world\"!"))
    ("\"\"\"hello \"\"world\"\"!\"\"\"" . (multiline-string "hello \"\"world\"\"!"))))

(test-opam-syntax
  "parse-lists" list-pat
  '(("" . #f)
    ("[]" . list-pat)
    ("[make]" . (list-pat (var "make")))
    ("[\"make\"]" . (list-pat (string-pat "make")))
    ("[\n  a\n  b\n  c]" . (list-pat (var "a") (var "b") (var "c")))
    ("[a   b     \"c\"]" . (list-pat (var "a") (var "b") (string-pat "c")))
    ;; complex lists
    ("[(a & b)]" . (list-pat (choice-pat (group-pat (var "a") (var "b")))))
    ("[(a | b & c)]" . (list-pat (choice-pat (var "a") (group-pat (var "b") (var "c")))))
    ("[a (b | c) d]" . (list-pat (var "a") (choice-pat (var "b") (var "c")) (var "d")))))

(test-opam-syntax
  "parse-dicts" dict
  '(("" . #f)
    ("{}" . dict)
    ("{a: \"b\"}" . (dict (record "a" (string-pat "b"))))
    ("{a: \"b\"\nc: \"d\"}" . (dict (record "a" (string-pat "b")) (record "c" (string-pat "d"))))))

(test-opam-syntax
  "parse-conditions" condition
  '(("" . #f)
    ("{}" . #f)
    ("{build}" . (condition-var "build"))
    ("{>= \"0.2.0\"}" . (condition-greater-or-equal
                          (condition-string "0.2.0")))
    ("{>= \"0.2.0\" & test}" . (condition-and
                                 (condition-greater-or-equal
                                   (condition-string "0.2.0"))
                                 (condition-var "test")))
    ("{>= \"0.2.0\" | build}" . (condition-or
                                 (condition-greater-or-equal
                                   (condition-string "0.2.0"))
                                 (condition-var "build")))
    ("{ = \"1.0+beta19\" }" . (condition-eq
                                (condition-string "1.0+beta19")))))

(test-opam-syntax
  "parse-comment" list-pat
  '(("" . #f)
    ("[#comment\n]" . list-pat)))

(test-end "opam")


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 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 (tests-openpgp)
  #:use-module (guix openpgp)
  #:use-module (gcrypt base16)
  #:use-module (gcrypt hash)
  #:use-module (gcrypt pk-crypto)
  #:use-module (ice-9 binary-ports)
  #:use-module (ice-9 match)
  #:use-module (rnrs bytevectors)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-64)
  #:use-module (srfi srfi-71))

(define %radix-64-sample
  ;; Example of Radix-64 encoding from Section 6.6 of RFC4880.
  "\
-----BEGIN PGP MESSAGE-----
Version: OpenPrivacy 0.99

yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS
vBSFjNSiVHsuAA==
=njUN
-----END PGP MESSAGE-----\n")

(define %radix-64-sample/crc-mismatch
  ;; This time with a wrong CRC24 value.
  "\
-----BEGIN PGP MESSAGE-----

yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS
vBSFjNSiVHsuAA==
=AAAA
-----END PGP MESSAGE-----\n")

(define %binary-sample
  ;; Same message as %radix-64-sample, decoded into bytevector.
  (base16-string->bytevector
  "c838013b6d96c411efecef17ecefe3ca0004ce8979ea250a897995f979a9\
0ad9a9a9050a890ac5a9c945a940c1a2fcd2bc14858cd4a2547b2e00"))

(define %civodul-fingerprint
  "3CE4 6455 8A84 FDC6 9DB4  0CFB 090B 1199 3D9A EBB5")

(define %civodul-key-id #x090B11993D9AEBB5)       ;civodul.key

;; Test keys.  They were generated in a container along these lines:
;;    guix environment -CP --ad-hoc gnupg pinentry
;; then, within the container:
;;    mkdir ~/.gnupg
;;    echo pinentry-program ~/.guix-profile/bin/pinentry-tty > ~/.gnupg/gpg-agent.conf
;;    gpg --quick-gen-key '<ludo+test-rsa@chbouib.org>' rsa
;; or similar.
(define %rsa-key-id      #xAE25DA2A70DEED59)      ;rsa.key
(define %dsa-key-id      #x587918047BE8BD2C)      ;dsa.key
(define %ed25519-key-id  #x771F49CBFAAE072D)      ;ed25519.key

(define %rsa-key-fingerprint
  (base16-string->bytevector
   (string-downcase "385F86CFC86B665A5C165E6BAE25DA2A70DEED59")))
(define %dsa-key-fingerprint
  (base16-string->bytevector
   (string-downcase "2884A980422330A4F33DD97F587918047BE8BD2C")))
(define %ed25519-key-fingerprint
  (base16-string->bytevector
   (string-downcase "44D31E21AF7138F9B632280A771F49CBFAAE072D")))

;;; The following are detached signatures created commands like:
;;;    echo 'Hello!' | gpg -sba --digest-algo sha512
;;; They are detached (no PACKET-ONE-PASS-SIGNATURE) and uncompressed.

(define %hello-signature/rsa
  ;; Signature of the ASCII string "Hello!\n".
  "\
-----BEGIN PGP SIGNATURE-----

iQEzBAABCAAdFiEEOF+Gz8hrZlpcFl5rriXaKnDe7VkFAl4SRF0ACgkQriXaKnDe
7VlIyQf/TU5rGUK42/C1ULoWvvm25Mjwh6xxoPPkuBxvos8bE6yKr/vJZePU3aSE
mjbVFcO7DioxHMqLd49j803bUtdllJVU18ex9MkKbKjapkgEGkJsuTTzqyONprgk
7xtZGBWuxkP1M6hJICJkA3Ys+sTdKalux/pzr5OWAe+gxytTF/vr/EyJzdmBxbJv
/fhd1SeVIXSw4c5gf2Wcvcgfy4N5CiLaUb7j4646KBTvDvmUMcDZ+vmKqC/XdQeQ
PrjArGKt40ErVd98fwvNHZnw7VQMx0A3nL3joL5g7/RckDOUb4mqKoqLsLd0wPHP
y32DiDUY9s3sy5OMzX4Y49em8vxvlg==
=ASEm
-----END PGP SIGNATURE-----")

(define %hello-signature/dsa
  "\
-----BEGIN PGP SIGNATURE-----

iHUEABEIAB0WIQQohKmAQiMwpPM92X9YeRgEe+i9LAUCXhJFpQAKCRBYeRgEe+i9
LDAaAQC0lXPQepvZBANAUtRLMZuOwL9NQPkfhIwUXtLEBBzyFQD/So8DcybXpRBi
JKOiyAQQjMs/GJ6qMEQpRAhyyJRAock=
=iAEc
-----END PGP SIGNATURE-----")

(define %hello-signature/ed25519/sha256           ;digest-algo: sha256
  "\
-----BEGIN PGP SIGNATURE-----

iHUEABYIAB0WIQRE0x4hr3E4+bYyKAp3H0nL+q4HLQUCXqRADAAKCRB3H0nL+q4H
LUImAP9/foaSjPFC/MSr52LNV5ROSL9haea4jPpUP+N6ViFGowEA+AE/xpXPIqsz
R6CdxMevURuqUpqQ7rHeiMmdUepeewU=
=tLXy
-----END PGP SIGNATURE-----")

(define %hello-signature/ed25519/sha512           ;digest-algo: sha512
  "\
-----BEGIN PGP SIGNATURE-----

iHUEABYKAB0WIQRE0x4hr3E4+bYyKAp3H0nL+q4HLQUCXqRAGgAKCRB3H0nL+q4H
LTeKAP0S8LiiosJXOARlYNdhfGw9j26lHrbwJh5CORGlaqqIJAEAoMYcmtNa2b6O
inlEwB/KQM88O9RwA8xH7X5a0rodOw4=
=68r/
-----END PGP SIGNATURE-----")

(define %hello-signature/ed25519/sha1             ;digest-algo: sha1
  "\
-----BEGIN PGP SIGNATURE-----

iHUEABYCAB0WIQRE0x4hr3E4+bYyKAp3H0nL+q4HLQUCXqRALQAKCRB3H0nL+q4H
LdhEAQCfkdYhIVRa43oTNw9EL/TDFGQjXSHNRFVU0ktjkWbkQwEAjIXhvj2sqy79
Pz7oopeN72xgggYUNT37ezqN3MeCqw0=
=AE4G
-----END PGP SIGNATURE-----")

(test-begin "openpgp")

(test-equal "read-radix-64"
  '(#t "PGP MESSAGE")
  (let-values (((data type)
                (call-with-input-string %radix-64-sample read-radix-64)))
    (list (bytevector? data) type)))

(test-equal "read-radix-64, CRC mismatch"
  '(#f "PGP MESSAGE")
  (call-with-values
      (lambda ()
        (call-with-input-string %radix-64-sample/crc-mismatch
          read-radix-64))
    list))

(test-assert "port-ascii-armored?, #t"
  (call-with-input-string %radix-64-sample port-ascii-armored?))

(test-assert "port-ascii-armored?, #f"
  (not (port-ascii-armored? (open-bytevector-input-port %binary-sample))))

(test-assert "get-openpgp-keyring"
  (let* ((key (search-path %load-path "tests/civodul.key"))
         (keyring (get-openpgp-keyring
                   (open-bytevector-input-port
                    (call-with-input-file key read-radix-64)))))
    (let-values (((primary packets)
                  (lookup-key-by-id keyring %civodul-key-id)))
      (let ((fingerprint (openpgp-public-key-fingerprint primary)))
        (and (= (openpgp-public-key-id primary) %civodul-key-id)
             (not (openpgp-public-key-subkey? primary))
             (string=? (openpgp-format-fingerprint fingerprint)
                       %civodul-fingerprint)
             (string=? (openpgp-user-id-value (find openpgp-user-id? packets))
                       "Ludovic Courtès <ludo@gnu.org>")
             (eq? (lookup-key-by-fingerprint keyring fingerprint)
                  primary))))))

(test-equal "get-openpgp-detached-signature/ascii"
  (list `(,%dsa-key-id ,%dsa-key-fingerprint dsa sha256)
        `(,%rsa-key-id ,%rsa-key-fingerprint rsa sha256)
        `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha256)
        `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha512)
        `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha1))
  (map (lambda (str)
         (let ((signature (get-openpgp-detached-signature/ascii
                           (open-input-string str))))
           (list (openpgp-signature-issuer-key-id signature)
                 (openpgp-signature-issuer-fingerprint signature)
                 (openpgp-signature-public-key-algorithm signature)
                 (openpgp-signature-hash-algorithm signature))))
       (list %hello-signature/dsa
             %hello-signature/rsa
             %hello-signature/ed25519/sha256
             %hello-signature/ed25519/sha512
             %hello-signature/ed25519/sha1)))

(test-equal "verify-openpgp-signature, missing key"
  `(missing-key ,%rsa-key-fingerprint)
  (let* ((keyring   (get-openpgp-keyring (%make-void-port "r")))
         (signature (string->openpgp-packet %hello-signature/rsa)))
    (let-values (((status key)
                  (verify-openpgp-signature signature keyring
                                            (open-input-string "Hello!\n"))))
      (list status key))))

(test-equal "verify-openpgp-signature, good signatures"
  `((good-signature ,%rsa-key-id)
    (good-signature ,%dsa-key-id)
    (good-signature ,%ed25519-key-id)
    (good-signature ,%ed25519-key-id)
    (good-signature ,%ed25519-key-id))
  (map (lambda (key signature)
         (let* ((key       (search-path %load-path key))
                (keyring   (get-openpgp-keyring
                            (open-bytevector-input-port
                             (call-with-input-file key read-radix-64))))
                (signature (string->openpgp-packet signature)))
           (let-values (((status key)
                         (verify-openpgp-signature signature keyring
                                                   (open-input-string "Hello!\n"))))
             (list status (openpgp-public-key-id key)))))
       (list "tests/rsa.key" "tests/dsa.key"
             "tests/ed25519.key" "tests/ed25519.key" "tests/ed25519.key")
       (list %hello-signature/rsa %hello-signature/dsa
             %hello-signature/ed25519/sha256
             %hello-signature/ed25519/sha512
             %hello-signature/ed25519/sha1)))

(test-equal "verify-openpgp-signature, bad signature"
  `((bad-signature ,%rsa-key-id)
    (bad-signature ,%dsa-key-id)
    (bad-signature ,%ed25519-key-id)
    (bad-signature ,%ed25519-key-id)
    (bad-signature ,%ed25519-key-id))
  (let ((keyring (fold (lambda (key keyring)
                         (let ((key (search-path %load-path key)))
                           (get-openpgp-keyring
                            (open-bytevector-input-port
                             (call-with-input-file key read-radix-64))
                            keyring)))
                       %empty-keyring
                       '("tests/rsa.key" "tests/dsa.key"
                         "tests/ed25519.key" "tests/ed25519.key"
                         "tests/ed25519.key"))))
    (map (lambda (signature)
           (let ((signature (string->openpgp-packet signature)))
             (let-values (((status key)
                           (verify-openpgp-signature signature keyring
                                                     (open-input-string "What?!"))))
               (list status (openpgp-public-key-id key)))))
         (list %hello-signature/rsa %hello-signature/dsa
               %hello-signature/ed25519/sha256
               %hello-signature/ed25519/sha512
               %hello-signature/ed25519/sha1))))

(test-end "openpgp")


code:
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; 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-pack)
  #:use-module (guix scripts pack)
  #:use-module (guix store)
  #:use-module (guix derivations)
  #:use-module (guix profiles)
  #:use-module (guix packages)
  #:use-module (guix monads)
  #:use-module (guix grafts)
  #:use-module (guix tests)
  #:use-module (guix gexp)
  #:use-module (guix modules)
  #:use-module (gnu packages)
  #:use-module ((gnu packages base) #:select (glibc-utf8-locales))
  #:use-module (gnu packages bootstrap)
  #:use-module ((gnu packages compression) #:select (squashfs-tools))
  #:use-module ((gnu packages debian) #:select (dpkg))
  #:use-module ((gnu packages guile) #:select (guile-sqlite3))
  #:use-module ((gnu packages gnupg) #:select (guile-gcrypt))
  #:use-module (srfi srfi-64))

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

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

(define-syntax-rule (test-assertm name store exp)
  (test-assert name
    (let ((guile (package-derivation store %bootstrap-guile)))
      (run-with-store store exp
                      #:guile-for-build guile))))

(define %gzip-compressor
  ;; Compressor that uses the bootstrap 'gzip'.
  ((@ (guix scripts pack) compressor) "gzip"
   ".gz"
   #~(#+(file-append %bootstrap-coreutils&co "/bin/gzip") "-6n")))

(define %tar-bootstrap %bootstrap-coreutils&co)

(define %ar-bootstrap %bootstrap-binutils)

(test-begin "pack")

(unless (network-reachable?) (test-skip 1))
(test-assertm "self-contained-tarball" %store
  (mlet* %store-monad
      ((profile -> (profile
                    (content (packages->manifest (list %bootstrap-guile)))
                    (hooks '())
                    (locales? #f)))
       (tarball (self-contained-tarball "pack" profile
                                        #:symlinks '(("/bin/Guile"
                                                      -> "bin/guile"))
                                        #:compressor %gzip-compressor
                                        #:archiver %tar-bootstrap))
       (check   (gexp->derivation
                 "check-tarball"
                 (with-imported-modules '((guix build utils))
                   #~(begin
                       (use-modules (guix build utils)
                                    (srfi srfi-1))

                       (define store
                         ;; The unpacked store.
                         (string-append "." (%store-directory) "/"))

                       (define (canonical? file)
                         ;; Return #t if FILE is read-only and its mtime is 1.
                         (let ((st (lstat file)))
                           (or (not (string-prefix? store file))
                               (eq? 'symlink (stat:type st))
                               (and (= 1 (stat:mtime st))
                                    (zero? (logand #o222
                                                   (stat:mode st)))))))

                       (define bin
                         (string-append "." #$profile "/bin"))

                       (setenv "PATH"
                               (string-append #$%tar-bootstrap "/bin"))
                       (system* "tar" "xvf" #$tarball)
                       (mkdir #$output)
                       (exit
                        (and (file-exists? (string-append bin "/guile"))
                             (file-exists? store)
                             (every canonical?
                                    (find-files "." (const #t)
                                                #:directories? #t))
                             (string=? (string-append #$%bootstrap-guile "/bin")
                                       (readlink bin))
                             (string=? (string-append ".." #$profile
                                                      "/bin/guile")
                                       (readlink "bin/Guile")))))))))
    (built-derivations (list check))))

;; The following test needs guile-sqlite3, libgcrypt, etc. as a consequence of
;; commit c45477d2a1a651485feede20fe0f3d15aec48b39 and related changes.  Thus,
;; run it on the user's store, if it's available, on the grounds that these
;; dependencies may be already there, or we can get substitutes or build them
;; quite inexpensively; see <https://bugs.gnu.org/32184>.

(with-external-store store
  (unless store (test-skip 1))
  (test-assertm "self-contained-tarball + localstatedir" store
    (mlet* %store-monad
        ((guile   (set-guile-for-build (default-guile)))
         (profile (profile-derivation (packages->manifest
                                       (list %bootstrap-guile))
                                      #:hooks '()
                                      #:locales? #f))
         (tarball (self-contained-tarball "tar-pack" profile
                                          #:localstatedir? #t))
         (check   (gexp->derivation
                   "check-tarball"
                   #~(let ((bin (string-append "." #$profile "/bin")))
                       (setenv "PATH"
                               (string-append #$%tar-bootstrap "/bin"))
                       (system* "tar" "xvf" #$tarball)
                       (mkdir #$output)
                       (exit
                        (and (file-exists? "var/guix/db/db.sqlite")
                             (string=? (string-append #$%bootstrap-guile "/bin")
                                       (readlink bin))))))))
      (built-derivations (list check))))

  (unless store (test-skip 1))
  (test-assertm "self-contained-tarball + localstatedir, UTF-8 file names" store
    (mlet* %store-monad
        ((guile   (set-guile-for-build (default-guile)))
         (tree    (interned-file-tree
                   `("directory-with-utf8-file-names" directory
                     ("α" regular (data "alpha"))
                     ("λ" regular (data "lambda")))))
         (tarball (self-contained-tarball "tar-pack" tree
                                          #:localstatedir? #t))
         (check   (gexp->derivation
                   "check-tarball"
                   (with-extensions (list guile-sqlite3 guile-gcrypt)
                     (with-imported-modules (source-module-closure
                                             '((guix store database)))
                       #~(begin
                           (use-modules (guix store database)
                                        (rnrs io ports)
                                        (srfi srfi-1))

                           (define (valid-file? basename data)
                             (define file
                               (string-append "./" #$tree "/" basename))

                             (string=? (call-with-input-file (pk 'file file)
                                         get-string-all)
                                       data))

                           (setenv "PATH"
                                   (string-append #$%tar-bootstrap "/bin"))
                           (system* "tar" "xvf" #$tarball)

                           (sql-schema
                            #$(local-file (search-path %load-path
                                                       "guix/store/schema.sql")))
                           (with-database "var/guix/db/db.sqlite" db
                             ;; Make sure non-ASCII file names are properly
                             ;; handled.
                             (setenv "GUIX_LOCPATH"
                                     #+(file-append glibc-utf8-locales
                                                    "/lib/locale"))
                             (setlocale LC_ALL "en_US.utf8")

                             (mkdir #$output)
                             (exit
                              (and (every valid-file?
                                          '("α" "λ")
                                          '("alpha" "lambda"))
                                   (integer? (path-id db #$tree)))))))))))
      (built-derivations (list check))))

  (unless store (test-skip 1))
  (test-assertm "docker-image + localstatedir" store
    (mlet* %store-monad
        ((guile   (set-guile-for-build (default-guile)))
         (profile (profile-derivation (packages->manifest
                                       (list %bootstrap-guile))
                                      #:hooks '()
                                      #:locales? #f))
         (tarball (docker-image "docker-pack" profile
                                #:symlinks '(("/bin/Guile" -> "bin/guile"))
                                #:localstatedir? #t))
         (check   (gexp->derivation
                   "check-tarball"
                   (with-imported-modules '((guix build utils))
                     #~(begin
                         (use-modules (guix build utils)
                                      (ice-9 match))

                         (define bin
                           (string-append "." #$profile "/bin"))

                         (setenv "PATH" (string-append #$%tar-bootstrap "/bin"))
                         (mkdir "base")
                         (with-directory-excursion "base"
                           (invoke "tar" "xvf" #$tarball))

                         (match (find-files "base" "layer.tar")
                           ((layer)
                            (invoke "tar" "xvf" layer)))

                         (when
                          (and (file-exists? (string-append bin "/guile"))
                               (file-exists? "var/guix/db/db.sqlite")
                               (file-is-directory? "tmp")
                               (string=? (string-append #$%bootstrap-guile "/bin")
                                         (pk 'binlink (readlink bin)))
                               (string=? (string-append #$profile "/bin/guile")
                                         (pk 'guilelink (readlink "bin/Guile"))))
                          (mkdir #$output)))))))
      (built-derivations (list check))))

  (unless store (test-skip 1))
  (test-assertm "squashfs-image + localstatedir" store
    (mlet* %store-monad
        ((guile   (set-guile-for-build (default-guile)))
         (profile (profile-derivation (packages->manifest
                                       (list %bootstrap-guile))
                                      #:hooks '()
                                      #:locales? #f))
         (image   (squashfs-image "squashfs-pack" profile
                                  #:symlinks '(("/bin" -> "bin"))
                                  #:localstatedir? #t))
         (check   (gexp->derivation
                   "check-tarball"
                   (with-imported-modules '((guix build utils))
                     #~(begin
                         (use-modules (guix build utils)
                                      (ice-9 match))

                         (define bin
                           (string-append "." #$profile "/bin"))

                         (setenv "PATH"
                                 (string-append #$squashfs-tools "/bin"))
                         (invoke "unsquashfs" #$image)
                         (with-directory-excursion "squashfs-root"
                           (when (and (file-exists? (string-append bin
                                                                   "/guile"))
                                      (file-exists? "var/guix/db/db.sqlite")
                                      (string=? (string-append #$%bootstrap-guile "/bin")
                                                (pk 'binlink (readlink bin)))

                                      ;; This is a relative symlink target.
                                      (string=? (string-drop
                                                 (string-append #$profile "/bin")
                                                 1)
                                                (pk 'guilelink (readlink "bin"))))
                             (mkdir #$output))))))))
      (built-derivations (list check))))

  (unless store (test-skip 1))
  (test-assertm "deb archive with symlinks and control files" store
    (mlet* %store-monad
        ((guile   (set-guile-for-build (default-guile)))
         (profile (profile-derivation (packages->manifest
                                       (list %bootstrap-guile))
                                      #:hooks '()
                                      #:locales? #f))
         (deb (debian-archive
               "deb-pack" profile
               #:compressor %gzip-compressor
               #:symlinks '(("/opt/gnu/bin" -> "bin"))
               #:archiver %tar-bootstrap
               #:extra-options
               (list #:triggers-file
                     (plain-file "triggers"
                                 "activate-noawait /usr/share/icons/hicolor\n")
                     #:postinst-file
                     (plain-file "postinst"
                                 "echo running configure script\n"))))
         (check
          (gexp->derivation "check-deb-pack"
            (with-imported-modules '((guix build utils))
              #~(begin
                  (use-modules (guix build utils)
                               (ice-9 match)
                               (ice-9 popen)
                               (ice-9 rdelim)
                               (ice-9 textual-ports)
                               (rnrs base))

                  (setenv "PATH" (string-join
                                  (list (string-append #+%tar-bootstrap "/bin")
                                        (string-append #+dpkg "/bin")
                                        (string-append #+%ar-bootstrap "/bin"))
                                  ":"))

                  ;; Validate the output of 'dpkg --info'.
                  (let* ((port (open-pipe* OPEN_READ "dpkg" "--info" #$deb))
                         (info (get-string-all port))
                         (exit-val (status:exit-val (close-pipe port))))
                    (assert (zero? exit-val))

                    (assert (string-contains
                             info
                             (string-append "Package: "
                                            #+(package-name %bootstrap-guile))))

                    (assert (string-contains
                             info
                             (string-append "Version: "
                                            #+(package-version %bootstrap-guile)))))

                  ;; Sanity check .deb contents.
                  (invoke "ar" "-xv" #$deb)
                  (assert (file-exists? "debian-binary"))
                  (assert (file-exists? "data.tar.gz"))
                  (assert (file-exists? "control.tar.gz"))

                  ;; Verify there are no hard links in data.tar.gz, as hard
                  ;; links would cause dpkg to fail unpacking the archive.
                  (define hard-links
                    (let ((port (open-pipe* OPEN_READ "tar" "-tvf" "data.tar.gz")))
                      (let loop ((hard-links '()))
                        (match (read-line port)
                          ((? eof-object?)
                           (assert (zero? (status:exit-val (close-pipe port))))
                           hard-links)
                          (line
                           (if (string-prefix? "u" line)
                               (loop (cons line hard-links))
                               (loop hard-links)))))))

                  (unless (null? hard-links)
                    (error "hard links found in data.tar.gz" hard-links))

                  ;; Verify the presence of the control files.
                  (invoke "tar" "-xf" "control.tar.gz")
                  (assert (file-exists? "control"))
                  (assert (and (file-exists? "postinst")
                               (= #o111 ;script is executable
                                  (logand #o111 (stat:perms
                                                 (stat "postinst"))))))
                  (assert (file-exists? "triggers"))

                  (mkdir #$output))))))
      (built-derivations (list check)))))

(test-end)

;; Local Variables:
;; eval: (put 'test-assertm 'scheme-indent-function 2)
;; End: