Skip to content

Commit abfb50b

Browse files
committed
Revisit PytesterManageEnv
1 parent 8eb3804 commit abfb50b

File tree

2 files changed

+82
-19
lines changed

2 files changed

+82
-19
lines changed

src/_pytest/pytester/__init__.py

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import py.path
3232

33+
import _pytest.terminal
3334
import pytest
3435
from _pytest._code import Source
3536
from _pytest.capture import CLOSE_STDIN
@@ -56,6 +57,7 @@
5657

5758

5859
if TYPE_CHECKING:
60+
from typing import Any
5961
from typing import Type
6062
from typing_extensions import Literal # noqa: F401
6163

@@ -542,20 +544,53 @@ def _display_running(header: str, *args: str) -> None:
542544

543545

544546
class PytesterManageEnv:
547+
"""Setup/activate testdir's monkeypatching only during test calls.
548+
549+
When it would be done via the instance/fixture directly it would also be
550+
active during teardown (e.g. with the terminal plugin's reporting), where
551+
it might mess with the column width etc.
552+
"""
545553
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
546554
def pytest_runtest_setup(self):
547-
initial_home = os.getenv("HOME")
555+
self._initial_env = {
556+
k: os.getenv(k)
557+
for k in (
558+
"HOME",
559+
"PYTEST_ADDOPTS",
560+
"PYTEST_DEBUG_TEMPROOT",
561+
"PY_COLORS",
562+
"TOX_ENV_DIR",
563+
"USERPROFILE",
564+
)
565+
}
566+
self._initial_attr = {
567+
(
568+
_pytest.terminal,
569+
"get_terminal_width",
570+
): _pytest.terminal.get_terminal_width,
571+
}
548572
yield
549-
self._initial_home_changed = os.getenv("HOME") != initial_home
573+
574+
def is_unchanged(self, initial: "Any", current: "Any") -> bool:
575+
if initial is None:
576+
return current is None
577+
else:
578+
return bool(initial == current)
579+
580+
def setenv(self, mp: "MonkeyPatch", key: str, value: "Optional[str]") -> None:
581+
initial = self._initial_env[key]
582+
current = os.getenv(key)
583+
if self.is_unchanged(initial, current):
584+
mp.setenv(key, value)
585+
586+
def setattr(self, mp: "MonkeyPatch", target: object, name: str, value: "Any") -> None:
587+
initial = self._initial_attr[(target, name)]
588+
current = getattr(target, name)
589+
if self.is_unchanged(initial, current):
590+
mp.setattr(target, name, value)
550591

551592
@pytest.hookimpl(hookwrapper=True, trylast=True)
552593
def pytest_runtest_call(self, item: Function) -> Generator[None, None, None]:
553-
"""Setup/activate testdir's monkeypatching only during test calls.
554-
555-
When it would be done via the instance/fixture directly it would also be
556-
active during teardown (e.g. with the terminal plugin's reporting), where
557-
it might mess with the column width etc.
558-
"""
559594
try:
560595
funcargs = item.funcargs
561596
except AttributeError:
@@ -567,20 +602,19 @@ def pytest_runtest_call(self, item: Function) -> Generator[None, None, None]:
567602
return
568603

569604
mp = testdir.monkeypatch
570-
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(testdir.test_tmproot))
605+
self.setenv(mp, "PYTEST_DEBUG_TEMPROOT", str(testdir.test_tmproot))
571606
# Ensure no unexpected caching via tox.
572-
mp.delenv("TOX_ENV_DIR", raising=False)
607+
self.setenv(mp, "TOX_ENV_DIR", None)
573608
# Discard outer pytest options.
574-
mp.delenv("PYTEST_ADDOPTS", raising=False)
609+
self.setenv(mp, "PYTEST_ADDOPTS", None)
575610
# Ensure no user config is used.
576-
if not self._initial_home_changed:
577-
tmphome = str(testdir.tmpdir)
578-
mp.setenv("HOME", tmphome)
579-
mp.setenv("USERPROFILE", tmphome)
611+
tmphome = str(testdir.tmpdir)
612+
self.setenv(mp, "HOME", tmphome)
613+
self.setenv(mp, "USERPROFILE", tmphome)
580614
# Do not use colors for inner runs by default.
581-
mp.setenv("PY_COLORS", "0")
615+
self.setenv(mp, "PY_COLORS", "0")
582616

583-
mp.setattr("_pytest.terminal.get_terminal_width", lambda: 80)
617+
self.setattr(mp, _pytest.terminal, "get_terminal_width", lambda: 80)
584618
try:
585619
yield
586620
finally:

testing/test_pytester.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,19 +1435,48 @@ def test_makefiles_sequence_duplicate(self, testdir: "Testdir") -> None:
14351435
assert tuple(x.read_text() for x in files) == ("foo2", "bar", "foo2")
14361436

14371437

1438-
def test_home_used_from_fixture(testdir: "Testdir") -> None:
1438+
def test_env_used_from_fixture(testdir: "Testdir") -> None:
14391439
p1 = testdir.makepyfile(
14401440
"""
14411441
import os
1442+
import _pytest.terminal
14421443
import pytest
14431444
14441445
@pytest.fixture
14451446
def fix():
1447+
orig_home = os.getenv("HOME")
14461448
os.environ["HOME"] = "/new/home"
1449+
orig_userprofile = os.getenv("USERPROFILE")
1450+
1451+
yield
1452+
1453+
try:
1454+
assert os.getenv("HOME") == "/new/home" # Not changed/undone.
1455+
assert os.getenv("USERPROFILE") == orig_userprofile # Undone.
1456+
finally:
1457+
os.environ["HOME"] = orig_home
14471458
1448-
def test(testdir, fix):
1459+
def test_1(testdir, fix):
14491460
assert os.environ["HOME"] == "/new/home"
1461+
assert os.environ["USERPROFILE"] == str(testdir.tmpdir)
1462+
assert _pytest.terminal.get_terminal_width() == 80
1463+
1464+
@pytest.fixture
1465+
def fix_get_terminal_width():
1466+
orig = _pytest.terminal.get_terminal_width
1467+
_pytest.terminal.get_terminal_width = lambda: 42
1468+
1469+
yield
1470+
1471+
try:
1472+
assert _pytest.terminal.get_terminal_width() == 42 # Not changed/undone.
1473+
finally:
1474+
_pytest.terminal.get_terminal_width = orig
1475+
1476+
def test_2(testdir, fix_get_terminal_width):
1477+
assert _pytest.terminal.get_terminal_width() == 42
14501478
"""
14511479
)
14521480
result = testdir.runpytest(p1, "-ppytester")
14531481
assert result.ret == 0
1482+
result.stdout.fnmatch_lines(["*= 2 passed in *"])

0 commit comments

Comments
 (0)