Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a2fd96305 | ||
|
|
138b19a930 | ||
|
|
822686e880 | ||
|
|
9dc54f79b0 | ||
|
|
93dbae24e1 | ||
|
|
02fdbe2e76 | ||
|
|
12e7db85af | ||
|
|
56e4392444 | ||
|
|
8220eca963 |
@@ -6,6 +6,7 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-6.2.3
|
||||
release-6.2.2
|
||||
release-6.2.1
|
||||
release-6.2.0
|
||||
|
||||
19
doc/en/announce/release-6.2.3.rst
Normal file
19
doc/en/announce/release-6.2.3.rst
Normal file
@@ -0,0 +1,19 @@
|
||||
pytest-6.2.3
|
||||
=======================================
|
||||
|
||||
pytest 6.2.3 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -28,6 +28,24 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 6.2.3 (2021-04-03)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#8414 <https://github.com/pytest-dev/pytest/issues/8414>`_: pytest used to create directories under ``/tmp`` with world-readable
|
||||
permissions. This means that any user in the system was able to read
|
||||
information written by tests in temporary directories (such as those created by
|
||||
the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with
|
||||
private permissions.
|
||||
|
||||
pytest used silenty use a pre-existing ``/tmp/pytest-of-<username>`` directory,
|
||||
even if owned by another user. This means another user could pre-create such a
|
||||
directory and gain control of another user's temporary directory. Now such a
|
||||
condition results in an error.
|
||||
|
||||
|
||||
pytest 6.2.2 (2021-01-25)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -508,11 +508,12 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
ssssssssssss...ssssssssssss [100%]
|
||||
sssssssssssssssssssssssssss [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [12] multipython.py:29: 'python3.5' not found
|
||||
SKIPPED [12] multipython.py:29: 'python3.7' not found
|
||||
3 passed, 24 skipped in 0.12s
|
||||
SKIPPED [9] multipython.py:29: 'python3.5' not found
|
||||
SKIPPED [9] multipython.py:29: 'python3.6' not found
|
||||
SKIPPED [9] multipython.py:29: 'python3.7' not found
|
||||
27 skipped in 0.12s
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
|
||||
@@ -28,7 +28,7 @@ Install ``pytest``
|
||||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 6.2.2
|
||||
pytest 6.2.3
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
pytest: helps you write better programs
|
||||
=======================================
|
||||
|
||||
.. module:: pytest
|
||||
|
||||
The ``pytest`` framework makes it easy to write small tests, yet
|
||||
scales to support complex functional testing for applications and libraries.
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. module:: pytest
|
||||
|
||||
This page contains the full reference to pytest's API.
|
||||
|
||||
.. contents::
|
||||
|
||||
@@ -64,13 +64,6 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
|
||||
return path.joinpath(".lock")
|
||||
|
||||
|
||||
def ensure_reset_dir(path: Path) -> None:
|
||||
"""Ensure the given path is an empty directory."""
|
||||
if path.exists():
|
||||
rm_rf(path)
|
||||
path.mkdir()
|
||||
|
||||
|
||||
def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
||||
"""Handle known read-only errors during rmtree.
|
||||
|
||||
@@ -214,7 +207,7 @@ def _force_symlink(
|
||||
pass
|
||||
|
||||
|
||||
def make_numbered_dir(root: Path, prefix: str) -> Path:
|
||||
def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> 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
|
||||
@@ -222,7 +215,7 @@ def make_numbered_dir(root: Path, prefix: str) -> Path:
|
||||
new_number = max_existing + 1
|
||||
new_path = root.joinpath(f"{prefix}{new_number}")
|
||||
try:
|
||||
new_path.mkdir()
|
||||
new_path.mkdir(mode=mode)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
@@ -354,13 +347,13 @@ def cleanup_numbered_dir(
|
||||
|
||||
|
||||
def make_numbered_dir_with_cleanup(
|
||||
root: Path, prefix: str, keep: int, lock_timeout: float
|
||||
root: Path, prefix: str, keep: int, lock_timeout: float, mode: int,
|
||||
) -> Path:
|
||||
"""Create a numbered dir with a cleanup lock and remove old ones."""
|
||||
e = None
|
||||
for i in range(10):
|
||||
try:
|
||||
p = make_numbered_dir(root, prefix)
|
||||
p = make_numbered_dir(root, prefix, mode)
|
||||
lock_path = create_cleanup_lock(p)
|
||||
register_cleanup_lock_removal(lock_path)
|
||||
except Exception as exc:
|
||||
|
||||
@@ -1426,7 +1426,7 @@ class Pytester:
|
||||
:rtype: RunResult
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
p = make_numbered_dir(root=self.path, prefix="runpytest-")
|
||||
p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
|
||||
args = ("--basetemp=%s" % p,) + args
|
||||
plugins = [x for x in self.plugins if isinstance(x, str)]
|
||||
if plugins:
|
||||
@@ -1445,7 +1445,7 @@ class Pytester:
|
||||
The pexpect child is returned.
|
||||
"""
|
||||
basetemp = self.path / "temp-pexpect"
|
||||
basetemp.mkdir()
|
||||
basetemp.mkdir(mode=0o700)
|
||||
invoke = " ".join(map(str, self._getpytestargs()))
|
||||
cmd = f"{invoke} --basetemp={basetemp} {string}"
|
||||
return self.spawn(cmd, expect_timeout=expect_timeout)
|
||||
|
||||
@@ -8,10 +8,10 @@ from typing import Optional
|
||||
import attr
|
||||
import py
|
||||
|
||||
from .pathlib import ensure_reset_dir
|
||||
from .pathlib import LOCK_TIMEOUT
|
||||
from .pathlib import make_numbered_dir
|
||||
from .pathlib import make_numbered_dir_with_cleanup
|
||||
from .pathlib import rm_rf
|
||||
from _pytest.compat import final
|
||||
from _pytest.config import Config
|
||||
from _pytest.deprecated import check_ispytest
|
||||
@@ -90,20 +90,22 @@ class TempPathFactory:
|
||||
basename = self._ensure_relative_to_basetemp(basename)
|
||||
if not numbered:
|
||||
p = self.getbasetemp().joinpath(basename)
|
||||
p.mkdir()
|
||||
p.mkdir(mode=0o700)
|
||||
else:
|
||||
p = make_numbered_dir(root=self.getbasetemp(), prefix=basename)
|
||||
p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700)
|
||||
self._trace("mktemp", p)
|
||||
return p
|
||||
|
||||
def getbasetemp(self) -> Path:
|
||||
"""Return base temporary directory."""
|
||||
"""Return the base temporary directory, creating it if needed."""
|
||||
if self._basetemp is not None:
|
||||
return self._basetemp
|
||||
|
||||
if self._given_basetemp is not None:
|
||||
basetemp = self._given_basetemp
|
||||
ensure_reset_dir(basetemp)
|
||||
if basetemp.exists():
|
||||
rm_rf(basetemp)
|
||||
basetemp.mkdir(mode=0o700)
|
||||
basetemp = basetemp.resolve()
|
||||
else:
|
||||
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
|
||||
@@ -112,14 +114,37 @@ class TempPathFactory:
|
||||
# use a sub-directory in the temproot to speed-up
|
||||
# make_numbered_dir() call
|
||||
rootdir = temproot.joinpath(f"pytest-of-{user}")
|
||||
rootdir.mkdir(exist_ok=True)
|
||||
rootdir.mkdir(mode=0o700, 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).
|
||||
# Also, to keep things private, fixup any world-readable temp
|
||||
# rootdir's permissions. Historically 0o755 was used, so we can't
|
||||
# just error out on this, at least for a while.
|
||||
if hasattr(os, "getuid"):
|
||||
rootdir_stat = rootdir.stat()
|
||||
uid = os.getuid()
|
||||
# getuid shouldn't fail, but cpython defines such a case.
|
||||
# Let's hope for the best.
|
||||
if uid != -1:
|
||||
if rootdir_stat.st_uid != uid:
|
||||
raise OSError(
|
||||
f"The temporary directory {rootdir} is not owned by the current user. "
|
||||
"Fix this and try again."
|
||||
)
|
||||
if (rootdir_stat.st_mode & 0o077) != 0:
|
||||
os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
|
||||
basetemp = make_numbered_dir_with_cleanup(
|
||||
prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
|
||||
prefix="pytest-",
|
||||
root=rootdir,
|
||||
keep=3,
|
||||
lock_timeout=LOCK_TIMEOUT,
|
||||
mode=0o700,
|
||||
)
|
||||
assert basetemp is not None, basetemp
|
||||
self._basetemp = t = basetemp
|
||||
self._trace("new basetemp", t)
|
||||
return t
|
||||
self._basetemp = basetemp
|
||||
self._trace("new basetemp", basetemp)
|
||||
return basetemp
|
||||
|
||||
|
||||
@final
|
||||
|
||||
@@ -445,3 +445,44 @@ def test_basetemp_with_read_only_files(pytester: Pytester) -> None:
|
||||
# running a second time and ensure we don't crash
|
||||
result = pytester.runpytest("--basetemp=tmp")
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(not hasattr(os, "getuid"), reason="checks unix permissions")
|
||||
def test_tmp_path_factory_create_directory_with_safe_permissions(
|
||||
tmp_path: Path, monkeypatch,
|
||||
) -> None:
|
||||
"""Verify that pytest creates directories under /tmp with private permissions."""
|
||||
# Use the test's tmp_path as the system temproot (/tmp).
|
||||
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
|
||||
tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
|
||||
basetemp = tmp_factory.getbasetemp()
|
||||
|
||||
# No world-readable permissions.
|
||||
assert (basetemp.stat().st_mode & 0o077) == 0
|
||||
# Parent too (pytest-of-foo).
|
||||
assert (basetemp.parent.stat().st_mode & 0o077) == 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(not hasattr(os, "getuid"), reason="checks unix permissions")
|
||||
def test_tmp_path_factory_fixes_up_world_readable_permissions(
|
||||
tmp_path: Path, monkeypatch,
|
||||
) -> None:
|
||||
"""Verify that if a /tmp/pytest-of-foo directory already exists with
|
||||
world-readable permissions, it is fixed.
|
||||
|
||||
pytest used to mkdir with such permissions, that's why we fix it up.
|
||||
"""
|
||||
# Use the test's tmp_path as the system temproot (/tmp).
|
||||
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
|
||||
tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
|
||||
basetemp = tmp_factory.getbasetemp()
|
||||
|
||||
# Before - simulate bad perms.
|
||||
os.chmod(basetemp.parent, 0o777)
|
||||
assert (basetemp.parent.stat().st_mode & 0o077) != 0
|
||||
|
||||
tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
|
||||
basetemp = tmp_factory.getbasetemp()
|
||||
|
||||
# After - fixed.
|
||||
assert (basetemp.parent.stat().st_mode & 0o077) == 0
|
||||
|
||||
Reference in New Issue
Block a user