Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ Javier Romero
Jeff Rackauckas
Jeff Widman
Jenni Rinker
Jim Brannlund
John Eddie Ayson
John Litborn
John Towler
Expand Down
1 change: 1 addition & 0 deletions changelog/10738.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ``PYTEST_TMPDIR_FILE_MODE`` environment variable which controls the file permissions of any files or directories created by the ``tmp_path`` fixtures.
12 changes: 12 additions & 0 deletions doc/en/how-to/tmp_path.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 `700`.

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
4 changes: 4 additions & 0 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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*.

.. envvar:: PYTEST_TMPDIR_FILE_MODE

Sets the file mode of any temporary files or directories. Defaults to `700`. See :fixture:`tmp_path` :fixture:`tmp_path_factory`.

.. envvar:: PY_COLORS

When set to ``1``, pytest will use color in terminal output.
Expand Down
7 changes: 6 additions & 1 deletion src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,9 @@ 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: Union[int, None] = None) -> Path:
"""Create a directory with an increased number as suffix for the given prefix."""
mode = mode or tmpdir_file_mode()
for i in range(10):
# try up to 10 times to create the folder
max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1)
Expand Down Expand Up @@ -746,3 +747,7 @@ def copytree(source: Path, target: Path) -> None:
shutil.copyfile(x, newx)
elif x.is_dir():
newx.mkdir(exist_ok=True)


def tmpdir_file_mode() -> int:
return int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "700"), 8)
7 changes: 5 additions & 2 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import copytree
from _pytest.pathlib import make_numbered_dir
from _pytest.pathlib import tmpdir_file_mode
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
from _pytest.tmpdir import TempPathFactory
Expand Down Expand Up @@ -1502,7 +1503,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_mode()
)
args = ("--basetemp=%s" % p,) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
Expand All @@ -1521,7 +1524,7 @@ def spawn_pytest(
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)
Expand Down
18 changes: 11 additions & 7 deletions src/_pytest/tmpdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
from .pathlib import make_numbered_dir_with_cleanup
from .pathlib import rm_rf
from .pathlib import cleanup_dead_symlink
from _pytest.compat import final, get_user_id
from .pathlib import tmpdir_file_mode
from _pytest.compat import final
from _pytest.compat import get_user_id
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
Expand Down Expand Up @@ -135,9 +137,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_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

Expand All @@ -154,7 +158,7 @@ def getbasetemp(self) -> Path:
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")
Expand All @@ -164,11 +168,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_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).
Expand All @@ -193,7 +197,7 @@ def getbasetemp(self) -> Path:
root=rootdir,
keep=keep,
lock_timeout=LOCK_TIMEOUT,
mode=0o700,
mode=tmpdir_file_mode(),
)
assert basetemp is not None, basetemp
self._basetemp = basetemp
Expand Down
16 changes: 16 additions & 0 deletions testing/test_tmpdir.py
Original file line number Diff line number Diff line change
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_MODE", "777")
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