Skip to content

Commit

Permalink
Add PYTEST_TMPDIR_FILE_MASK environment variable
Browse files Browse the repository at this point in the history
  • Loading branch information
BeyondEvil committed Mar 3, 2023
1 parent 88c9e92 commit 7ef00db
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 9 deletions.
1 change: 1 addition & 0 deletions changelog/10738.trivial.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.
10 changes: 10 additions & 0 deletions doc/en/how-to/tmp_path.rst
Expand Up @@ -155,4 +155,14 @@ 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 ``PYTEST_TMPDIR_FILE_MASK`` environment variable.

.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
4 changes: 3 additions & 1 deletion src/_pytest/pathlib.py
Expand Up @@ -51,6 +51,8 @@
1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
)

TMPDIR_FILE_MASK = int(os.getenv("PYTEST_TMPDIR_FILE_MASK", "0o700"), 8)


def _ignore_error(exception):
return (
Expand Down Expand Up @@ -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_MASK) -> 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
Expand Down
8 changes: 6 additions & 2 deletions src/_pytest/pytester.py
Expand Up @@ -82,6 +82,8 @@
"/var/lib/sss/mc/passwd"
]

TMPDIR_FILE_MASK = int(os.getenv("PYTEST_TMPDIR_FILE_MASK", "0o700"), 8)


def pytest_addoption(parser: Parser) -> None:
parser.addoption(
Expand Down Expand Up @@ -1502,7 +1504,9 @@ def runpytest_subprocess(
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_MASK
)
args = ("--basetemp=%s" % p,) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
Expand All @@ -1521,7 +1525,7 @@ def spawn_pytest(
The pexpect child is returned.
"""
basetemp = self.path / "temp-pexpect"
basetemp.mkdir(mode=0o700)
basetemp.mkdir(mode=TMPDIR_FILE_MASK)
invoke = " ".join(map(str, self._getpytestargs()))
cmd = f"{invoke} --basetemp={basetemp} {string}"
return self.spawn(cmd, expect_timeout=expect_timeout)
Expand Down
16 changes: 10 additions & 6 deletions src/_pytest/tmpdir.py
Expand Up @@ -41,6 +41,8 @@

tmppath_result_key = StashKey[Dict[str, bool]]()

TMPDIR_FILE_MASK = int(os.getenv("PYTEST_TMPDIR_FILE_MASK", "0o700"), 8)


@final
@dataclasses.dataclass
Expand Down Expand Up @@ -136,9 +138,11 @@ def mktemp(self, basename: str, numbered: bool = True) -> Path:
basename = self._ensure_relative_to_basetemp(basename)
if not numbered:
p = self.getbasetemp().joinpath(basename)
p.mkdir(mode=0o700)
p.mkdir(mode=TMPDIR_FILE_MASK)
else:
p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700)
p = make_numbered_dir(
root=self.getbasetemp(), prefix=basename, mode=TMPDIR_FILE_MASK
)
self._trace("mktemp", p)
return p

Expand All @@ -155,7 +159,7 @@ def getbasetemp(self) -> Path:
basetemp = self._given_basetemp
if basetemp.exists():
rm_rf(basetemp)
basetemp.mkdir(mode=0o700)
basetemp.mkdir(mode=TMPDIR_FILE_MASK)
basetemp = basetemp.resolve()
else:
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
Expand All @@ -165,11 +169,11 @@ def getbasetemp(self) -> Path:
# make_numbered_dir() call
rootdir = temproot.joinpath(f"pytest-of-{user}")
try:
rootdir.mkdir(mode=0o700, exist_ok=True)
rootdir.mkdir(mode=TMPDIR_FILE_MASK, 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_MASK, 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).
Expand Down Expand Up @@ -197,7 +201,7 @@ def getbasetemp(self) -> Path:
root=rootdir,
keep=keep,
lock_timeout=LOCK_TIMEOUT,
mode=0o700,
mode=TMPDIR_FILE_MASK,
)
assert basetemp is not None, basetemp
self._basetemp = basetemp
Expand Down
16 changes: 16 additions & 0 deletions testing/test_tmpdir.py
Expand Up @@ -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_MASK", "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

0 comments on commit 7ef00db

Please sign in to comment.