Skip to content
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

[7.3.x] Python 3.12 alpha fixes #10898

Merged
merged 1 commit into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/10875.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Python 3.12 support: fixed ``RuntimeError: TestResult has no addDuration method`` when running ``unittest`` tests.
1 change: 1 addition & 0 deletions changelog/10890.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Python 3.12 support: fixed ``shutil.rmtree(onerror=...)`` deprecation warning when using :fixture:`tmp_path`.
32 changes: 25 additions & 7 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import shutil
import sys
import types
import uuid
import warnings
from enum import Enum
Expand All @@ -28,6 +29,8 @@
from typing import Iterator
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union

Expand Down Expand Up @@ -63,21 +66,33 @@
return path.joinpath(".lock")


def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
def on_rm_rf_error(
func,
path: str,
excinfo: Union[
BaseException,
Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]],
],
*,
start_path: Path,
) -> bool:
"""Handle known read-only errors during rmtree.

The returned value is used only by our own tests.
"""
exctype, excvalue = exc[:2]
if isinstance(excinfo, BaseException):
exc = excinfo
else:
exc = excinfo[1]

# Another process removed the file in the middle of the "rm_rf" (xdist for example).
# More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
if isinstance(excvalue, FileNotFoundError):
if isinstance(exc, FileNotFoundError):
return False

if not isinstance(excvalue, PermissionError):
if not isinstance(exc, PermissionError):
warnings.warn(
PytestWarning(f"(rm_rf) error removing {path}\n{exctype}: {excvalue}")
PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}")
)
return False

Expand All @@ -86,7 +101,7 @@
warnings.warn(
PytestWarning(
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
func, path, exctype, excvalue
func, path, type(exc), exc
)
)
)
Expand Down Expand Up @@ -149,7 +164,10 @@
are read-only."""
path = ensure_extended_length_path(path)
onerror = partial(on_rm_rf_error, start_path=path)
shutil.rmtree(str(path), onerror=onerror)
if sys.version_info >= (3, 12):
shutil.rmtree(str(path), onexc=onerror)

Check warning on line 168 in src/_pytest/pathlib.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/pathlib.py#L168

Added line #L168 was not covered by tests
else:
shutil.rmtree(str(path), onerror=onerror)


def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
Expand Down
3 changes: 3 additions & 0 deletions src/_pytest/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@
def stopTest(self, testcase: "unittest.TestCase") -> None:
pass

def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None:
pass

Check warning on line 302 in src/_pytest/unittest.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/unittest.py#L302

Added line #L302 was not covered by tests

def runtest(self) -> None:
from _pytest.debugging import maybe_wrap_pytest_function_for_tracing

Expand Down
12 changes: 6 additions & 6 deletions testing/test_tmpdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,33 +512,33 @@ def test_on_rm_rf_error(self, tmp_path: Path) -> None:

# unknown exception
with pytest.warns(pytest.PytestWarning):
exc_info1 = (None, RuntimeError(), None)
exc_info1 = (RuntimeError, RuntimeError(), None)
on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path)
assert fn.is_file()

# we ignore FileNotFoundError
exc_info2 = (None, FileNotFoundError(), None)
exc_info2 = (FileNotFoundError, FileNotFoundError(), None)
assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path)

# unknown function
with pytest.warns(
pytest.PytestWarning,
match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ",
match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\n<class 'PermissionError'>: ",
):
exc_info3 = (None, PermissionError(), None)
exc_info3 = (PermissionError, PermissionError(), None)
on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path)
assert fn.is_file()

# ignored function
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None) as warninfo: # type: ignore[call-overload]
exc_info4 = (None, PermissionError(), None)
exc_info4 = PermissionError()
on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
assert fn.is_file()
assert not [x.message for x in warninfo]

exc_info5 = (None, PermissionError(), None)
exc_info5 = PermissionError()
on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)
assert not fn.is_file()

Expand Down