Merge 5e6ce8f433
into b31db4809b
This commit is contained in:
commit
72686992a0
|
@ -1,4 +1,3 @@
|
||||||
import atexit
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
@ -244,7 +243,7 @@ def create_cleanup_lock(p: Path) -> Path:
|
||||||
return lock_path
|
return lock_path
|
||||||
|
|
||||||
|
|
||||||
def register_cleanup_lock_removal(lock_path: Path, register=atexit.register):
|
def register_cleanup_lock_removal(lock_path: Path, register):
|
||||||
"""Register a cleanup function for removing a lock, by default on atexit."""
|
"""Register a cleanup function for removing a lock, by default on atexit."""
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
|
|
||||||
|
@ -362,6 +361,7 @@ def make_numbered_dir_with_cleanup(
|
||||||
keep: int,
|
keep: int,
|
||||||
lock_timeout: float,
|
lock_timeout: float,
|
||||||
mode: int,
|
mode: int,
|
||||||
|
register,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
"""Create a numbered dir with a cleanup lock and remove old ones."""
|
"""Create a numbered dir with a cleanup lock and remove old ones."""
|
||||||
e = None
|
e = None
|
||||||
|
@ -371,13 +371,13 @@ def make_numbered_dir_with_cleanup(
|
||||||
# Only lock the current dir when keep is not 0
|
# Only lock the current dir when keep is not 0
|
||||||
if keep != 0:
|
if keep != 0:
|
||||||
lock_path = create_cleanup_lock(p)
|
lock_path = create_cleanup_lock(p)
|
||||||
register_cleanup_lock_removal(lock_path)
|
register_cleanup_lock_removal(lock_path, register=register)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
e = exc
|
e = exc
|
||||||
else:
|
else:
|
||||||
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
|
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
|
||||||
# Register a cleanup for program exit
|
# Register a cleanup for program exit
|
||||||
atexit.register(
|
register(
|
||||||
cleanup_numbered_dir,
|
cleanup_numbered_dir,
|
||||||
root,
|
root,
|
||||||
prefix,
|
prefix,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from contextlib import ExitStack
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
@ -77,6 +78,7 @@ class TempPathFactory:
|
||||||
self._retention_count = retention_count
|
self._retention_count = retention_count
|
||||||
self._retention_policy = retention_policy
|
self._retention_policy = retention_policy
|
||||||
self._basetemp = basetemp
|
self._basetemp = basetemp
|
||||||
|
self._exit_stack = ExitStack()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_config(
|
def from_config(
|
||||||
|
@ -196,6 +198,7 @@ class TempPathFactory:
|
||||||
keep=keep,
|
keep=keep,
|
||||||
lock_timeout=LOCK_TIMEOUT,
|
lock_timeout=LOCK_TIMEOUT,
|
||||||
mode=0o700,
|
mode=0o700,
|
||||||
|
register=self._exit_stack.callback,
|
||||||
)
|
)
|
||||||
assert basetemp is not None, basetemp
|
assert basetemp is not None, basetemp
|
||||||
self._basetemp = basetemp
|
self._basetemp = basetemp
|
||||||
|
@ -303,19 +306,23 @@ def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]):
|
||||||
the policy is "failed", and the basetemp is not specified by a user.
|
the policy is "failed", and the basetemp is not specified by a user.
|
||||||
"""
|
"""
|
||||||
tmp_path_factory: TempPathFactory = session.config._tmp_path_factory
|
tmp_path_factory: TempPathFactory = session.config._tmp_path_factory
|
||||||
if tmp_path_factory._basetemp is None:
|
|
||||||
return
|
# tmporal directory cleanup, which is registered to
|
||||||
policy = tmp_path_factory._retention_policy
|
# this ExitStack, will be executed at the end of this scope
|
||||||
if (
|
with tmp_path_factory._exit_stack:
|
||||||
exitstatus == 0
|
if tmp_path_factory._basetemp is None:
|
||||||
and policy == "failed"
|
return
|
||||||
and tmp_path_factory._given_basetemp is None
|
policy = tmp_path_factory._retention_policy
|
||||||
):
|
if (
|
||||||
passed_dir = tmp_path_factory._basetemp
|
exitstatus == 0
|
||||||
if passed_dir.exists():
|
and policy == "failed"
|
||||||
# We do a "best effort" to remove files, but it might not be possible due to some leaked resource,
|
and tmp_path_factory._given_basetemp is None
|
||||||
# permissions, etc, in which case we ignore it.
|
):
|
||||||
rmtree(passed_dir, ignore_errors=True)
|
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)
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(tryfirst=True, hookwrapper=True)
|
@hookimpl(tryfirst=True, hookwrapper=True)
|
||||||
|
|
|
@ -92,6 +92,73 @@ class TestConfigTmpPath:
|
||||||
assert mytemp.exists()
|
assert mytemp.exists()
|
||||||
assert not mytemp.joinpath("hello").exists()
|
assert not mytemp.joinpath("hello").exists()
|
||||||
|
|
||||||
|
def test_policy_none_delete_all(self, pytester: Pytester) -> None:
|
||||||
|
p = pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_1(tmp_path):
|
||||||
|
assert 0 == 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
p_failed = pytester.makepyfile(
|
||||||
|
another_file_name="""
|
||||||
|
def test_1(tmp_path):
|
||||||
|
assert 0 == 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
pytester.makepyprojecttoml(
|
||||||
|
"""
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
tmp_path_retention_policy = "none"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.inline_run(p)
|
||||||
|
pytester.inline_run(p_failed)
|
||||||
|
|
||||||
|
root = pytester._test_tmproot
|
||||||
|
for child in root.iterdir():
|
||||||
|
base_dir = list(child.iterdir())
|
||||||
|
# Check the base dir itself is gone without depending on test results
|
||||||
|
assert base_dir == []
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("policy", ['"failed"', '"all"'])
|
||||||
|
@pytest.mark.parametrize("count", [0, 1, 3])
|
||||||
|
def test_retention_count(self, pytester: Pytester, policy, count) -> None:
|
||||||
|
p = pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_1(tmp_path):
|
||||||
|
assert 0 == 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
p_failed = pytester.makepyfile(
|
||||||
|
another_file_name="""
|
||||||
|
def test_1(tmp_path):
|
||||||
|
assert 0 == 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.makepyprojecttoml(
|
||||||
|
f"""
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
tmp_path_retention_policy = {policy}
|
||||||
|
tmp_path_retention_count = {count}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.inline_run(p)
|
||||||
|
pytester.inline_run(p_failed)
|
||||||
|
pytester.inline_run(p)
|
||||||
|
pytester.inline_run(p_failed)
|
||||||
|
pytester.inline_run(p)
|
||||||
|
pytester.inline_run(p_failed)
|
||||||
|
pytester.inline_run(p)
|
||||||
|
pytester.inline_run(p_failed)
|
||||||
|
|
||||||
|
root = pytester._test_tmproot
|
||||||
|
for child in root.iterdir():
|
||||||
|
base_dir = filter(lambda x: not x.is_symlink(), child.iterdir())
|
||||||
|
assert len(list(base_dir)) == count
|
||||||
|
|
||||||
def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None:
|
def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None:
|
||||||
p = pytester.makepyfile(
|
p = pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
@ -119,26 +186,6 @@ class TestConfigTmpPath:
|
||||||
assert len(test_dir) == 1
|
assert len(test_dir) == 1
|
||||||
assert test_dir[0].name == "test_20"
|
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.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
|
# issue #10502
|
||||||
def test_policy_failed_removes_dir_when_skipped_from_fixture(
|
def test_policy_failed_removes_dir_when_skipped_from_fixture(
|
||||||
self, pytester: Pytester
|
self, pytester: Pytester
|
||||||
|
@ -160,10 +207,8 @@ class TestConfigTmpPath:
|
||||||
# Check if the whole directory is removed
|
# Check if the whole directory is removed
|
||||||
root = pytester._test_tmproot
|
root = pytester._test_tmproot
|
||||||
for child in root.iterdir():
|
for child in root.iterdir():
|
||||||
base_dir = list(
|
base_dir = list(child.iterdir())
|
||||||
filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
|
assert base_dir == []
|
||||||
)
|
|
||||||
assert len(base_dir) == 0
|
|
||||||
|
|
||||||
# issue #10502
|
# issue #10502
|
||||||
def test_policy_all_keeps_dir_when_skipped_from_fixture(
|
def test_policy_all_keeps_dir_when_skipped_from_fixture(
|
||||||
|
|
Loading…
Reference in New Issue