aboutsummaryrefslogtreecommitdiff
path: root/tests/store-database.scm
blob: 4d9188425056612d67ee434e33662316c6b36dbf (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017, 2018 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-store-database)
  #:use-module (guix tests)
  #:use-module (guix store)
  #:use-module (guix store database)
  #:use-module ((guix utils) #:select (call-with-temporary-output-file))
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-64))

;; Test the (guix store database) module.

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


(test-begin "store-database")

(test-equal "register-path"
  '(1 1)
  (let ((file (string-append (%store-prefix) "/" (make-string 32 #\f)
                             "-fake")))
    (when (valid-path? %store file)
      (delete-paths %store (list file)))
    (false-if-exception (delete-file file))

    (let ((ref (add-text-to-store %store "ref-of-fake" (random-text)))
          (drv (string-append file ".drv")))
      (call-with-output-file file
        (cut display "This is a fake store item.\n" <>))
      (register-path file
                     #:references (list ref)
                     #:deriver drv)

      (and (valid-path? %store file)
           (equal? (references %store file) (list ref))
           (null? (valid-derivers %store file))
           (null? (referrers %store file))
           (list (stat:mtime (lstat file))
                 (stat:mtime (lstat ref)))))))

(test-equal "new database"
  (list 1 2)
  (call-with-temporary-output-file
   (lambda (db-file port)
     (delete-file db-file)
     (with-database db-file db
       (sqlite-register db
                        #:path "/gnu/foo"
                        #:references '()
                        #:deriver "/gnu/foo.drv"
                        #:hash (string-append "sha256:" (make-string 64 #\e))
                        #:nar-size 1234)
       (sqlite-register db
                        #:path "/gnu/bar"
                        #:references '("/gnu/foo")
                        #:deriver "/gnu/bar.drv"
                        #:hash (string-append "sha256:" (make-string 64 #\a))
                        #:nar-size 4321)
       (let ((path-id (@@ (guix store database) path-id)))
         (list (path-id db "/gnu/foo")
               (path-id db "/gnu/bar")))))))

(test-assert "register-path with unregistered references"
  ;; Make sure we get a "NOT NULL constraint failed: Refs.reference" error
  ;; when we try to add references that are not registered yet.  Better safe
  ;; than sorry.
  (call-with-temporary-output-file
   (lambda (db-file port)
     (delete-file db-file)
     (catch 'sqlite-error
       (lambda ()
         (with-database db-file db
           (sqlite-register db #:path "/gnu/foo"
                            #:references '("/gnu/bar")
                            #:deriver "/gnu/foo.drv"
                            #:hash (string-append "sha256:" (make-string 64 #\e))
                            #:nar-size 1234))
         #f)
       (lambda args
         (pk 'welcome-exception! args)
         #t)))))

(test-end "store-database")
le2) (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 ;; Note: TEXT1 and TEXT2 must be longer than %DEDUPLICATION-MINIMUM-SIZE. (let* ((text1 (string-concatenate (make-list 200 (random-text)))) (text2 (string-concatenate (make-list 200 (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: