Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/11315.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory.
If you use ``pytester`` in combination with :func:`monkeypatch.undo() <pytest.MonkeyPatch.undo>`, the CWD might get restored.
Use :func:`monkeypatch.context() <pytest.MonkeyPatch.context>` instead.
14 changes: 2 additions & 12 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,14 +625,6 @@ def assert_outcomes(
)


class CwdSnapshot:
def __init__(self) -> None:
self.__saved = os.getcwd()

def restore(self) -> None:
os.chdir(self.__saved)


class SysModulesSnapshot:
def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None:
self.__preserve = preserve
Expand Down Expand Up @@ -696,15 +688,14 @@ def __init__(
#: be added to the list. The type of items to add to the list depends on
#: the method using them so refer to them for details.
self.plugins: List[Union[str, _PluggyPlugin]] = []
self._cwd_snapshot = CwdSnapshot()
self._sys_path_snapshot = SysPathsSnapshot()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we log a followup to ensure sys.path/sys.modules handling in coordination with monkeypatch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be possible but some complications:

  • SysPathSnapshot also saves sys.meta_path, monkeypatch doesn't.
  • Monkeypatch doesn't save sys.path by default, though pytester can probably force it to do it
  • SysModulesSnapshot has no equivalent in monkeypatch currenty as far as know. Also has some preserve_modules exclusion...

self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
self.chdir()
self._request.addfinalizer(self._finalize)
self._method = self._request.config.getoption("--runpytest")
self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)

self._monkeypatch = mp = monkeypatch
self.chdir()
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
# Ensure no unexpected caching via tox.
mp.delenv("TOX_ENV_DIR", raising=False)
Expand Down Expand Up @@ -735,7 +726,6 @@ def _finalize(self) -> None:
"""
self._sys_modules_snapshot.restore()
self._sys_path_snapshot.restore()
self._cwd_snapshot.restore()

def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
# Some zope modules used by twisted-related tests keep internal state
Expand All @@ -760,7 +750,7 @@ def chdir(self) -> None:

This is done automatically upon instantiation.
"""
os.chdir(self.path)
self._monkeypatch.chdir(self.path)

def _makefile(
self,
Expand Down
16 changes: 8 additions & 8 deletions testing/_py/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,14 +1080,14 @@ def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir):
name = "pointsback123"
ModuleType = type(os)
p = tmpdir.ensure(name + ".py")
for ending in (".pyc", "$py.class", ".pyo"):
mod = ModuleType(name)
pseudopath = tmpdir.ensure(name + ending)
mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod)
newmod = p.pyimport()
assert mod == newmod
monkeypatch.undo()
with monkeypatch.context() as mp:
for ending in (".pyc", "$py.class", ".pyo"):
mod = ModuleType(name)
pseudopath = tmpdir.ensure(name + ending)
mod.__file__ = str(pseudopath)
mp.setitem(sys.modules, name, mod)
newmod = p.pyimport()
assert mod == newmod
mod = ModuleType(name)
pseudopath = tmpdir.ensure(name + "123.py")
mod.__file__ = str(pseudopath)
Expand Down
22 changes: 13 additions & 9 deletions testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,11 @@ def entry():
reprtb = p.repr_traceback(excinfo)
assert len(reprtb.reprentries) == 3

