Skip to content

Commit 65fb513

Browse files
BeyondEvilnicoddemus
authored andcommitted
Add PYTEST_TMPDIR_FILE_MASK environment variable
1 parent 97a2761 commit 65fb513

File tree

8 files changed

+53
-9
lines changed

8 files changed

+53
-9
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Javier Romero
172172
Jeff Rackauckas
173173
Jeff Widman
174174
Jenni Rinker
175+
Jim Brannlund
175176
John Eddie Ayson
176177
John Litborn
177178
John Towler

changelog/10738.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added ``PYTEST_TMPDIR_FILE_MASK`` environment variable which controls the file permissions of any files or directories created by the ``tmp_path`` fixtures.

doc/en/how-to/tmp_path.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,16 @@ When distributing tests on the local machine using ``pytest-xdist``, care is tak
155155
automatically configure a basetemp directory for the sub processes such that all temporary
156156
data lands below a single per-test run basetemp directory.
157157

158+
159+
.. _`file permissions`:
160+
161+
File permissions
162+
----------------
163+
164+
Any file or directory created by the above fixtures are by default created with private permissions (file mask 700).
165+
166+
You can override the file mask by setting the :envvar:`PYTEST_TMPDIR_FILE_MODE` environment variable as an octal string, the default being `0o700`.
167+
168+
This is for example useful in cases where created files or directories have to be shared by docker containers etc.
169+
158170
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html

doc/en/reference/reference.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,10 @@ Sets a `pygment style <https://pygments.org/docs/styles/>`_ to use for the code
10901090

10911091
Sets the :envvar:`PYTEST_THEME` to be either *dark* or *light*.
10921092

1093+
.. envar:: PYTEST_TMPDIR_FILE_MODE
1094+
1095+
Sets the file mode of any temporary files or directories. Defaults to `0o700`. See :fixture:`tmp_path` :fixture:`tmp_path_factory`.
1096+
10931097
.. envvar:: PY_COLORS
10941098

10951099
When set to ``1``, pytest will use color in terminal output.

src/_pytest/pathlib.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
5252
)
5353

54+
TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8)
55+
5456

5557
def _ignore_error(exception):
5658
return (
@@ -206,7 +208,7 @@ def _force_symlink(
206208
pass
207209

208210

209-
def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path:
211+
def make_numbered_dir(root: Path, prefix: str, mode: int = TMPDIR_FILE_MODE) -> Path:
210212
"""Create a directory with an increased number as suffix for the given prefix."""
211213
for i in range(10):
212214
# try up to 10 times to create the folder

src/_pytest/pytester.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
"/var/lib/sss/mc/passwd"
8383
]
8484

85+
TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8)
86+
8587

8688
def pytest_addoption(parser: Parser) -> None:
8789
parser.addoption(
@@ -1502,7 +1504,9 @@ def runpytest_subprocess(
15021504
The result.
15031505
"""
15041506
__tracebackhide__ = True
1505-
p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
1507+
p = make_numbered_dir(
1508+
root=self.path, prefix="runpytest-", mode=TMPDIR_FILE_MODE
1509+
)
15061510
args = ("--basetemp=%s" % p,) + args
15071511
plugins = [x for x in self.plugins if isinstance(x, str)]
15081512
if plugins:
@@ -1521,7 +1525,7 @@ def spawn_pytest(
15211525
The pexpect child is returned.
15221526
"""
15231527
basetemp = self.path / "temp-pexpect"
1524-
basetemp.mkdir(mode=0o700)
1528+
basetemp.mkdir(mode=TMPDIR_FILE_MODE)
15251529
invoke = " ".join(map(str, self._getpytestargs()))
15261530
cmd = f"{invoke} --basetemp={basetemp} {string}"
15271531
return self.spawn(cmd, expect_timeout=expect_timeout)

src/_pytest/tmpdir.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141

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

44+
TMPDIR_FILE_MODE = int(os.getenv("PYTEST_TMPDIR_FILE_MODE", "0o700"), 8)
45+
4446

4547
@final
4648
@dataclasses.dataclass
@@ -136,9 +138,11 @@ def mktemp(self, basename: str, numbered: bool = True) -> Path:
136138
basename = self._ensure_relative_to_basetemp(basename)
137139
if not numbered:
138140
p = self.getbasetemp().joinpath(basename)
139-
p.mkdir(mode=0o700)
141+
p.mkdir(mode=TMPDIR_FILE_MODE)
140142
else:
141-
p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700)
143+
p = make_numbered_dir(
144+
root=self.getbasetemp(), prefix=basename, mode=TMPDIR_FILE_MODE
145+
)
142146
self._trace("mktemp", p)
143147
return p
144148

@@ -155,7 +159,7 @@ def getbasetemp(self) -> Path:
155159
basetemp = self._given_basetemp
156160
if basetemp.exists():
157161
rm_rf(basetemp)
158-
basetemp.mkdir(mode=0o700)
162+
basetemp.mkdir(mode=TMPDIR_FILE_MODE)
159163
basetemp = basetemp.resolve()
160164
else:
161165
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
@@ -165,11 +169,11 @@ def getbasetemp(self) -> Path:
165169
# make_numbered_dir() call
166170
rootdir = temproot.joinpath(f"pytest-of-{user}")
167171
try:
168-
rootdir.mkdir(mode=0o700, exist_ok=True)
172+
rootdir.mkdir(mode=TMPDIR_FILE_MODE, exist_ok=True)
169173
except OSError:
170174
# getuser() likely returned illegal characters for the platform, use unknown back off mechanism
171175
rootdir = temproot.joinpath("pytest-of-unknown")
172-
rootdir.mkdir(mode=0o700, exist_ok=True)
176+
rootdir.mkdir(mode=TMPDIR_FILE_MODE, exist_ok=True)
173177
# Because we use exist_ok=True with a predictable name, make sure
174178
# we are the owners, to prevent any funny business (on unix, where
175179
# temproot is usually shared).
@@ -197,7 +201,7 @@ def getbasetemp(self) -> Path:
197201
root=rootdir,
198202
keep=keep,
199203
lock_timeout=LOCK_TIMEOUT,
200-
mode=0o700,
204+
mode=TMPDIR_FILE_MODE,
201205
)
202206
assert basetemp is not None, basetemp
203207
self._basetemp = basetemp

testing/test_tmpdir.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,3 +623,19 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions(
623623

624624
# After - fixed.
625625
assert (basetemp.parent.stat().st_mode & 0o077) == 0
626+
627+
628+
def test_tmp_path_factory_user_specified_permissions(
629+
tmp_path: Path, monkeypatch: MonkeyPatch
630+
) -> None:
631+
"""Verify that pytest creates directories under /tmp with user specified permissions."""
632+
# Use the test's tmp_path as the system temproot (/tmp).
633+
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
634+
monkeypatch.setenv("PYTEST_TMPDIR_FILE_MODE", "0o777")
635+
tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True)
636+
basetemp = tmp_factory.getbasetemp()
637+
638+
# User specified permissions.
639+
assert (basetemp.stat().st_mode & 0o000) == 0
640+
# Parent too (pytest-of-foo).
641+
assert (basetemp.parent.stat().st_mode & 0o000) == 0

0 commit comments

Comments
 (0)