diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 6edb88b92..e5324a442 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1723,6 +1723,40 @@ passed multiple times. The expected format is ``name=value``. For example:: directories when executing from the root directory. +.. confval:: tmp_path_retention_count + + + + How many sessions should we keep the `tmp_path` directories, + according to `tmp_path_retention_policy`. + + .. code-block:: ini + + [pytest] + tmp_path_retention_count = 3 + + Default: 3 + + +.. confval:: tmp_path_retention_policy + + + + Controls which directories created by the `tmp_path` fixture are kept around, + based on test outcome. + + * `all`: retains directories for all tests, regardless of the outcome. + * `failed`: retains directories only for tests with outcome `error` or `failed`. + * `none`: directories are always removed after each test ends, regardless of the outcome. + + .. code-block:: ini + + [pytest] + tmp_path_retention_policy = "all" + + Default: failed + + .. confval:: usefixtures List of fixtures that will be applied to all test functions; this is semantically the same to apply diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 1813f7904..61fb7eaa4 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -228,23 +228,6 @@ def pytest_addoption(parser: Parser) -> None: ), ) - group.addoption( - "--tmp-path-retention-count", - dest="tmp_path_retention_count", - default=3, - type=int, - metavar="num", - help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.", - ) - - group.addoption( - "--tmp-path-retention-policy", - default="failed", - choices=["all", "failed", "none"], - dest="tmp_path_retention_policy", - help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome.", - ) - def validate_basetemp(path: str) -> str: # GH 7119 diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index c43dfa849..52355dff7 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -7,9 +7,16 @@ import tempfile from pathlib import Path from shutil import rmtree from typing import Optional +from typing import TYPE_CHECKING from typing import Union +if TYPE_CHECKING: + from typing_extensions import Literal + + import attr +from _pytest.config.argparsing import Parser + from .pathlib import LOCK_TIMEOUT from .pathlib import make_numbered_dir @@ -23,6 +30,8 @@ from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch +RetentionPolicy = Literal["all", "failed", "none"] + @final @attr.s(init=False) @@ -36,13 +45,13 @@ class TempPathFactory: _trace = attr.ib() _basetemp = attr.ib(type=Optional[Path]) _retention_count = attr.ib(type=int) - _retention_policy = attr.ib(type=str) + _retention_policy = attr.ib(type=RetentionPolicy) def __init__( self, given_basetemp: Optional[Path], retention_count: int, - retention_policy: str, + retention_policy: RetentionPolicy, trace, basetemp: Optional[Path] = None, *, @@ -73,11 +82,23 @@ class TempPathFactory: :meta private: """ check_ispytest(_ispytest) + count = int(config.getini("tmp_path_retention_count")) + if count < 0: + raise ValueError( + f"tmp_path_retention_count must be >= 0. Current input: {count}." + ) + + policy = config.getini("tmp_path_retention_policy") + if policy not in ("all", "failed", "none"): + raise ValueError( + f"tmp_path_retention_policy must be either all, failed, none. Current intput: {policy}." + ) + return cls( given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir"), - retention_count=config.option.tmp_path_retention_count, - retention_policy=config.option.tmp_path_retention_policy, + retention_count=count, + retention_policy=policy, _ispytest=True, ) @@ -199,6 +220,21 @@ def pytest_configure(config: Config) -> None: mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False) +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "tmp_path_retention_count", + help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.", + default=3, + ) + + parser.addini( + "tmp_path_retention_policy", + help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. " + "(all/failed/none)", + default="failed", + ) + + @fixture(scope="session") def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: """Return a :class:`pytest.TempPathFactory` instance for the test session.""" diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 7a081287f..23b4528c0 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -34,8 +34,6 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: @attr.s class FakeConfig: basetemp = attr.ib() - tmp_path_retention_count = attr.ib(default=3) - tmp_path_retention_policy = attr.ib(default="failed") @property def trace(self): @@ -44,6 +42,14 @@ class FakeConfig: def get(self, key): return lambda *k: None + def getini(self, name): + if name == "tmp_path_retention_count": + return 3 + elif name == "tmp_path_retention_policy": + return "failed" + else: + assert False + @property def option(self): return self @@ -453,7 +459,7 @@ def test_tmp_path_factory_create_directory_with_safe_permissions( """Verify that pytest creates directories under /tmp with private permissions.""" # Use the test's tmp_path as the system temproot (/tmp). monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) - tmp_factory = TempPathFactory(None, 3, "fail", lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "failed", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # No world-readable permissions. @@ -473,14 +479,14 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions( """ # Use the test's tmp_path as the system temproot (/tmp). monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) - tmp_factory = TempPathFactory(None, 3, "fail", lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "failed", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # Before - simulate bad perms. os.chmod(basetemp.parent, 0o777) assert (basetemp.parent.stat().st_mode & 0o077) != 0 - tmp_factory = TempPathFactory(None, 3, "fail", lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "failed", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # After - fixed.