123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- ;;; GNU Guix --- Functional package management for GNU
- ;;; Copyright © 2015-2023 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-graph)
- #:use-module (guix tests)
- #:use-module (guix graph)
- #:use-module (guix scripts graph)
- #:use-module (guix packages)
- #:use-module (guix derivations)
- #:use-module (guix store)
- #:use-module (guix monads)
- #:use-module (guix build-system gnu)
- #:use-module (guix build-system trivial)
- #:use-module (guix gexp)
- #:use-module (guix utils)
- #:use-module (gnu packages)
- #:use-module (gnu packages base)
- #:use-module (gnu packages bootstrap)
- #:use-module (gnu packages guile)
- #:use-module (gnu packages libunistring)
- #:use-module (gnu packages bootstrap)
- #:use-module (ice-9 match)
- #:use-module (ice-9 sandbox)
- #:use-module (srfi srfi-1)
- #:use-module (srfi srfi-11)
- #:use-module (srfi srfi-26)
- #:use-module (srfi srfi-64))
- (define %store
- (open-connection-for-tests))
- ;; Globally disable grafts because they can trigger early builds.
- (%graft? #f)
- (define (make-recording-backend)
- "Return a <graph-backend> and a thunk that returns the recorded nodes and
- edges."
- (let ((nodes '())
- (edges '()))
- (define (record-node id label port)
- (set! nodes (cons (list id label) nodes)))
- (define (record-edge source target port)
- (set! edges (cons (list source target) edges)))
- (define (return)
- (values (reverse nodes) (reverse edges)))
- (values (graph-backend "test" "This is the test backend."
- (const #t) (const #t)
- record-node record-edge)
- return)))
- (define (package->tuple package)
- "Return a tuple representing PACKAGE as produced by %PACKAGE-NODE-TYPE."
- (list (object-address package)
- (package-full-name package)))
- (define (edge->tuple source target)
- "Likewise for an edge from SOURCE to TARGET."
- (list (object-address source)
- (object-address target)))
- (test-begin "graph")
- (test-assert "package DAG"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (let* ((p1 (dummy-package "p1"))
- (p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
- (p3 (dummy-package "p3" (inputs `(("p2" ,p2) ("p1", p1))))))
- (run-with-store %store
- (export-graph (list p3) 'port
- #:node-type %package-node-type
- #:backend backend))
- ;; We should see nothing more than these 3 packages.
- (let-values (((nodes edges) (nodes+edges)))
- (and (equal? nodes (map package->tuple (list p3 p2 p1)))
- (equal? edges
- (map edge->tuple
- (list p3 p3 p2)
- (list p2 p1 p1))))))))
- (test-assert "package DAG, limited depth"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (let* ((p1 (dummy-package "p1"))
- (p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
- (p3 (dummy-package "p3" (inputs `(("p1" ,p1)))))
- (p4 (dummy-package "p4" (inputs `(("p2" ,p2) ("p3" ,p3))))))
- (run-with-store %store
- (export-graph (list p4) 'port
- #:max-depth 1
- #:node-type %package-node-type
- #:backend backend))
- ;; We should see nothing more than these 3 packages.
- (let-values (((nodes edges) (nodes+edges)))
- (and (equal? nodes (map package->tuple (list p4 p2 p3)))
- (equal? edges
- (map edge->tuple
- (list p4 p4)
- (list p2 p3))))))))
- (test-assert "package DAG, oops it was a cycle"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (letrec ((p1 (dummy-package "p1" (inputs `(("p3" ,p3)))))
- (p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
- (p3 (dummy-package "p3" (inputs `(("p2" ,p2) ("p1", p1))))))
- (call-with-time-limit
- 600 ;; If ever this test should fail, we still want it to terminate
- (lambda ()
- (run-with-store %store
- (export-graph (list p3) 'port
- #:node-type %package-node-type
- #:backend backend)))
- (lambda ()
- (run-with-store %store
- (export-graph
- (list (dummy-package "timeout-reached"))
- 'port
- #:node-type %package-node-type
- #:backend backend))))
- ;; We should see nothing more than these 3 packages.
- (let-values (((nodes edges) (nodes+edges)))
- (and (equal? nodes (map package->tuple (list p3 p2 p1)))
- (equal? edges
- (map edge->tuple
- (list p3 p3 p2 p1)
- (list p2 p1 p1 p3))))))))
- (test-assert "reverse package DAG"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (run-with-store %store
- (export-graph (list libunistring) 'port
- #:node-type %reverse-package-node-type
- #:backend backend))
- ;; We should see nothing more than these 3 packages.
- (let-values (((nodes edges) (nodes+edges)))
- (and (member (package->tuple guile-2.0) nodes)
- (->bool (member (edge->tuple libunistring guile-2.0) edges))))))
- (test-assert "bag-emerged DAG"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (let* ((o (dummy-origin (method (lambda _
- (text-file "foo" "bar")))))
- (p (dummy-package "p" (source o)))
- (implicit (map (match-lambda
- ((label package) package)
- ((label package output) package))
- (standard-packages))))
- (run-with-store %store
- (export-graph (list p) 'port
- #:node-type %bag-emerged-node-type
- #:backend backend))
- ;; We should see exactly P and IMPLICIT, with one edge from P to each
- ;; element of IMPLICIT. O must not appear among NODES. Note: IMPLICIT
- ;; contains "glibc" twice, once for "out" and a second time for
- ;; "static", hence the 'delete-duplicates' call below.
- (let-values (((nodes edges) (nodes+edges)))
- (and (equal? (match nodes
- (((labels names) ...)
- names))
- (map package-full-name
- (cons p (delete-duplicates implicit))))
- (equal? (match edges
- (((sources destinations) ...)
- (zip (map store-path-package-name sources)
- (map store-path-package-name destinations))))
- (map (lambda (destination)
- (list "p-0.drv"
- (string-append
- (package-full-name destination "-")
- ".drv")))
- implicit)))))))
- (test-assert "bag DAG" ;a big town in Iraq
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (let ((p (dummy-package "p")))
- (run-with-store %store
- (export-graph (list p) 'port
- #:node-type %bag-node-type
- #:backend backend))
- ;; We should see P, its implicit inputs as well as the whole DAG, which
- ;; should include bootstrap binaries.
- (let-values (((nodes edges) (nodes+edges)))
- (every (lambda (name)
- (find (cut string=? name <>)
- (match nodes
- (((labels names) ...)
- names))))
- (match (%bootstrap-inputs)
- (((labels packages) ...)
- (map package-full-name (filter package? packages)))))))))
- (test-assert "bag DAG, including origins"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (let* ((m (lambda* (uri hash-type hash name #:key system)
- (text-file "foo-1.2.3.tar.gz" "This is a fake!")))
- (o (origin
- (method m) (uri "the-uri")
- (sha256
- (base32
- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))))
- (p (dummy-package "p" (source o))))
- (run-with-store %store
- (export-graph (list p) 'port
- #:node-type %bag-with-origins-node-type
- #:backend backend))
- ;; We should see O among the nodes, with an edge coming from P.
- (let-values (((nodes edges) (nodes+edges)))
- (run-with-store %store
- (mlet %store-monad ((o* (lower-object o))
- (p* (lower-object p))
- (g (lower-object (default-guile))))
- (return
- (and (find (match-lambda
- ((file "the-uri") #t)
- (_ #f))
- nodes)
- (find (match-lambda
- ((source target)
- (and (string=? source (derivation-file-name p*))
- (string=? target o*))))
- edges)
- ;; There must also be an edge from O to G.
- (find (match-lambda
- ((source target)
- (and (string=? source o*)
- (string=? target (derivation-file-name g)))))
- edges)))))))))
- (test-assert "reverse bag DAG"
- (let-values (((dune camomile utop)
- (values (specification->package "dune")
- (specification->package "ocaml-camomile")
- (specification->package "ocaml-utop")))
- ((backend nodes+edges) (make-recording-backend)))
- (run-with-store %store
- (export-graph (list dune) 'port
- #:node-type %reverse-bag-node-type
- #:backend backend))
- (run-with-store %store
- (mlet %store-monad ((dune-drv (package->derivation dune))
- (camomile-drv (package->derivation camomile))
- (utop-drv (package->derivation utop)))
- ;; CAMOMILE uses 'dune-build-system' so DUNE is a direct dependency.
- ;; UTOP is much higher in the stack but it should be there.
- (let-values (((nodes edges) (nodes+edges)))
- (return
- (and (member `(,(derivation-file-name camomile-drv)
- ,(package-full-name camomile))
- nodes)
- (->bool (member (map derivation-file-name
- (list dune-drv utop-drv))
- edges)))))))))
- (test-assert "derivation DAG"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (run-with-store %store
- (mlet* %store-monad ((txt (text-file "text-file" "Hello!"))
- (guile (package->derivation %bootstrap-guile))
- (drv (gexp->derivation "output"
- #~(symlink #$txt #$output)
- #:guile-for-build
- guile)))
- ;; We should get at least these 3 nodes and corresponding edges.
- (mbegin %store-monad
- (export-graph (list drv) 'port
- #:node-type %derivation-node-type
- #:backend backend)
- (let-values (((nodes edges) (nodes+edges)))
- ;; XXX: For some reason we need to throw in some 'basename'.
- (return (and (match nodes
- (((ids labels) ...)
- (let ((ids (map basename ids)))
- (every (lambda (item)
- (member (basename item) ids))
- (list txt
- (derivation-file-name drv)
- (derivation-file-name guile))))))
- (every (cut member <>
- (map (lambda (edge)
- (map basename edge))
- edges))
- (list (map (compose basename derivation-file-name)
- (list drv guile))
- (list (basename (derivation-file-name drv))
- (basename txt))))))))))))
- (test-assert "reference DAG"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (run-with-store %store
- (mlet* %store-monad ((txt (text-file "text-file" "Hello!"))
- (guile (package->derivation %bootstrap-guile))
- (drv (gexp->derivation "output"
- #~(symlink #$txt #$output)
- #:guile-for-build
- guile))
- (out -> (derivation->output-path drv)))
- ;; We should see only OUT and TXT, with an edge from the former to the
- ;; latter.
- (mbegin %store-monad
- (built-derivations (list drv))
- (export-graph (list (derivation->output-path drv)) 'port
- #:node-type %reference-node-type
- #:backend backend)
- (let-values (((nodes edges) (nodes+edges)))
- (return
- (and (equal? (match nodes
- (((ids labels) ...)
- ids))
- (list out txt))
- (equal? edges `((,out ,txt)))))))))))
- (test-assert "referrer DAG"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (run-with-store %store
- (mlet* %store-monad ((txt (text-file "referrer-node" (random-text)))
- (drv (gexp->derivation "referrer"
- #~(symlink #$txt #$output)))
- (out -> (derivation->output-path drv)))
- ;; We should see only TXT and OUT, with an edge from the former to the
- ;; latter.
- (mbegin %store-monad
- (built-derivations (list drv))
- (export-graph (list txt) 'port
- #:node-type %referrer-node-type
- #:backend backend)
- (let-values (((nodes edges) (nodes+edges)))
- (return
- (and (equal? (match nodes
- (((ids labels) ...)
- ids))
- (list txt out))
- (equal? edges `((,txt ,out)))))))))))
- (test-assert "module graph"
- (let-values (((backend nodes+edges) (make-recording-backend)))
- (run-with-store %store
- (export-graph '((gnu packages guile)) 'port
- #:node-type %module-node-type
- #:backend backend))
- (let-values (((nodes edges) (nodes+edges)))
- (and (member '(gnu packages guile)
- (match nodes
- (((ids labels) ...) ids)))
- (->bool (and (member (list '(gnu packages guile)
- '(gnu packages libunistring))
- edges)
- (member (list '(gnu packages guile)
- '(gnu packages bdw-gc))
- edges)))))))
- (test-assert "node-edges"
- (run-with-store %store
- (let ((packages (fold-packages cons '())))
- (mlet %store-monad ((edges (node-edges %package-node-type packages)))
- (return (and (null? (edges hello))
- (lset= eq?
- (edges guile-2.0)
- (match (package-direct-inputs guile-2.0)
- (((labels packages _ ...) ...)
- packages)))))))))
- (test-equal "node-transitive-edges + node-back-edges"
- '()
- (run-with-store %store
- (let ((packages (fold-packages cons '()))
- (bootstrap? (lambda (package)
- (string-contains
- (location-file (package-location package))
- "bootstrap.scm")))
- (trivial? (lambda (package)
- (eq? (package-build-system package)
- trivial-build-system)))
- (system-specific? (lambda (package)
- (memq #:system (package-arguments package)))))
- (mlet %store-monad ((edges (node-back-edges %bag-node-type packages)))
- (let* ((glibc (canonical-package glibc))
- (dependents (node-transitive-edges (list glibc) edges))
- (diff (lset-difference eq? packages dependents)))
- ;; All the packages depend on libc, except bootstrap packages, some
- ;; packages that use TRIVIAL-BUILD-SYSTEM, and some that target a
- ;; specific system and thus may depend on a different libc package
- ;; object.
- (return (remove (lambda (package)
- (or (trivial? package)
- (bootstrap? package)
- (system-specific? package)))
- diff)))))))
- (test-assert "node-transitive-edges, no duplicates"
- (run-with-store %store
- (let* ((p0 (dummy-package "p0"))
- (p1a (dummy-package "p1a" (inputs `(("p0" ,p0)))))
- (p1b (dummy-package "p1b" (inputs `(("p0" ,p0)))))
- (p2 (dummy-package "p2" (inputs `(("p1a" ,p1a) ("p1b" ,p1b))))))
- (mlet %store-monad ((edges (node-edges %package-node-type
- (list p2 p1a p1b p0))))
- (return (lset= eq? (node-transitive-edges (list p2) edges)
- (list p1a p1b p0)))))))
- (test-assert "node-transitive-edges, references"
- (run-with-store %store
- (mlet* %store-monad ((d0 (package->derivation %bootstrap-guile))
- (d1 (gexp->derivation "d1"
- #~(begin
- (mkdir #$output)
- (symlink #$%bootstrap-guile
- (string-append
- #$output "/l")))))
- (d2 (gexp->derivation "d2"
- #~(begin
- (mkdir #$output)
- (symlink #$d1
- (string-append
- #$output "/l")))))
- (_ (built-derivations (list d2)))
- (->node -> (node-type-convert %reference-node-type))
- (o2 (->node (derivation->output-path d2)))
- (o1 (->node (derivation->output-path d1)))
- (o0 (->node (derivation->output-path d0)))
- (edges (node-edges %reference-node-type
- (append o0 o1 o2)))
- (reqs ((store-lift requisites) o2)))
- (return (lset= string=?
- (append o2 (node-transitive-edges o2 edges)) reqs)))))
- (test-equal "node-reachable-count"
- '(3 3)
- (run-with-store %store
- (let* ((p0 (dummy-package "p0"))
- (p1a (dummy-package "p1a" (inputs `(("p0" ,p0)))))
- (p1b (dummy-package "p1b" (inputs `(("p0" ,p0)))))
- (p2 (dummy-package "p2" (inputs `(("p1a" ,p1a) ("p1b" ,p1b))))))
- (mlet* %store-monad ((all -> (list p2 p1a p1b p0))
- (edges (node-edges %package-node-type all))
- (back (node-back-edges %package-node-type all)))
- (return (list (node-reachable-count (list p2) edges)
- (node-reachable-count (list p0) back)))))))
- (test-equal "shortest-path, packages + derivations"
- '(("p5" "p4" "p1" "p0")
- ("p3" "p2" "p1" "p0")
- #f
- ("p5-0.drv" "p4-0.drv" "p1-0.drv" "p0-0.drv"))
- (run-with-store %store
- (let* ((p0 (dummy-package "p0"))
- (p1 (dummy-package "p1" (inputs `(("p0" ,p0)))))
- (p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
- (p3 (dummy-package "p3" (inputs `(("p2" ,p2)))))
- (p4 (dummy-package "p4" (inputs `(("p1" ,p1)))))
- (p5 (dummy-package "p5" (inputs `(("p4" ,p4) ("p3" ,p3))))))
- (mlet* %store-monad ((path1 (shortest-path p5 p0 %package-node-type))
- (path2 (shortest-path p3 p0 %package-node-type))
- (nope (shortest-path p3 p4 %package-node-type))
- (drv5 (package->derivation p5))
- (drv0 (package->derivation p0))
- (path3 (shortest-path drv5 drv0
- %derivation-node-type)))
- (return (append (map (lambda (path)
- (and path (map package-name path)))
- (list path1 path2 nope))
- (list (map (node-type-label %derivation-node-type)
- path3))))))))
- (test-equal "shortest-path, reverse packages"
- '("libffi" "guile" "guile-json")
- (run-with-store %store
- (mlet %store-monad ((path (shortest-path (specification->package "libffi")
- guile-json-1
- %reverse-package-node-type)))
- (return (map package-name path)))))
- (test-equal "shortest-path, references"
- `(("d2" "d1" ,(package-full-name %bootstrap-guile "-"))
- (,(package-full-name %bootstrap-guile "-") "d1" "d2"))
- (run-with-store %store
- (mlet* %store-monad ((d0 (package->derivation %bootstrap-guile))
- (d1 (gexp->derivation "d1"
- #~(begin
- (mkdir #$output)
- (symlink #$%bootstrap-guile
- (string-append
- #$output "/l")))))
- (d2 (gexp->derivation "d2"
- #~(begin
- (mkdir #$output)
- (symlink #$d1
- (string-append
- #$output "/l")))))
- (_ (built-derivations (list d2)))
- (->node -> (node-type-convert %reference-node-type))
- (o2 (->node (derivation->output-path d2)))
- (o0 (->node (derivation->output-path d0)))
- (path (shortest-path (first o2) (first o0)
- %reference-node-type))
- (rpath (shortest-path (first o0) (first o2)
- %referrer-node-type)))
- (return (list (map (node-type-label %reference-node-type) path)
- (map (node-type-label %referrer-node-type) rpath))))))
- (test-end "graph")
|