diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index d474df4b94e..aeab5794eef 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -500,6 +500,8 @@ def __init__(self, request, tmpdir_factory): mp.delenv("TOX_ENV_DIR", raising=False) # Discard outer pytest options. mp.delenv("PYTEST_ADDOPTS", raising=False) + # Do not load entrypoint plugins by default. + mp.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") def __repr__(self): return "" % (self.tmpdir,) @@ -790,6 +792,14 @@ def inline_genitems(self, *args): items = [x.item for x in rec.getcalls("pytest_itemcollected")] return items, rec + def _get_isolated_env(self): + tmpdir = str(self.tmpdir) + return ( + # Do not load user config. + ("HOME", tmpdir), + ("USERPROFILE", tmpdir), + ) + def inline_run(self, *args, **kwargs): """Run ``pytest.main()`` in-process, returning a HookRecorder. @@ -811,8 +821,8 @@ def inline_run(self, *args, **kwargs): try: # Do not load user config (during runs only). mp_run = MonkeyPatch() - mp_run.setenv("HOME", str(self.tmpdir)) - mp_run.setenv("USERPROFILE", str(self.tmpdir)) + for k, v in self._get_isolated_env(): + mp_run.setenv(k, v) finalizers.append(mp_run.undo) # When running pytest inline any plugins active in the main test @@ -1035,26 +1045,38 @@ def collect_by_name(self, modcol, name): def popen(self, cmdargs, stdout, stderr, **kw): """Invoke subprocess.Popen. - This calls subprocess.Popen making sure the current working directory - is in the PYTHONPATH. + This calls subprocess.Popen, making sure the current working directory + is in the PYTHONPATH by default. - You probably want to use :py:meth:`run` instead. + Optional keyword arguments: + + :param env: OS environment to be used as is (no PYTHONPATH adjustment, + nor isolation for HOME etc). + :param env_update: OS environment values to update the current + environment with. + PYTHONPATH gets adjusted if not passed in explicitly. + You probably want to use :py:meth:`run` instead. """ - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join( - filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) - ) - # Do not load user config. - env["HOME"] = str(self.tmpdir) - env["USERPROFILE"] = env["HOME"] - kw["env"] = env + if "env" in kw: + env = kw.pop("env") + if "env_update" in kw: + raise ValueError("env and env_update are mutually exclusive") + else: + env = os.environ.copy() + env.update(self._get_isolated_env()) + + env_update = kw.pop("env_update", {}) + if "PYTHONPATH" not in env_update: + env["PYTHONPATH"] = os.pathsep.join( + filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) + ) + env.update(env_update) popen = subprocess.Popen( - cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw + cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, env=env, **kw ) popen.stdin.close() - return popen def run(self, *cmdargs, **kwargs): diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 408fa076e1f..9269244153e 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -157,6 +157,7 @@ def my_iter(group, name=None): monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter) params = ("-p", "mycov") if load_cov_early else () + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") testdir.runpytest_inprocess(*params) if load_cov_early: assert loaded == ["mycov", "myplugin1", "myplugin2"] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 330b711afb7..fcc446e4979 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -155,7 +155,7 @@ def test_foo(pytestconfig): @pytest.mark.parametrize("mode", ["plain", "rewrite"]) @pytest.mark.parametrize("plugin_state", ["development", "installed"]) def test_installed_plugin_rewrite(self, testdir, mode, plugin_state, monkeypatch): - monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) + monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") # Make sure the hook is installed early enough so that plugins # installed via setuptools are rewritten. testdir.tmpdir.join("hampkg").ensure(dir=1) diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 9c7806d5489..a9b06b00989 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -7,6 +7,7 @@ def test_version(testdir, pytestconfig): + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") result = testdir.runpytest("--version") assert result.ret == 0 # p = py.path.local(py.__file__).dirpath() diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 2e4877463a8..1ffabeb90c9 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -21,6 +21,11 @@ from _pytest.pytester import SysModulesSnapshot from _pytest.pytester import SysPathsSnapshot +try: + import mock +except ImportError: + import unittest.mock as mock + def test_make_hook_recorder(testdir): item = testdir.getitem("def test_func(): pass") @@ -482,3 +487,36 @@ def test_pytester_addopts(request, monkeypatch): testdir.finalize() assert os.environ["PYTEST_ADDOPTS"] == "--orig-unused" + + +def test_popen_env(testdir, monkeypatch): + monkeypatch.delenv("PYTHONPATH", raising=False) + popen_args = (["cmd"], None, None) + + with mock.patch("subprocess.Popen") as m: + testdir.popen(*popen_args) + env = m.call_args[1]["env"] + assert set(env.keys()) == set( + list(os.environ.keys()) + ["PYTHONPATH", "USERPROFILE", "HOME"] + ) + assert env["PYTHONPATH"] == os.getcwd() + + # Updates PYTHONPATH by default. + monkeypatch.setenv("PYTHONPATH", "custom") + testdir.popen(*popen_args) + env = m.call_args[1]["env"] + assert env["PYTHONPATH"] == os.pathsep.join((os.getcwd(), "custom")) + + # Uses explicit PYTHONPATH via env_update. + testdir.popen(*popen_args, env_update={"PYTHONPATH": "mypp", "CUSTOM_ENV": "1"}) + env = m.call_args[1]["env"] + assert env["PYTHONPATH"] == "mypp" + assert env["CUSTOM_ENV"] == "1" + + # Uses explicit env only. + testdir.popen(*popen_args, env={"CUSTOM_ENV": "1"}) + env = m.call_args[1]["env"] + assert env == {"CUSTOM_ENV": "1"} + + with pytest.raises(ValueError, match="env and env_update are mutually exclusive"): + testdir.popen(*popen_args, env={}, env_update={}) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index d0fdce23eb6..e66fc9a4cf6 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -568,6 +568,7 @@ def test_method(self): assert result.ret == 0 def test_header_trailer_info(self, testdir, request): + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") testdir.makepyfile( """ def test_passes(): @@ -677,6 +678,7 @@ def test_verbose_reporting(self, verbose_testfile, testdir, pytestconfig): def test_verbose_reporting_xdist(self, verbose_testfile, testdir, pytestconfig): if not pytestconfig.pluginmanager.get_plugin("xdist"): pytest.skip("xdist plugin not installed") + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") result = testdir.runpytest( verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning"