Skip to content

The test suite breaks if some other pytest plugins are installed #48

@mgorny

Description

@mgorny

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions