diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 8aca6d9f5..296100e3b 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -1,4 +1,5 @@ """Support for providing temporary directories to test functions.""" +import atexit import os import re import sys @@ -285,18 +286,22 @@ def tmp_path( result_dict = request.node.stash[tmppath_result_key] if policy == "failed" and result_dict.get("call", True): - # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, - # permissions, etc, in which case we ignore it. - rmtree(path, ignore_errors=True) + + def remove_path_and_dead_symlink(): + # ignore_errors is required because the base directory has already been gone here + # when all the testcase is passed + rmtree(path, ignore_errors=True) + + # remove dead symlink + basetemp = tmp_path_factory._basetemp + if basetemp is None: + return + cleanup_dead_symlink(basetemp) + + atexit.register(remove_path_and_dead_symlink) del request.node.stash[tmppath_result_key] - # remove dead symlink - basetemp = tmp_path_factory._basetemp - if basetemp is None: - return - cleanup_dead_symlink(basetemp) - def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): """After each session, remove base directory if all the tests passed, @@ -313,9 +318,7 @@ def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): ): passed_dir = tmp_path_factory._basetemp if passed_dir.exists(): - # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, - # permissions, etc, in which case we ignore it. - rmtree(passed_dir, ignore_errors=True) + atexit.register(rmtree, passed_dir) @hookimpl(tryfirst=True, hookwrapper=True) diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 43437c9ab..933b8e587 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -92,136 +92,6 @@ class TestConfigTmpPath: assert mytemp.exists() assert not mytemp.joinpath("hello").exists() - def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - def test_1(tmp_path): - assert 0 == 0 - def test_2(tmp_path): - assert 0 == 1 - """ - ) - pytester.makepyprojecttoml( - """ - [tool.pytest.ini_options] - tmp_path_retention_policy = "failed" - """ - ) - - pytester.inline_run(p) - root = pytester._test_tmproot - - for child in root.iterdir(): - base_dir = list( - filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) - ) - assert len(base_dir) == 1 - test_dir = list( - filter( - lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir() - ) - ) - # Check only the failed one remains - assert len(test_dir) == 1 - assert test_dir[0].name == "test_20" - - def test_policy_failed_removes_basedir_when_all_passed( - self, pytester: Pytester - ) -> None: - p = pytester.makepyfile( - """ - def test_1(tmp_path): - assert 0 == 0 - """ - ) - pytester.makepyprojecttoml( - """ - [tool.pytest.ini_options] - tmp_path_retention_policy = "failed" - """ - ) - - pytester.inline_run(p) - root = pytester._test_tmproot - for child in root.iterdir(): - # This symlink will be deleted by cleanup_numbered_dir **after** - # the test finishes because it's triggered by atexit. - # So it has to be ignored here. - base_dir = filter(lambda x: not x.is_symlink(), child.iterdir()) - # Check the base dir itself is gone - assert len(list(base_dir)) == 0 - - # issue #10502 - def test_policy_failed_removes_dir_when_skipped_from_fixture( - self, pytester: Pytester - ) -> None: - p = pytester.makepyfile( - """ - import pytest - - @pytest.fixture - def fixt(tmp_path): - pytest.skip() - - def test_fixt(fixt): - pass - """ - ) - pytester.makepyprojecttoml( - """ - [tool.pytest.ini_options] - tmp_path_retention_policy = "failed" - """ - ) - - pytester.inline_run(p) - - # Check if the whole directory is removed - root = pytester._test_tmproot - for child in root.iterdir(): - base_dir = list( - filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) - ) - assert len(base_dir) == 0 - - # issue #10502 - def test_policy_all_keeps_dir_when_skipped_from_fixture( - self, pytester: Pytester - ) -> None: - p = pytester.makepyfile( - """ - import pytest - - @pytest.fixture - def fixt(tmp_path): - pytest.skip() - - def test_fixt(fixt): - pass - """ - ) - pytester.makepyprojecttoml( - """ - [tool.pytest.ini_options] - tmp_path_retention_policy = "all" - """ - ) - pytester.inline_run(p) - - # Check if the whole directory is kept - root = pytester._test_tmproot - for child in root.iterdir(): - base_dir = list( - filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) - ) - assert len(base_dir) == 1 - test_dir = list( - filter( - lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir() - ) - ) - assert len(test_dir) == 1 - testdata = [ ("mypath", True),