;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2015 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
#:use-module (guix derivations)
#:use-module (guix store)
#:use-module (guix monads)
+ #:use-module (guix grafts)
#: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 guile)
+ #:use-module (gnu packages libunistring)
#:use-module (gnu packages bootstrap)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
(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."
(define (return)
(values (reverse nodes) (reverse edges)))
- (values (graph-backend (const #t) (const #t)
+ (values (graph-backend "test" "This is the test backend."
+ (const #t) (const #t)
record-node record-edge)
return)))
(list p3 p3 p2)
(list p2 p1 p1))))))))
+(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 ((p (dummy-package "p"))
- (implicit (map (match-lambda
- ((label package) package))
- (standard-packages))))
+ (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.
+ ;; 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 implicit)))
+ (map package-full-name
+ (cons p (delete-duplicates implicit))))
(equal? (match edges
(((sources destinations) ...)
(zip (map store-path-package-name sources)
(map (lambda (destination)
(list "p-0.drv"
(string-append
- (package-full-name destination)
+ (package-full-name destination "-")
".drv")))
implicit)))))))
-(test-assert "bag DAG"
+(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
(match nodes
(((labels names) ...)
names))))
- (match %bootstrap-inputs
+ (match (%bootstrap-inputs)
(((labels packages) ...)
- (map package-full-name 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 #vu8(0 1 2))))
+ (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 bap ocaml-base)
+ (values (specification->package "dune")
+ (specification->package "bap")
+ (specification->package "ocaml4.07-base")))
+ ((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))
+ (bap-drv (package->derivation bap))
+ (ocaml-base-drv (package->derivation ocaml-base)))
+ ;; OCAML-BASE uses 'dune-build-system' so DUNE is a direct dependency.
+ ;; BAP is much higher in the stack but it should be there.
+ (let-values (((nodes edges) (nodes+edges)))
+ (return
+ (and (member `(,(derivation-file-name bap-drv)
+ ,(package-full-name bap))
+ nodes)
+ (->bool (member (map derivation-file-name
+ (list dune-drv ocaml-base-drv))
+ edges)))))))))
(test-assert "derivation DAG"
(let-values (((backend nodes+edges) (make-recording-backend)))
(list out txt))
(equal? edges `((,out ,txt)))))))))))
-(test-end "graph")
+(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)))))))))))
-\f
-(exit (= (test-runner-fail-count (test-runner-current)) 0))
+(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-assert "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))))
+ (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 and
+ ;; some that use TRIVIAL-BUILD-SYSTEM.
+ (return (null? (remove (lambda (package)
+ (or (trivial? package)
+ (bootstrap? 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-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-end "graph")