diff --git a/AUTHORS b/AUTHORS index 0395feceb..e4d9e413a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -172,6 +172,7 @@ Javier Romero Jeff Rackauckas Jeff Widman Jenni Rinker +Jim Brannlund John Eddie Ayson John Litborn John Towler diff --git a/changelog/10738.feature.rst b/changelog/10738.feature.rst new file mode 100644 index 000000000..7c7b668dc --- /dev/null +++ b/changelog/10738.feature.rst @@ -0,0 +1 @@ +Added ``PYTEST_TMPDIR_FILE_MASK`` environment variable which controls the file permissions of any files or directories created by the ``tmp_path`` fixtures. diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index 792933dd8..a58c1eb97 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -155,4 +155,16 @@ When distributing tests on the local machine using ``pytest-xdist``, care is tak automatically configure a basetemp directory for the sub processes such that all temporary data lands below a single per-test run basetemp directory. + +.. _`file permissions`: + +File permissions +---------------- + +Any file or directory created by the above fixtures are by default created with private permissions (file mask 700). + +You can override the file mask by setting the :envvar:`PYTEST_TMPDIR_FILE_MODE` environment variable as an octal string, the default being `0o700`. + +This is for example useful in cases where created files or directories have to be shared by docker containers etc. + .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index c882157ee..adced601c 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1090,6 +1090,10 @@ Sets a `pygment style `_ to use for the code Sets the :envvar:`PYTEST_THEME` to be either *dark* or *light*. +.. envar:: PYTEST_TMPDIR_FILE_MODE + +Sets the file mode of any temporary files or directories. Defaults to `0o700`. See :fixture:`tmp_path` :fixture:`tmp_path_factory`. + .. envvar:: PY_COLORS When set to ``1``, pytest will use color in terminal output. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 9f9463d88..065ddebc4 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -51,6 +51,8 @@ _IGNORED_WINERRORS = ( 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself ) +TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8) + def _ignore_error(exception): return ( @@ -206,7 +208,7 @@ def _force_symlink( pass -def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path: +def make_numbered_dir(root: Path, prefix: str, mode: int = TMPDIR_FILE_MODE) -> Path: """Create a directory with an increased number as suffix for the given prefix.""" for i in range(10): # try up to 10 times to create the folder diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index a9299944d..a9f15d4eb 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -82,6 +82,8 @@ IGNORE_PAM = [ # filenames added when obtaining details about the current user "/var/lib/sss/mc/passwd" ] +TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8) + def pytest_addoption(parser: Parser) -> None: parser.addoption( @@ -1502,7 +1504,9 @@ class Pytester: The result. """ __tracebackhide__ = True - p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) + p = make_numbered_dir( + root=self.path, prefix="runpytest-", mode=TMPDIR_FILE_MODE + ) args = ("--basetemp=%s" % p,) + args plugins = [x for x in self.plugins if isinstance(x, str)] if plugins: @@ -1521,7 +1525,7 @@ class Pytester: The pexpect child is returned. """ basetemp = self.path / "temp-pexpect" - basetemp.mkdir(mode=0o700) + basetemp.mkdir(mode=TMPDIR_FILE_MODE) invoke = " ".join(map(str, self._getpytestargs())) cmd = f"{invoke} --basetemp={basetemp} {string}" return self.spawn(cmd, expect_timeout=expect_timeout) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index ec44623dc..0a50200ee 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -41,6 +41,8 @@ from _pytest.monkeypatch import MonkeyPatch tmppath_result_key = StashKey[Dict[str, bool]]() +TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8) + @final @dataclasses.dataclass @@ -136,9 +138,11 @@ class TempPathFactory: basename = self._ensure_relative_to_basetemp(basename) if not numbered: p = self.getbasetemp().joinpath(basename) - p.mkdir(mode=0o700) + p.mkdir(mode=TMPDIR_FILE_MODE) else: - p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700) + p = make_numbered_dir( + root=self.getbasetemp(), prefix=basename, mode=TMPDIR_FILE_MODE + ) self._trace("mktemp", p) return p @@ -155,7 +159,7 @@ class TempPathFactory: basetemp = self._given_basetemp if basetemp.exists(): rm_rf(basetemp) - basetemp.mkdir(mode=0o700) + basetemp.mkdir(mode=TMPDIR_FILE_MODE) basetemp = basetemp.resolve() else: from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") @@ -165,11 +169,11 @@ class TempPathFactory: # make_numbered_dir() call rootdir = temproot.joinpath(f"pytest-of-{user}") try: - rootdir.mkdir(mode=0o700, exist_ok=True) + rootdir.mkdir(mode=TMPDIR_FILE_MODE, exist_ok=True) except OSError: # getuser() likely returned illegal characters for the platform, use unknown back off mechanism rootdir = temproot.joinpath("pytest-of-unknown") - rootdir.mkdir(mode=0o700, exist_ok=True) + rootdir.mkdir(mode=TMPDIR_FILE_MODE, exist_ok=True) # Because we use exist_ok=True with a predictable name, make sure # we are the owners, to prevent any funny business (on unix, where # temproot is usually shared). @@ -197,7 +201,7 @@ class TempPathFactory: root=rootdir, keep=keep, lock_timeout=LOCK_TIMEOUT, - mode=0o700, + mode=TMPDIR_FILE_MODE, ) assert basetemp is not None, basetemp self._basetemp = basetemp diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index fcb0775dd..8b34985ec 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -623,3 +623,19 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions( # After - fixed. assert (basetemp.parent.stat().st_mode & 0o077) == 0 + + +def test_tmp_path_factory_user_specified_permissions( + tmp_path: Path, monkeypatch: MonkeyPatch +) -> None: + """Verify that pytest creates directories under /tmp with user specified permissions.""" + # Use the test's tmp_path as the system temproot (/tmp). + monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) + monkeypatch.setenv("PYTEST_TMPDIR_FILE_MODE", "0o777") + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) + basetemp = tmp_factory.getbasetemp() + + # User specified permissions. + assert (basetemp.stat().st_mode & 0o000) == 0 + # Parent too (pytest-of-foo). + assert (basetemp.parent.stat().st_mode & 0o000) == 0