Skip to content

Commit

Permalink
Merge pull request #10898 from pytest-dev/backport-10893-to-7.3.x
Browse files Browse the repository at this point in the history
[7.3.x] Python 3.12 alpha fixes
  • Loading branch information
bluetech committed Apr 11, 2023
2 parents 1a427d3 + 23cf1fe commit 6e26c2b
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 13 deletions.
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 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
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 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
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 @@ def rm_rf(path: Path) -> None:
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)
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 addSuccess(self, testcase: "unittest.TestCase") -> None:
def stopTest(self, testcase: "unittest.TestCase") -> None:
pass

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

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

0 comments on commit 6e26c2b

Please sign in to comment.