Merge pull request #7619 from bluetech/py-to-pathlib

Some py.path -> pathlib conversions
This commit is contained in:
Ran Benita 2020-08-07 11:33:21 +03:00 committed by GitHub
commit 384b6f6866
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 324 additions and 200 deletions

View File

@ -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"]

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,
)

View File

@ -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)

View File

@ -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))

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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