diff mbox series

[bug#70570,v2,2/2] guix: pyproject-build-system: Ignore unwanted pytest flags.

Message ID 20240427165504.8843-2-ngraves@ngraves.fr
State New
Headers show
Series [bug#70570,v2,1/2] guix: import: pypi: Ignore pypi-ignored-inputs. | expand

Commit Message

Nicolas Graves April 27, 2024, 4:54 p.m. UTC
* guix/build/pyproject-build-system.scm : Ignore unwanted pytest flags.

Change-Id: Ib9f1602e5af11227e5b7ce124f0f9be4fa2b78e4
---
 guix/build/pyproject-build-system.scm | 91 ++++++++++++++++++++++++++-
 1 file changed, 89 insertions(+), 2 deletions(-)

Comments

Nicolas Graves April 28, 2024, 10:14 a.m. UTC | #1
On 2024-04-27 18:54, Nicolas Graves wrote:

> * guix/build/pyproject-build-system.scm : Ignore unwanted pytest flags.
>
> Change-Id: Ib9f1602e5af11227e5b7ce124f0f9be4fa2b78e4
> ---
>  guix/build/pyproject-build-system.scm | 91 ++++++++++++++++++++++++++-
>  1 file changed, 89 insertions(+), 2 deletions(-)
>
> diff --git a/guix/build/pyproject-build-system.scm b/guix/build/pyproject-build-system.scm
> index 947d240114..ebe4e1941d 100644
> --- a/guix/build/pyproject-build-system.scm
> +++ b/guix/build/pyproject-build-system.scm
> @@ -1,6 +1,7 @@
>  ;;; GNU Guix --- Functional package management for GNU
>  ;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
>  ;;; Copyright © 2022 Marius Bakke <marius@gnu.org>
> +;;; Copyright © 2024 Nicolas Graves <ngraves@ngraves.fr>
>  ;;;
>  ;;; This file is part of GNU Guix.
>  ;;;
> @@ -142,7 +143,89 @@ (define* (build #:key outputs build-backend backend-path configure-flags #:allow
>       wheel-dir
>       config-settings)))
>  
> -(define* (check #:key tests? test-backend test-flags #:allow-other-keys)
> +(define pytest-default-ignore-alist
> +  '(("cov" . ("--cov" "--cov-reset" "--cov-report" "--cov-config"
> +              "--no-cov-on-fail" "--no-cov" "--cov-fail-under"
> +              "--cov-append" "--cov-branch" "--cov-context"))
> +    ("mypy" . ("--mypy" "--mypy-config-file" "--mypy-ignore-missing-imports"))
> +    ("isort" . ("--isort"))
> +    ("flake8" . ("--flake8"))
> +    ("black" . ("--black"))
> +    ("flakes" . ("--flakes"))
> +    ("pep8" . ("--pep8"))))
> +
> +(define (pytest-ignore-flags-plugin flags)
> +  "This function converts an list of flags into a string that can
> +  be instantiated as a python pytest plugin."
> +  (format #f "\
> +import pytest
> +
> +def pytest_addoption(parser):
> +    group = parser.getgroup('guix','Guix ignored options')
> +    options = [~{~s, ~}]
> +    for option in options:
> +        group.addoption(option, action='append', nargs='?')"
> +          flags))
> +
> +(define (call-with-guix-pytest-plugin inputs thunk)
> +  "This function emulates command line options provided by pytest plugins in
> +the absence of the plugins defining these options.
> +
> +This is done by selecting absent plugins, gettings their flags defined in
> +PYTEST-DEFAULT-IGNORE-ALIST, and generating the plugin from there with
> +PYTEST-IGNORE-FLAGS-PLUGIN."
> +  (let* ((former-path (getenv "PYTHONPATH"))
> +         (input-names
> +          (filter (match-lambda
> +                    (((name . _) ...)
> +                     (if (string-prefix? "python-pytest-" name)
> +                         name
> +                         #f))
> +                    ( _ #f))
> +                  inputs))

This filter is not working properly as it doesn't output names but
pairs. Will be changed in the next revision with a map car.

> +         (filtered-flags
> +          (filter identity
> +                  (append-map
> +                   (match-lambda
> +                     ((group . flags)
> +                      (if (member (string-append "python-pytest-" group)
> +                                  input-names)
> +                          (list #f)
> +                          flags))
> +                     (_ (list #f)))
> +                   pytest-default-ignore-alist))))
> +    (dynamic-wind
> +      (lambda ()
> +        (setenv "PYTHONPATH"
> +                (string-append
> +                 (if former-path
> +                     (string-append former-path ":")
> +                     "")
> +                 ".guix-pytest"))

I found that in some edge cases, a hidden directory in the current dir
can cause tests to fail. It only happened for one package which was
failing on the __init__.py from .guix-pytest, meaning that tests could
consider this directory.

So it's better to put that out-of-source in ../.guix-pytest. Will be
done in the next revision.

> +        (setenv "PYTEST_PLUGINS"
> +                (string-append
> +                 (if (getenv "PYTEST_PLUGINS")
> +                     (string-append former-path ",")
> +                     "")
> +                 "pytest_guix_plugin"))
> +        (mkdir-p ".guix-pytest")
> +        (with-output-to-file ".guix-pytest/__init__.py"
> +          (lambda _ (display "")))
> +        (with-output-to-file ".guix-pytest/pytest_guix_plugin.py"
> +          (lambda _
> +            (display (pytest-ignore-flags-plugin filtered-flags)))))
> +      thunk
> +      (lambda ()
> +        (setenv "PYTHONPATH" former-path)
> +        (unsetenv "PYTEST_PLUGINS")
> +        (when (file-exists? ".guix-pytest")
> +          (delete-file-recursively ".guix-pytest"))))))
> +
> +(define-syntax-rule (with-guix-pytest-plugin inputs exp ...)
> +  "Evaluate EXP in a context where the Guix pytest plugin is added."
> +  (call-with-guix-pytest-plugin inputs (lambda () exp ...)))
> +
> +(define* (check #:key inputs tests? test-backend test-flags #:allow-other-keys)
>    "Run the test suite of a given Python package."
>    (if tests?
>        ;; Unfortunately with PEP 517 there is no common method to specify test
> @@ -165,7 +248,8 @@ (define* (check #:key tests? test-backend test-flags #:allow-other-keys)
>          (format #t "Using ~a~%" use-test-backend)
>          (match use-test-backend
>            ('pytest
> -           (apply invoke pytest "-vv" test-flags))
> +           (with-guix-pytest-plugin inputs
> +             (apply invoke pytest "-vv" test-flags)))
>            ('nose
>             (apply invoke nosetests "-v" test-flags))
>            ('nose2
> @@ -386,3 +470,6 @@ (define* (pyproject-build #:key inputs (phases %standard-phases)
>    (apply python:python-build #:inputs inputs #:phases phases args))
>  
>  ;;; pyproject-build-system.scm ends here
> +;;; Local Variables:
> +;;; eval: (put 'with-guix-pytest-plugin 'scheme-indent-function 1)
> +;;; End:
diff mbox series

Patch

diff --git a/guix/build/pyproject-build-system.scm b/guix/build/pyproject-build-system.scm
index 947d240114..ebe4e1941d 100644
--- a/guix/build/pyproject-build-system.scm
+++ b/guix/build/pyproject-build-system.scm
@@ -1,6 +1,7 @@ 
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2021 Lars-Dominik Braun <lars@6xq.net>
 ;;; Copyright © 2022 Marius Bakke <marius@gnu.org>
+;;; Copyright © 2024 Nicolas Graves <ngraves@ngraves.fr>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -142,7 +143,89 @@  (define* (build #:key outputs build-backend backend-path configure-flags #:allow
      wheel-dir
      config-settings)))
 
-(define* (check #:key tests? test-backend test-flags #:allow-other-keys)
+(define pytest-default-ignore-alist
+  '(("cov" . ("--cov" "--cov-reset" "--cov-report" "--cov-config"
+              "--no-cov-on-fail" "--no-cov" "--cov-fail-under"
+              "--cov-append" "--cov-branch" "--cov-context"))
+    ("mypy" . ("--mypy" "--mypy-config-file" "--mypy-ignore-missing-imports"))
+    ("isort" . ("--isort"))
+    ("flake8" . ("--flake8"))
+    ("black" . ("--black"))
+    ("flakes" . ("--flakes"))
+    ("pep8" . ("--pep8"))))
+
+(define (pytest-ignore-flags-plugin flags)
+  "This function converts an list of flags into a string that can
+  be instantiated as a python pytest plugin."
+  (format #f "\
+import pytest
+
+def pytest_addoption(parser):
+    group = parser.getgroup('guix','Guix ignored options')
+    options = [~{~s, ~}]
+    for option in options:
+        group.addoption(option, action='append', nargs='?')"
+          flags))
+
+(define (call-with-guix-pytest-plugin inputs thunk)
+  "This function emulates command line options provided by pytest plugins in
+the absence of the plugins defining these options.
+
+This is done by selecting absent plugins, gettings their flags defined in
+PYTEST-DEFAULT-IGNORE-ALIST, and generating the plugin from there with
+PYTEST-IGNORE-FLAGS-PLUGIN."
+  (let* ((former-path (getenv "PYTHONPATH"))
+         (input-names
+          (filter (match-lambda
+                    (((name . _) ...)
+                     (if (string-prefix? "python-pytest-" name)
+                         name
+                         #f))
+                    ( _ #f))
+                  inputs))
+         (filtered-flags
+          (filter identity
+                  (append-map
+                   (match-lambda
+                     ((group . flags)
+                      (if (member (string-append "python-pytest-" group)
+                                  input-names)
+                          (list #f)
+                          flags))
+                     (_ (list #f)))
+                   pytest-default-ignore-alist))))
+    (dynamic-wind
+      (lambda ()
+        (setenv "PYTHONPATH"
+                (string-append
+                 (if former-path
+                     (string-append former-path ":")
+                     "")
+                 ".guix-pytest"))
+        (setenv "PYTEST_PLUGINS"
+                (string-append
+                 (if (getenv "PYTEST_PLUGINS")
+                     (string-append former-path ",")
+                     "")
+                 "pytest_guix_plugin"))
+        (mkdir-p ".guix-pytest")
+        (with-output-to-file ".guix-pytest/__init__.py"
+          (lambda _ (display "")))
+        (with-output-to-file ".guix-pytest/pytest_guix_plugin.py"
+          (lambda _
+            (display (pytest-ignore-flags-plugin filtered-flags)))))
+      thunk
+      (lambda ()
+        (setenv "PYTHONPATH" former-path)
+        (unsetenv "PYTEST_PLUGINS")
+        (when (file-exists? ".guix-pytest")
+          (delete-file-recursively ".guix-pytest"))))))
+
+(define-syntax-rule (with-guix-pytest-plugin inputs exp ...)
+  "Evaluate EXP in a context where the Guix pytest plugin is added."
+  (call-with-guix-pytest-plugin inputs (lambda () exp ...)))
+
+(define* (check #:key inputs tests? test-backend test-flags #:allow-other-keys)
   "Run the test suite of a given Python package."
   (if tests?
       ;; Unfortunately with PEP 517 there is no common method to specify test
@@ -165,7 +248,8 @@  (define* (check #:key tests? test-backend test-flags #:allow-other-keys)
         (format #t "Using ~a~%" use-test-backend)
         (match use-test-backend
           ('pytest
-           (apply invoke pytest "-vv" test-flags))
+           (with-guix-pytest-plugin inputs
+             (apply invoke pytest "-vv" test-flags)))
           ('nose
            (apply invoke nosetests "-v" test-flags))
           ('nose2
@@ -386,3 +470,6 @@  (define* (pyproject-build #:key inputs (phases %standard-phases)
   (apply python:python-build #:inputs inputs #:phases phases args))
 
 ;;; pyproject-build-system.scm ends here
+;;; Local Variables:
+;;; eval: (put 'with-guix-pytest-plugin 'scheme-indent-function 1)
+;;; End: