Skip to content

tmp_path-fixtured tests generally do not expect neither parallelism nor repetition #109

@ogrisel

Description

@ogrisel

Consider a test file named test_with_tmp_path_fixture.py that uses the tmp_path fixture:

def test_with_tmp_path(tmp_path):
    assert tmp_path.exists()
    assert tmp_path.is_dir()
    assert len(list(tmp_path.iterdir())) == 0
    d = tmp_path / "sub"
    assert not d.exists()
    d.mkdir()
    assert d.exists()

It's quite typical from such tests to assume that the folder generated by the fixture is empty when executed.

Running such a test with pytest-run-parallel will therefore fail either because of parallelism or repetition:

  • parallelism (one execution per thread):
pytest -v --parallel-threads=4 --iterations=1 test_with_tmp_path_fixture.py
================================================================= test session starts ==================================================================
platform darwin -- Python 3.13.3, pytest-8.4.0, pluggy-1.6.0 -- /Users/ogrisel/miniforge3/envs/dev/bin/python3.13
cachedir: .pytest_cache
rootdir: /Users/ogrisel/tmp
plugins: run-parallel-0.6.1.dev0, xdist-3.7.0, anyio-4.9.0
collected 1 item                                                                                                                                       
Collected 1 items to run in parallel

test_with_tmp_path_fixture.py::test_with_tmp_path PARALLEL FAILED                                                                                [100%]

======================================================================== ERRORS ========================================================================
_________________________________________________________ ERROR at call of test_with_tmp_path __________________________________________________________

tmp_path = PosixPath('/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-15/test_with_tmp_path0')

    def test_with_tmp_path(tmp_path):
        assert tmp_path.exists()
        assert tmp_path.is_dir()
        assert len(list(tmp_path.iterdir())) == 0
        d = tmp_path / "sub"
        assert not d.exists()
>       d.mkdir()

test_with_tmp_path_fixture.py:7: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-15/test_with_tmp_path0/sub'), mode = 511
parents = False, exist_ok = False

    def mkdir(self, mode=0o777, parents=False, exist_ok=False):
        """
        Create a new directory at this given path.
        """
        try:
>           os.mkdir(self, mode)
E           FileExistsError: [Errno 17] File exists: '/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-15/test_with_tmp_path0/sub'

../miniforge3/envs/dev/lib/python3.13/pathlib/_local.py:722: FileExistsError
************************************************************** pytest-run-parallel report **************************************************************
All tests were run in parallel! 🎉
=============================================================== short test summary info ================================================================
PARALLEL FAILED test_with_tmp_path_fixture.py::test_with_tmp_path - FileExistsError: [Errno 17] File exists: '/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-15/test_with_tmp_path0/...
=================================================================== 1 error in 0.10s ===================================================================

  • sequential repetition:
pytest -v --parallel-threads=1 --iterations=2 test_with_tmp_path_fixture.py
================================================================= test session starts ==================================================================
platform darwin -- Python 3.13.3, pytest-8.4.0, pluggy-1.6.0 -- /Users/ogrisel/miniforge3/envs/dev/bin/python3.13
cachedir: .pytest_cache
rootdir: /Users/ogrisel/tmp
plugins: run-parallel-0.6.1.dev0, xdist-3.7.0, anyio-4.9.0
collected 1 item                                                                                                                                       
Collected 0 items to run in parallel

test_with_tmp_path_fixture.py::test_with_tmp_path FAILED                                                                                         [100%]

======================================================================= FAILURES =======================================================================
__________________________________________________________________ test_with_tmp_path __________________________________________________________________

tmp_path = PosixPath('/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-16/test_with_tmp_path0')

    def test_with_tmp_path(tmp_path):
        assert tmp_path.exists()
        assert tmp_path.is_dir()
>       assert len(list(tmp_path.iterdir())) == 0
E       AssertionError: assert 1 == 0
E        +  where 1 = len([PosixPath('/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-16/test_with_tmp_path0/sub')])
E        +    where [PosixPath('/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-16/test_with_tmp_path0/sub')] = list(<map object at 0x10c9704f0>)
E        +      where <map object at 0x10c9704f0> = iterdir()
E        +        where iterdir = PosixPath('/private/var/folders/93/0_k_9bh97fj_15hzk2bhy5r00000gn/T/pytest-of-ogrisel/pytest-16/test_with_tmp_path0').iterdir

test_with_tmp_path_fixture.py:4: AssertionError
=============================================================== short test summary info ================================================================
FAILED test_with_tmp_path_fixture.py::test_with_tmp_path - AssertionError: assert 1 == 0
================================================================== 1 failed in 0.35s ===================================================================

but this tests does pass as expected when disabling both parallelism and repetition together:

pytest -v --parallel-threads=1 --iterations=1 test_with_tmp_path_fixture.py
================================================================= test session starts ==================================================================
platform darwin -- Python 3.13.3, pytest-8.4.0, pluggy-1.6.0 -- /Users/ogrisel/miniforge3/envs/dev/bin/python3.13
cachedir: .pytest_cache
rootdir: /Users/ogrisel/tmp
plugins: run-parallel-0.6.1.dev0, xdist-3.7.0, anyio-4.9.0
collected 1 item                                                                                                                                       
Collected 0 items to run in parallel

test_with_tmp_path_fixture.py::test_with_tmp_path PASSED                                                                                         [100%]

================================================================== 1 passed in 0.09s ===================================================================

Note that it is now possible to configure pyproject.toml as follows:

[tool.pytest.ini_options]
thread_unsafe_fixtures = [
  "tmp_path",  # does not isolate temporary directories across threads.
]

This configuration provides a low-maintenance solution to the first problem (maybe this should be the default?). However, as far as I know, there is no equivalent configuration to automatically mark those tests as repetition-unsafe.

Final note: there are other fixtures with the same problem, for instance tmpdir.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions