gtk and wayland update
[jackhill/guix/guix.git] / tests / publish.scm
CommitLineData
aff8ce7c
DT
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2015 David Thompson <davet@gnu.org>
6955cff9 3;;; Copyright © 2020 by Amar M. Singh <nly@disroot.org>
435ffae2 4;;; Copyright © 2016-2022 Ludovic Courtès <ludo@gnu.org>
aff8ce7c
DT
5;;;
6;;; This file is part of GNU Guix.
7;;;
8;;; GNU Guix is free software; you can redistribute it and/or modify it
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
13;;; GNU Guix is distributed in the hope that it will be useful, but
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
c74f0cb2
LC
21;; Avoid interference.
22(unsetenv "http_proxy")
23
aff8ce7c
DT
24(define-module (test-publish)
25 #:use-module (guix scripts publish)
26 #:use-module (guix tests)
27 #:use-module (guix config)
28 #:use-module (guix utils)
ca719424 29 #:use-module (gcrypt hash)
aff8ce7c 30 #:use-module (guix store)
ff6638d1
LC
31 #:use-module (guix derivations)
32 #:use-module (guix gexp)
aff8ce7c
DT
33 #:use-module (guix base32)
34 #:use-module (guix base64)
4a1fc562 35 #:use-module ((guix records) #:select (recutils->alist))
aff8ce7c 36 #:use-module ((guix serialization) #:select (restore-file))
ca719424 37 #:use-module (gcrypt pk-crypto)
ab2a74e4 38 #:use-module ((guix pki) #:select (%public-key-file %private-key-file))
4c0c65ac
MO
39 #:use-module (zlib)
40 #:use-module (lzlib)
d288a4de 41 #:autoload (zstd) (call-with-zstd-input-port)
93961f02 42 #:use-module (web uri)
aff8ce7c 43 #:use-module (web client)
c1719a0a 44 #:use-module (web request)
aff8ce7c 45 #:use-module (web response)
c1719a0a 46 #:use-module ((guix http-client) #:select (http-multiple-get))
aff8ce7c 47 #:use-module (rnrs bytevectors)
4a1fc562 48 #:use-module (ice-9 binary-ports)
aff8ce7c
DT
49 #:use-module (srfi srfi-1)
50 #:use-module (srfi srfi-26)
51 #:use-module (srfi srfi-64)
c1719a0a 52 #:use-module (srfi srfi-71)
fde60bfb 53 #:use-module (ice-9 threads)
93961f02 54 #:use-module (ice-9 format)
aff8ce7c
DT
55 #:use-module (ice-9 match)
56 #:use-module (ice-9 rdelim))
57
58(define %store
59 (open-connection-for-tests))
60
d288a4de
LC
61(define (zstd-supported?)
62 (resolve-module '(zstd) #t #f #:ensure #f))
63
aff8ce7c
DT
64(define %reference (add-text-to-store %store "ref" "foo"))
65
66(define %item (add-text-to-store %store "item" "bar" (list %reference)))
67
68(define (http-get-body uri)
69 (call-with-values (lambda () (http-get uri))
70 (lambda (response body) body)))
71
4a1fc562 72(define (http-get-port uri)
37402ecb
LC
73 (let ((socket (open-socket-for-uri uri)))
74 ;; Make sure to use an unbuffered port so that we can then peek at the
75 ;; underlying file descriptor via 'call-with-gzip-input-port'.
76832d34 76 (setvbuf socket 'none)
37402ecb
LC
77 (call-with-values
78 (lambda ()
79 (http-get uri #:port socket #:streaming? #t))
80 (lambda (response port)
76832d34 81 ;; Don't (setvbuf port 'none) because of <http://bugs.gnu.org/19610>
2c7b48c2 82 ;; (PORT might be a custom binary input port).
37402ecb 83 port))))
4a1fc562 84
aff8ce7c
DT
85(define (publish-uri route)
86 (string-append "http://localhost:6789" route))
87
a5c37603
LC
88(define-syntax-rule (with-separate-output-ports exp ...)
89 ;; Since ports aren't thread-safe in Guile 2.0, duplicate the output and
90 ;; error ports to make sure the two threads don't end up stepping on each
91 ;; other's toes.
92 (with-output-to-port (duplicate-port (current-output-port) "w")
93 (lambda ()
94 (with-error-to-port (duplicate-port (current-error-port) "w")
95 (lambda ()
96 exp ...)))))
97
aff8ce7c 98;; Run a local publishing server in a separate thread.
a5c37603
LC
99(with-separate-output-ports
100 (call-with-new-thread
101 (lambda ()
102 (guix-publish "--port=6789" "-C0")))) ;attempt to avoid port collision
4a1fc562
LC
103
104(define (wait-until-ready port)
105 ;; Wait until the server is accepting connections.
106 (let ((conn (socket PF_INET SOCK_STREAM 0)))
107 (let loop ()
108 (unless (false-if-exception
109 (connect conn AF_INET (inet-pton AF_INET "127.0.0.1") port))
110 (loop)))))
aff8ce7c 111
ffa5e0a6
LC
112(define (wait-for-file file)
113 ;; Wait until FILE shows up.
114 (let loop ((i 20))
115 (cond ((file-exists? file)
116 #t)
117 ((zero? i)
118 (error "file didn't show up" file))
119 (else
120 (pk 'wait-for-file file)
121 (sleep 1)
122 (loop (- i 1))))))
123
33988f9b
LC
124(define %gzip-magic-bytes
125 ;; Magic bytes of gzip file.
126 #vu8(#x1f #x8b))
127
4a1fc562
LC
128;; Wait until the two servers are ready.
129(wait-until-ready 6789)
aff8ce7c 130
ab2a74e4
LC
131;; Initialize the public/private key SRFI-39 parameters.
132(%public-key (read-file-sexp %public-key-file))
133(%private-key (read-file-sexp %private-key-file))
134
c74f0cb2 135\f
aff8ce7c
DT
136(test-begin "publish")
137
138(test-equal "/nix-cache-info"
139 (format #f "StoreDir: ~a\nWantMassQuery: 0\nPriority: 100\n"
140 %store-directory)
141 (http-get-body (publish-uri "/nix-cache-info")))
142
143(test-equal "/*.narinfo"
144 (let* ((info (query-path-info %store %item))
145 (unsigned-info
146 (format #f
147 "StorePath: ~a
aff8ce7c
DT
148NarHash: sha256:~a
149NarSize: ~d
b8fa86ad 150References: ~a~%"
aff8ce7c 151 %item
9cced526 152 (bytevector->nix-base32-string
aff8ce7c
DT
153 (path-info-hash info))
154 (path-info-nar-size info)
b8fa86ad 155 (basename (first (path-info-references info)))))
aff8ce7c
DT
156 (signature (base64-encode
157 (string->utf8
158 (canonical-sexp->string
4fe01b09 159 (signed-string unsigned-info))))))
6adce153
LC
160 (format #f "~aSignature: 1;~a;~a
161URL: nar/~a
162Compression: none
163FileSize: ~a\n"
164 unsigned-info (gethostname) signature
165 (basename %item)
166 (path-info-nar-size info)))
aff8ce7c
DT
167 (utf8->string
168 (http-get-body
169 (publish-uri
170 (string-append "/" (store-path-hash-part %item) ".narinfo")))))
171
c1719a0a
LC
172(test-equal "/*.narinfo pipeline"
173 (make-list 500 200)
174 ;; Make sure clients can pipeline requests and correct responses, in the
175 ;; right order. See <https://issues.guix.gnu.org/54723>.
176 (let* ((uri (string->uri (publish-uri
177 (string-append "/"
178 (store-path-hash-part %item)
179 ".narinfo"))))
180 (_ expected (http-get uri #:streaming? #f #:decode-body? #f)))
181 (http-multiple-get (string->uri (publish-uri ""))
182 (lambda (request response port result)
183 (and (bytevector=? expected
184 (get-bytevector-n port
185 (response-content-length
186 response)))
187 (cons (response-code response) result)))
188 '()
189 (make-list 500 (build-request uri))
190 #:batch-size 77)))
191
93961f02
LC
192(test-equal "/*.narinfo with properly encoded '+' sign"
193 ;; See <http://bugs.gnu.org/21888>.
194 (let* ((item (add-text-to-store %store "fake-gtk+" "Congrats!"))
195 (info (query-path-info %store item))
196 (unsigned-info
197 (format #f
198 "StorePath: ~a
93961f02
LC
199NarHash: sha256:~a
200NarSize: ~d
b8fa86ad 201References: ~%"
93961f02 202 item
93961f02
LC
203 (bytevector->nix-base32-string
204 (path-info-hash info))
205 (path-info-nar-size info)))
206 (signature (base64-encode
207 (string->utf8
208 (canonical-sexp->string
4fe01b09 209 (signed-string unsigned-info))))))
6adce153
LC
210 (format #f "~aSignature: 1;~a;~a
211URL: nar/~a
212Compression: none
213FileSize: ~a~%"
214 unsigned-info (gethostname) signature
215 (uri-encode (basename item))
216 (path-info-nar-size info)))
93961f02
LC
217
218 (let ((item (add-text-to-store %store "fake-gtk+" "Congrats!")))
219 (utf8->string
220 (http-get-body
221 (publish-uri
222 (string-append "/" (store-path-hash-part item) ".narinfo"))))))
223
aff8ce7c
DT
224(test-equal "/nar/*"
225 "bar"
226 (call-with-temporary-output-file
227 (lambda (temp port)
228 (let ((nar (utf8->string
229 (http-get-body
230 (publish-uri
231 (string-append "/nar/" (basename %item)))))))
232 (call-with-input-string nar (cut restore-file <> temp)))
233 (call-with-input-file temp read-string))))
234
4a1fc562
LC
235(test-equal "/nar/gzip/*"
236 "bar"
237 (call-with-temporary-output-file
238 (lambda (temp port)
239 (let ((nar (http-get-port
240 (publish-uri
241 (string-append "/nar/gzip/" (basename %item))))))
242 (call-with-gzip-input-port nar
243 (cut restore-file <> temp)))
244 (call-with-input-file temp read-string))))
245
33988f9b
LC
246(test-equal "/nar/gzip/* is really gzip"
247 %gzip-magic-bytes
248 ;; Since 'gzdopen' (aka. 'call-with-gzip-input-port') transparently reads
249 ;; uncompressed gzip, the test above doesn't check whether it's actually
250 ;; gzip. This is what this test does. See <https://bugs.gnu.org/30184>.
251 (let ((nar (http-get-port
252 (publish-uri
253 (string-append "/nar/gzip/" (basename %item))))))
254 (get-bytevector-n nar (bytevector-length %gzip-magic-bytes))))
255
66229b04
LC
256(test-equal "/nar/lzip/*"
257 "bar"
258 (call-with-temporary-output-file
259 (lambda (temp port)
260 (let ((nar (http-get-port
261 (publish-uri
262 (string-append "/nar/lzip/" (basename %item))))))
263 (call-with-lzip-input-port nar
264 (cut restore-file <> temp)))
265 (call-with-input-file temp read-string))))
266
d288a4de
LC
267(unless (zstd-supported?) (test-skip 1))
268(test-equal "/nar/zstd/*"
269 "bar"
270 (call-with-temporary-output-file
271 (lambda (temp port)
272 (let ((nar (http-get-port
273 (publish-uri
274 (string-append "/nar/zstd/" (basename %item))))))
275 (call-with-zstd-input-port nar
276 (cut restore-file <> temp)))
277 (call-with-input-file temp read-string))))
278
4a1fc562
LC
279(test-equal "/*.narinfo with compression"
280 `(("StorePath" . ,%item)
281 ("URL" . ,(string-append "nar/gzip/" (basename %item)))
282 ("Compression" . "gzip"))
a5c37603
LC
283 (let ((thread (with-separate-output-ports
284 (call-with-new-thread
285 (lambda ()
286 (guix-publish "--port=6799" "-C5"))))))
4a1fc562
LC
287 (wait-until-ready 6799)
288 (let* ((url (string-append "http://localhost:6799/"
289 (store-path-hash-part %item) ".narinfo"))
290 (body (http-get-port url)))
291 (filter (lambda (item)
292 (match item
293 (("Compression" . _) #t)
294 (("StorePath" . _) #t)
295 (("URL" . _) #t)
296 (_ #f)))
297 (recutils->alist body)))))
298
66229b04
LC
299(test-equal "/*.narinfo with lzip compression"
300 `(("StorePath" . ,%item)
301 ("URL" . ,(string-append "nar/lzip/" (basename %item)))
302 ("Compression" . "lzip"))
303 (let ((thread (with-separate-output-ports
304 (call-with-new-thread
305 (lambda ()
306 (guix-publish "--port=6790" "-Clzip"))))))
307 (wait-until-ready 6790)
308 (let* ((url (string-append "http://localhost:6790/"
309 (store-path-hash-part %item) ".narinfo"))
310 (body (http-get-port url)))
311 (filter (lambda (item)
312 (match item
313 (("Compression" . _) #t)
314 (("StorePath" . _) #t)
315 (("URL" . _) #t)
316 (_ #f)))
317 (recutils->alist body)))))
318
089b1678
LC
319(test-equal "/*.narinfo for a compressed file"
320 '("none" "nar") ;compression-less nar
321 ;; Assume 'guix publish -C' is already running on port 6799.
322 (let* ((item (add-text-to-store %store "fake.tar.gz"
323 "This is a fake compressed file."))
324 (url (string-append "http://localhost:6799/"
325 (store-path-hash-part item) ".narinfo"))
326 (body (http-get-port url))
327 (info (recutils->alist body)))
328 (list (assoc-ref info "Compression")
329 (dirname (assoc-ref info "URL")))))
330
b8fa86ad
LC
331(test-equal "/*.narinfo with lzip + gzip"
332 `((("StorePath" . ,%item)
333 ("URL" . ,(string-append "nar/gzip/" (basename %item)))
334 ("Compression" . "gzip")
335 ("URL" . ,(string-append "nar/lzip/" (basename %item)))
336 ("Compression" . "lzip"))
337 200
338 200)
339 (call-with-temporary-directory
340 (lambda (cache)
341 (let ((thread (with-separate-output-ports
342 (call-with-new-thread
343 (lambda ()
344 (guix-publish "--port=6793" "-Cgzip:2" "-Clzip:2"))))))
345 (wait-until-ready 6793)
346 (let* ((base "http://localhost:6793/")
347 (part (store-path-hash-part %item))
348 (url (string-append base part ".narinfo"))
349 (body (http-get-port url)))
6adce153
LC
350 (list (filter (match-lambda
351 (("StorePath" . _) #t)
352 (("URL" . _) #t)
353 (("Compression" . _) #t)
354 (_ #f))
355 (recutils->alist body))
b8fa86ad
LC
356 (response-code
357 (http-get (string-append base "nar/gzip/"
358 (basename %item))))
359 (response-code
360 (http-get (string-append base "nar/lzip/"
361 (basename %item))))))))))
362
cdd7a7d2
LC
363(test-equal "custom nar path"
364 ;; Serve nars at /foo/bar/chbouib instead of /nar.
365 (list `(("StorePath" . ,%item)
366 ("URL" . ,(string-append "foo/bar/chbouib/" (basename %item)))
367 ("Compression" . "none"))
368 200
369 404)
370 (let ((thread (with-separate-output-ports
371 (call-with-new-thread
372 (lambda ()
373 (guix-publish "--port=6798" "-C0"
374 "--nar-path=///foo/bar//chbouib/"))))))
375 (wait-until-ready 6798)
376 (let* ((base "http://localhost:6798/")
377 (part (store-path-hash-part %item))
378 (url (string-append base part ".narinfo"))
379 (nar-url (string-append base "foo/bar/chbouib/"
380 (basename %item)))
381 (body (http-get-port url)))
382 (list (filter (lambda (item)
383 (match item
384 (("Compression" . _) #t)
385 (("StorePath" . _) #t)
386 (("URL" . _) #t)
387 (_ #f)))
388 (recutils->alist body))
389 (response-code (http-get nar-url))
390 (response-code
391 (http-get (string-append base "nar/" (basename %item))))))))
392
93961f02
LC
393(test-equal "/nar/ with properly encoded '+' sign"
394 "Congrats!"
395 (let ((item (add-text-to-store %store "fake-gtk+" "Congrats!")))
396 (call-with-temporary-output-file
397 (lambda (temp port)
398 (let ((nar (utf8->string
399 (http-get-body
400 (publish-uri
401 (string-append "/nar/" (uri-encode (basename item))))))))
402 (call-with-input-string nar (cut restore-file <> temp)))
403 (call-with-input-file temp read-string)))))
404
00435580
LC
405(test-equal "/nar/invalid"
406 404
407 (begin
408 (call-with-output-file (string-append (%store-prefix) "/invalid")
409 (lambda (port)
410 (display "This file is not a valid store item." port)))
411 (response-code (http-get (publish-uri (string-append "/nar/invalid"))))))
412
ff6638d1
LC
413(test-equal "/file/NAME/sha256/HASH"
414 "Hello, Guix world!"
415 (let* ((data "Hello, Guix world!")
416 (hash (call-with-input-string data port-sha256))
417 (drv (run-with-store %store
418 (gexp->derivation "the-file.txt"
419 #~(call-with-output-file #$output
420 (lambda (port)
421 (display #$data port)))
422 #:hash-algo 'sha256
423 #:hash hash)))
424 (out (build-derivations %store (list drv))))
425 (utf8->string
426 (http-get-body
427 (publish-uri
428 (string-append "/file/the-file.txt/sha256/"
429 (bytevector->nix-base32-string hash)))))))
430
431(test-equal "/file/NAME/sha256/INVALID-NIX-BASE32-STRING"
432 404
433 (let ((uri (publish-uri
434 "/file/the-file.txt/sha256/not-a-nix-base32-string")))
435 (response-code (http-get uri))))
436
437(test-equal "/file/NAME/sha256/INVALID-HASH"
438 404
439 (let ((uri (publish-uri
440 (string-append "/file/the-file.txt/sha256/"
441 (bytevector->nix-base32-string
442 (call-with-input-string "" port-sha256))))))
443 (response-code (http-get uri))))
444
00753f70
LC
445(test-equal "with cache"
446 (list #t
447 `(("StorePath" . ,%item)
448 ("URL" . ,(string-append "nar/gzip/" (basename %item)))
449 ("Compression" . "gzip"))
450 200 ;nar/gzip/…
451 #t ;Content-Length
dff3189c 452 #t ;FileSize
e5788ebb 453 404) ;nar/…
00753f70
LC
454 (call-with-temporary-directory
455 (lambda (cache)
00753f70
LC
456 (let ((thread (with-separate-output-ports
457 (call-with-new-thread
458 (lambda ()
459 (guix-publish "--port=6797" "-C2"
ecaa102a
LC
460 (string-append "--cache=" cache)
461 "--cache-bypass-threshold=0"))))))
00753f70
LC
462 (wait-until-ready 6797)
463 (let* ((base "http://localhost:6797/")
464 (part (store-path-hash-part %item))
465 (url (string-append base part ".narinfo"))
2a405034 466 (nar-url (string-append base "nar/gzip/" (basename %item)))
00753f70
LC
467 (cached (string-append cache "/gzip/" (basename %item)
468 ".narinfo"))
469 (nar (string-append cache "/gzip/"
470 (basename %item) ".nar"))
471 (response (http-get url)))
472 (and (= 404 (response-code response))
24b21720
LC
473
474 ;; We should get an explicitly short TTL for 404 in this case
475 ;; because it's going to become 200 shortly.
476 (match (assq-ref (response-headers response) 'cache-control)
477 ((('max-age . ttl))
478 (< ttl 3600)))
479
00753f70 480 (wait-for-file cached)
464e7437
LC
481
482 ;; Both the narinfo and nar should be world-readable.
c536f0b2
CG
483 (= #o444 (logand #o444 (stat:perms (lstat cached))))
484 (= #o444 (logand #o444 (stat:perms (lstat nar))))
464e7437 485
dff3189c
LC
486 (let* ((body (http-get-port url))
487 (compressed (http-get nar-url))
488 (uncompressed (http-get (string-append base "nar/"
489 (basename %item))))
490 (narinfo (recutils->alist body)))
00753f70
LC
491 (list (file-exists? nar)
492 (filter (lambda (item)
493 (match item
494 (("Compression" . _) #t)
495 (("StorePath" . _) #t)
496 (("URL" . _) #t)
497 (_ #f)))
dff3189c 498 narinfo)
00753f70
LC
499 (response-code compressed)
500 (= (response-content-length compressed)
501 (stat:size (stat nar)))
dff3189c
LC
502 (= (string->number
503 (assoc-ref narinfo "FileSize"))
504 (stat:size (stat nar)))
00753f70
LC
505 (response-code uncompressed)))))))))
506
b8fa86ad
LC
507(test-equal "with cache, lzip + gzip"
508 '(200 200 404)
509 (call-with-temporary-directory
510 (lambda (cache)
511 (let ((thread (with-separate-output-ports
512 (call-with-new-thread
513 (lambda ()
514 (guix-publish "--port=6794" "-Cgzip:2" "-Clzip:2"
ecaa102a
LC
515 (string-append "--cache=" cache)
516 "--cache-bypass-threshold=0"))))))
b8fa86ad
LC
517 (wait-until-ready 6794)
518 (let* ((base "http://localhost:6794/")
519 (part (store-path-hash-part %item))
520 (url (string-append base part ".narinfo"))
521 (nar-url (cute string-append "nar/" <> "/"
522 (basename %item)))
523 (cached (cute string-append cache "/" <> "/"
524 (basename %item) ".narinfo"))
525 (nar (cute string-append cache "/" <> "/"
526 (basename %item) ".nar"))
527 (response (http-get url)))
528 (wait-for-file (cached "gzip"))
529 (let* ((body (http-get-port url))
530 (narinfo (recutils->alist body))
531 (uncompressed (string-append base "nar/"
532 (basename %item))))
533 (and (file-exists? (nar "gzip"))
534 (file-exists? (nar "lzip"))
6adce153
LC
535 (match (pk 'narinfo/gzip+lzip narinfo)
536 ((("StorePath" . path)
537 _ ...
538 ("Signature" . _)
539 ("URL" . gzip-url)
540 ("Compression" . "gzip")
541 ("FileSize" . (= string->number gzip-size))
542 ("URL" . lzip-url)
543 ("Compression" . "lzip")
544 ("FileSize" . (= string->number lzip-size)))
545 (and (string=? gzip-url (nar-url "gzip"))
546 (string=? lzip-url (nar-url "lzip"))
547 (= gzip-size
548 (stat:size (stat (nar "gzip"))))
549 (= lzip-size
550 (stat:size (stat (nar "lzip")))))))
b8fa86ad
LC
551 (list (response-code
552 (http-get (string-append base (nar-url "gzip"))))
553 (response-code
554 (http-get (string-append base (nar-url "lzip"))))
555 (response-code
556 (http-get uncompressed))))))))))
557
ffa5e0a6
LC
558(let ((item (add-text-to-store %store "fake-compressed-thing.tar.gz"
559 (random-text))))
560 (test-equal "with cache, uncompressed"
e5788ebb 561 (list #t
9b9de084 562 (* 42 3600) ;TTL on narinfo
ffa5e0a6
LC
563 `(("StorePath" . ,item)
564 ("URL" . ,(string-append "nar/" (basename item)))
565 ("Compression" . "none"))
566 200 ;nar/…
9b9de084 567 (* 42 3600) ;TTL on nar/…
ffa5e0a6
LC
568 (path-info-nar-size
569 (query-path-info %store item)) ;FileSize
570 404) ;nar/gzip/…
571 (call-with-temporary-directory
572 (lambda (cache)
573 (let ((thread (with-separate-output-ports
574 (call-with-new-thread
575 (lambda ()
9b9de084 576 (guix-publish "--port=6796" "-C2" "--ttl=42h"
ecaa102a
LC
577 (string-append "--cache=" cache)
578 "--cache-bypass-threshold=0"))))))
ffa5e0a6
LC
579 (wait-until-ready 6796)
580 (let* ((base "http://localhost:6796/")
581 (part (store-path-hash-part item))
582 (url (string-append base part ".narinfo"))
583 (cached (string-append cache "/none/"
584 (basename item) ".narinfo"))
585 (nar (string-append cache "/none/"
586 (basename item) ".nar"))
587 (response (http-get url)))
588 (and (= 404 (response-code response))
589
590 (wait-for-file cached)
9b9de084
LC
591 (let* ((response (http-get url))
592 (body (http-get-port url))
ffa5e0a6
LC
593 (compressed (http-get (string-append base "nar/gzip/"
594 (basename item))))
595 (uncompressed (http-get (string-append base "nar/"
596 (basename item))))
597 (narinfo (recutils->alist body)))
598 (list (file-exists? nar)
9b9de084
LC
599 (match (assq-ref (response-headers response)
600 'cache-control)
601 ((('max-age . ttl)) ttl)
602 (_ #f))
603
ffa5e0a6
LC
604 (filter (lambda (item)
605 (match item
606 (("Compression" . _) #t)
607 (("StorePath" . _) #t)
608 (("URL" . _) #t)
609 (_ #f)))
610 narinfo)
611 (response-code uncompressed)
9b9de084
LC
612 (match (assq-ref (response-headers uncompressed)
613 'cache-control)
614 ((('max-age . ttl)) ttl)
615 (_ #f))
616
ffa5e0a6
LC
617 (string->number
618 (assoc-ref narinfo "FileSize"))
619 (response-code compressed))))))))))
620
493375cd
LC
621(test-equal "with cache, vanishing item" ;<https://bugs.gnu.org/33897>
622 200
623 (call-with-temporary-directory
624 (lambda (cache)
625 (let ((thread (with-separate-output-ports
626 (call-with-new-thread
627 (lambda ()
628 (guix-publish "--port=6795"
629 (string-append "--cache=" cache)))))))
630 (wait-until-ready 6795)
631
632 ;; Make sure that, even if ITEM disappears, we're still able to fetch
633 ;; it.
634 (let* ((base "http://localhost:6795/")
635 (item (add-text-to-store %store "random" (random-text)))
636 (part (store-path-hash-part item))
637 (url (string-append base part ".narinfo"))
4c0c65ac 638 (cached (string-append cache "/gzip/"
493375cd
LC
639 (basename item)
640 ".narinfo"))
641 (response (http-get url)))
ecaa102a 642 (and (= 200 (response-code response)) ;we're below the threshold
493375cd
LC
643 (wait-for-file cached)
644 (begin
645 (delete-paths %store (list item))
646 (response-code (pk 'response (http-get url))))))))))
647
ecaa102a
LC
648(test-equal "with cache, cache bypass"
649 200
650 (call-with-temporary-directory
651 (lambda (cache)
652 (let ((thread (with-separate-output-ports
653 (call-with-new-thread
654 (lambda ()
655 (guix-publish "--port=6788" "-C" "gzip"
656 (string-append "--cache=" cache)))))))
657 (wait-until-ready 6788)
658
659 (let* ((base "http://localhost:6788/")
660 (item (add-text-to-store %store "random" (random-text)))
661 (part (store-path-hash-part item))
662 (narinfo (string-append base part ".narinfo"))
663 (nar (string-append base "nar/gzip/" (basename item)))
664 (cached (string-append cache "/gzip/" (basename item)
665 ".narinfo")))
666 ;; We're below the default cache bypass threshold, so NAR and NARINFO
667 ;; should immediately return 200. The NARINFO request should trigger
668 ;; caching, and the next request to NAR should return 200 as well.
669 (and (let ((response (pk 'r1 (http-get nar))))
670 (and (= 200 (response-code response))
671 (not (response-content-length response)))) ;not known
672 (= 200 (response-code (http-get narinfo)))
673 (begin
674 (wait-for-file cached)
675 (let ((response (pk 'r2 (http-get nar))))
676 (and (> (response-content-length response)
677 (stat:size (stat item)))
678 (response-code response))))))))))
679
5e7cf66f
LC
680(test-equal "with cache, cache bypass, unmapped hash part"
681 200
682
683 ;; This test reproduces the bug described in <https://bugs.gnu.org/44442>:
684 ;; the daemon connection would be closed as a side effect of a nar request
685 ;; for a non-existing file name.
686 (call-with-temporary-directory
687 (lambda (cache)
688 (let ((thread (with-separate-output-ports
689 (call-with-new-thread
690 (lambda ()
691 (guix-publish "--port=6787" "-C" "gzip"
692 (string-append "--cache=" cache)))))))
693 (wait-until-ready 6787)
694
695 (let* ((base "http://localhost:6787/")
696 (item (add-text-to-store %store "random" (random-text)))
697 (part (store-path-hash-part item))
698 (narinfo (string-append base part ".narinfo"))
699 (nar (string-append base "nar/gzip/" (basename item)))
700 (cached (string-append cache "/gzip/" (basename item)
701 ".narinfo")))
702 ;; The first response used to be 500 and to terminate the daemon
703 ;; connection as a side effect.
704 (and (= (response-code
705 (http-get (string-append base "nar/gzip/"
706 (make-string 32 #\e)
707 "-does-not-exist")))
708 404)
709 (= 200 (response-code (http-get nar)))
710 (= 200 (response-code (http-get narinfo)))
711 (begin
712 (wait-for-file cached)
713 (response-code (http-get nar)))))))))
714
c04ffadb 715(test-equal "/log/NAME"
435ffae2 716 `(200 #t text/plain (gzip))
c04ffadb
LC
717 (let ((drv (run-with-store %store
718 (gexp->derivation "with-log"
719 #~(call-with-output-file #$output
720 (lambda (port)
721 (display "Hello, build log!"
722 (current-error-port))
04d2a16c 723 (display #$(random-text) port)))))))
c04ffadb
LC
724 (build-derivations %store (list drv))
725 (let* ((response (http-get
726 (publish-uri (string-append "/log/"
727 (basename (derivation->output-path drv))))
728 #:decode-body? #f))
729 (base (basename (derivation-file-name drv)))
730 (log (string-append (dirname %state-directory)
731 "/log/guix/drvs/" (string-take base 2)
435ffae2 732 "/" (string-drop base 2) ".gz")))
c04ffadb
LC
733 (list (response-code response)
734 (= (response-content-length response) (stat:size (stat log)))
435ffae2
LC
735 (first (response-content-type response))
736 (response-content-encoding response)))))
c04ffadb 737
938ffcbb
LC
738(test-equal "negative TTL"
739 `(404 42)
740
741 (call-with-temporary-directory
742 (lambda (cache)
743 (let ((thread (with-separate-output-ports
744 (call-with-new-thread
745 (lambda ()
746 (guix-publish "--port=6786" "-C0"
747 "--negative-ttl=42s"))))))
748 (wait-until-ready 6786)
749
750 (let* ((base "http://localhost:6786/")
751 (url (string-append base (make-string 32 #\z)
752 ".narinfo"))
753 (response (http-get url)))
754 (list (response-code response)
755 (match (assq-ref (response-headers response) 'cache-control)
756 ((('max-age . ttl)) ttl)
757 (_ #f))))))))
758
759(test-equal "no negative TTL"
760 `(404 #f)
761 (let* ((uri (publish-uri
762 (string-append "/" (make-string 32 #\z)
763 ".narinfo")))
764 (response (http-get uri)))
765 (list (response-code response)
766 (assq-ref (response-headers response) 'cache-control))))
767
c04ffadb
LC
768(test-equal "/log/NAME not found"
769 404
770 (let ((uri (publish-uri "/log/does-not-exist")))
771 (response-code (http-get uri))))
772
6955cff9
AS
773(test-equal "/signing-key.pub"
774 200
775 (response-code (http-get (publish-uri "/signing-key.pub"))))
776
c1cd155a
LC
777(test-equal "non-GET query"
778 '(200 404)
779 (let ((path (string-append "/" (store-path-hash-part %item)
780 ".narinfo")))
781 (map response-code
782 (list (http-get (publish-uri path))
783 (http-post (publish-uri path))))))
784
aff8ce7c 785(test-end "publish")