Add PYTEST_TMPDIR_FILE_MASK environment variable

This commit is contained in:
Jim Brännlund 2023-03-03 01:00:30 +01:00 committed by Bruno Oliveira
parent 97a2761d72
commit 65fb513b9e
8 changed files with 53 additions and 9 deletions

View File

@ -172,6 +172,7 @@ Javier Romero
Jeff Rackauckas Jeff Rackauckas
Jeff Widman Jeff Widman
Jenni Rinker Jenni Rinker
Jim Brannlund
John Eddie Ayson John Eddie Ayson
John Litborn John Litborn
John Towler John Towler

View File

@ -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.

View File

@ -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 automatically configure a basetemp directory for the sub processes such that all temporary
data lands below a single per-test run basetemp directory. 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 .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html

View File

@ -1090,6 +1090,10 @@ Sets a `pygment style <https://pygments.org/docs/styles/>`_ to use for the code
Sets the :envvar:`PYTEST_THEME` to be either *dark* or *light*. 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 .. envvar:: PY_COLORS
When set to ``1``, pytest will use color in terminal output. When set to ``1``, pytest will use color in terminal output.

View File

@ -51,6 +51,8 @@ _IGNORED_WINERRORS = (
1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself 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): def _ignore_error(exception):
return ( return (
@ -206,7 +208,7 @@ def _force_symlink(
pass 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.""" """Create a directory with an increased number as suffix for the given prefix."""
for i in range(10): for i in range(10):
# try up to 10 times to create the folder # try up to 10 times to create the folder

View File

@ -82,6 +82,8 @@ IGNORE_PAM = [ # filenames added when obtaining details about the current user
"/var/lib/sss/mc/passwd" "/var/lib/sss/mc/passwd"
] ]
TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8)
def pytest_addoption(parser: Parser) -> None: def pytest_addoption(parser: Parser) -> None:
parser.addoption( parser.addoption(
@ -1502,7 +1504,9 @@ class Pytester:
The result. The result.
""" """
__tracebackhide__ = True __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 args = ("--basetemp=%s" % p,) + args
plugins = [x for x in self.plugins if isinstance(x, str)] plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins: if plugins:
@ -1521,7 +1525,7 @@ class Pytester:
The pexpect child is returned. The pexpect child is returned.
""" """
basetemp = self.path / "temp-pexpect" basetemp = self.path / "temp-pexpect"
basetemp.mkdir(mode=0o700) basetemp.mkdir(mode=TMPDIR_FILE_MODE)
invoke = " ".join(map(str, self._getpytestargs())) invoke = " ".join(map(str, self._getpytestargs()))
cmd = f"{invoke} --basetemp={basetemp} {string}" cmd = f"{invoke} --basetemp={basetemp} {string}"
return self.spawn(cmd, expect_timeout=expect_timeout) return self.spawn(cmd, expect_timeout=expect_timeout)

View File

@ -41,6 +41,8 @@ from _pytest.monkeypatch import MonkeyPatch
tmppath_result_key = StashKey[Dict[str, bool]]() tmppath_result_key = StashKey[Dict[str, bool]]()
TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8)
@final @final
@dataclasses.dataclass @dataclasses.dataclass
@ -136,9 +138,11 @@ class TempPathFactory:
basename = self._ensure_relative_to_basetemp(basename) basename = self._ensure_relative_to_basetemp(basename)
if not numbered: if not numbered:
p = self.getbasetemp().joinpath(basename) p = self.getbasetemp().joinpath(basename)
p.mkdir(mode=0o700) p.mkdir(mode=TMPDIR_FILE_MODE)
else: 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) self._trace("mktemp", p)
return p return p
@ -155,7 +159,7 @@ class TempPathFactory:
basetemp = self._given_basetemp basetemp = self._given_basetemp
if basetemp.exists(): if basetemp.exists():
rm_rf(basetemp) rm_rf(basetemp)
basetemp.mkdir(mode=0o700) basetemp.mkdir(mode=TMPDIR_FILE_MODE)
basetemp = basetemp.resolve() basetemp = basetemp.resolve()
else: else:
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
@ -165,11 +169,11 @@ class TempPathFactory:
# make_numbered_dir() call # make_numbered_dir() call
rootdir = temproot.joinpath(f"pytest-of-{user}") rootdir = temproot.joinpath(f"pytest-of-{user}")
try: try:
rootdir.mkdir(mode=0o700, exist_ok=True) rootdir.mkdir(mode=TMPDIR_FILE_MODE, exist_ok=True)
except OSError: except OSError:
# getuser() likely returned illegal characters for the platform, use unknown back off mechanism # getuser() likely returned illegal characters for the platform, use unknown back off mechanism
rootdir = temproot.joinpath("pytest-of-unknown") 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 # Because we use exist_ok=True with a predictable name, make sure
# we are the owners, to prevent any funny business (on unix, where # we are the owners, to prevent any funny business (on unix, where
# temproot is usually shared). # temproot is usually shared).
@ -197,7 +201,7 @@ class TempPathFactory:
root=rootdir, root=rootdir,
keep=keep, keep=keep,
lock_timeout=LOCK_TIMEOUT, lock_timeout=LOCK_TIMEOUT,
mode=0o700, mode=TMPDIR_FILE_MODE,
) )
assert basetemp is not None, basetemp assert basetemp is not None, basetemp
self._basetemp = basetemp self._basetemp = basetemp

View File

@ -623,3 +623,19 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions(
# After - fixed. # After - fixed.
assert (basetemp.parent.stat().st_mode & 0o077) == 0 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