pytest-dev / pytest

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing
https://pytest.org
MIT License
12.13k stars 2.68k forks source link

Tests not collected if parametrized with `itertools` objects and imported in multiple files #12685

Open david-cortes opened 3 months ago

david-cortes commented 3 months ago

I'm observing a very odd behavior of tests that are imported from other files getting collected only for one test file when they have parametrized arguments that are generated through itertools.

Example with 3 files:

class CommonTestSuite: @pytest.mark.parametrize( "m,n", itertools.product( [100, 1_000], [100, 1_000], ) ) def test_no_negative_numbers(self, generator, m, n): assert generator(m,n).min() >= 0.

* `test_poisson.py`:
```python
from common import CommonTestSuite
import pytest
import numpy as np

class PoissonGenerator:
    def __init__(self):
        self.rng = np.random.default_rng(seed=123)
    def generate(self, m, n):
        return self.rng.poisson(1, size=(m,n))

class TestPoisson(CommonTestSuite):
    obj = PoissonGenerator()

    @pytest.fixture
    def generator(self):
        return self.obj.generate

class GammaGenerator: def init(self): self.rng = np.random.default_rng(seed=123) def generate(self, m, n): return self.rng.standard_gamma(1, size=(m,n))

class TestGamma(CommonTestSuite): obj = GammaGenerator()

@pytest.fixture
def generator(self):
    return self.obj.generate

If I run `pytest . --co`, this is what I get:

============================================ test session starts ============================================ platform linux -- Python 3.11.0, pytest-7.4.0, pluggy-1.0.0 rootdir: /home/david/del/pytest_issue plugins: anyio-3.5.0, mypy-0.10.3 collected 5 items

``` That is: the tests from the common class were only collected for the first file that is executed when running `pytest .`. Same behavior happens if I add more files importing from `common.py`. If I modify the file `common.py` as follows (no itertools-generated parametrization): ```python import pytest class CommonTestSuite: @pytest.mark.parametrize( "m,n", [ (100, 10), (10, 100) ] ) def test_no_negative_numbers(self, generator, m, n): assert generator(m,n).min() >= 0. ``` Then the tests get collected as expected: ``` platform linux -- Python 3.11.0, pytest-7.4.0, pluggy-1.0.0 rootdir: /home/david/del/pytest_issue plugins: anyio-3.5.0, mypy-0.10.3 collected 4 items ``` If instead of importing the common tests from the same file in both `test_gamma.py` and `test_poisson.py` I copy-paste the code with `itertools` or "import" the file by using `file.read()` + `exec()`, then the tests are collected correctly as expected.
RonnyPfannschmidt commented 3 months ago

I recall we added a warning for this,

Please materialize the generator, pytest needs to consume them multiple times

Pytest should start to error on non repeatable parametrize marks

david-cortes commented 3 months ago

I recall we added a warning for this,

Please materialize the generator, pytest needs to consume them multiple times

Pytest should start to error on non repeatable parametrize marks

FWIW, the latest version of pytest (==8.3.2) doesn't give any warnings here.

RonnyPfannschmidt commented 3 months ago

Also a addition note parameterization natively does cross products