graph.scm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (test-graph)
  19. #:use-module (guix tests)
  20. #:use-module (guix graph)
  21. #:use-module (guix scripts graph)
  22. #:use-module (guix packages)
  23. #:use-module (guix derivations)
  24. #:use-module (guix store)
  25. #:use-module (guix monads)
  26. #:use-module (guix grafts)
  27. #:use-module (guix build-system gnu)
  28. #:use-module (guix build-system trivial)
  29. #:use-module (guix gexp)
  30. #:use-module (guix utils)
  31. #:use-module (gnu packages)
  32. #:use-module (gnu packages base)
  33. #:use-module (gnu packages bootstrap)
  34. #:use-module (gnu packages guile)
  35. #:use-module (gnu packages libunistring)
  36. #:use-module (gnu packages bootstrap)
  37. #:use-module (ice-9 match)
  38. #:use-module (srfi srfi-1)
  39. #:use-module (srfi srfi-11)
  40. #:use-module (srfi srfi-26)
  41. #:use-module (srfi srfi-64))
  42. (define %store
  43. (open-connection-for-tests))
  44. ;; Globally disable grafts because they can trigger early builds.
  45. (%graft? #f)
  46. (define (make-recording-backend)
  47. "Return a <graph-backend> and a thunk that returns the recorded nodes and
  48. edges."
  49. (let ((nodes '())
  50. (edges '()))
  51. (define (record-node id label port)
  52. (set! nodes (cons (list id label) nodes)))
  53. (define (record-edge source target port)
  54. (set! edges (cons (list source target) edges)))
  55. (define (return)
  56. (values (reverse nodes) (reverse edges)))
  57. (values (graph-backend "test" "This is the test backend."
  58. (const #t) (const #t)
  59. record-node record-edge)
  60. return)))
  61. (define (package->tuple package)
  62. "Return a tuple representing PACKAGE as produced by %PACKAGE-NODE-TYPE."
  63. (list (object-address package)
  64. (package-full-name package)))
  65. (define (edge->tuple source target)
  66. "Likewise for an edge from SOURCE to TARGET."
  67. (list (object-address source)
  68. (object-address target)))
  69. (test-begin "graph")
  70. (test-assert "package DAG"
  71. (let-values (((backend nodes+edges) (make-recording-backend)))
  72. (let* ((p1 (dummy-package "p1"))
  73. (p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
  74. (p3 (dummy-package "p3" (inputs `(("p2" ,p2) ("p1", p1))))))
  75. (run-with-store %store
  76. (export-graph (list p3) 'port
  77. #:node-type %package-node-type
  78. #:backend backend))
  79. ;; We should see nothing more than these 3 packages.
  80. (let-values (((nodes edges) (nodes+edges)))
  81. (and (equal? nodes (map package->tuple (list p3 p2 p1)))
  82. (equal? edges
  83. (map edge->tuple
  84. (list p3 p3 p2)
  85. (list p2 p1 p1))))))))
  86. (test-assert "reverse package DAG"
  87. (let-values (((backend nodes+edges) (make-recording-backend)))
  88. (run-with-store %store
  89. (export-graph (list libunistring) 'port
  90. #:node-type %reverse-package-node-type
  91. #:backend backend))
  92. ;; We should see nothing more than these 3 packages.
  93. (let-values (((nodes edges) (nodes+edges)))
  94. (and (member (package->tuple guile-2.0) nodes)
  95. (->bool (member (edge->tuple libunistring guile-2.0) edges))))))
  96. (test-assert "bag-emerged DAG"
  97. (let-values (((backend nodes+edges) (make-recording-backend)))
  98. (let* ((o (dummy-origin (method (lambda _
  99. (text-file "foo" "bar")))))
  100. (p (dummy-package "p" (source o)))
  101. (implicit (map (match-lambda
  102. ((label package) package)
  103. ((label package output) package))
  104. (standard-packages))))
  105. (run-with-store %store
  106. (export-graph (list p) 'port
  107. #:node-type %bag-emerged-node-type
  108. #:backend backend))
  109. ;; We should see exactly P and IMPLICIT, with one edge from P to each
  110. ;; element of IMPLICIT. O must not appear among NODES. Note: IMPLICIT
  111. ;; contains "glibc" twice, once for "out" and a second time for
  112. ;; "static", hence the 'delete-duplicates' call below.
  113. (let-values (((nodes edges) (nodes+edges)))
  114. (and (equal? (match nodes
  115. (((labels names) ...)
  116. names))
  117. (map package-full-name
  118. (cons p (delete-duplicates implicit))))
  119. (equal? (match edges
  120. (((sources destinations) ...)
  121. (zip (map store-path-package-name sources)
  122. (map store-path-package-name destinations))))
  123. (map (lambda (destination)
  124. (list "p-0.drv"
  125. (string-append
  126. (package-full-name destination "-")
  127. ".drv")))
  128. implicit)))))))
  129. (test-assert "bag DAG" ;a big town in Iraq
  130. (let-values (((backend nodes+edges) (make-recording-backend)))
  131. (let ((p (dummy-package "p")))
  132. (run-with-store %store
  133. (export-graph (list p) 'port
  134. #:node-type %bag-node-type
  135. #:backend backend))
  136. ;; We should see P, its implicit inputs as well as the whole DAG, which
  137. ;; should include bootstrap binaries.
  138. (let-values (((nodes edges) (nodes+edges)))
  139. (every (lambda (name)
  140. (find (cut string=? name <>)
  141. (match nodes
  142. (((labels names) ...)
  143. names))))
  144. (match (%bootstrap-inputs)
  145. (((labels packages) ...)
  146. (map package-full-name (filter package? packages)))))))))
  147. (test-assert "bag DAG, including origins"
  148. (let-values (((backend nodes+edges) (make-recording-backend)))
  149. (let* ((m (lambda* (uri hash-type hash name #:key system)
  150. (text-file "foo-1.2.3.tar.gz" "This is a fake!")))
  151. (o (origin
  152. (method m) (uri "the-uri")
  153. (sha256
  154. (base32
  155. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))))
  156. (p (dummy-package "p" (source o))))
  157. (run-with-store %store
  158. (export-graph (list p) 'port
  159. #:node-type %bag-with-origins-node-type
  160. #:backend backend))
  161. ;; We should see O among the nodes, with an edge coming from P.
  162. (let-values (((nodes edges) (nodes+edges)))
  163. (run-with-store %store
  164. (mlet %store-monad ((o* (lower-object o))
  165. (p* (lower-object p))
  166. (g (lower-object (default-guile))))
  167. (return
  168. (and (find (match-lambda
  169. ((file "the-uri") #t)
  170. (_ #f))
  171. nodes)
  172. (find (match-lambda
  173. ((source target)
  174. (and (string=? source (derivation-file-name p*))
  175. (string=? target o*))))
  176. edges)
  177. ;; There must also be an edge from O to G.
  178. (find (match-lambda
  179. ((source target)
  180. (and (string=? source o*)
  181. (string=? target (derivation-file-name g)))))
  182. edges)))))))))
  183. (test-assert "reverse bag DAG"
  184. (let-values (((dune bap ocaml-base)
  185. (values (specification->package "dune")
  186. (specification->package "bap")
  187. (specification->package "ocaml4.07-base")))
  188. ((backend nodes+edges) (make-recording-backend)))
  189. (run-with-store %store
  190. (export-graph (list dune) 'port
  191. #:node-type %reverse-bag-node-type
  192. #:backend backend))
  193. (run-with-store %store
  194. (mlet %store-monad ((dune-drv (package->derivation dune))
  195. (bap-drv (package->derivation bap))
  196. (ocaml-base-drv (package->derivation ocaml-base)))
  197. ;; OCAML-BASE uses 'dune-build-system' so DUNE is a direct dependency.
  198. ;; BAP is much higher in the stack but it should be there.
  199. (let-values (((nodes edges) (nodes+edges)))
  200. (return
  201. (and (member `(,(derivation-file-name bap-drv)
  202. ,(package-full-name bap))
  203. nodes)
  204. (->bool (member (map derivation-file-name
  205. (list dune-drv ocaml-base-drv))
  206. edges)))))))))
  207. (test-assert "derivation DAG"
  208. (let-values (((backend nodes+edges) (make-recording-backend)))
  209. (run-with-store %store
  210. (mlet* %store-monad ((txt (text-file "text-file" "Hello!"))
  211. (guile (package->derivation %bootstrap-guile))
  212. (drv (gexp->derivation "output"
  213. #~(symlink #$txt #$output)
  214. #:guile-for-build
  215. guile)))
  216. ;; We should get at least these 3 nodes and corresponding edges.
  217. (mbegin %store-monad
  218. (export-graph (list drv) 'port
  219. #:node-type %derivation-node-type
  220. #:backend backend)
  221. (let-values (((nodes edges) (nodes+edges)))
  222. ;; XXX: For some reason we need to throw in some 'basename'.
  223. (return (and (match nodes
  224. (((ids labels) ...)
  225. (let ((ids (map basename ids)))
  226. (every (lambda (item)
  227. (member (basename item) ids))
  228. (list txt
  229. (derivation-file-name drv)
  230. (derivation-file-name guile))))))
  231. (every (cut member <>
  232. (map (lambda (edge)
  233. (map basename edge))
  234. edges))
  235. (list (map (compose basename derivation-file-name)
  236. (list drv guile))
  237. (list (basename (derivation-file-name drv))
  238. (basename txt))))))))))))
  239. (test-assert "reference DAG"
  240. (let-values (((backend nodes+edges) (make-recording-backend)))
  241. (run-with-store %store
  242. (mlet* %store-monad ((txt (text-file "text-file" "Hello!"))
  243. (guile (package->derivation %bootstrap-guile))
  244. (drv (gexp->derivation "output"
  245. #~(symlink #$txt #$output)
  246. #:guile-for-build
  247. guile))
  248. (out -> (derivation->output-path drv)))
  249. ;; We should see only OUT and TXT, with an edge from the former to the
  250. ;; latter.
  251. (mbegin %store-monad
  252. (built-derivations (list drv))
  253. (export-graph (list (derivation->output-path drv)) 'port
  254. #:node-type %reference-node-type
  255. #:backend backend)
  256. (let-values (((nodes edges) (nodes+edges)))
  257. (return
  258. (and (equal? (match nodes
  259. (((ids labels) ...)
  260. ids))
  261. (list out txt))
  262. (equal? edges `((,out ,txt)))))))))))
  263. (test-assert "referrer DAG"
  264. (let-values (((backend nodes+edges) (make-recording-backend)))
  265. (run-with-store %store
  266. (mlet* %store-monad ((txt (text-file "referrer-node" (random-text)))
  267. (drv (gexp->derivation "referrer"
  268. #~(symlink #$txt #$output)))
  269. (out -> (derivation->output-path drv)))
  270. ;; We should see only TXT and OUT, with an edge from the former to the
  271. ;; latter.
  272. (mbegin %store-monad
  273. (built-derivations (list drv))
  274. (export-graph (list txt) 'port
  275. #:node-type %referrer-node-type
  276. #:backend backend)
  277. (let-values (((nodes edges) (nodes+edges)))
  278. (return
  279. (and (equal? (match nodes
  280. (((ids labels) ...)
  281. ids))
  282. (list txt out))
  283. (equal? edges `((,txt ,out)))))))))))
  284. (test-assert "module graph"
  285. (let-values (((backend nodes+edges) (make-recording-backend)))
  286. (run-with-store %store
  287. (export-graph '((gnu packages guile)) 'port
  288. #:node-type %module-node-type
  289. #:backend backend))
  290. (let-values (((nodes edges) (nodes+edges)))
  291. (and (member '(gnu packages guile)
  292. (match nodes
  293. (((ids labels) ...) ids)))
  294. (->bool (and (member (list '(gnu packages guile)
  295. '(gnu packages libunistring))
  296. edges)
  297. (member (list '(gnu packages guile)
  298. '(gnu packages bdw-gc))
  299. edges)))))))
  300. (test-assert "node-edges"
  301. (run-with-store %store
  302. (let ((packages (fold-packages cons '())))
  303. (mlet %store-monad ((edges (node-edges %package-node-type packages)))
  304. (return (and (null? (edges hello))
  305. (lset= eq?
  306. (edges guile-2.0)
  307. (match (package-direct-inputs guile-2.0)
  308. (((labels packages _ ...) ...)
  309. packages)))))))))
  310. (test-assert "node-transitive-edges + node-back-edges"
  311. (run-with-store %store
  312. (let ((packages (fold-packages cons '()))
  313. (bootstrap? (lambda (package)
  314. (string-contains
  315. (location-file (package-location package))
  316. "bootstrap.scm")))
  317. (trivial? (lambda (package)
  318. (eq? (package-build-system package)
  319. trivial-build-system))))
  320. (mlet %store-monad ((edges (node-back-edges %bag-node-type packages)))
  321. (let* ((glibc (canonical-package glibc))
  322. (dependents (node-transitive-edges (list glibc) edges))
  323. (diff (lset-difference eq? packages dependents)))
  324. ;; All the packages depend on libc, except bootstrap packages and
  325. ;; some that use TRIVIAL-BUILD-SYSTEM.
  326. (return (null? (remove (lambda (package)
  327. (or (trivial? package)
  328. (bootstrap? package)))
  329. diff))))))))
  330. (test-assert "node-transitive-edges, no duplicates"
  331. (run-with-store %store
  332. (let* ((p0 (dummy-package "p0"))
  333. (p1a (dummy-package "p1a" (inputs `(("p0" ,p0)))))
  334. (p1b (dummy-package "p1b" (inputs `(("p0" ,p0)))))
  335. (p2 (dummy-package "p2" (inputs `(("p1a" ,p1a) ("p1b" ,p1b))))))
  336. (mlet %store-monad ((edges (node-edges %package-node-type
  337. (list p2 p1a p1b p0))))
  338. (return (lset= eq? (node-transitive-edges (list p2) edges)
  339. (list p1a p1b p0)))))))
  340. (test-assert "node-transitive-edges, references"
  341. (run-with-store %store
  342. (mlet* %store-monad ((d0 (package->derivation %bootstrap-guile))
  343. (d1 (gexp->derivation "d1"
  344. #~(begin
  345. (mkdir #$output)
  346. (symlink #$%bootstrap-guile
  347. (string-append
  348. #$output "/l")))))
  349. (d2 (gexp->derivation "d2"
  350. #~(begin
  351. (mkdir #$output)
  352. (symlink #$d1
  353. (string-append
  354. #$output "/l")))))
  355. (_ (built-derivations (list d2)))
  356. (->node -> (node-type-convert %reference-node-type))
  357. (o2 (->node (derivation->output-path d2)))
  358. (o1 (->node (derivation->output-path d1)))
  359. (o0 (->node (derivation->output-path d0)))
  360. (edges (node-edges %reference-node-type
  361. (append o0 o1 o2)))
  362. (reqs ((store-lift requisites) o2)))
  363. (return (lset= string=?
  364. (append o2 (node-transitive-edges o2 edges)) reqs)))))
  365. (test-equal "node-reachable-count"
  366. '(3 3)
  367. (run-with-store %store
  368. (let* ((p0 (dummy-package "p0"))
  369. (p1a (dummy-package "p1a" (inputs `(("p0" ,p0)))))
  370. (p1b (dummy-package "p1b" (inputs `(("p0" ,p0)))))
  371. (p2 (dummy-package "p2" (inputs `(("p1a" ,p1a) ("p1b" ,p1b))))))
  372. (mlet* %store-monad ((all -> (list p2 p1a p1b p0))
  373. (edges (node-edges %package-node-type all))
  374. (back (node-back-edges %package-node-type all)))
  375. (return (list (node-reachable-count (list p2) edges)
  376. (node-reachable-count (list p0) back)))))))
  377. (test-equal "shortest-path, packages + derivations"
  378. '(("p5" "p4" "p1" "p0")
  379. ("p3" "p2" "p1" "p0")
  380. #f
  381. ("p5-0.drv" "p4-0.drv" "p1-0.drv" "p0-0.drv"))
  382. (run-with-store %store
  383. (let* ((p0 (dummy-package "p0"))
  384. (p1 (dummy-package "p1" (inputs `(("p0" ,p0)))))
  385. (p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
  386. (p3 (dummy-package "p3" (inputs `(("p2" ,p2)))))
  387. (p4 (dummy-package "p4" (inputs `(("p1" ,p1)))))
  388. (p5 (dummy-package "p5" (inputs `(("p4" ,p4) ("p3" ,p3))))))
  389. (mlet* %store-monad ((path1 (shortest-path p5 p0 %package-node-type))
  390. (path2 (shortest-path p3 p0 %package-node-type))
  391. (nope (shortest-path p3 p4 %package-node-type))
  392. (drv5 (package->derivation p5))
  393. (drv0 (package->derivation p0))
  394. (path3 (shortest-path drv5 drv0
  395. %derivation-node-type)))
  396. (return (append (map (lambda (path)
  397. (and path (map package-name path)))
  398. (list path1 path2 nope))
  399. (list (map (node-type-label %derivation-node-type)
  400. path3))))))))
  401. (test-equal "shortest-path, reverse packages"
  402. '("libffi" "guile" "guile-json")
  403. (run-with-store %store
  404. (mlet %store-monad ((path (shortest-path (specification->package "libffi")
  405. guile-json
  406. %reverse-package-node-type)))
  407. (return (map package-name path)))))
  408. (test-equal "shortest-path, references"
  409. `(("d2" "d1" ,(package-full-name %bootstrap-guile "-"))
  410. (,(package-full-name %bootstrap-guile "-") "d1" "d2"))
  411. (run-with-store %store
  412. (mlet* %store-monad ((d0 (package->derivation %bootstrap-guile))
  413. (d1 (gexp->derivation "d1"
  414. #~(begin
  415. (mkdir #$output)
  416. (symlink #$%bootstrap-guile
  417. (string-append
  418. #$output "/l")))))
  419. (d2 (gexp->derivation "d2"
  420. #~(begin
  421. (mkdir #$output)
  422. (symlink #$d1
  423. (string-append
  424. #$output "/l")))))
  425. (_ (built-derivations (list d2)))
  426. (->node -> (node-type-convert %reference-node-type))
  427. (o2 (->node (derivation->output-path d2)))
  428. (o0 (->node (derivation->output-path d0)))
  429. (path (shortest-path (first o2) (first o0)
  430. %reference-node-type))
  431. (rpath (shortest-path (first o0) (first o2)
  432. %referrer-node-type)))
  433. (return (list (map (node-type-label %reference-node-type) path)
  434. (map (node-type-label %referrer-node-type) rpath))))))
  435. (test-end "graph")