mirror of
https://git.in.rschanz.org/ryan77627/guix.git
synced 2024-12-25 05:48:07 -05:00
tests: pypi: Rewrite tests using a local HTTP server.
* guix/import/pypi.scm (%pypi-base-url): New variable. (pypi-fetch): Use it. * tests/pypi.scm (foo-json): Compute URLs relative to '%local-url'. (test-json-1, test-json-2, test-source-hash): Remove. (file-dump): New procedure. (with-pypi): New macro. ("pypi->guix-package, no wheel") ("pypi->guix-package, wheels") ("pypi->guix-package, no usable requirement file.") ("pypi->guix-package, package name contains \"-\" followed by digits"): Rewrite using 'with-pypi'.
This commit is contained in:
parent
09526da78f
commit
d2f36abd02
2 changed files with 160 additions and 202 deletions
|
@ -55,7 +55,8 @@ (define-module (guix import pypi)
|
||||||
#:use-module (guix packages)
|
#:use-module (guix packages)
|
||||||
#:use-module (guix upstream)
|
#:use-module (guix upstream)
|
||||||
#:use-module ((guix licenses) #:prefix license:)
|
#:use-module ((guix licenses) #:prefix license:)
|
||||||
#:export (parse-requires.txt
|
#:export (%pypi-base-url
|
||||||
|
parse-requires.txt
|
||||||
parse-wheel-metadata
|
parse-wheel-metadata
|
||||||
specification->requirement-name
|
specification->requirement-name
|
||||||
guix-package->pypi-name
|
guix-package->pypi-name
|
||||||
|
@ -67,6 +68,10 @@ (define-module (guix import pypi)
|
||||||
;; The PyPI API (notice the rhyme) is "documented" at:
|
;; The PyPI API (notice the rhyme) is "documented" at:
|
||||||
;; <https://warehouse.readthedocs.io/api-reference/json/>.
|
;; <https://warehouse.readthedocs.io/api-reference/json/>.
|
||||||
|
|
||||||
|
(define %pypi-base-url
|
||||||
|
;; Base URL of the PyPI API.
|
||||||
|
(make-parameter "https://pypi.org/pypi/"))
|
||||||
|
|
||||||
(define non-empty-string-or-false
|
(define non-empty-string-or-false
|
||||||
(match-lambda
|
(match-lambda
|
||||||
("" #f)
|
("" #f)
|
||||||
|
@ -123,7 +128,7 @@ (define-json-mapping <distribution> make-distribution distribution?
|
||||||
|
|
||||||
(define (pypi-fetch name)
|
(define (pypi-fetch name)
|
||||||
"Return a <pypi-project> record for package NAME, or #f on failure."
|
"Return a <pypi-project> record for package NAME, or #f on failure."
|
||||||
(and=> (json-fetch (string-append "https://pypi.org/pypi/" name "/json"))
|
(and=> (json-fetch (string-append (%pypi-base-url) name "/json"))
|
||||||
json->pypi-project))
|
json->pypi-project))
|
||||||
|
|
||||||
;; For packages found on PyPI that lack a source distribution.
|
;; For packages found on PyPI that lack a source distribution.
|
||||||
|
|
353
tests/pypi.scm
353
tests/pypi.scm
|
@ -27,10 +27,11 @@ (define-module (test-pypi)
|
||||||
#:use-module (guix utils)
|
#:use-module (guix utils)
|
||||||
#:use-module (gcrypt hash)
|
#:use-module (gcrypt hash)
|
||||||
#:use-module (guix tests)
|
#:use-module (guix tests)
|
||||||
|
#:use-module (guix tests http)
|
||||||
#:use-module (guix build-system python)
|
#:use-module (guix build-system python)
|
||||||
#:use-module ((guix build utils)
|
#:use-module ((guix build utils)
|
||||||
#:select (delete-file-recursively
|
#:select (delete-file-recursively
|
||||||
which mkdir-p
|
which mkdir-p dump-port
|
||||||
with-directory-excursion))
|
with-directory-excursion))
|
||||||
#:use-module ((guix diagnostics) #:select (guix-warning-port))
|
#:use-module ((guix diagnostics) #:select (guix-warning-port))
|
||||||
#:use-module ((guix build syscalls) #:select (mkdtemp!))
|
#:use-module ((guix build syscalls) #:select (mkdtemp!))
|
||||||
|
@ -57,25 +58,19 @@ (define* (foo-json #:key (name "foo") (name-in-url #f))
|
||||||
(urls . #())
|
(urls . #())
|
||||||
(releases
|
(releases
|
||||||
. ((1.0.0
|
. ((1.0.0
|
||||||
. #(((url . ,(format #f "https://example.com/~a-1.0.0.egg"
|
. #(((url . ,(format #f "~a/~a-1.0.0.egg"
|
||||||
|
(%local-url #:path "")
|
||||||
(or name-in-url name)))
|
(or name-in-url name)))
|
||||||
(packagetype . "bdist_egg"))
|
(packagetype . "bdist_egg"))
|
||||||
((url . ,(format #f "https://example.com/~a-1.0.0.tar.gz"
|
((url . ,(format #f "~a/~a-1.0.0.tar.gz"
|
||||||
|
(%local-url #:path "")
|
||||||
(or name-in-url name)))
|
(or name-in-url name)))
|
||||||
(packagetype . "sdist"))
|
(packagetype . "sdist"))
|
||||||
((url . ,(format #f "https://example.com/~a-1.0.0-py2.py3-none-any.whl"
|
((url . ,(format #f "~a/~a-1.0.0-py2.py3-none-any.whl"
|
||||||
|
(%local-url #:path "")
|
||||||
(or name-in-url name)))
|
(or name-in-url name)))
|
||||||
(packagetype . "bdist_wheel")))))))))
|
(packagetype . "bdist_wheel")))))))))
|
||||||
|
|
||||||
(define test-json-1
|
|
||||||
(foo-json))
|
|
||||||
|
|
||||||
(define test-json-2
|
|
||||||
(foo-json #:name "foo-99"))
|
|
||||||
|
|
||||||
(define test-source-hash
|
|
||||||
"")
|
|
||||||
|
|
||||||
(define test-specifications
|
(define test-specifications
|
||||||
'("Fizzy [foo, bar]"
|
'("Fizzy [foo, bar]"
|
||||||
"PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1"
|
"PickyThing<1.6,>1.9,!=1.9.6,<2.0a0,==2.4c1"
|
||||||
|
@ -187,6 +182,18 @@ (define (wheel-file name specs)
|
||||||
(delete-file-recursively directory)
|
(delete-file-recursively directory)
|
||||||
whl-file))
|
whl-file))
|
||||||
|
|
||||||
|
(define (file-dump file)
|
||||||
|
"Return a procedure that dumps FILE to the given port."
|
||||||
|
(lambda (output)
|
||||||
|
(call-with-input-file file
|
||||||
|
(lambda (input)
|
||||||
|
(dump-port input output)))))
|
||||||
|
|
||||||
|
(define-syntax-rule (with-pypi responses body ...)
|
||||||
|
(with-http-server responses
|
||||||
|
(parameterize ((%pypi-base-url (%local-url #:path "/")))
|
||||||
|
body ...)))
|
||||||
|
|
||||||
|
|
||||||
(test-begin "pypi")
|
(test-begin "pypi")
|
||||||
|
|
||||||
|
@ -275,200 +282,146 @@ (define (wheel-file name specs)
|
||||||
"https://files.pythonhosted.org/packages/f0/f00/goo-0.0.0.tar.gz"))
|
"https://files.pythonhosted.org/packages/f0/f00/goo-0.0.0.tar.gz"))
|
||||||
|
|
||||||
(test-assert "pypi->guix-package, no wheel"
|
(test-assert "pypi->guix-package, no wheel"
|
||||||
;; Replace network resources with sample data.
|
(let ((tarball (pypi-tarball
|
||||||
(mock ((guix import utils) url-fetch
|
"foo-1.0.0"
|
||||||
(lambda (url file-name)
|
`(("src/bizarre.egg-info/requires.txt"
|
||||||
(match url
|
,test-requires.txt))))
|
||||||
("https://example.com/foo-1.0.0.tar.gz"
|
(twice (lambda (lst) (append lst lst))))
|
||||||
;; Unusual requires.txt location should still be found.
|
(with-pypi (twice `(("/foo-1.0.0.tar.gz" 200 ,(file-dump tarball))
|
||||||
(let ((tarball (pypi-tarball "foo-1.0.0"
|
("/foo-1.0.0-py2.py3-none-any.whl" 404 "")
|
||||||
`(("src/bizarre.egg-info/requires.txt"
|
("/foo/json" 200 ,(lambda (port)
|
||||||
,test-requires.txt)))))
|
(display (foo-json) port)))))
|
||||||
(copy-file tarball file-name)
|
(match (pypi->guix-package "foo")
|
||||||
(set! test-source-hash
|
(('package
|
||||||
(call-with-input-file file-name port-sha256))))
|
('name "python-foo")
|
||||||
("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
|
('version "1.0.0")
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('source ('origin
|
||||||
(mock ((guix http-client) http-fetch
|
('method 'url-fetch)
|
||||||
(lambda (url . rest)
|
('uri ('pypi-uri "foo" 'version))
|
||||||
(match url
|
('sha256
|
||||||
("https://pypi.org/pypi/foo/json"
|
('base32
|
||||||
(values (open-input-string test-json-1)
|
(? string? hash)))))
|
||||||
(string-length test-json-1)))
|
('build-system 'pyproject-build-system)
|
||||||
("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
|
('propagated-inputs ('list 'python-bar 'python-foo))
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('native-inputs ('list 'python-pytest))
|
||||||
(match (pypi->guix-package "foo")
|
('home-page "http://example.com")
|
||||||
(('package
|
('synopsis "summary")
|
||||||
('name "python-foo")
|
('description "summary")
|
||||||
('version "1.0.0")
|
('license 'license:lgpl2.0))
|
||||||
('source ('origin
|
(and (string=? (bytevector->nix-base32-string
|
||||||
('method 'url-fetch)
|
(file-sha256 tarball))
|
||||||
('uri ('pypi-uri "foo" 'version))
|
hash)
|
||||||
('sha256
|
(equal? (pypi->guix-package "foo" #:version "1.0.0")
|
||||||
('base32
|
(pypi->guix-package "foo"))
|
||||||
(? string? hash)))))
|
(guard (c ((error? c) #t))
|
||||||
('build-system 'pyproject-build-system)
|
(pypi->guix-package "foo" #:version "42"))))
|
||||||
('propagated-inputs ('list 'python-bar 'python-foo))
|
(x
|
||||||
('native-inputs ('list 'python-pytest))
|
(pk 'fail x #f))))))
|
||||||
('home-page "http://example.com")
|
|
||||||
('synopsis "summary")
|
|
||||||
('description "summary")
|
|
||||||
('license 'license:lgpl2.0))
|
|
||||||
(and (string=? (bytevector->nix-base32-string
|
|
||||||
test-source-hash)
|
|
||||||
hash)
|
|
||||||
(equal? (pypi->guix-package "foo" #:version "1.0.0")
|
|
||||||
(pypi->guix-package "foo"))
|
|
||||||
(guard (c ((error? c) #t))
|
|
||||||
(pypi->guix-package "foo" #:version "42"))))
|
|
||||||
(x
|
|
||||||
(pk 'fail x #f))))))
|
|
||||||
|
|
||||||
(test-skip (if (which "zip") 0 1))
|
(test-skip (if (which "zip") 0 1))
|
||||||
(test-assert "pypi->guix-package, wheels"
|
(test-assert "pypi->guix-package, wheels"
|
||||||
;; Replace network resources with sample data.
|
(let ((tarball (pypi-tarball
|
||||||
(mock ((guix import utils) url-fetch
|
"foo-1.0.0"
|
||||||
(lambda (url file-name)
|
'(("foo-1.0.0/foo.egg-info/requires.txt"
|
||||||
(match url
|
"wrong data \
|
||||||
("https://example.com/foo-1.0.0.tar.gz"
|
to make sure we're testing wheels"))))
|
||||||
(let ((tarball (pypi-tarball
|
(wheel (wheel-file "foo-1.0.0"
|
||||||
"foo-1.0.0"
|
`(("METADATA" ,test-metadata)))))
|
||||||
'(("foo-1.0.0/foo.egg-info/requires.txt"
|
(with-pypi `(("/foo-1.0.0.tar.gz" 200 ,(file-dump tarball))
|
||||||
"wrong data \
|
("/foo-1.0.0-py2.py3-none-any.whl"
|
||||||
to make sure we're testing wheels")))))
|
200 ,(file-dump wheel))
|
||||||
(copy-file tarball file-name)
|
("/foo/json" 200 ,(lambda (port)
|
||||||
(set! test-source-hash
|
(display (foo-json) port))))
|
||||||
(call-with-input-file file-name port-sha256))))
|
;; Not clearing the memoization cache here would mean returning the value
|
||||||
("https://example.com/foo-1.0.0-py2.py3-none-any.whl"
|
;; computed in the previous test.
|
||||||
(let ((wheel (wheel-file "foo-1.0.0"
|
(invalidate-memoization! pypi->guix-package)
|
||||||
`(("METADATA" ,test-metadata)))))
|
(match (pypi->guix-package "foo")
|
||||||
(copy-file wheel file-name)))
|
(('package
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('name "python-foo")
|
||||||
(mock ((guix http-client) http-fetch
|
('version "1.0.0")
|
||||||
(lambda (url . rest)
|
('source ('origin
|
||||||
(match url
|
('method 'url-fetch)
|
||||||
("https://pypi.org/pypi/foo/json"
|
('uri ('pypi-uri "foo" 'version))
|
||||||
(values (open-input-string test-json-1)
|
('sha256
|
||||||
(string-length test-json-1)))
|
('base32
|
||||||
("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
|
(? string? hash)))))
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('build-system 'pyproject-build-system)
|
||||||
;; Not clearing the memoization cache here would mean returning the value
|
('propagated-inputs ('list 'python-bar 'python-baz))
|
||||||
;; computed in the previous test.
|
('native-inputs ('list 'python-pytest))
|
||||||
(invalidate-memoization! pypi->guix-package)
|
('home-page "http://example.com")
|
||||||
(match (pypi->guix-package "foo")
|
('synopsis "summary")
|
||||||
(('package
|
('description "summary")
|
||||||
('name "python-foo")
|
('license 'license:lgpl2.0))
|
||||||
('version "1.0.0")
|
(string=? (bytevector->nix-base32-string (file-sha256 tarball))
|
||||||
('source ('origin
|
hash))
|
||||||
('method 'url-fetch)
|
(x
|
||||||
('uri ('pypi-uri "foo" 'version))
|
(pk 'fail x #f))))))
|
||||||
('sha256
|
|
||||||
('base32
|
|
||||||
(? string? hash)))))
|
|
||||||
('build-system 'pyproject-build-system)
|
|
||||||
('propagated-inputs ('list 'python-bar 'python-baz))
|
|
||||||
('native-inputs ('list 'python-pytest))
|
|
||||||
('home-page "http://example.com")
|
|
||||||
('synopsis "summary")
|
|
||||||
('description "summary")
|
|
||||||
('license 'license:lgpl2.0))
|
|
||||||
(string=? (bytevector->nix-base32-string
|
|
||||||
test-source-hash)
|
|
||||||
hash))
|
|
||||||
(x
|
|
||||||
(pk 'fail x #f))))))
|
|
||||||
|
|
||||||
(test-assert "pypi->guix-package, no usable requirement file."
|
(test-assert "pypi->guix-package, no usable requirement file."
|
||||||
;; Replace network resources with sample data.
|
(let ((tarball (pypi-tarball "foo-1.0.0"
|
||||||
(mock ((guix import utils) url-fetch
|
'(("foo.egg-info/.empty" "")))))
|
||||||
(lambda (url file-name)
|
(with-pypi `(("/foo-1.0.0.tar.gz" 200 ,(file-dump tarball))
|
||||||
(match url
|
("/foo-1.0.0-py2.py3-none-any.whl" 404 "")
|
||||||
("https://example.com/foo-1.0.0.tar.gz"
|
("/foo/json" 200 ,(lambda (port)
|
||||||
(let ((tarball (pypi-tarball "foo-1.0.0"
|
(display (foo-json) port))))
|
||||||
'(("foo.egg-info/.empty" "")))))
|
;; Not clearing the memoization cache here would mean returning the
|
||||||
(copy-file tarball file-name)
|
;; value computed in the previous test.
|
||||||
(set! test-source-hash
|
(invalidate-memoization! pypi->guix-package)
|
||||||
(call-with-input-file file-name port-sha256))))
|
(match (pypi->guix-package "foo")
|
||||||
("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
|
(('package
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('name "python-foo")
|
||||||
(mock ((guix http-client) http-fetch
|
('version "1.0.0")
|
||||||
(lambda (url . rest)
|
('source ('origin
|
||||||
(match url
|
('method 'url-fetch)
|
||||||
("https://pypi.org/pypi/foo/json"
|
('uri ('pypi-uri "foo" 'version))
|
||||||
(values (open-input-string test-json-1)
|
('sha256
|
||||||
(string-length test-json-1)))
|
('base32
|
||||||
("https://example.com/foo-1.0.0-py2.py3-none-any.whl" #f)
|
(? string? hash)))))
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('build-system 'pyproject-build-system)
|
||||||
;; Not clearing the memoization cache here would mean returning the value
|
('home-page "http://example.com")
|
||||||
;; computed in the previous test.
|
('synopsis "summary")
|
||||||
(invalidate-memoization! pypi->guix-package)
|
('description "summary")
|
||||||
(match (pypi->guix-package "foo")
|
('license 'license:lgpl2.0))
|
||||||
(('package
|
(string=? (bytevector->nix-base32-string (file-sha256 tarball))
|
||||||
('name "python-foo")
|
hash))
|
||||||
('version "1.0.0")
|
(x
|
||||||
('source ('origin
|
(pk 'fail x #f))))))
|
||||||
('method 'url-fetch)
|
|
||||||
('uri ('pypi-uri "foo" 'version))
|
|
||||||
('sha256
|
|
||||||
('base32
|
|
||||||
(? string? hash)))))
|
|
||||||
('build-system 'pyproject-build-system)
|
|
||||||
('home-page "http://example.com")
|
|
||||||
('synopsis "summary")
|
|
||||||
('description "summary")
|
|
||||||
('license 'license:lgpl2.0))
|
|
||||||
(string=? (bytevector->nix-base32-string
|
|
||||||
test-source-hash)
|
|
||||||
hash))
|
|
||||||
(x
|
|
||||||
(pk 'fail x #f))))))
|
|
||||||
|
|
||||||
(test-assert "pypi->guix-package, package name contains \"-\" followed by digits"
|
(test-assert "pypi->guix-package, package name contains \"-\" followed by digits"
|
||||||
;; Replace network resources with sample data.
|
(let ((tarball (pypi-tarball "foo-99-1.0.0"
|
||||||
(mock ((guix import utils) url-fetch
|
`(("src/bizarre.egg-info/requires.txt"
|
||||||
(lambda (url file-name)
|
,test-requires.txt)))))
|
||||||
(match url
|
(with-pypi `(("/foo-99-1.0.0.tar.gz" 200 ,(file-dump tarball))
|
||||||
("https://example.com/foo-99-1.0.0.tar.gz"
|
("/foo-99-1.0.0-py2.py3-none-any.whl" 404 "")
|
||||||
(let ((tarball (pypi-tarball "foo-99-1.0.0"
|
("/foo-99/json" 200 ,(lambda (port)
|
||||||
`(("src/bizarre.egg-info/requires.txt"
|
(display (foo-json #:name "foo-99")
|
||||||
,test-requires.txt)))))
|
port))))
|
||||||
;; Unusual requires.txt location should still be found.
|
(match (pypi->guix-package "foo-99")
|
||||||
(copy-file tarball file-name)
|
(('package
|
||||||
(set! test-source-hash
|
('name "python-foo-99")
|
||||||
(call-with-input-file file-name port-sha256))))
|
('version "1.0.0")
|
||||||
("https://example.com/foo-99-1.0.0-py2.py3-none-any.whl" #f)
|
('source ('origin
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('method 'url-fetch)
|
||||||
(mock ((guix http-client) http-fetch
|
('uri ('pypi-uri "foo-99" 'version))
|
||||||
(lambda (url . rest)
|
('sha256
|
||||||
(match url
|
('base32
|
||||||
("https://pypi.org/pypi/foo-99/json"
|
(? string? hash)))))
|
||||||
(values (open-input-string test-json-2)
|
('properties ('quote (("upstream-name" . "foo-99"))))
|
||||||
(string-length test-json-2)))
|
('build-system 'pyproject-build-system)
|
||||||
("https://example.com/foo-99-1.0.0-py2.py3-none-any.whl" #f)
|
('propagated-inputs ('list 'python-bar 'python-foo))
|
||||||
(_ (error "Unexpected URL: " url)))))
|
('native-inputs ('list 'python-pytest))
|
||||||
(match (pypi->guix-package "foo-99")
|
('home-page "http://example.com")
|
||||||
(('package
|
('synopsis "summary")
|
||||||
('name "python-foo-99")
|
('description "summary")
|
||||||
('version "1.0.0")
|
('license 'license:lgpl2.0))
|
||||||
('source ('origin
|
(string=? (bytevector->nix-base32-string (file-sha256 tarball))
|
||||||
('method 'url-fetch)
|
hash))
|
||||||
('uri ('pypi-uri "foo-99" 'version))
|
(x
|
||||||
('sha256
|
(pk 'fail x #f))))))
|
||||||
('base32
|
|
||||||
(? string? hash)))))
|
|
||||||
('properties ('quote (("upstream-name" . "foo-99"))))
|
|
||||||
('build-system 'pyproject-build-system)
|
|
||||||
('propagated-inputs ('list 'python-bar 'python-foo))
|
|
||||||
('native-inputs ('list 'python-pytest))
|
|
||||||
('home-page "http://example.com")
|
|
||||||
('synopsis "summary")
|
|
||||||
('description "summary")
|
|
||||||
('license 'license:lgpl2.0))
|
|
||||||
(string=? (bytevector->nix-base32-string
|
|
||||||
test-source-hash)
|
|
||||||
hash))
|
|
||||||
(x
|
|
||||||
(pk 'fail x #f))))))
|
|
||||||
|
|
||||||
(test-end "pypi")
|
(test-end "pypi")
|
||||||
(delete-file-recursively sample-directory)
|
(delete-file-recursively sample-directory)
|
||||||
|
|
||||||
|
;; Local Variables:
|
||||||
|
;; eval: (put 'with-pypi 'scheme-indent-function 1)
|
||||||
|
;; End:
|
||||||
|
|
Loading…
Reference in a new issue