diff --git a/CHANGES.rst b/CHANGES.rst index 2ba51b6b2..570a22f91 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,7 +23,8 @@ upgrading your version of coverage.py. Unreleased ---------- -Nothing yet. +- Fix: the PYTHONSAFEPATH environment variable new in Python 3.11 is properly + supported, closing `issue 1696`_. Thanks, `Philipp A. `_. .. start-releases @@ -554,6 +555,8 @@ Version 7.3.3 — 2023-12-14 - ``dataop2`` writes the full data being added to CoverageData objects. +.. _issue 1696: https://github.com/nedbat/coveragepy/issues/1696 +.. _pull 1700: https://github.com/nedbat/coveragepy/pull/1700 .. _issue 684: https://github.com/nedbat/coveragepy/issues/684 .. _pull 1705: https://github.com/nedbat/coveragepy/pull/1705 .. _issue 1709: https://github.com/nedbat/coveragepy/issues/1709 diff --git a/coverage/execfile.py b/coverage/execfile.py index cbecec847..847685469 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -89,7 +89,10 @@ def prepare(self) -> None: This needs to happen before any importing, and without importing anything. """ path0: str | None - if self.as_module: + if env.PYVERSION >= (3, 11) and os.getenv('PYTHONSAFEPATH'): + # See https://docs.python.org/3/using/cmdline.html#cmdoption-P + path0 = None + elif self.as_module: path0 = os.getcwd() elif os.path.isdir(self.arg0): # Running a directory means running the __main__.py file in that diff --git a/tests/test_execfile.py b/tests/test_execfile.py index cd12dea99..f8d405220 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -13,7 +13,7 @@ import py_compile import re import sys - +from pathlib import Path from typing import Any from collections.abc import Iterator @@ -24,6 +24,7 @@ from coverage.files import python_reported_file from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin +from tests.helpers import change_dir TRY_EXECFILE = os.path.join(TESTS_DIR, "modules/process_test/try_execfile.py") @@ -307,6 +308,15 @@ def test_pkg1_init(self) -> None: assert out == "pkg1.__init__: pkg1\npkg1.__init__: __main__\n" assert err == "" + def test_pythonpath(self, tmp_path: Path) -> None: + self.set_environ("PYTHONSAFEPATH", "1") + with change_dir(tmp_path): + run_python_module(["process_test.try_execfile"]) + out, err = self.stdouterr() + mod_globs = json.loads(out) + assert tmp_path not in mod_globs["path"] + assert err == "" + def test_no_such_module(self) -> None: with pytest.raises(NoSource, match="No module named '?i_dont_exist'?"): run_python_module(["i_dont_exist"]) diff --git a/tests/test_process.py b/tests/test_process.py index 082fa917f..6834fbbba 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -807,6 +807,25 @@ def test_coverage_zip_is_like_python(self) -> None: actual = self.run_command(f"python {cov_main} run run_me.py") self.assert_tryexecfile_output(expected, actual) + def test_pythonsafepath(self) -> None: + with open(TRY_EXECFILE) as f: + self.make_file("run_me.py", f.read()) + self.set_environ("PYTHONSAFEPATH", "1") + expected = self.run_command("python run_me.py") + actual = self.run_command("coverage run run_me.py") + self.assert_tryexecfile_output(expected, actual) + + @pytest.mark.skipif(env.PYVERSION < (3, 11), reason="PYTHONSAFEPATH is new in 3.11") + def test_pythonsafepath_dashm(self) -> None: + with open(TRY_EXECFILE) as f: + self.make_file("with_main/__main__.py", f.read()) + + self.set_environ("PYTHONSAFEPATH", "1") + expected = self.run_command("python -m with_main") + actual = self.run_command("coverage run -m with_main") + assert re.search("No module named '?with_main'?", actual) + assert re.search("No module named '?with_main'?", expected) + def test_coverage_custom_script(self) -> None: # https://github.com/nedbat/coveragepy/issues/678 # If sys.path[0] isn't the Python default, then coverage.py won't