-
-
Notifications
You must be signed in to change notification settings - Fork 7
Closed
Description
The test suite is very aggressive in stripping environment variables. As a result, if other pytest plugins are installed on the system, they explode and cause test failures, e.g.:
___________________________________________________ test_env[new key - add to env] ____________________________________________________
testdir = <Testdir local('/tmp/pytest-of-mgorny/pytest-1/test_env0')>, env = {}, ini = '[pytest]\nenv = MAGIC=alpha'
expected_env = {'MAGIC': 'alpha'}, request = <FixtureRequest for <Function test_env[new key - add to env]>>
@pytest.mark.parametrize(
("env", "ini", "expected_env"),
[
pytest.param(
{},
"[pytest]\nenv = MAGIC=alpha",
{"MAGIC": "alpha"},
id="new key - add to env",
),
pytest.param(
{},
"[pytest]\nenv = MAGIC=alpha\n SORCERY=beta",
{"MAGIC": "alpha", "SORCERY": "beta"},
id="two new keys - add to env",
),
pytest.param(
# This test also tests for non-interference of env variables between this test and tests above
{},
"[pytest]\nenv = d:MAGIC=beta",
{"MAGIC": "beta"},
id="D flag - add to env",
),
pytest.param(
{"MAGIC": "alpha"},
"[pytest]\nenv = MAGIC=beta",
{"MAGIC": "beta"},
id="key exists in env - overwrite",
),
pytest.param(
{"MAGIC": "alpha"},
"[pytest]\nenv = D:MAGIC=beta",
{"MAGIC": "alpha"},
id="D exists - original val kept",
),
pytest.param(
{"PLANET": "world"},
"[pytest]\nenv = MAGIC=hello_{PLANET}",
{"MAGIC": "hello_world"},
id="curly exist - interpolate var",
),
pytest.param(
{"PLANET": "world"},
"[pytest]\nenv = R:MAGIC=hello_{PLANET}",
{"MAGIC": "hello_{PLANET}"},
id="R exists - not interpolate var",
),
pytest.param(
{"MAGIC": "a"},
"[pytest]\nenv = R:MAGIC={MAGIC}b\n D:MAGIC={MAGIC}c\n MAGIC={MAGIC}d",
{"MAGIC": "{MAGIC}bd"},
id="incremental interpolation",
),
pytest.param(
{"PLANET": "world"},
"[pytest]\nenv = D:R:RESULT=hello_{PLANET}",
{"RESULT": "hello_{PLANET}"},
id="two flags",
),
pytest.param(
{"PLANET": "world"},
"[pytest]\nenv = R:D:RESULT=hello_{PLANET}",
{"RESULT": "hello_{PLANET}"},
id="two flags - reversed",
),
pytest.param(
{"PLANET": "world"},
"[pytest]\nenv = d:r:RESULT=hello_{PLANET}",
{"RESULT": "hello_{PLANET}"},
id="lowercase flags",
),
pytest.param(
{"PLANET": "world"},
"[pytest]\nenv = D : R : RESULT = hello_{PLANET}",
{"RESULT": "hello_{PLANET}"},
id="whitespace is ignored",
),
pytest.param(
{"MAGIC": "zero"},
"",
{"MAGIC": "zero"},
id="empty ini works",
),
],
)
def test_env(
testdir: pytest.Testdir,
env: dict[str, str],
ini: str,
expected_env: dict[str, str],
request: pytest.FixtureRequest,
) -> None:
tmp_dir = Path(str(testdir.tmpdir))
test_name = re.sub(r"\W|^(?=\d)", "_", request.node.callspec.id).lower()
Path(str(tmp_dir / f"test_{test_name}.py")).symlink_to(Path(__file__).parent / "template.py")
(tmp_dir / "pytest.ini").write_text(ini, encoding="utf-8")
# monkeypatch persists env variables across parametrized tests, therefore using mock.patch.dict
with mock.patch.dict(os.environ, {**env, "_TEST_ENV": repr(expected_env)}, clear=True):
result = testdir.runpytest()
> result.assert_outcomes(passed=1)
/tmp/pytest-env/tests/test_env.py:111:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/_pytest/pytester.py:569: in parseoutcomes
return self.parse_summary_nouns(self.outlines)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class '_pytest.pytester.RunResult'>, lines = []
@classmethod
def parse_summary_nouns(cls, lines) -> Dict[str, int]:
"""Extract the nouns from a pytest terminal summary line.
It always returns the plural noun for consistency::
======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``.
"""
for line in reversed(lines):
if rex_session_duration.search(line):
outcomes = rex_outcome.findall(line)
ret = {noun: int(count) for (count, noun) in outcomes}
break
else:
> raise ValueError("Pytest terminal summary report not found")
E ValueError: Pytest terminal summary report not found
/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/_pytest/pytester.py:587: ValueError
-------------------------------------------------------- Captured stderr call ---------------------------------------------------------
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/_pytest/main.py", line 266, in wrap_session
INTERNALERROR> config._do_configure()
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/_pytest/config/__init__.py", line 1054, in _do_configure
INTERNALERROR> self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/pluggy/_hooks.py", line 452, in call_historic
INTERNALERROR> res = self._hookexec(self.name, self._hookimpls, kwargs, False)
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/pluggy/_manager.py", line 112, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/pluggy/_callers.py", line 116, in _multicall
INTERNALERROR> raise exception.with_traceback(exception.__traceback__)
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/pluggy/_callers.py", line 80, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/pytest_xvfb.py", line 101, in pytest_configure
INTERNALERROR> elif backend is None and not has_executable("Xvfb"):
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File "/tmp/pytest-env/.tox/py312/lib/python3.12/site-packages/pytest_xvfb.py", line 34, in has_executable
INTERNALERROR> for path in os.environ["PATH"].split(os.pathsep)
INTERNALERROR> ~~~~~~~~~~^^^^^^^^
INTERNALERROR> File "<frozen os>", line 685, in __getitem__
INTERNALERROR> KeyError: 'PATH'
Normally we avoid this problem by setting PYTEST_DISABLE_PLUGIN_AUTOLOAD
in the test environment. However, the test suite strips that variable as well, effectively making it impossible to run tests reliably in non-pristine environments.
Metadata
Metadata
Assignees
Labels
No labels