Merge branch 'master' into core-updates
[jackhill/guix/guix.git] / tests / lint.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2012, 2013 Cyril Roelandt <tipecaml@gmail.com>
3 ;;; Copyright © 2014, 2015, 2016 Eric Bavier <bavier@member.fsf.org>
4 ;;; Copyright © 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
5 ;;; Copyright © 2015, 2016 Mathieu Lirzin <mthl@gnu.org>
6 ;;; Copyright © 2016 Hartmut Goebel <h.goebel@crazy-compilers.com>
7 ;;; Copyright © 2017 Alex Kost <alezost@gmail.com>
8 ;;;
9 ;;; This file is part of GNU Guix.
10 ;;;
11 ;;; GNU Guix is free software; you can redistribute it and/or modify it
12 ;;; under the terms of the GNU General Public License as published by
13 ;;; the Free Software Foundation; either version 3 of the License, or (at
14 ;;; your option) any later version.
15 ;;;
16 ;;; GNU Guix is distributed in the hope that it will be useful, but
17 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;;; GNU General Public License for more details.
20 ;;;
21 ;;; You should have received a copy of the GNU General Public License
22 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24 ;; Avoid interference.
25 (unsetenv "http_proxy")
26
27 (define-module (test-lint)
28 #:use-module (guix tests)
29 #:use-module (guix tests http)
30 #:use-module (guix download)
31 #:use-module (guix git-download)
32 #:use-module (guix build-system gnu)
33 #:use-module (guix packages)
34 #:use-module (guix scripts lint)
35 #:use-module (guix ui)
36 #:use-module (gnu packages)
37 #:use-module (gnu packages glib)
38 #:use-module (gnu packages pkg-config)
39 #:use-module (gnu packages python)
40 #:use-module (web server)
41 #:use-module (web server http)
42 #:use-module (web response)
43 #:use-module (ice-9 match)
44 #:use-module (srfi srfi-9 gnu)
45 #:use-module (srfi srfi-64))
46
47 ;; Test the linter.
48
49 ;; Avoid collisions with other tests.
50 (%http-server-port 9999)
51
52 (define %null-sha256
53 ;; SHA256 of the empty string.
54 (base32
55 "0mdqa9w1p6cmli6976v4wi0sw9r4p5prkj7lzfd1877wk11c9c73"))
56
57 (define %long-string
58 (make-string 2000 #\a))
59
60 \f
61 (test-begin "lint")
62
63 (define (call-with-warnings thunk)
64 (let ((port (open-output-string)))
65 (parameterize ((guix-warning-port port))
66 (thunk))
67 (get-output-string port)))
68
69 (define-syntax-rule (with-warnings body ...)
70 (call-with-warnings (lambda () body ...)))
71
72 (test-assert "description: not a string"
73 (->bool
74 (string-contains (with-warnings
75 (let ((pkg (dummy-package "x"
76 (description 'foobar))))
77 (check-description-style pkg)))
78 "invalid description")))
79
80 (test-assert "description: not empty"
81 (->bool
82 (string-contains (with-warnings
83 (let ((pkg (dummy-package "x"
84 (description ""))))
85 (check-description-style pkg)))
86 "description should not be empty")))
87
88 (test-assert "description: valid Texinfo markup"
89 (->bool
90 (string-contains
91 (with-warnings
92 (check-description-style (dummy-package "x" (description "f{oo}b@r"))))
93 "Texinfo markup in description is invalid")))
94
95 (test-assert "description: does not start with an upper-case letter"
96 (->bool
97 (string-contains (with-warnings
98 (let ((pkg (dummy-package "x"
99 (description "bad description."))))
100 (check-description-style pkg)))
101 "description should start with an upper-case letter")))
102
103 (test-assert "description: may start with a digit"
104 (string-null?
105 (with-warnings
106 (let ((pkg (dummy-package "x"
107 (description "2-component library."))))
108 (check-description-style pkg)))))
109
110 (test-assert "description: may start with lower-case package name"
111 (string-null?
112 (with-warnings
113 (let ((pkg (dummy-package "x"
114 (description "x is a dummy package."))))
115 (check-description-style pkg)))))
116
117 (test-assert "description: two spaces after end of sentence"
118 (->bool
119 (string-contains (with-warnings
120 (let ((pkg (dummy-package "x"
121 (description "Bad. Quite bad."))))
122 (check-description-style pkg)))
123 "sentences in description should be followed by two spaces")))
124
125 (test-assert "description: end-of-sentence detection with abbreviations"
126 (string-null?
127 (with-warnings
128 (let ((pkg (dummy-package "x"
129 (description
130 "E.g. Foo, i.e. Bar resp. Baz (a.k.a. DVD)."))))
131 (check-description-style pkg)))))
132
133 (test-assert "description: may not contain trademark signs"
134 (and (->bool
135 (string-contains (with-warnings
136 (let ((pkg (dummy-package "x"
137 (description "Does The Right Thing™"))))
138 (check-description-style pkg)))
139 "should not contain trademark sign"))
140 (->bool
141 (string-contains (with-warnings
142 (let ((pkg (dummy-package "x"
143 (description "Works with Format®"))))
144 (check-description-style pkg)))
145 "should not contain trademark sign"))))
146
147 (test-assert "description: suggest ornament instead of quotes"
148 (->bool
149 (string-contains (with-warnings
150 (let ((pkg (dummy-package "x"
151 (description "This is a 'quoted' thing."))))
152 (check-description-style pkg)))
153 "use @code")))
154
155 (test-assert "synopsis: not a string"
156 (->bool
157 (string-contains (with-warnings
158 (let ((pkg (dummy-package "x"
159 (synopsis #f))))
160 (check-synopsis-style pkg)))
161 "invalid synopsis")))
162
163 (test-assert "synopsis: not empty"
164 (->bool
165 (string-contains (with-warnings
166 (let ((pkg (dummy-package "x"
167 (synopsis ""))))
168 (check-synopsis-style pkg)))
169 "synopsis should not be empty")))
170
171 (test-assert "synopsis: valid Texinfo markup"
172 (->bool
173 (string-contains
174 (with-warnings
175 (check-synopsis-style (dummy-package "x" (synopsis "Bad $@ texinfo"))))
176 "Texinfo markup in synopsis is invalid")))
177
178 (test-assert "synopsis: does not start with an upper-case letter"
179 (->bool
180 (string-contains (with-warnings
181 (let ((pkg (dummy-package "x"
182 (synopsis "bad synopsis."))))
183 (check-synopsis-style pkg)))
184 "synopsis should start with an upper-case letter")))
185
186 (test-assert "synopsis: may start with a digit"
187 (string-null?
188 (with-warnings
189 (let ((pkg (dummy-package "x"
190 (synopsis "5-dimensional frobnicator"))))
191 (check-synopsis-style pkg)))))
192
193 (test-assert "synopsis: ends with a period"
194 (->bool
195 (string-contains (with-warnings
196 (let ((pkg (dummy-package "x"
197 (synopsis "Bad synopsis."))))
198 (check-synopsis-style pkg)))
199 "no period allowed at the end of the synopsis")))
200
201 (test-assert "synopsis: ends with 'etc.'"
202 (string-null? (with-warnings
203 (let ((pkg (dummy-package "x"
204 (synopsis "Foo, bar, etc."))))
205 (check-synopsis-style pkg)))))
206
207 (test-assert "synopsis: starts with 'A'"
208 (->bool
209 (string-contains (with-warnings
210 (let ((pkg (dummy-package "x"
211 (synopsis "A bad synopŝis"))))
212 (check-synopsis-style pkg)))
213 "no article allowed at the beginning of the synopsis")))
214
215 (test-assert "synopsis: starts with 'An'"
216 (->bool
217 (string-contains (with-warnings
218 (let ((pkg (dummy-package "x"
219 (synopsis "An awful synopsis"))))
220 (check-synopsis-style pkg)))
221 "no article allowed at the beginning of the synopsis")))
222
223 (test-assert "synopsis: starts with 'a'"
224 (->bool
225 (string-contains (with-warnings
226 (let ((pkg (dummy-package "x"
227 (synopsis "a bad synopsis"))))
228 (check-synopsis-style pkg)))
229 "no article allowed at the beginning of the synopsis")))
230
231 (test-assert "synopsis: starts with 'an'"
232 (->bool
233 (string-contains (with-warnings
234 (let ((pkg (dummy-package "x"
235 (synopsis "an awful synopsis"))))
236 (check-synopsis-style pkg)))
237 "no article allowed at the beginning of the synopsis")))
238
239 (test-assert "synopsis: too long"
240 (->bool
241 (string-contains (with-warnings
242 (let ((pkg (dummy-package "x"
243 (synopsis (make-string 80 #\x)))))
244 (check-synopsis-style pkg)))
245 "synopsis should be less than 80 characters long")))
246
247 (test-assert "synopsis: start with package name"
248 (->bool
249 (string-contains (with-warnings
250 (let ((pkg (dummy-package "x"
251 (name "foo")
252 (synopsis "foo, a nice package"))))
253 (check-synopsis-style pkg)))
254 "synopsis should not start with the package name")))
255
256 (test-assert "synopsis: start with package name prefix"
257 (string-null?
258 (with-warnings
259 (let ((pkg (dummy-package "arb"
260 (synopsis "Arbitrary precision"))))
261 (check-synopsis-style pkg)))))
262
263 (test-assert "synopsis: start with abbreviation"
264 (string-null?
265 (with-warnings
266 (let ((pkg (dummy-package "uucp"
267 ;; Same problem with "APL interpreter", etc.
268 (synopsis "UUCP implementation")
269 (description "Imagine this is Taylor UUCP."))))
270 (check-synopsis-style pkg)))))
271
272 (test-assert "inputs: pkg-config is probably a native input"
273 (->bool
274 (string-contains
275 (with-warnings
276 (let ((pkg (dummy-package "x"
277 (inputs `(("pkg-config" ,pkg-config))))))
278 (check-inputs-should-be-native pkg)))
279 "'pkg-config' should probably be a native input")))
280
281 (test-assert "inputs: glib:bin is probably a native input"
282 (->bool
283 (string-contains
284 (with-warnings
285 (let ((pkg (dummy-package "x"
286 (inputs `(("glib" ,glib "bin"))))))
287 (check-inputs-should-be-native pkg)))
288 "'glib:bin' should probably be a native input")))
289
290 (test-assert
291 "inputs: python-setuptools should not be an input at all (input)"
292 (->bool
293 (string-contains
294 (with-warnings
295 (let ((pkg (dummy-package "x"
296 (inputs `(("python-setuptools" ,python-setuptools))))))
297 (check-inputs-should-not-be-an-input-at-all pkg)))
298 "'python-setuptools' should probably not be an input at all")))
299
300 (test-assert
301 "inputs: python-setuptools should not be an input at all (native-input)"
302 (->bool
303 (string-contains
304 (with-warnings
305 (let ((pkg (dummy-package "x"
306 (native-inputs
307 `(("python-setuptools" ,python-setuptools))))))
308 (check-inputs-should-not-be-an-input-at-all pkg)))
309 "'python-setuptools' should probably not be an input at all")))
310
311 (test-assert
312 "inputs: python-setuptools should not be an input at all (propagated-input)"
313 (->bool
314 (string-contains
315 (with-warnings
316 (let ((pkg (dummy-package "x"
317 (propagated-inputs
318 `(("python-setuptools" ,python-setuptools))))))
319 (check-inputs-should-not-be-an-input-at-all pkg)))
320 "'python-setuptools' should probably not be an input at all")))
321
322 (test-assert "patches: file names"
323 (->bool
324 (string-contains
325 (with-warnings
326 (let ((pkg (dummy-package "x"
327 (source
328 (dummy-origin
329 (patches (list "/path/to/y.patch")))))))
330 (check-patch-file-names pkg)))
331 "file names of patches should start with the package name")))
332
333 (test-assert "patches: not found"
334 (->bool
335 (string-contains
336 (with-warnings
337 (let ((pkg (dummy-package "x"
338 (source
339 (dummy-origin
340 (patches
341 (list (search-patch "this-patch-does-not-exist!"))))))))
342 (check-patch-file-names pkg)))
343 "patch not found")))
344
345 (test-assert "derivation: invalid arguments"
346 (->bool
347 (string-contains
348 (with-warnings
349 (let ((pkg (dummy-package "x"
350 (arguments
351 '(#:imported-modules (invalid-module))))))
352 (check-derivation pkg)))
353 "failed to create derivation")))
354
355 (test-assert "license: invalid license"
356 (string-contains
357 (with-warnings
358 (check-license (dummy-package "x" (license #f))))
359 "invalid license"))
360
361 (test-assert "home-page: wrong home-page"
362 (->bool
363 (string-contains
364 (with-warnings
365 (let ((pkg (package
366 (inherit (dummy-package "x"))
367 (home-page #f))))
368 (check-home-page pkg)))
369 "invalid")))
370
371 (test-assert "home-page: invalid URI"
372 (->bool
373 (string-contains
374 (with-warnings
375 (let ((pkg (package
376 (inherit (dummy-package "x"))
377 (home-page "foobar"))))
378 (check-home-page pkg)))
379 "invalid home page URL")))
380
381 (test-assert "home-page: host not found"
382 (->bool
383 (string-contains
384 (with-warnings
385 (let ((pkg (package
386 (inherit (dummy-package "x"))
387 (home-page "http://does-not-exist"))))
388 (check-home-page pkg)))
389 "domain not found")))
390
391 (test-skip (if (force %http-server-socket) 0 1))
392 (test-assert "home-page: Connection refused"
393 (->bool
394 (string-contains
395 (with-warnings
396 (let ((pkg (package
397 (inherit (dummy-package "x"))
398 (home-page (%local-url)))))
399 (check-home-page pkg)))
400 "Connection refused")))
401
402 (test-skip (if (force %http-server-socket) 0 1))
403 (test-equal "home-page: 200"
404 ""
405 (with-warnings
406 (with-http-server 200 %long-string
407 (let ((pkg (package
408 (inherit (dummy-package "x"))
409 (home-page (%local-url)))))
410 (check-home-page pkg)))))
411
412 (test-skip (if (force %http-server-socket) 0 1))
413 (test-assert "home-page: 200 but short length"
414 (->bool
415 (string-contains
416 (with-warnings
417 (with-http-server 200 "This is too small."
418 (let ((pkg (package
419 (inherit (dummy-package "x"))
420 (home-page (%local-url)))))
421 (check-home-page pkg))))
422 "suspiciously small")))
423
424 (test-skip (if (force %http-server-socket) 0 1))
425 (test-assert "home-page: 404"
426 (->bool
427 (string-contains
428 (with-warnings
429 (with-http-server 404 %long-string
430 (let ((pkg (package
431 (inherit (dummy-package "x"))
432 (home-page (%local-url)))))
433 (check-home-page pkg))))
434 "not reachable: 404")))
435
436 (test-assert "source-file-name"
437 (->bool
438 (string-contains
439 (with-warnings
440 (let ((pkg (dummy-package "x"
441 (version "3.2.1")
442 (source
443 (origin
444 (method url-fetch)
445 (uri "http://www.example.com/3.2.1.tar.gz")
446 (sha256 %null-sha256))))))
447 (check-source-file-name pkg)))
448 "file name should contain the package name")))
449
450 (test-assert "source-file-name: v prefix"
451 (->bool
452 (string-contains
453 (with-warnings
454 (let ((pkg (dummy-package "x"
455 (version "3.2.1")
456 (source
457 (origin
458 (method url-fetch)
459 (uri "http://www.example.com/v3.2.1.tar.gz")
460 (sha256 %null-sha256))))))
461 (check-source-file-name pkg)))
462 "file name should contain the package name")))
463
464 (test-assert "source-file-name: bad checkout"
465 (->bool
466 (string-contains
467 (with-warnings
468 (let ((pkg (dummy-package "x"
469 (version "3.2.1")
470 (source
471 (origin
472 (method git-fetch)
473 (uri (git-reference
474 (url "http://www.example.com/x.git")
475 (commit "0")))
476 (sha256 %null-sha256))))))
477 (check-source-file-name pkg)))
478 "file name should contain the package name")))
479
480 (test-assert "source-file-name: good checkout"
481 (not
482 (->bool
483 (string-contains
484 (with-warnings
485 (let ((pkg (dummy-package "x"
486 (version "3.2.1")
487 (source
488 (origin
489 (method git-fetch)
490 (uri (git-reference
491 (url "http://git.example.com/x.git")
492 (commit "0")))
493 (file-name (string-append "x-" version))
494 (sha256 %null-sha256))))))
495 (check-source-file-name pkg)))
496 "file name should contain the package name"))))
497
498 (test-assert "source-file-name: valid"
499 (not
500 (->bool
501 (string-contains
502 (with-warnings
503 (let ((pkg (dummy-package "x"
504 (version "3.2.1")
505 (source
506 (origin
507 (method url-fetch)
508 (uri "http://www.example.com/x-3.2.1.tar.gz")
509 (sha256 %null-sha256))))))
510 (check-source-file-name pkg)))
511 "file name should contain the package name"))))
512
513 (test-skip (if (force %http-server-socket) 0 1))
514 (test-equal "source: 200"
515 ""
516 (with-warnings
517 (with-http-server 200 %long-string
518 (let ((pkg (package
519 (inherit (dummy-package "x"))
520 (source (origin
521 (method url-fetch)
522 (uri (%local-url))
523 (sha256 %null-sha256))))))
524 (check-source pkg)))))
525
526 (test-skip (if (force %http-server-socket) 0 1))
527 (test-assert "source: 200 but short length"
528 (->bool
529 (string-contains
530 (with-warnings
531 (with-http-server 200 "This is too small."
532 (let ((pkg (package
533 (inherit (dummy-package "x"))
534 (source (origin
535 (method url-fetch)
536 (uri (%local-url))
537 (sha256 %null-sha256))))))
538 (check-source pkg))))
539 "suspiciously small")))
540
541 (test-skip (if (force %http-server-socket) 0 1))
542 (test-assert "source: 404"
543 (->bool
544 (string-contains
545 (with-warnings
546 (with-http-server 404 %long-string
547 (let ((pkg (package
548 (inherit (dummy-package "x"))
549 (source (origin
550 (method url-fetch)
551 (uri (%local-url))
552 (sha256 %null-sha256))))))
553 (check-source pkg))))
554 "not reachable: 404")))
555
556 (test-assert "mirror-url"
557 (string-null?
558 (with-warnings
559 (let ((source (origin
560 (method url-fetch)
561 (uri "http://example.org/foo/bar.tar.gz")
562 (sha256 %null-sha256))))
563 (check-mirror-url (dummy-package "x" (source source)))))))
564
565 (test-assert "mirror-url: one suggestion"
566 (string-contains
567 (with-warnings
568 (let ((source (origin
569 (method url-fetch)
570 (uri "http://ftp.gnu.org/pub/gnu/foo/foo.tar.gz")
571 (sha256 %null-sha256))))
572 (check-mirror-url (dummy-package "x" (source source)))))
573 "mirror://gnu/foo/foo.tar.gz"))
574
575 (test-assert "cve"
576 (mock ((guix scripts lint) package-vulnerabilities (const '()))
577 (string-null?
578 (with-warnings (check-vulnerabilities (dummy-package "x"))))))
579
580 (test-assert "cve: one vulnerability"
581 (mock ((guix scripts lint) package-vulnerabilities
582 (lambda (package)
583 (list (make-struct (@@ (guix cve) <vulnerability>) 0
584 "CVE-2015-1234"
585 (list (cons (package-name package)
586 (package-version package)))))))
587 (string-contains
588 (with-warnings
589 (check-vulnerabilities (dummy-package "pi" (version "3.14"))))
590 "vulnerable to CVE-2015-1234")))
591
592 (test-assert "cve: one patched vulnerability"
593 (mock ((guix scripts lint) package-vulnerabilities
594 (lambda (package)
595 (list (make-struct (@@ (guix cve) <vulnerability>) 0
596 "CVE-2015-1234"
597 (list (cons (package-name package)
598 (package-version package)))))))
599 (string-null?
600 (with-warnings
601 (check-vulnerabilities
602 (dummy-package "pi"
603 (version "3.14")
604 (source
605 (dummy-origin
606 (patches
607 (list "/a/b/pi-CVE-2015-1234.patch"))))))))))
608
609 (test-assert "cve: vulnerability fixed in replacement version"
610 (mock ((guix scripts lint) package-vulnerabilities
611 (lambda (package)
612 (match (package-version package)
613 ("0"
614 (list (make-struct (@@ (guix cve) <vulnerability>) 0
615 "CVE-2015-1234"
616 (list (cons (package-name package)
617 (package-version package))))))
618 ("1"
619 '()))))
620 (and (not (string-null?
621 (with-warnings
622 (check-vulnerabilities
623 (dummy-package "foo" (version "0"))))))
624 (string-null?
625 (with-warnings
626 (check-vulnerabilities
627 (dummy-package
628 "foo" (version "0")
629 (replacement (dummy-package "foo" (version "1"))))))))))
630
631 (test-assert "cve: patched vulnerability in replacement"
632 (mock ((guix scripts lint) package-vulnerabilities
633 (lambda (package)
634 (list (make-struct (@@ (guix cve) <vulnerability>) 0
635 "CVE-2015-1234"
636 (list (cons (package-name package)
637 (package-version package)))))))
638 (string-null?
639 (with-warnings
640 (check-vulnerabilities
641 (dummy-package
642 "pi" (version "3.14") (source (dummy-origin))
643 (replacement (dummy-package
644 "pi" (version "3.14")
645 (source
646 (dummy-origin
647 (patches
648 (list "/a/b/pi-CVE-2015-1234.patch"))))))))))))
649
650 (test-assert "formatting: lonely parentheses"
651 (string-contains
652 (with-warnings
653 (check-formatting
654 (
655 dummy-package "ugly as hell!"
656 )
657 ))
658 "lonely"))
659
660 (test-assert "formatting: tabulation"
661 (string-contains
662 (with-warnings
663 (check-formatting (dummy-package "leave the tab here: ")))
664 "tabulation"))
665
666 (test-assert "formatting: trailing white space"
667 (string-contains
668 (with-warnings
669 ;; Leave the trailing white space on the next line!
670 (check-formatting (dummy-package "x")))
671 "trailing white space"))
672
673 (test-assert "formatting: long line"
674 (string-contains
675 (with-warnings
676 (check-formatting
677 (dummy-package "x" ;here is a stupid comment just to make a long line
678 )))
679 "too long"))
680
681 (test-assert "formatting: alright"
682 (string-null?
683 (with-warnings
684 (check-formatting (dummy-package "x")))))
685
686 (test-end "lint")
687
688 ;; Local Variables:
689 ;; eval: (put 'with-http-server 'scheme-indent-function 2)
690 ;; eval: (put 'with-warnings 'scheme-indent-function 0)
691 ;; End: