pytest icon indicating copy to clipboard operation
pytest copied to clipboard

Another way to disable rewrites?

Open nedbat opened this issue 6 years ago • 3 comments

I am trying to add a pytest plugin to coverage.py. Because the plugin is in the coverage package, coverage/init.py will be imported when the plugin is imported. Because I'm using coverage to run pytest in the first place, coverage/__init__.py has already been imported. This leads to a warning:

PytestAssertRewriteWarning: Module already imported so cannot be rewritten: coverage

I can disable this warning by adding PYTEST_DONT_REWRITE to the coverage/__init__.py docstring, but then that word is visible in the docstring for my package. Ugly.

Much of pytest is controlled by specially named attributes in modules. It would be great if I could control rewriting by adding an attribute to my module instead of amending the docstring.

If needed, I can provide a reproducer that shows the warning.

nedbat avatar Jun 23 '19 19:06 nedbat

Here is a gist showing the warning happening: https://gist.github.com/nedbat/2ba6834eab6d008dbd1c494c39f61bad (the warning about __main__ is a confusion between coverage's file and pytest's, and only happens with a -e install of coverage, so I am not concerned about it.)

nedbat avatar Jun 23 '19 20:06 nedbat

Yes, controlling this in another way sounds useful.

One solution for this might be to skip the warning during import of the (entrypoint) plugin (and also for -p coverage.pytest_plugin), only explicitly for __init__.py files seen during it (the import process). It should/would still happen for coverage.pytest_plugin itself then.

blueyed avatar Jun 23 '19 20:06 blueyed

Hi, we've run into a similar issue with Basilisp, where the basilisp package includes a pytest plugin as part of its source tree:

In pyrproject.toml

[tool.poetry.plugins.pytest11]:
basilisp_test_runner = "basilisp.contrib.pytest.testrunner"

The package also provides a CLI that invokes pytest.main(): From src/basilisp/cli.py:

from basilisp import main as basilisp
#...
def test(
    parser: argparse.ArgumentParser,
    args: argparse.Namespace,
    extra: list[str],
) -> None:  # pragma: no cover
#...
        sys.exit(pytest.main(args=list(extra)))

However, since:

  1. basilisp declares a pytest plugin, enabling assertion rewriting, and
  2. the basilisp module is already imported when pytest.main() is called,

pytest raises the warning:: Module already imported so cannot be rewritten: basilisp

We'd like to suppress this warning for basilisp, as assertion rewriting isn't needed here. While pytest supports suppressing warnings using the -W option:

-W ignore:Module already imported so cannot be rewritten: basilisp:pytest.PytestAssertRewriteWarning:

attempting this like so:

from basilisp import main as basilisp
#...
def test(
    parser: argparse.ArgumentParser,
    args: argparse.Namespace,
    extra: list[str],
) -> None:  # pragma: no cover
#...
      extra = [
            "-W",
            "ignore:Module already imported so cannot be rewritten:* basilisp:pytest.PytestAssertRewriteWarning",
        ] + extra
        sys.exit(pytest.main(args=list(extra)))

fails with the error:

ERROR: while parsing the following warning configuration:
 ignore:Module already imported so cannot be rewritten:* basilisp:pytest.PytestAssertRewriteWarning
 This error occurred:
 Traceback (most recent call last):
  File "d:\src\basilisp\.venv\Lib\site-packages\_pytest\config\__init__.py", line 1918, in parse_warning_filter
    category: type[Warning] = _resolve_warning_category(category_)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "d:\src\basilisp\.venv\Lib\site-packages\_pytest\config\__init__.py", line 1957, in _resolve_warning_category
    cat = getattr(m, klass)
          ^^^^^^^^^^^^^^^^^
AttributeError: module 'builtins' has no attribute '* basilisp'

this doesn't work because the message contains a literal : (as in ...rewritten: basilisp), which is treated as field separator in -W, corrupting the filter definition.

Since wildcards aren't supported in -W filters one possible solution is to change the warning message in https://github.com/pytest-dev/pytest/blob/919ae9d7c12bec52ea0c18a14299223eadb2b890/src/_pytest/assertion/rewrite.py#L284

to replace : for example with something like ;:

              f"Module already imported so cannot be rewritten; {name}"

So that the -W filter can now work (there is no extra : in the filter any more): -W ignore:Module already imported so cannot be rewritten; basilisp:pytest.PytestAssertRewriteWarning.

Using : in warning messages makes it incompatible with some Python’s warning filter syntax, so replacing it would improve usability. I’m planning to raise a PR to suggest this change.

Alternatives considered

  • Use Python's warning.filterwarnings() prior to calling pytest.main:
import warnings
warnings.filterwarnings("ignore", message="Module already imported so cannot be rewritten: basilisp", category=pytest.PytestAssertRewriteWarning)
sys.exit(pytest.main(args=list(extra)))

This doesn't work doesn't work because pytest disables all filters internally when emitting this warning:

https://github.com/pytest-dev/pytest/blob/20c51f70c99ecaa4745d622a69bbe93fbf59ad16/src/_pytest/config/init.py#L1541-L1574

  • Users adding explicit filterwarnings in their pyproject.toml:
[tool.pytest.ini_options]
filterwarnings = [
    "ignore:Module already imported so cannot be rewritten. basilisp:pytest.PytestAssertRewriteWarning",
]

This works but requires the user to know and handle this technical limitation explicitly. Ideally, the warning wouldn’t be shown in the first place.

Thanks

ikappaki avatar May 12 '25 22:05 ikappaki