def test_traceback_short_no_source(self, importasmod, monkeypatch) -> None:
def test_traceback_short_no_source(
self,
importasmod,
monkeypatch: pytest.MonkeyPatch,
) -> None:
mod = importasmod(
"""
def func1():
Expand All @@ -866,14 +870,14 @@ def entry():
excinfo = pytest.raises(ValueError, mod.entry)
from _pytest._code.code import Code

monkeypatch.setattr(Code, "path", "bogus")
p = FormattedExcinfo(style="short")
reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
lines = reprtb.lines
last_p = FormattedExcinfo(style="short")
last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
last_lines = last_reprtb.lines
monkeypatch.undo()
with monkeypatch.context() as mp:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because others use pytester, I think it would be worth adding a trivial changelog explaining this change, in case others find/have the same pattern in their test suites.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - added a changelog.

mp.setattr(Code, "path", "bogus")
p = FormattedExcinfo(style="short")
reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
lines = reprtb.lines
last_p = FormattedExcinfo(style="short")
last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
last_lines = last_reprtb.lines
assert lines[0] == " func1()"

assert last_lines[0] == ' raise ValueError("hello")'
Expand Down
20 changes: 12 additions & 8 deletions testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,11 @@ def test_foo():
)

@pytest.mark.skipif('"__pypy__" in sys.modules')
def test_pyc_vs_pyo(self, pytester: Pytester, monkeypatch) -> None:
def test_pyc_vs_pyo(
self,
pytester: Pytester,
monkeypatch: pytest.MonkeyPatch,
) -> None:
pytester.makepyfile(
"""
import pytest
Expand All @@ -905,13 +909,13 @@ def test_optimized():
)
p = make_numbered_dir(root=Path(pytester.path), prefix="runpytest-")
tmp = "--basetemp=%s" % p
monkeypatch.setenv("PYTHONOPTIMIZE", "2")
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False)
assert pytester.runpytest_subprocess(tmp).ret == 0
tagged = "test_pyc_vs_pyo." + PYTEST_TAG
assert tagged + ".pyo" in os.listdir("__pycache__")
monkeypatch.undo()
with monkeypatch.context() as mp:
mp.setenv("PYTHONOPTIMIZE", "2")
mp.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
mp.delenv("PYTHONPYCACHEPREFIX", raising=False)
assert pytester.runpytest_subprocess(tmp).ret == 0
tagged = "test_pyc_vs_pyo." + PYTEST_TAG
assert tagged + ".pyo" in os.listdir("__pycache__")
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False)
assert pytester.runpytest_subprocess(tmp).ret == 1
Expand Down
18 changes: 9 additions & 9 deletions testing/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,15 @@ def test_check_filepath_consistency(
name = "pointsback123"
p = tmp_path.joinpath(name + ".py")
p.touch()
for ending in (".pyc", ".pyo"):
mod = ModuleType(name)
pseudopath = tmp_path.joinpath(name + ending)
pseudopath.touch()
mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod)
newmod = import_path(p, root=tmp_path)
assert mod == newmod
monkeypatch.undo()
with monkeypatch.context() as mp:
for ending in (".pyc", ".pyo"):
mod = ModuleType(name)
pseudopath = tmp_path.joinpath(name + ending)
pseudopath.touch()
mod.__file__ = str(pseudopath)
mp.setitem(sys.modules, name, mod)
newmod = import_path(p, root=tmp_path)
assert mod == newmod
mod = ModuleType(name)
pseudopath = tmp_path.joinpath(name + "123.py")
pseudopath.touch()
Expand Down
13 changes: 0 additions & 13 deletions testing/test_pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import subprocess
import sys
import time
from pathlib import Path
from types import ModuleType
from typing import List

Expand All @@ -11,7 +10,6 @@
from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import CwdSnapshot
from _pytest.pytester import HookRecorder
from _pytest.pytester import LineMatcher
from _pytest.pytester import Pytester
Expand Down Expand Up @@ -301,17 +299,6 @@ def test_assert_outcomes_after_pytest_error(pytester: Pytester) -> None:
result.assert_outcomes(passed=0)


def test_cwd_snapshot(pytester: Pytester) -> None:
foo = pytester.mkdir("foo")
bar = pytester.mkdir("bar")
os.chdir(foo)
snapshot = CwdSnapshot()
os.chdir(bar)
assert Path().absolute() == bar
snapshot.restore()
assert Path().absolute() == foo


class TestSysModulesSnapshot:
key = "my-test-module"

Expand Down