import: pypi: Improve parsing of requirement specifications.
[jackhill/guix/guix.git] / tests / pypi.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2014 David Thompson <davet@gnu.org>
3 ;;; Copyright © 2016 Ricardo Wurmus <rekado@elephly.net>
4 ;;;
5 ;;; This file is part of GNU Guix.
6 ;;;
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
11 ;;;
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;;; GNU General Public License for more details.
16 ;;;
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19
20 (define-module (test-pypi)
21 #:use-module (guix import pypi)
22 #:use-module (guix base32)
23 #:use-module (gcrypt hash)
24 #:use-module (guix tests)
25 #:use-module (guix build-system python)
26 #:use-module ((guix build utils) #:select (delete-file-recursively which mkdir-p))
27 #:use-module (srfi srfi-64)
28 #:use-module (ice-9 match))
29
30 (define test-json
31 "{
32 \"info\": {
33 \"version\": \"1.0.0\",
34 \"name\": \"foo\",
35 \"license\": \"GNU LGPL\",
36 \"summary\": \"summary\",
37 \"home_page\": \"http://example.com\",
38 },
39 \"releases\": {
40 \"1.0.0\": [
41 {
42 \"url\": \"https://example.com/foo-1.0.0.egg\",
43 \"packagetype\": \"bdist_egg\",
44 }, {
45 \"url\": \"https://example.com/foo-1.0.0.tar.gz\",
46 \"packagetype\": \"sdist\",
47 }, {
48 \"url\": \"https://example.com/foo-1.0.0-py2.py3-none-any.whl\",
49 \"packagetype\": \"bdist_wheel\",
50 }
51 ]
52 }
53 }")
54
55 (define test-source-hash
56 "")
57
58 (define test-specifications
59 '("Fizzy [foo, bar]"
60 "PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1"
61 "SomethingWithMarker[foo]>1.0;python_version<\"2.7\""
62 "requests [security,tests] >= 2.8.1, == 2.8.* ; python_version < \"2.7\""
63 "pip @ https://github.com/pypa/pip/archive/1.3.1.zip#\
64 sha1=da9234ee9982d4bbb3c72346a6de940a148ea686"))
65
66 (define test-requires.txt "\
67 # A comment
68 # A comment after a space
69 bar
70 baz > 13.37
71 ")
72
73 (define test-requires-with-sections "\
74 foo ~= 3
75 bar != 2
76
77 [test]
78 pytest (>=2.5.0)
79 ")
80
81 (define test-metadata
82 "{
83 \"run_requires\": [
84 {
85 \"requires\": [
86 \"bar\",
87 \"baz (>13.37)\"
88 ]
89 }
90 ]
91 }")
92
93 (test-begin "pypi")
94
95 (test-equal "guix-package->pypi-name, old URL style"
96 "psutil"
97 (guix-package->pypi-name
98 (dummy-package "foo"
99 (source (dummy-origin
100 (uri
101 "https://pypi.org/packages/source/p/psutil/psutil-4.3.0.tar.gz"))))))
102
103 (test-equal "guix-package->pypi-name, new URL style"
104 "certbot"
105 (guix-package->pypi-name
106 (dummy-package "foo"
107 (source (dummy-origin
108 (uri
109 "https://pypi.org/packages/a2/3b/4756e6a0ceb14e084042a2a65c615d68d25621c6fd446d0fc10d14c4ce7d/certbot-0.8.1.tar.gz"))))))
110
111 (test-equal "guix-package->pypi-name, several URLs"
112 "cram"
113 (guix-package->pypi-name
114 (dummy-package "foo"
115 (source
116 (dummy-origin
117 (uri (list "https://bitheap.org/cram/cram-0.7.tar.gz"
118 (pypi-uri "cram" "0.7"))))))))
119
120 (test-equal "specification->requirement-name"
121 '("Fizzy" "PickyThing" "SomethingWithMarker" "requests" "pip")
122 (map specification->requirement-name test-specifications))
123
124 (test-equal "parse-requires.txt, with sections"
125 '("foo" "bar")
126 (mock ((ice-9 ports) call-with-input-file
127 call-with-input-string)
128 (parse-requires.txt test-requires-with-sections)))
129
130 (test-assert "pypi->guix-package"
131 ;; Replace network resources with sample data.
132 (mock ((guix import utils) url-fetch
133 (lambda (url file-name)
134 (match url
135 ("https://example.com/foo-1.0.0.tar.gz"
136 (begin
137 (mkdir-p "foo-1.0.0/foo.egg-info/")
138 (with-output-to-file "foo-1.0.0/foo.egg-info/requires.txt"
139 (lambda ()
140 (display test-requires.txt)))
141 (parameterize ((current-output-port (%make-void-port "rw+")))
142 (system* "tar" "czvf" file-name "foo-1.0.0/"))
143 (delete-file-recursively "foo-1.0.0")
144 (set! test-source-hash
145 (call-with-input-file file-name port-sha256))))
146 ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
147 (_ (error "Unexpected URL: " url)))))
148 (mock ((guix http-client) http-fetch
149 (lambda (url . rest)
150 (match url
151 ("https://pypi.org/pypi/foo/json"
152 (values (open-input-string test-json)
153 (string-length test-json)))
154 ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
155 (_ (error "Unexpected URL: " url)))))
156 (match (pypi->guix-package "foo")
157 (('package
158 ('name "python-foo")
159 ('version "1.0.0")
160 ('source ('origin
161 ('method 'url-fetch)
162 ('uri ('pypi-uri "foo" 'version))
163 ('sha256
164 ('base32
165 (? string? hash)))))
166 ('build-system 'python-build-system)
167 ('propagated-inputs
168 ('quasiquote
169 (("python-bar" ('unquote 'python-bar))
170 ("python-baz" ('unquote 'python-baz)))))
171 ('home-page "http://example.com")
172 ('synopsis "summary")
173 ('description "summary")
174 ('license 'license:lgpl2.0))
175 (string=? (bytevector->nix-base32-string
176 test-source-hash)
177 hash))
178 (x
179 (pk 'fail x #f))))))
180
181 (test-skip (if (which "zip") 0 1))
182 (test-assert "pypi->guix-package, wheels"
183 ;; Replace network resources with sample data.
184 (mock ((guix import utils) url-fetch
185 (lambda (url file-name)
186 (match url
187 ("https://example.com/foo-1.0.0.tar.gz"
188 (begin
189 (mkdir-p "foo-1.0.0/foo.egg-info/")
190 (with-output-to-file "foo-1.0.0/foo.egg-info/requires.txt"
191 (lambda ()
192 (display test-requires.txt)))
193 (parameterize ((current-output-port (%make-void-port "rw+")))
194 (system* "tar" "czvf" file-name "foo-1.0.0/"))
195 (delete-file-recursively "foo-1.0.0")
196 (set! test-source-hash
197 (call-with-input-file file-name port-sha256))))
198 ("https://example.com/foo-1.0.0-py2.py3-none-any.whl"
199 (begin
200 (mkdir "foo-1.0.0.dist-info")
201 (with-output-to-file "foo-1.0.0.dist-info/metadata.json"
202 (lambda ()
203 (display test-metadata)))
204 (let ((zip-file (string-append file-name ".zip")))
205 ;; zip always adds a "zip" extension to the file it creates,
206 ;; so we need to rename it.
207 (system* "zip" zip-file "foo-1.0.0.dist-info/metadata.json")
208 (rename-file zip-file file-name))
209 (delete-file-recursively "foo-1.0.0.dist-info")))
210 (_ (error "Unexpected URL: " url)))))
211 (mock ((guix http-client) http-fetch
212 (lambda (url . rest)
213 (match url
214 ("https://pypi.org/pypi/foo/json"
215 (values (open-input-string test-json)
216 (string-length test-json)))
217 ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
218 (_ (error "Unexpected URL: " url)))))
219 (match (pypi->guix-package "foo")
220 (('package
221 ('name "python-foo")
222 ('version "1.0.0")
223 ('source ('origin
224 ('method 'url-fetch)
225 ('uri ('pypi-uri "foo" 'version))
226 ('sha256
227 ('base32
228 (? string? hash)))))
229 ('build-system 'python-build-system)
230 ('propagated-inputs
231 ('quasiquote
232 (("python-bar" ('unquote 'python-bar))
233 ("python-baz" ('unquote 'python-baz)))))
234 ('home-page "http://example.com")
235 ('synopsis "summary")
236 ('description "summary")
237 ('license 'license:lgpl2.0))
238 (string=? (bytevector->nix-base32-string
239 test-source-hash)
240 hash))
241 (x
242 (pk 'fail x #f))))))
243
244 (test-end "pypi")