7119: data loss with mistyped --basetemp (#7170)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
Co-authored-by: Ran Benita <ran@unusedvar.com>
This commit is contained in:
Prashant Anand 2020-06-09 09:54:22 +09:00 committed by GitHub
parent a76855912b
commit e78207c936
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 0 deletions

View File

@ -227,6 +227,7 @@ Pedro Algarvio
Philipp Loose Philipp Loose
Pieter Mulder Pieter Mulder
Piotr Banaszkiewicz Piotr Banaszkiewicz
Prashant Anand
Pulkit Goyal Pulkit Goyal
Punyashloka Biswal Punyashloka Biswal
Quentin Pradet Quentin Pradet

View File

@ -0,0 +1,2 @@
Exit with an error if the ``--basetemp`` argument is empty, the current working directory or parent directory of it.
This is done to protect against accidental data loss, as any directory passed to this argument is cleared.

View File

@ -1,4 +1,5 @@
""" core implementation of testing process: init, session, runtest loop. """ """ core implementation of testing process: init, session, runtest loop. """
import argparse
import fnmatch import fnmatch
import functools import functools
import importlib import importlib
@ -30,6 +31,7 @@ from _pytest.config import UsageError
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureManager from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit from _pytest.outcomes import exit
from _pytest.pathlib import Path
from _pytest.reports import CollectReport from _pytest.reports import CollectReport
from _pytest.reports import TestReport from _pytest.reports import TestReport
from _pytest.runner import collect_one_node from _pytest.runner import collect_one_node
@ -177,6 +179,7 @@ def pytest_addoption(parser: Parser) -> None:
"--basetemp", "--basetemp",
dest="basetemp", dest="basetemp",
default=None, default=None,
type=validate_basetemp,
metavar="dir", metavar="dir",
help=( help=(
"base temporary directory for this test run." "base temporary directory for this test run."
@ -185,6 +188,34 @@ def pytest_addoption(parser: Parser) -> None:
) )
def validate_basetemp(path: str) -> str:
# GH 7119
msg = "basetemp must not be empty, the current working directory or any parent directory of it"
# empty path
if not path:
raise argparse.ArgumentTypeError(msg)
def is_ancestor(base: Path, query: Path) -> bool:
""" return True if query is an ancestor of base, else False."""
if base == query:
return True
for parent in base.parents:
if parent == query:
return True
return False
# check if path is an ancestor of cwd
if is_ancestor(Path.cwd(), Path(path).absolute()):
raise argparse.ArgumentTypeError(msg)
# check symlinks for ancestors
if is_ancestor(Path.cwd().resolve(), Path(path).resolve()):
raise argparse.ArgumentTypeError(msg)
return path
def wrap_session( def wrap_session(
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
) -> Union[int, ExitCode]: ) -> Union[int, ExitCode]:

View File

@ -1,7 +1,9 @@
import argparse
from typing import Optional from typing import Optional
import pytest import pytest
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.main import validate_basetemp
from _pytest.pytester import Testdir from _pytest.pytester import Testdir
@ -75,3 +77,24 @@ def test_wrap_session_exit_sessionfinish(
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
assert result.stdout.lines[-1] == "collected 0 items" assert result.stdout.lines[-1] == "collected 0 items"
assert result.stderr.lines == ["Exit: exit_pytest_sessionfinish"] assert result.stderr.lines == ["Exit: exit_pytest_sessionfinish"]
@pytest.mark.parametrize("basetemp", ["foo", "foo/bar"])
def test_validate_basetemp_ok(tmp_path, basetemp, monkeypatch):
monkeypatch.chdir(str(tmp_path))
validate_basetemp(tmp_path / basetemp)
@pytest.mark.parametrize("basetemp", ["", ".", ".."])
def test_validate_basetemp_fails(tmp_path, basetemp, monkeypatch):
monkeypatch.chdir(str(tmp_path))
msg = "basetemp must not be empty, the current working directory or any parent directory of it"
with pytest.raises(argparse.ArgumentTypeError, match=msg):
if basetemp:
basetemp = tmp_path / basetemp
validate_basetemp(basetemp)
def test_validate_basetemp_integration(testdir):
result = testdir.runpytest("--basetemp=.")
result.stderr.fnmatch_lines("*basetemp must not be*")