-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds a feature to control how directories created by the tmp_path
fixture are kept.
#10442
Conversation
tmp_path
fixture are kept.
tmp_path
fixture are kept.tmp_path
fixture are kept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @yusuke-kadowaki for the contribution.
We also need to update the tmpdir.rst
docs with the new options, and mention the configuration variables (as I requested them to be changed from command-line options to conf variables) in reference.rst
.
src/_pytest/main.py
Outdated
@@ -228,6 +228,23 @@ def pytest_addoption(parser: Parser) -> None: | |||
), | |||
) | |||
|
|||
group.addoption( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make those configuration variables instead, seems like something users will configure once instead of passing them in the command-line all the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
I was confused with command line options and configuration options.
src/_pytest/tmpdir.py
Outdated
@@ -31,10 +35,14 @@ class TempPathFactory: | |||
_given_basetemp = attr.ib(type=Optional[Path]) | |||
_trace = attr.ib() | |||
_basetemp = attr.ib(type=Optional[Path]) | |||
_retention_count = attr.ib(type=int) | |||
_retention_policy = attr.ib(type=str) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_retention_policy = attr.ib(type=str) | |
_retention_policy = attr.ib(type=Literal["none", "failed", "all"]) |
We should probably make a TypeAlias
for that and reuse it everywhere it is declared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this.
RetentionType = Literal["all", "failed", "none"]
src/_pytest/tmpdir.py
Outdated
rmtree(passed_dir) | ||
|
||
# Register to remove the base directory before starting cleanup_numbered_dir | ||
atexit.register(cleanup_passed_dir, tmp_path_factory._basetemp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to explicitly remove them at this point instead of relying on atexit
.
pytest might be embedded, for example as part of user script or IDE, which might not actually exit the interpreter for a awhile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Is there any reason for using atexit
to execute cleanup_numbered_dir
in the first place? That makes it really difficult to test the behavior.
changelog/8141.feature.rst
Outdated
@@ -0,0 +1 @@ | |||
Added `--tmp-path-retention-count` and `--tmp-path-retention-policy` options to control how directories created by the `tmp_path` fixture are kept. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also mention explicitly that the default has changed to remove all directories if all tests passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ce71d7b
tmp_path_factory: TempPathFactory = session.config._tmp_path_factory | ||
if tmp_path_factory._basetemp is None: | ||
return | ||
policy = tmp_path_factory._retention_policy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was under the impression that the failed
policy would remove all passed tests, even if some tests failed.
Here it will remove all directories only if all tests pass.
Or is that handled somewhere else that I am missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it will remove all directories only if all tests pass.
This is actually how I implemented it. And I added some implementation to match the behavior you mentioned.
0abbf8c
Only the Windows tests are failing but I'm not familiar with Windows so I need some help. My first guess is we need to stick to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @yusuke-kadowaki!
Left a few comments.
@RonnyPfannschmidt would also appreciate your review given your familiarity with this code.
changelog/8141.feature.rst
Outdated
@@ -0,0 +1,2 @@ | |||
Added `--tmp-path-retention-count` and `--tmp-path-retention-policy` options to control how directories created by the `tmp_path` fixture are kept. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added `--tmp-path-retention-count` and `--tmp-path-retention-policy` options to control how directories created by the `tmp_path` fixture are kept. | |
Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with c7eb5a3.
changelog/8141.feature.rst
Outdated
@@ -0,0 +1,2 @@ | |||
Added `--tmp-path-retention-count` and `--tmp-path-retention-policy` options to control how directories created by the `tmp_path` fixture are kept. | |||
The default behavior has changed to remove all directories if all tests passed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default behavior has changed to remove all directories if all tests passed. | |
The default behavior has changed to keep only directories for failed tests, equivalent to `tmp_path_retention_policy="failed"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with c7eb5a3.
src/_pytest/tmpdir.py
Outdated
tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore | ||
policy = tmp_path_factory._retention_policy | ||
if policy == "failed" and request.node.result_call.passed: | ||
rmtree(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rmtree(path) | |
# 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with 94740c0.
src/_pytest/tmpdir.py
Outdated
def pytest_runtest_makereport(item, call): | ||
outcome = yield | ||
result = outcome.get_result() | ||
setattr(item, "result_" + result.when, result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us make this private to not encourage users and plugins to depend on it.
setattr(item, "result_" + result.when, result) | |
setattr(item, "_tmp_path_result_" + result.when, result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with dc11283.
src/_pytest/tmpdir.py
Outdated
# Register to remove the base directory before starting cleanup_numbered_dir | ||
passed_dir = tmp_path_factory._basetemp | ||
if passed_dir.exists(): | ||
rmtree(passed_dir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rmtree(passed_dir) | |
# 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with 94740c0.
elif name == "tmp_path_retention_policy": | ||
return "failed" | ||
else: | ||
assert False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/_pytest/tmpdir.py
Outdated
basetemp = tmp_path_factory._basetemp | ||
if basetemp is None: | ||
return | ||
for left_dir in basetemp.iterdir(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets move this block of code to a function in pathlib.py
and reuse it in cleanup_numbered_dir
. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with b04bbb1.
pytester.inline_run(p) | ||
root = pytester._test_tmproot | ||
for child in root.iterdir(): | ||
base_dir = filter(lambda x: not x.is_symlink(), child.iterdir()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we want to ensure the symlinks are removed too?
base_dir = filter(lambda x: not x.is_symlink(), child.iterdir()) | |
base_dir = list(child.iterdir()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This symlink will be deleted by cleanup_numbered_dir
after the test finishes because it's triggered by atexit
. So you cannot really test it here.
Added comments about it here instead.
Sorry I accidentally removed @RonnyPfannschmidt from reviewers and cannot get it back. |
Thanks @yusuke-kadowaki, great work! |
@RonnyPfannschmidt |
@nicoddemus |
Let's go ahead and merge this, @RonnyPfannschmidt can always take a look later and we can do a follow up if needed. 👍 |
Thanks @yusuke-kadowaki! |
thanks for landing this, this pr is good, i just dont have bandwidth for deep reviews for family reasons |
Thank you for the reviews and also maintaining this great library! |
def pytest_runtest_makereport(item, call): | ||
outcome = yield | ||
result = outcome.get_result() | ||
setattr(item, "_tmp_path_result_" + result.when, result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those aren't guaranteed to exist if a phase wasn't run, see #10502.
But also, didn't we introduce the .stash
stuff to avoid having to monkeypatch random attributes on items? It seems like we should use that here, no? Search for e.g. caplog_records_key
in the codebase to see how this is done elsewhere, including storing things based on when
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a fix for this #10517.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But also, didn't we introduce the .stash stuff to avoid having to monkeypatch random attributes on items? It seems like we should use that here, no?
Thanks for catching this @The-Compiler, completely missed that on my review. 👍
closes #8141
I recognize that the tests are insufficient but I couldn't find a good reference. Is there any integration tests that actually make sure only 3 directories remain after executing a test that uses
tmp_dir
fixture for multiple times? I assume no and it's probably because it's difficult to deal withatexit.register
?