Merge pull request #7619 from bluetech/py-to-pathlib
Some py.path -> pathlib conversions
This commit is contained in:
commit
384b6f6866
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import py
|
||||
import requests
|
||||
|
||||
issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues"
|
||||
|
@ -31,12 +31,12 @@ def get_issues():
|
|||
|
||||
|
||||
def main(args):
|
||||
cachefile = py.path.local(args.cache)
|
||||
cachefile = Path(args.cache)
|
||||
if not cachefile.exists() or args.refresh:
|
||||
issues = get_issues()
|
||||
cachefile.write(json.dumps(issues))
|
||||
cachefile.write_text(json.dumps(issues), "utf-8")
|
||||
else:
|
||||
issues = json.loads(cachefile.read())
|
||||
issues = json.loads(cachefile.read_text("utf-8"))
|
||||
|
||||
open_issues = [x for x in issues if x["state"] == "open"]
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ from _pytest.compat import ATTRS_EQ_FIELD
|
|||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
@ -1190,12 +1191,12 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
|
|||
# note: if we need to add more paths than what we have now we should probably use a list
|
||||
# for better maintenance.
|
||||
|
||||
_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||
_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc"))
|
||||
# pluggy is either a package or a single module depending on the version
|
||||
if _PLUGGY_DIR.basename == "__init__.py":
|
||||
_PLUGGY_DIR = _PLUGGY_DIR.dirpath()
|
||||
_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
|
||||
_PY_DIR = py.path.local(py.__file__).dirpath()
|
||||
if _PLUGGY_DIR.name == "__init__.py":
|
||||
_PLUGGY_DIR = _PLUGGY_DIR.parent
|
||||
_PYTEST_DIR = Path(_pytest.__file__).parent
|
||||
_PY_DIR = Path(py.__file__).parent
|
||||
|
||||
|
||||
def filter_traceback(entry: TracebackEntry) -> bool:
|
||||
|
@ -1213,9 +1214,17 @@ def filter_traceback(entry: TracebackEntry) -> bool:
|
|||
is_generated = "<" in raw_filename and ">" in raw_filename
|
||||
if is_generated:
|
||||
return False
|
||||
|
||||
# entry.path might point to a non-existing file, in which case it will
|
||||
# also return a str object. See #1133.
|
||||
p = py.path.local(entry.path)
|
||||
return (
|
||||
not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
|
||||
)
|
||||
p = Path(entry.path)
|
||||
|
||||
parents = p.parents
|
||||
if _PLUGGY_DIR in parents:
|
||||
return False
|
||||
if _PYTEST_DIR in parents:
|
||||
return False
|
||||
if _PY_DIR in parents:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -18,7 +18,6 @@ from typing import TypeVar
|
|||
from typing import Union
|
||||
|
||||
import attr
|
||||
import py
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
|
@ -104,13 +103,18 @@ def is_async_function(func: object) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def getlocation(function, curdir=None) -> str:
|
||||
def getlocation(function, curdir: Optional[str] = None) -> str:
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
function = get_real_func(function)
|
||||
fn = py.path.local(inspect.getfile(function))
|
||||
fn = Path(inspect.getfile(function))
|
||||
lineno = function.__code__.co_firstlineno
|
||||
if curdir is not None:
|
||||
relfn = fn.relto(curdir)
|
||||
if relfn:
|
||||
try:
|
||||
relfn = fn.relative_to(curdir)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
return "%s:%d" % (relfn, lineno + 1)
|
||||
return "%s:%d" % (fn, lineno + 1)
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ def filter_traceback_for_conftest_import_failure(
|
|||
|
||||
|
||||
def main(
|
||||
args: Optional[List[str]] = None,
|
||||
args: Optional[Union[List[str], py.path.local]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> Union[int, ExitCode]:
|
||||
"""Perform an in-process test run.
|
||||
|
@ -1006,12 +1006,15 @@ class Config:
|
|||
ns, unknown_args = self._parser.parse_known_and_unknown_args(
|
||||
args, namespace=copy.copy(self.option)
|
||||
)
|
||||
self.rootdir, self.inifile, self.inicfg = determine_setup(
|
||||
rootpath, inipath, inicfg = determine_setup(
|
||||
ns.inifilename,
|
||||
ns.file_or_dir + unknown_args,
|
||||
rootdir_cmd_arg=ns.rootdir or None,
|
||||
config=self,
|
||||
)
|
||||
self.rootdir = py.path.local(str(rootpath))
|
||||
self.inifile = py.path.local(str(inipath)) if inipath else None
|
||||
self.inicfg = inicfg
|
||||
self._parser.extra_info["rootdir"] = self.rootdir
|
||||
self._parser.extra_info["inifile"] = self.inifile
|
||||
self._parser.addini("addopts", "extra command line options", "args")
|
||||
|
@ -1305,7 +1308,7 @@ class Config:
|
|||
values = [] # type: List[py.path.local]
|
||||
for relroot in relroots:
|
||||
if not isinstance(relroot, py.path.local):
|
||||
relroot = relroot.replace("/", py.path.local.sep)
|
||||
relroot = relroot.replace("/", os.sep)
|
||||
relroot = modpath.join(relroot, abs=True)
|
||||
values.append(relroot)
|
||||
return values
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
import itertools
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import iniconfig
|
||||
import py
|
||||
|
||||
from .exceptions import UsageError
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.pathlib import absolutepath
|
||||
from _pytest.pathlib import commonpath
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Config
|
||||
|
||||
|
||||
def _parse_ini_config(path: py.path.local) -> iniconfig.IniConfig:
|
||||
def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
|
||||
"""Parse the given generic '.ini' file using legacy IniConfig parser, returning
|
||||
the parsed object.
|
||||
|
||||
|
@ -30,7 +35,7 @@ def _parse_ini_config(path: py.path.local) -> iniconfig.IniConfig:
|
|||
|
||||
|
||||
def load_config_dict_from_file(
|
||||
filepath: py.path.local,
|
||||
filepath: Path,
|
||||
) -> Optional[Dict[str, Union[str, List[str]]]]:
|
||||
"""Load pytest configuration from the given file path, if supported.
|
||||
|
||||
|
@ -38,18 +43,18 @@ def load_config_dict_from_file(
|
|||
"""
|
||||
|
||||
# Configuration from ini files are obtained from the [pytest] section, if present.
|
||||
if filepath.ext == ".ini":
|
||||
if filepath.suffix == ".ini":
|
||||
iniconfig = _parse_ini_config(filepath)
|
||||
|
||||
if "pytest" in iniconfig:
|
||||
return dict(iniconfig["pytest"].items())
|
||||
else:
|
||||
# "pytest.ini" files are always the source of configuration, even if empty.
|
||||
if filepath.basename == "pytest.ini":
|
||||
if filepath.name == "pytest.ini":
|
||||
return {}
|
||||
|
||||
# '.cfg' files are considered if they contain a "[tool:pytest]" section.
|
||||
elif filepath.ext == ".cfg":
|
||||
elif filepath.suffix == ".cfg":
|
||||
iniconfig = _parse_ini_config(filepath)
|
||||
|
||||
if "tool:pytest" in iniconfig.sections:
|
||||
|
@ -60,7 +65,7 @@ def load_config_dict_from_file(
|
|||
fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False)
|
||||
|
||||
# '.toml' files are considered if they contain a [tool.pytest.ini_options] table.
|
||||
elif filepath.ext == ".toml":
|
||||
elif filepath.suffix == ".toml":
|
||||
import toml
|
||||
|
||||
config = toml.load(str(filepath))
|
||||
|
@ -79,9 +84,9 @@ def load_config_dict_from_file(
|
|||
|
||||
|
||||
def locate_config(
|
||||
args: Iterable[Union[str, py.path.local]]
|
||||
args: Iterable[Path],
|
||||
) -> Tuple[
|
||||
Optional[py.path.local], Optional[py.path.local], Dict[str, Union[str, List[str]]],
|
||||
Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]],
|
||||
]:
|
||||
"""Search in the list of arguments for a valid ini-file for pytest,
|
||||
and return a tuple of (rootdir, inifile, cfg-dict)."""
|
||||
|
@ -93,62 +98,77 @@ def locate_config(
|
|||
]
|
||||
args = [x for x in args if not str(x).startswith("-")]
|
||||
if not args:
|
||||
args = [py.path.local()]
|
||||
args = [Path.cwd()]
|
||||
for arg in args:
|
||||
arg = py.path.local(arg)
|
||||
for base in arg.parts(reverse=True):
|
||||
argpath = absolutepath(arg)
|
||||
for base in itertools.chain((argpath,), reversed(argpath.parents)):
|
||||
for config_name in config_names:
|
||||
p = base.join(config_name)
|
||||
if p.isfile():
|
||||
p = base / config_name
|
||||
if p.is_file():
|
||||
ini_config = load_config_dict_from_file(p)
|
||||
if ini_config is not None:
|
||||
return base, p, ini_config
|
||||
return None, None, {}
|
||||
|
||||
|
||||
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
|
||||
common_ancestor = None # type: Optional[py.path.local]
|
||||
def get_common_ancestor(paths: Iterable[Path]) -> Path:
|
||||
common_ancestor = None # type: Optional[Path]
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
continue
|
||||
if common_ancestor is None:
|
||||
common_ancestor = path
|
||||
else:
|
||||
if path.relto(common_ancestor) or path == common_ancestor:
|
||||
if common_ancestor in path.parents or path == common_ancestor:
|
||||
continue
|
||||
elif common_ancestor.relto(path):
|
||||
elif path in common_ancestor.parents:
|
||||
common_ancestor = path
|
||||
else:
|
||||
shared = path.common(common_ancestor)
|
||||
shared = commonpath(path, common_ancestor)
|
||||
if shared is not None:
|
||||
common_ancestor = shared
|
||||
if common_ancestor is None:
|
||||
common_ancestor = py.path.local()
|
||||
elif common_ancestor.isfile():
|
||||
common_ancestor = common_ancestor.dirpath()
|
||||
common_ancestor = Path.cwd()
|
||||
elif common_ancestor.is_file():
|
||||
common_ancestor = common_ancestor.parent
|
||||
return common_ancestor
|
||||
|
||||
|
||||
def get_dirs_from_args(args: Iterable[str]) -> List[py.path.local]:
|
||||
def get_dirs_from_args(args: Iterable[str]) -> List[Path]:
|
||||
def is_option(x: str) -> bool:
|
||||
return x.startswith("-")
|
||||
|
||||
def get_file_part_from_node_id(x: str) -> str:
|
||||
return x.split("::")[0]
|
||||
|
||||
def get_dir_from_path(path: py.path.local) -> py.path.local:
|
||||
if path.isdir():
|
||||
def get_dir_from_path(path: Path) -> Path:
|
||||
if path.is_dir():
|
||||
return path
|
||||
return py.path.local(path.dirname)
|
||||
return path.parent
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
|
||||
def safe_exists(path: Path) -> bool:
|
||||
# On Python<3.8, this can throw on paths that contain characters
|
||||
# unrepresentable at the OS level.
|
||||
try:
|
||||
return path.exists()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
else:
|
||||
|
||||
def safe_exists(path: Path) -> bool:
|
||||
return path.exists()
|
||||
|
||||
# These look like paths but may not exist
|
||||
possible_paths = (
|
||||
py.path.local(get_file_part_from_node_id(arg))
|
||||
absolutepath(get_file_part_from_node_id(arg))
|
||||
for arg in args
|
||||
if not is_option(arg)
|
||||
)
|
||||
|
||||
return [get_dir_from_path(path) for path in possible_paths if path.exists()]
|
||||
return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)]
|
||||
|
||||
|
||||
CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
|
||||
|
@ -156,15 +176,15 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte
|
|||
|
||||
def determine_setup(
|
||||
inifile: Optional[str],
|
||||
args: List[str],
|
||||
args: Sequence[str],
|
||||
rootdir_cmd_arg: Optional[str] = None,
|
||||
config: Optional["Config"] = None,
|
||||
) -> Tuple[py.path.local, Optional[py.path.local], Dict[str, Union[str, List[str]]]]:
|
||||
) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
|
||||
rootdir = None
|
||||
dirs = get_dirs_from_args(args)
|
||||
if inifile:
|
||||
inipath_ = py.path.local(inifile)
|
||||
inipath = inipath_ # type: Optional[py.path.local]
|
||||
inipath_ = absolutepath(inifile)
|
||||
inipath = inipath_ # type: Optional[Path]
|
||||
inicfg = load_config_dict_from_file(inipath_) or {}
|
||||
if rootdir_cmd_arg is None:
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
|
@ -172,8 +192,10 @@ def determine_setup(
|
|||
ancestor = get_common_ancestor(dirs)
|
||||
rootdir, inipath, inicfg = locate_config([ancestor])
|
||||
if rootdir is None and rootdir_cmd_arg is None:
|
||||
for possible_rootdir in ancestor.parts(reverse=True):
|
||||
if possible_rootdir.join("setup.py").exists():
|
||||
for possible_rootdir in itertools.chain(
|
||||
(ancestor,), reversed(ancestor.parents)
|
||||
):
|
||||
if (possible_rootdir / "setup.py").is_file():
|
||||
rootdir = possible_rootdir
|
||||
break
|
||||
else:
|
||||
|
@ -181,16 +203,16 @@ def determine_setup(
|
|||
rootdir, inipath, inicfg = locate_config(dirs)
|
||||
if rootdir is None:
|
||||
if config is not None:
|
||||
cwd = config.invocation_dir
|
||||
cwd = config.invocation_params.dir
|
||||
else:
|
||||
cwd = py.path.local()
|
||||
cwd = Path.cwd()
|
||||
rootdir = get_common_ancestor([cwd, ancestor])
|
||||
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
|
||||
if is_fs_root:
|
||||
rootdir = ancestor
|
||||
if rootdir_cmd_arg:
|
||||
rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not rootdir.isdir():
|
||||
rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not rootdir.is_dir():
|
||||
raise UsageError(
|
||||
"Directory '{}' not found. Check your '--rootdir' option.".format(
|
||||
rootdir
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
|
@ -1515,8 +1516,8 @@ class FixtureManager:
|
|||
# by their test id).
|
||||
if p.basename.startswith("conftest.py"):
|
||||
nodeid = p.dirpath().relto(self.config.rootdir)
|
||||
if p.sep != nodes.SEP:
|
||||
nodeid = nodeid.replace(p.sep, nodes.SEP)
|
||||
if os.sep != nodes.SEP:
|
||||
nodeid = nodeid.replace(os.sep, nodes.SEP)
|
||||
|
||||
self.parsefactories(plugin, nodeid)
|
||||
|
||||
|
|
|
@ -569,3 +569,44 @@ def visit(
|
|||
for entry in entries:
|
||||
if entry.is_dir(follow_symlinks=False) and recurse(entry):
|
||||
yield from visit(entry.path, recurse)
|
||||
|
||||
|
||||
def absolutepath(path: Union[Path, str]) -> Path:
|
||||
"""Convert a path to an absolute path using os.path.abspath.
|
||||
|
||||
Prefer this over Path.resolve() (see #6523).
|
||||
Prefer this over Path.absolute() (not public, doesn't normalize).
|
||||
"""
|
||||
return Path(os.path.abspath(str(path)))
|
||||
|
||||
|
||||
def commonpath(path1: Path, path2: Path) -> Optional[Path]:
|
||||
"""Return the common part shared with the other path, or None if there is
|
||||
no common part."""
|
||||
try:
|
||||
return Path(os.path.commonpath((str(path1), str(path2))))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def bestrelpath(directory: Path, dest: Path) -> str:
|
||||
"""Return a string which is a relative path from directory to dest such
|
||||
that directory/bestrelpath == dest.
|
||||
|
||||
If no such path can be determined, returns dest.
|
||||
"""
|
||||
if dest == directory:
|
||||
return os.curdir
|
||||
# Find the longest common directory.
|
||||
base = commonpath(directory, dest)
|
||||
# Can be the case on Windows.
|
||||
if not base:
|
||||
return str(dest)
|
||||
reldirectory = directory.relative_to(base)
|
||||
reldest = dest.relative_to(base)
|
||||
return os.path.join(
|
||||
# Back from directory to base.
|
||||
*([os.pardir] * len(reldirectory.parts)),
|
||||
# Forward from base to dest.
|
||||
*reldest.parts,
|
||||
)
|
||||
|
|
|
@ -1339,7 +1339,7 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
|||
verbose = config.getvalue("verbose")
|
||||
|
||||
def get_best_relpath(func):
|
||||
loc = getlocation(func, curdir)
|
||||
loc = getlocation(func, str(curdir))
|
||||
return curdir.bestrelpath(py.path.local(loc))
|
||||
|
||||
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
|
||||
|
@ -1404,7 +1404,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
|
|||
if not fixturedefs:
|
||||
continue
|
||||
for fixturedef in fixturedefs:
|
||||
loc = getlocation(fixturedef.func, curdir)
|
||||
loc = getlocation(fixturedef.func, str(curdir))
|
||||
if (fixturedef.argname, loc) in seen:
|
||||
continue
|
||||
seen.add((fixturedef.argname, loc))
|
||||
|
@ -1434,7 +1434,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
|
|||
if verbose > 0:
|
||||
tw.write(" -- %s" % bestrel, yellow=True)
|
||||
tw.write("\n")
|
||||
loc = getlocation(fixturedef.func, curdir)
|
||||
loc = getlocation(fixturedef.func, str(curdir))
|
||||
doc = inspect.getdoc(fixturedef.func)
|
||||
if doc:
|
||||
write_docstring(tw, doc)
|
||||
|
|
|
@ -3,8 +3,6 @@ import os
|
|||
from typing import IO
|
||||
from typing import Union
|
||||
|
||||
import py
|
||||
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest.config import Config
|
||||
from _pytest.config.argparsing import Parser
|
||||
|
@ -106,5 +104,5 @@ class ResultLog:
|
|||
if excrepr.reprcrash is not None:
|
||||
path = excrepr.reprcrash.path
|
||||
else:
|
||||
path = "cwd:%s" % py.path.local()
|
||||
path = "cwd:%s" % os.getcwd()
|
||||
self.write_log_entry(path, "!", str(excrepr))
|
||||
|
|
|
@ -586,7 +586,7 @@ class TestInvocationVariants:
|
|||
):
|
||||
pytest.main("-h") # type: ignore[arg-type]
|
||||
|
||||
def test_invoke_with_path(self, tmpdir, capsys):
|
||||
def test_invoke_with_path(self, tmpdir: py.path.local, capsys) -> None:
|
||||
retcode = pytest.main(tmpdir)
|
||||
assert retcode == ExitCode.NO_TESTS_COLLECTED
|
||||
out, err = capsys.readouterr()
|
||||
|
|
|
@ -21,17 +21,27 @@ from _pytest.config.exceptions import UsageError
|
|||
from _pytest.config.findpaths import determine_setup
|
||||
from _pytest.config.findpaths import get_common_ancestor
|
||||
from _pytest.config.findpaths import locate_config
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.pathlib import Path
|
||||
from _pytest.pytester import Testdir
|
||||
|
||||
|
||||
class TestParseIni:
|
||||
@pytest.mark.parametrize(
|
||||
"section, filename", [("pytest", "pytest.ini"), ("tool:pytest", "setup.cfg")]
|
||||
)
|
||||
def test_getcfg_and_config(self, testdir, tmpdir, section, filename):
|
||||
sub = tmpdir.mkdir("sub")
|
||||
sub.chdir()
|
||||
tmpdir.join(filename).write(
|
||||
def test_getcfg_and_config(
|
||||
self,
|
||||
testdir: Testdir,
|
||||
tmp_path: Path,
|
||||
section: str,
|
||||
filename: str,
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
sub = tmp_path / "sub"
|
||||
sub.mkdir()
|
||||
monkeypatch.chdir(sub)
|
||||
(tmp_path / filename).write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
[{section}]
|
||||
|
@ -39,17 +49,14 @@ class TestParseIni:
|
|||
""".format(
|
||||
section=section
|
||||
)
|
||||
)
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
_, _, cfg = locate_config([sub])
|
||||
assert cfg["name"] == "value"
|
||||
config = testdir.parseconfigure(sub)
|
||||
config = testdir.parseconfigure(str(sub))
|
||||
assert config.inicfg["name"] == "value"
|
||||
|
||||
def test_getcfg_empty_path(self):
|
||||
"""Correctly handle zero length arguments (a la pytest '')."""
|
||||
locate_config([""])
|
||||
|
||||
def test_setupcfg_uses_toolpytest_with_pytest(self, testdir):
|
||||
p1 = testdir.makepyfile("def test(): pass")
|
||||
testdir.makefile(
|
||||
|
@ -1168,16 +1175,17 @@ def test_collect_pytest_prefix_bug(pytestconfig):
|
|||
|
||||
|
||||
class TestRootdir:
|
||||
def test_simple_noini(self, tmpdir):
|
||||
assert get_common_ancestor([tmpdir]) == tmpdir
|
||||
a = tmpdir.mkdir("a")
|
||||
assert get_common_ancestor([a, tmpdir]) == tmpdir
|
||||
assert get_common_ancestor([tmpdir, a]) == tmpdir
|
||||
with tmpdir.as_cwd():
|
||||
assert get_common_ancestor([]) == tmpdir
|
||||
no_path = tmpdir.join("does-not-exist")
|
||||
assert get_common_ancestor([no_path]) == tmpdir
|
||||
assert get_common_ancestor([no_path.join("a")]) == tmpdir
|
||||
def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
|
||||
assert get_common_ancestor([tmp_path]) == tmp_path
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
assert get_common_ancestor([a, tmp_path]) == tmp_path
|
||||
assert get_common_ancestor([tmp_path, a]) == tmp_path
|
||||
monkeypatch.chdir(tmp_path)
|
||||
assert get_common_ancestor([]) == tmp_path
|
||||
no_path = tmp_path / "does-not-exist"
|
||||
assert get_common_ancestor([no_path]) == tmp_path
|
||||
assert get_common_ancestor([no_path / "a"]) == tmp_path
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name, contents",
|
||||
|
@ -1190,44 +1198,49 @@ class TestRootdir:
|
|||
pytest.param("setup.cfg", "[tool:pytest]\nx=10", id="setup.cfg"),
|
||||
],
|
||||
)
|
||||
def test_with_ini(self, tmpdir: py.path.local, name: str, contents: str) -> None:
|
||||
inifile = tmpdir.join(name)
|
||||
inifile.write(contents)
|
||||
def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None:
|
||||
inipath = tmp_path / name
|
||||
inipath.write_text(contents, "utf-8")
|
||||
|
||||
a = tmpdir.mkdir("a")
|
||||
b = a.mkdir("b")
|
||||
for args in ([str(tmpdir)], [str(a)], [str(b)]):
|
||||
rootdir, parsed_inifile, _ = determine_setup(None, args)
|
||||
assert rootdir == tmpdir
|
||||
assert parsed_inifile == inifile
|
||||
rootdir, parsed_inifile, ini_config = determine_setup(None, [str(b), str(a)])
|
||||
assert rootdir == tmpdir
|
||||
assert parsed_inifile == inifile
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
b = a / "b"
|
||||
b.mkdir()
|
||||
for args in ([str(tmp_path)], [str(a)], [str(b)]):
|
||||
rootpath, parsed_inipath, _ = determine_setup(None, args)
|
||||
assert rootpath == tmp_path
|
||||
assert parsed_inipath == inipath
|
||||
rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)])
|
||||
assert rootpath == tmp_path
|
||||
assert parsed_inipath == inipath
|
||||
assert ini_config == {"x": "10"}
|
||||
|
||||
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
|
||||
def test_pytestini_overrides_empty_other(self, tmpdir: py.path.local, name) -> None:
|
||||
inifile = tmpdir.ensure("pytest.ini")
|
||||
a = tmpdir.mkdir("a")
|
||||
a.ensure(name)
|
||||
rootdir, parsed_inifile, _ = determine_setup(None, [str(a)])
|
||||
assert rootdir == tmpdir
|
||||
assert parsed_inifile == inifile
|
||||
@pytest.mark.parametrize("name", ["setup.cfg", "tox.ini"])
|
||||
def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> None:
|
||||
inipath = tmp_path / "pytest.ini"
|
||||
inipath.touch()
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
(a / name).touch()
|
||||
rootpath, parsed_inipath, _ = determine_setup(None, [str(a)])
|
||||
assert rootpath == tmp_path
|
||||
assert parsed_inipath == inipath
|
||||
|
||||
def test_setuppy_fallback(self, tmpdir: py.path.local) -> None:
|
||||
a = tmpdir.mkdir("a")
|
||||
a.ensure("setup.cfg")
|
||||
tmpdir.ensure("setup.py")
|
||||
rootdir, inifile, inicfg = determine_setup(None, [str(a)])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
def test_setuppy_fallback(self, tmp_path: Path) -> None:
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
(a / "setup.cfg").touch()
|
||||
(tmp_path / "setup.py").touch()
|
||||
rootpath, inipath, inicfg = determine_setup(None, [str(a)])
|
||||
assert rootpath == tmp_path
|
||||
assert inipath is None
|
||||
assert inicfg == {}
|
||||
|
||||
def test_nothing(self, tmpdir: py.path.local, monkeypatch) -> None:
|
||||
monkeypatch.chdir(str(tmpdir))
|
||||
rootdir, inifile, inicfg = determine_setup(None, [str(tmpdir)])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)])
|
||||
assert rootpath == tmp_path
|
||||
assert inipath is None
|
||||
assert inicfg == {}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -1242,45 +1255,58 @@ class TestRootdir:
|
|||
],
|
||||
)
|
||||
def test_with_specific_inifile(
|
||||
self, tmpdir: py.path.local, name: str, contents: str
|
||||
self, tmp_path: Path, name: str, contents: str
|
||||
) -> None:
|
||||
p = tmpdir.ensure(name)
|
||||
p.write(contents)
|
||||
rootdir, inifile, ini_config = determine_setup(str(p), [str(tmpdir)])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile == p
|
||||
p = tmp_path / name
|
||||
p.touch()
|
||||
p.write_text(contents, "utf-8")
|
||||
rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)])
|
||||
assert rootpath == tmp_path
|
||||
assert inipath == p
|
||||
assert ini_config == {"x": "10"}
|
||||
|
||||
def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch) -> None:
|
||||
monkeypatch.chdir(str(tmpdir))
|
||||
a = tmpdir.mkdir("a")
|
||||
b = tmpdir.mkdir("b")
|
||||
rootdir, inifile, _ = determine_setup(None, [str(a), str(b)])
|
||||
assert rootdir == tmpdir
|
||||
def test_with_arg_outside_cwd_without_inifile(
|
||||
self, tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
b = tmp_path / "b"
|
||||
b.mkdir()
|
||||
rootpath, inifile, _ = determine_setup(None, [str(a), str(b)])
|
||||
assert rootpath == tmp_path
|
||||
assert inifile is None
|
||||
|
||||
def test_with_arg_outside_cwd_with_inifile(self, tmpdir) -> None:
|
||||
a = tmpdir.mkdir("a")
|
||||
b = tmpdir.mkdir("b")
|
||||
inifile = a.ensure("pytest.ini")
|
||||
rootdir, parsed_inifile, _ = determine_setup(None, [str(a), str(b)])
|
||||
assert rootdir == a
|
||||
assert inifile == parsed_inifile
|
||||
def test_with_arg_outside_cwd_with_inifile(self, tmp_path: Path) -> None:
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
b = tmp_path / "b"
|
||||
b.mkdir()
|
||||
inipath = a / "pytest.ini"
|
||||
inipath.touch()
|
||||
rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)])
|
||||
assert rootpath == a
|
||||
assert inipath == parsed_inipath
|
||||
|
||||
@pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"]))
|
||||
def test_with_non_dir_arg(self, dirs, tmpdir) -> None:
|
||||
with tmpdir.ensure(dir=True).as_cwd():
|
||||
rootdir, inifile, _ = determine_setup(None, dirs)
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
def test_with_non_dir_arg(
|
||||
self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
rootpath, inipath, _ = determine_setup(None, dirs)
|
||||
assert rootpath == tmp_path
|
||||
assert inipath is None
|
||||
|
||||
def test_with_existing_file_in_subdir(self, tmpdir) -> None:
|
||||
a = tmpdir.mkdir("a")
|
||||
a.ensure("exist")
|
||||
with tmpdir.as_cwd():
|
||||
rootdir, inifile, _ = determine_setup(None, ["a/exist"])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
def test_with_existing_file_in_subdir(
|
||||
self, tmp_path: Path, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
a = tmp_path / "a"
|
||||
a.mkdir()
|
||||
(a / "exists").touch()
|
||||
monkeypatch.chdir(tmp_path)
|
||||
rootpath, inipath, _ = determine_setup(None, ["a/exist"])
|
||||
assert rootpath == tmp_path
|
||||
assert inipath is None
|
||||
|
||||
|
||||
class TestOverrideIniArgs:
|
||||
|
|
|
@ -1,74 +1,74 @@
|
|||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest.config.findpaths import get_common_ancestor
|
||||
from _pytest.config.findpaths import load_config_dict_from_file
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
|
||||
class TestLoadConfigDictFromFile:
|
||||
def test_empty_pytest_ini(self, tmpdir):
|
||||
def test_empty_pytest_ini(self, tmp_path: Path) -> None:
|
||||
"""pytest.ini files are always considered for configuration, even if empty"""
|
||||
fn = tmpdir.join("pytest.ini")
|
||||
fn.write("")
|
||||
fn = tmp_path / "pytest.ini"
|
||||
fn.write_text("", encoding="utf-8")
|
||||
assert load_config_dict_from_file(fn) == {}
|
||||
|
||||
def test_pytest_ini(self, tmpdir):
|
||||
def test_pytest_ini(self, tmp_path: Path) -> None:
|
||||
"""[pytest] section in pytest.ini files is read correctly"""
|
||||
fn = tmpdir.join("pytest.ini")
|
||||
fn.write("[pytest]\nx=1")
|
||||
fn = tmp_path / "pytest.ini"
|
||||
fn.write_text("[pytest]\nx=1", encoding="utf-8")
|
||||
assert load_config_dict_from_file(fn) == {"x": "1"}
|
||||
|
||||
def test_custom_ini(self, tmpdir):
|
||||
def test_custom_ini(self, tmp_path: Path) -> None:
|
||||
"""[pytest] section in any .ini file is read correctly"""
|
||||
fn = tmpdir.join("custom.ini")
|
||||
fn.write("[pytest]\nx=1")
|
||||
fn = tmp_path / "custom.ini"
|
||||
fn.write_text("[pytest]\nx=1", encoding="utf-8")
|
||||
assert load_config_dict_from_file(fn) == {"x": "1"}
|
||||
|
||||
def test_custom_ini_without_section(self, tmpdir):
|
||||
def test_custom_ini_without_section(self, tmp_path: Path) -> None:
|
||||
"""Custom .ini files without [pytest] section are not considered for configuration"""
|
||||
fn = tmpdir.join("custom.ini")
|
||||
fn.write("[custom]")
|
||||
fn = tmp_path / "custom.ini"
|
||||
fn.write_text("[custom]", encoding="utf-8")
|
||||
assert load_config_dict_from_file(fn) is None
|
||||
|
||||
def test_custom_cfg_file(self, tmpdir):
|
||||
def test_custom_cfg_file(self, tmp_path: Path) -> None:
|
||||
"""Custom .cfg files without [tool:pytest] section are not considered for configuration"""
|
||||
fn = tmpdir.join("custom.cfg")
|
||||
fn.write("[custom]")
|
||||
fn = tmp_path / "custom.cfg"
|
||||
fn.write_text("[custom]", encoding="utf-8")
|
||||
assert load_config_dict_from_file(fn) is None
|
||||
|
||||
def test_valid_cfg_file(self, tmpdir):
|
||||
def test_valid_cfg_file(self, tmp_path: Path) -> None:
|
||||
"""Custom .cfg files with [tool:pytest] section are read correctly"""
|
||||
fn = tmpdir.join("custom.cfg")
|
||||
fn.write("[tool:pytest]\nx=1")
|
||||
fn = tmp_path / "custom.cfg"
|
||||
fn.write_text("[tool:pytest]\nx=1", encoding="utf-8")
|
||||
assert load_config_dict_from_file(fn) == {"x": "1"}
|
||||
|
||||
def test_unsupported_pytest_section_in_cfg_file(self, tmpdir):
|
||||
def test_unsupported_pytest_section_in_cfg_file(self, tmp_path: Path) -> None:
|
||||
""".cfg files with [pytest] section are no longer supported and should fail to alert users"""
|
||||
fn = tmpdir.join("custom.cfg")
|
||||
fn.write("[pytest]")
|
||||
fn = tmp_path / "custom.cfg"
|
||||
fn.write_text("[pytest]", encoding="utf-8")
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
load_config_dict_from_file(fn)
|
||||
|
||||
def test_invalid_toml_file(self, tmpdir):
|
||||
def test_invalid_toml_file(self, tmp_path: Path) -> None:
|
||||
""".toml files without [tool.pytest.ini_options] are not considered for configuration."""
|
||||
fn = tmpdir.join("myconfig.toml")
|
||||
fn.write(
|
||||
fn = tmp_path / "myconfig.toml"
|
||||
fn.write_text(
|
||||
dedent(
|
||||
"""
|
||||
[build_system]
|
||||
x = 1
|
||||
"""
|
||||
)
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert load_config_dict_from_file(fn) is None
|
||||
|
||||
def test_valid_toml_file(self, tmpdir):
|
||||
def test_valid_toml_file(self, tmp_path: Path) -> None:
|
||||
""".toml files with [tool.pytest.ini_options] are read correctly, including changing
|
||||
data types to str/list for compatibility with other configuration options."""
|
||||
fn = tmpdir.join("myconfig.toml")
|
||||
fn.write(
|
||||
fn = tmp_path / "myconfig.toml"
|
||||
fn.write_text(
|
||||
dedent(
|
||||
"""
|
||||
[tool.pytest.ini_options]
|
||||
|
@ -77,7 +77,8 @@ class TestLoadConfigDictFromFile:
|
|||
values = ["tests", "integration"]
|
||||
name = "foo"
|
||||
"""
|
||||
)
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert load_config_dict_from_file(fn) == {
|
||||
"x": "1",
|
||||
|
@ -88,23 +89,22 @@ class TestLoadConfigDictFromFile:
|
|||
|
||||
|
||||
class TestCommonAncestor:
|
||||
def test_has_ancestor(self, tmpdir):
|
||||
fn1 = tmpdir.join("foo/bar/test_1.py").ensure(file=1)
|
||||
fn2 = tmpdir.join("foo/zaz/test_2.py").ensure(file=1)
|
||||
assert get_common_ancestor([fn1, fn2]) == tmpdir.join("foo")
|
||||
assert get_common_ancestor([py.path.local(fn1.dirname), fn2]) == tmpdir.join(
|
||||
"foo"
|
||||
)
|
||||
assert get_common_ancestor(
|
||||
[py.path.local(fn1.dirname), py.path.local(fn2.dirname)]
|
||||
) == tmpdir.join("foo")
|
||||
assert get_common_ancestor([fn1, py.path.local(fn2.dirname)]) == tmpdir.join(
|
||||
"foo"
|
||||
)
|
||||
def test_has_ancestor(self, tmp_path: Path) -> None:
|
||||
fn1 = tmp_path / "foo" / "bar" / "test_1.py"
|
||||
fn1.parent.mkdir(parents=True)
|
||||
fn1.touch()
|
||||
fn2 = tmp_path / "foo" / "zaz" / "test_2.py"
|
||||
fn2.parent.mkdir(parents=True)
|
||||
fn2.touch()
|
||||
assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo"
|
||||
assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo"
|
||||
assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo"
|
||||
assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo"
|
||||
|
||||
def test_single_dir(self, tmpdir):
|
||||
assert get_common_ancestor([tmpdir]) == tmpdir
|
||||
def test_single_dir(self, tmp_path: Path) -> None:
|
||||
assert get_common_ancestor([tmp_path]) == tmp_path
|
||||
|
||||
def test_single_file(self, tmpdir):
|
||||
fn = tmpdir.join("foo.py").ensure(file=1)
|
||||
assert get_common_ancestor([fn]) == tmpdir
|
||||
def test_single_file(self, tmp_path: Path) -> None:
|
||||
fn = tmp_path / "foo.py"
|
||||
fn.touch()
|
||||
assert get_common_ancestor([fn]) == tmp_path
|
||||
|
|
|
@ -6,6 +6,8 @@ from textwrap import dedent
|
|||
import py
|
||||
|
||||
import pytest
|
||||
from _pytest.pathlib import bestrelpath
|
||||
from _pytest.pathlib import commonpath
|
||||
from _pytest.pathlib import ensure_deletable
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.pathlib import get_extended_length_path_str
|
||||
|
@ -381,3 +383,21 @@ def test_suppress_error_removing_lock(tmp_path):
|
|||
# check now that we can remove the lock file in normal circumstances
|
||||
assert ensure_deletable(path, consider_lock_dead_if_created_before=mtime + 30)
|
||||
assert not lock.is_file()
|
||||
|
||||
|
||||
def test_bestrelpath() -> None:
|
||||
curdir = Path("/foo/bar/baz/path")
|
||||
assert bestrelpath(curdir, curdir) == "."
|
||||
assert bestrelpath(curdir, curdir / "hello" / "world") == "hello" + os.sep + "world"
|
||||
assert bestrelpath(curdir, curdir.parent / "sister") == ".." + os.sep + "sister"
|
||||
assert bestrelpath(curdir, curdir.parent) == ".."
|
||||
assert bestrelpath(curdir, Path("hello")) == "hello"
|
||||
|
||||
|
||||
def test_commonpath() -> None:
|
||||
path = Path("/foo/bar/baz/path")
|
||||
subpath = path / "sampledir"
|
||||
assert commonpath(path, subpath) == path
|
||||
assert commonpath(subpath, path) == path
|
||||
assert commonpath(Path(str(path) + "suffix"), path) == path.parent
|
||||
assert commonpath(path, path.parent.parent) == path.parent.parent
|
||||
|
|
Loading…
Reference in New Issue