Commit | Line | Data |
---|---|---|
1b3e9685 DT |
1 | ;;; GNU Guix --- Functional package management for GNU |
2 | ;;; Copyright © 2014 David Thompson <davet@gnu.org> | |
506abddb | 3 | ;;; Copyright © 2016 Ricardo Wurmus <rekado@elephly.net> |
d514276b | 4 | ;;; Copyright © 2019 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
1b3e9685 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 | ||
21 | (define-module (test-pypi) | |
22 | #:use-module (guix import pypi) | |
23 | #:use-module (guix base32) | |
c799ad72 | 24 | #:use-module (guix memoization) |
ca719424 | 25 | #:use-module (gcrypt hash) |
f0190a5d | 26 | #:use-module (guix memoization) |
694b317c | 27 | #:use-module (guix tests) |
4eaac4b7 | 28 | #:use-module (guix build-system python) |
01589acc | 29 | #:use-module ((guix build utils) #:select (delete-file-recursively which mkdir-p)) |
1b3e9685 DT |
30 | #:use-module (srfi srfi-64) |
31 | #:use-module (ice-9 match)) | |
32 | ||
1b3e9685 DT |
33 | (define test-json |
34 | "{ | |
35 | \"info\": { | |
36 | \"version\": \"1.0.0\", | |
37 | \"name\": \"foo\", | |
38 | \"license\": \"GNU LGPL\", | |
39 | \"summary\": \"summary\", | |
40 | \"home_page\": \"http://example.com\", | |
7657e61d LC |
41 | \"classifiers\": [], |
42 | \"download_url\": \"\" | |
1b3e9685 | 43 | }, |
7657e61d | 44 | \"urls\": [], |
1b3e9685 DT |
45 | \"releases\": { |
46 | \"1.0.0\": [ | |
47 | { | |
48 | \"url\": \"https://example.com/foo-1.0.0.egg\", | |
5dfe02c6 | 49 | \"packagetype\": \"bdist_egg\" |
1b3e9685 DT |
50 | }, { |
51 | \"url\": \"https://example.com/foo-1.0.0.tar.gz\", | |
5dfe02c6 | 52 | \"packagetype\": \"sdist\" |
266785d2 CR |
53 | }, { |
54 | \"url\": \"https://example.com/foo-1.0.0-py2.py3-none-any.whl\", | |
5dfe02c6 | 55 | \"packagetype\": \"bdist_wheel\" |
1b3e9685 DT |
56 | } |
57 | ] | |
58 | } | |
59 | }") | |
60 | ||
ff986890 CR |
61 | (define test-source-hash |
62 | "") | |
63 | ||
803fb336 MC |
64 | (define test-specifications |
65 | '("Fizzy [foo, bar]" | |
66 | "PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1" | |
67 | "SomethingWithMarker[foo]>1.0;python_version<\"2.7\"" | |
68 | "requests [security,tests] >= 2.8.1, == 2.8.* ; python_version < \"2.7\"" | |
69 | "pip @ https://github.com/pypa/pip/archive/1.3.1.zip#\ | |
70 | sha1=da9234ee9982d4bbb3c72346a6de940a148ea686")) | |
71 | ||
01589acc MC |
72 | (define test-requires.txt "\ |
73 | # A comment | |
ff986890 | 74 | # A comment after a space |
c4797121 MC |
75 | foo ~= 3 |
76 | bar != 2 | |
77 | ||
78 | [test] | |
79 | pytest (>=2.5.0) | |
80 | ") | |
81 | ||
d514276b MC |
82 | ;; Beaker contains only optional dependencies. |
83 | (define test-requires.txt-beaker "\ | |
84 | [crypto] | |
85 | pycryptopp>=0.5.12 | |
86 | ||
87 | [cryptography] | |
88 | cryptography | |
89 | ||
90 | [testsuite] | |
91 | Mock | |
92 | coverage | |
93 | ") | |
94 | ||
f0190a5d MC |
95 | (define test-metadata "\ |
96 | Classifier: Programming Language :: Python :: 3.7 | |
97 | Requires-Dist: baz ~= 3 | |
98 | Requires-Dist: bar != 2 | |
99 | Provides-Extra: test | |
d514276b | 100 | Requires-Dist: pytest (>=2.5.0) ; extra == 'test' |
f0190a5d MC |
101 | ") |
102 | ||
103 | (define test-metadata-with-extras " | |
104 | Classifier: Programming Language :: Python :: 3.7 | |
105 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* | |
106 | Requires-Dist: wrapt (<2,>=1) | |
107 | Requires-Dist: bar | |
108 | ||
109 | Provides-Extra: dev | |
110 | Requires-Dist: tox ; extra == 'dev' | |
111 | Requires-Dist: bumpversion (<1) ; extra == 'dev' | |
112 | ") | |
113 | ||
114 | ;;; Provides-Extra can appear before Requires-Dist. | |
115 | (define test-metadata-with-extras-jedi "\ | |
116 | Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* | |
117 | Provides-Extra: testing | |
118 | Requires-Dist: parso (>=0.3.0) | |
119 | Provides-Extra: testing | |
120 | Requires-Dist: pytest (>=3.1.0); extra == 'testing' | |
121 | ") | |
266785d2 | 122 | |
5dfe02c6 | 123 | \f |
1b3e9685 DT |
124 | (test-begin "pypi") |
125 | ||
8173ceee LC |
126 | (test-equal "guix-package->pypi-name, old URL style" |
127 | "psutil" | |
128 | (guix-package->pypi-name | |
129 | (dummy-package "foo" | |
130 | (source (dummy-origin | |
131 | (uri | |
7277d06d | 132 | "https://pypi.org/packages/source/p/psutil/psutil-4.3.0.tar.gz")))))) |
8173ceee LC |
133 | |
134 | (test-equal "guix-package->pypi-name, new URL style" | |
135 | "certbot" | |
136 | (guix-package->pypi-name | |
137 | (dummy-package "foo" | |
138 | (source (dummy-origin | |
139 | (uri | |
8440db45 | 140 | "https://pypi.org/packages/a2/3b/4756e6a0ceb14e084042a2a65c615d68d25621c6fd446d0fc10d14c4ce7d/certbot-0.8.1.tar.gz")))))) |
8173ceee | 141 | |
4eaac4b7 LC |
142 | (test-equal "guix-package->pypi-name, several URLs" |
143 | "cram" | |
144 | (guix-package->pypi-name | |
145 | (dummy-package "foo" | |
146 | (source | |
147 | (dummy-origin | |
148 | (uri (list "https://bitheap.org/cram/cram-0.7.tar.gz" | |
149 | (pypi-uri "cram" "0.7")))))))) | |
150 | ||
803fb336 MC |
151 | (test-equal "specification->requirement-name" |
152 | '("Fizzy" "PickyThing" "SomethingWithMarker" "requests" "pip") | |
153 | (map specification->requirement-name test-specifications)) | |
154 | ||
d514276b MC |
155 | (test-equal "parse-requires.txt" |
156 | (list '("foo" "bar") '("pytest")) | |
c4797121 MC |
157 | (mock ((ice-9 ports) call-with-input-file |
158 | call-with-input-string) | |
d514276b MC |
159 | (parse-requires.txt test-requires.txt))) |
160 | ||
161 | (test-equal "parse-requires.txt - Beaker" | |
162 | (list '() '("Mock" "coverage")) | |
163 | (mock ((ice-9 ports) call-with-input-file | |
164 | call-with-input-string) | |
165 | (parse-requires.txt test-requires.txt-beaker))) | |
c4797121 | 166 | |
f0190a5d | 167 | (test-equal "parse-wheel-metadata, with extras" |
d514276b | 168 | (list '("wrapt" "bar") '("tox" "bumpversion")) |
f0190a5d MC |
169 | (mock ((ice-9 ports) call-with-input-file |
170 | call-with-input-string) | |
171 | (parse-wheel-metadata test-metadata-with-extras))) | |
172 | ||
173 | (test-equal "parse-wheel-metadata, with extras - Jedi" | |
d514276b | 174 | (list '("parso") '("pytest")) |
f0190a5d MC |
175 | (mock ((ice-9 ports) call-with-input-file |
176 | call-with-input-string) | |
177 | (parse-wheel-metadata test-metadata-with-extras-jedi))) | |
178 | ||
d514276b | 179 | (test-assert "pypi->guix-package, no wheel" |
1b3e9685 | 180 | ;; Replace network resources with sample data. |
506abddb RW |
181 | (mock ((guix import utils) url-fetch |
182 | (lambda (url file-name) | |
183 | (match url | |
184 | ("https://example.com/foo-1.0.0.tar.gz" | |
185 | (begin | |
c799ad72 MC |
186 | ;; Unusual requires.txt location should still be found. |
187 | (mkdir-p "foo-1.0.0/src/bizarre.egg-info") | |
188 | (with-output-to-file "foo-1.0.0/src/bizarre.egg-info/requires.txt" | |
506abddb | 189 | (lambda () |
01589acc | 190 | (display test-requires.txt))) |
a853aceb MC |
191 | (parameterize ((current-output-port (%make-void-port "rw+"))) |
192 | (system* "tar" "czvf" file-name "foo-1.0.0/")) | |
506abddb RW |
193 | (delete-file-recursively "foo-1.0.0") |
194 | (set! test-source-hash | |
195 | (call-with-input-file file-name port-sha256)))) | |
196 | ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f) | |
197 | (_ (error "Unexpected URL: " url))))) | |
198 | (mock ((guix http-client) http-fetch | |
ce8963c5 | 199 | (lambda (url . rest) |
506abddb | 200 | (match url |
8440db45 | 201 | ("https://pypi.org/pypi/foo/json" |
506abddb RW |
202 | (values (open-input-string test-json) |
203 | (string-length test-json))) | |
204 | ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f) | |
205 | (_ (error "Unexpected URL: " url))))) | |
206 | (match (pypi->guix-package "foo") | |
207 | (('package | |
208 | ('name "python-foo") | |
209 | ('version "1.0.0") | |
210 | ('source ('origin | |
211 | ('method 'url-fetch) | |
b3d8153d | 212 | ('uri ('pypi-uri "foo" 'version)) |
506abddb RW |
213 | ('sha256 |
214 | ('base32 | |
215 | (? string? hash))))) | |
216 | ('build-system 'python-build-system) | |
217 | ('propagated-inputs | |
218 | ('quasiquote | |
219 | (("python-bar" ('unquote 'python-bar)) | |
d514276b MC |
220 | ("python-foo" ('unquote 'python-foo))))) |
221 | ('native-inputs | |
222 | ('quasiquote | |
223 | (("python-pytest" ('unquote 'python-pytest))))) | |
506abddb RW |
224 | ('home-page "http://example.com") |
225 | ('synopsis "summary") | |
226 | ('description "summary") | |
227 | ('license 'license:lgpl2.0)) | |
228 | (string=? (bytevector->nix-base32-string | |
229 | test-source-hash) | |
230 | hash)) | |
231 | (x | |
232 | (pk 'fail x #f)))))) | |
266785d2 CR |
233 | |
234 | (test-skip (if (which "zip") 0 1)) | |
235 | (test-assert "pypi->guix-package, wheels" | |
236 | ;; Replace network resources with sample data. | |
237 | (mock ((guix import utils) url-fetch | |
238 | (lambda (url file-name) | |
239 | (match url | |
266785d2 | 240 | ("https://example.com/foo-1.0.0.tar.gz" |
01589acc MC |
241 | (begin |
242 | (mkdir-p "foo-1.0.0/foo.egg-info/") | |
243 | (with-output-to-file "foo-1.0.0/foo.egg-info/requires.txt" | |
d514276b MC |
244 | (lambda () |
245 | (display "wrong data to make sure we're testing wheels "))) | |
a853aceb MC |
246 | (parameterize ((current-output-port (%make-void-port "rw+"))) |
247 | (system* "tar" "czvf" file-name "foo-1.0.0/")) | |
d514276b MC |
248 | (delete-file-recursively "foo-1.0.0") |
249 | (set! test-source-hash | |
250 | (call-with-input-file file-name port-sha256)))) | |
266785d2 | 251 | ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" |
d514276b MC |
252 | (begin |
253 | (mkdir "foo-1.0.0.dist-info") | |
254 | (with-output-to-file "foo-1.0.0.dist-info/METADATA" | |
255 | (lambda () | |
256 | (display test-metadata))) | |
257 | (let ((zip-file (string-append file-name ".zip"))) | |
258 | ;; zip always adds a "zip" extension to the file it creates, | |
259 | ;; so we need to rename it. | |
260 | (system* "zip" "-q" zip-file "foo-1.0.0.dist-info/METADATA") | |
261 | (rename-file zip-file file-name)) | |
262 | (delete-file-recursively "foo-1.0.0.dist-info"))) | |
ff986890 | 263 | (_ (error "Unexpected URL: " url))))) |
239f4632 | 264 | (mock ((guix http-client) http-fetch |
ce8963c5 | 265 | (lambda (url . rest) |
239f4632 | 266 | (match url |
8440db45 | 267 | ("https://pypi.org/pypi/foo/json" |
239f4632 RW |
268 | (values (open-input-string test-json) |
269 | (string-length test-json))) | |
270 | ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f) | |
271 | (_ (error "Unexpected URL: " url))))) | |
f0190a5d MC |
272 | ;; Not clearing the memoization cache here would mean returning the value |
273 | ;; computed in the previous test. | |
274 | (invalidate-memoization! pypi->guix-package) | |
239f4632 RW |
275 | (match (pypi->guix-package "foo") |
276 | (('package | |
277 | ('name "python-foo") | |
278 | ('version "1.0.0") | |
279 | ('source ('origin | |
280 | ('method 'url-fetch) | |
b3d8153d | 281 | ('uri ('pypi-uri "foo" 'version)) |
239f4632 RW |
282 | ('sha256 |
283 | ('base32 | |
284 | (? string? hash))))) | |
285 | ('build-system 'python-build-system) | |
286 | ('propagated-inputs | |
287 | ('quasiquote | |
288 | (("python-bar" ('unquote 'python-bar)) | |
b45dbfc9 | 289 | ("python-baz" ('unquote 'python-baz))))) |
d514276b MC |
290 | ('native-inputs |
291 | ('quasiquote | |
292 | (("python-pytest" ('unquote 'python-pytest))))) | |
239f4632 RW |
293 | ('home-page "http://example.com") |
294 | ('synopsis "summary") | |
295 | ('description "summary") | |
296 | ('license 'license:lgpl2.0)) | |
297 | (string=? (bytevector->nix-base32-string | |
298 | test-source-hash) | |
299 | hash)) | |
300 | (x | |
301 | (pk 'fail x #f)))))) | |
1b3e9685 | 302 | |
c799ad72 MC |
303 | (test-assert "pypi->guix-package, no usable requirement file." |
304 | ;; Replace network resources with sample data. | |
305 | (mock ((guix import utils) url-fetch | |
306 | (lambda (url file-name) | |
307 | (match url | |
308 | ("https://example.com/foo-1.0.0.tar.gz" | |
309 | (mkdir-p "foo-1.0.0/foo.egg-info/") | |
310 | (parameterize ((current-output-port (%make-void-port "rw+"))) | |
311 | (system* "tar" "czvf" file-name "foo-1.0.0/")) | |
312 | (delete-file-recursively "foo-1.0.0") | |
313 | (set! test-source-hash | |
314 | (call-with-input-file file-name port-sha256))) | |
315 | ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f) | |
316 | (_ (error "Unexpected URL: " url))))) | |
317 | (mock ((guix http-client) http-fetch | |
318 | (lambda (url . rest) | |
319 | (match url | |
320 | ("https://pypi.org/pypi/foo/json" | |
321 | (values (open-input-string test-json) | |
322 | (string-length test-json))) | |
323 | ("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f) | |
324 | (_ (error "Unexpected URL: " url))))) | |
325 | ;; Not clearing the memoization cache here would mean returning the value | |
326 | ;; computed in the previous test. | |
327 | (invalidate-memoization! pypi->guix-package) | |
328 | (match (pypi->guix-package "foo") | |
329 | (('package | |
330 | ('name "python-foo") | |
331 | ('version "1.0.0") | |
332 | ('source ('origin | |
333 | ('method 'url-fetch) | |
334 | ('uri ('pypi-uri "foo" 'version)) | |
335 | ('sha256 | |
336 | ('base32 | |
337 | (? string? hash))))) | |
338 | ('build-system 'python-build-system) | |
339 | ('home-page "http://example.com") | |
340 | ('synopsis "summary") | |
341 | ('description "summary") | |
342 | ('license 'license:lgpl2.0)) | |
343 | (string=? (bytevector->nix-base32-string | |
344 | test-source-hash) | |
345 | hash)) | |
346 | (x | |
347 | (pk 'fail x #f)))))) | |
348 | ||
1b3e9685 | 349 | (test-end "pypi") |