config/findpaths: convert from py.path.local to pathlib

This commit is contained in:
Ran Benita 2020-08-03 17:46:35 +03:00
parent 9e55288ba4
commit 70f3ad1c1f
4 changed files with 222 additions and 171 deletions

View File

@ -1006,12 +1006,15 @@ class Config:
ns, unknown_args = self._parser.parse_known_and_unknown_args( ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option) args, namespace=copy.copy(self.option)
) )
self.rootdir, self.inifile, self.inicfg = determine_setup( rootpath, inipath, inicfg = determine_setup(
ns.inifilename, ns.inifilename,
ns.file_or_dir + unknown_args, ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None, rootdir_cmd_arg=ns.rootdir or None,
config=self, 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["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile self._parser.extra_info["inifile"] = self.inifile
self._parser.addini("addopts", "extra command line options", "args") self._parser.addini("addopts", "extra command line options", "args")

View File

@ -1,23 +1,28 @@
import itertools
import os import os
import sys
from typing import Dict from typing import Dict
from typing import Iterable from typing import Iterable
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Sequence
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
import iniconfig import iniconfig
import py
from .exceptions import UsageError from .exceptions import UsageError
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.pathlib import absolutepath
from _pytest.pathlib import commonpath
from _pytest.pathlib import Path
if TYPE_CHECKING: if TYPE_CHECKING:
from . import Config 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 """Parse the given generic '.ini' file using legacy IniConfig parser, returning
the parsed object. the parsed object.
@ -30,7 +35,7 @@ def _parse_ini_config(path: py.path.local) -> iniconfig.IniConfig:
def load_config_dict_from_file( def load_config_dict_from_file(
filepath: py.path.local, filepath: Path,
) -> Optional[Dict[str, Union[str, List[str]]]]: ) -> Optional[Dict[str, Union[str, List[str]]]]:
"""Load pytest configuration from the given file path, if supported. """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. # 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) iniconfig = _parse_ini_config(filepath)
if "pytest" in iniconfig: if "pytest" in iniconfig:
return dict(iniconfig["pytest"].items()) return dict(iniconfig["pytest"].items())
else: else:
# "pytest.ini" files are always the source of configuration, even if empty. # "pytest.ini" files are always the source of configuration, even if empty.
if filepath.basename == "pytest.ini": if filepath.name == "pytest.ini":
return {} return {}
# '.cfg' files are considered if they contain a "[tool:pytest]" section. # '.cfg' files are considered if they contain a "[tool:pytest]" section.
elif filepath.ext == ".cfg": elif filepath.suffix == ".cfg":
iniconfig = _parse_ini_config(filepath) iniconfig = _parse_ini_config(filepath)
if "tool:pytest" in iniconfig.sections: 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) fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False)
# '.toml' files are considered if they contain a [tool.pytest.ini_options] table. # '.toml' files are considered if they contain a [tool.pytest.ini_options] table.
elif filepath.ext == ".toml": elif filepath.suffix == ".toml":
import toml import toml
config = toml.load(str(filepath)) config = toml.load(str(filepath))
@ -79,9 +84,9 @@ def load_config_dict_from_file(
def locate_config( def locate_config(
args: Iterable[Union[str, py.path.local]] args: Iterable[Path],
) -> Tuple[ ) -> 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, """Search in the list of arguments for a valid ini-file for pytest,
and return a tuple of (rootdir, inifile, cfg-dict).""" 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("-")] args = [x for x in args if not str(x).startswith("-")]
if not args: if not args:
args = [py.path.local()] args = [Path.cwd()]
for arg in args: for arg in args:
arg = py.path.local(arg) argpath = absolutepath(arg)
for base in arg.parts(reverse=True): for base in itertools.chain((argpath,), reversed(argpath.parents)):
for config_name in config_names: for config_name in config_names:
p = base.join(config_name) p = base / config_name
if p.isfile(): if p.is_file():
ini_config = load_config_dict_from_file(p) ini_config = load_config_dict_from_file(p)
if ini_config is not None: if ini_config is not None:
return base, p, ini_config return base, p, ini_config
return None, None, {} return None, None, {}
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local: def get_common_ancestor(paths: Iterable[Path]) -> Path:
common_ancestor = None # type: Optional[py.path.local] common_ancestor = None # type: Optional[Path]
for path in paths: for path in paths:
if not path.exists(): if not path.exists():
continue continue
if common_ancestor is None: if common_ancestor is None:
common_ancestor = path common_ancestor = path
else: else:
if path.relto(common_ancestor) or path == common_ancestor: if common_ancestor in path.parents or path == common_ancestor:
continue continue
elif common_ancestor.relto(path): elif path in common_ancestor.parents:
common_ancestor = path common_ancestor = path
else: else:
shared = path.common(common_ancestor) shared = commonpath(path, common_ancestor)
if shared is not None: if shared is not None:
common_ancestor = shared common_ancestor = shared
if common_ancestor is None: if common_ancestor is None:
common_ancestor = py.path.local() common_ancestor = Path.cwd()
elif common_ancestor.isfile(): elif common_ancestor.is_file():
common_ancestor = common_ancestor.dirpath() common_ancestor = common_ancestor.parent
return common_ancestor 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: def is_option(x: str) -> bool:
return x.startswith("-") return x.startswith("-")
def get_file_part_from_node_id(x: str) -> str: def get_file_part_from_node_id(x: str) -> str:
return x.split("::")[0] return x.split("::")[0]
def get_dir_from_path(path: py.path.local) -> py.path.local: def get_dir_from_path(path: Path) -> Path:
if path.isdir(): if path.is_dir():
return path 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 # These look like paths but may not exist
possible_paths = ( possible_paths = (
py.path.local(get_file_part_from_node_id(arg)) absolutepath(get_file_part_from_node_id(arg))
for arg in args for arg in args
if not is_option(arg) 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." 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( def determine_setup(
inifile: Optional[str], inifile: Optional[str],
args: List[str], args: Sequence[str],
rootdir_cmd_arg: Optional[str] = None, rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = 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 rootdir = None
dirs = get_dirs_from_args(args) dirs = get_dirs_from_args(args)
if inifile: if inifile:
inipath_ = py.path.local(inifile) inipath_ = absolutepath(inifile)
inipath = inipath_ # type: Optional[py.path.local] inipath = inipath_ # type: Optional[Path]
inicfg = load_config_dict_from_file(inipath_) or {} inicfg = load_config_dict_from_file(inipath_) or {}
if rootdir_cmd_arg is None: if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs) rootdir = get_common_ancestor(dirs)
@ -172,8 +192,10 @@ def determine_setup(
ancestor = get_common_ancestor(dirs) ancestor = get_common_ancestor(dirs)
rootdir, inipath, inicfg = locate_config([ancestor]) rootdir, inipath, inicfg = locate_config([ancestor])
if rootdir is None and rootdir_cmd_arg is None: if rootdir is None and rootdir_cmd_arg is None:
for possible_rootdir in ancestor.parts(reverse=True): for possible_rootdir in itertools.chain(
if possible_rootdir.join("setup.py").exists(): (ancestor,), reversed(ancestor.parents)
):
if (possible_rootdir / "setup.py").is_file():
rootdir = possible_rootdir rootdir = possible_rootdir
break break
else: else:
@ -181,16 +203,16 @@ def determine_setup(
rootdir, inipath, inicfg = locate_config(dirs) rootdir, inipath, inicfg = locate_config(dirs)
if rootdir is None: if rootdir is None:
if config is not None: if config is not None:
cwd = config.invocation_dir cwd = config.invocation_params.dir
else: else:
cwd = py.path.local() cwd = Path.cwd()
rootdir = get_common_ancestor([cwd, ancestor]) rootdir = get_common_ancestor([cwd, ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
if is_fs_root: if is_fs_root:
rootdir = ancestor rootdir = ancestor
if rootdir_cmd_arg: if rootdir_cmd_arg:
rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg)) rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
if not rootdir.isdir(): if not rootdir.is_dir():
raise UsageError( raise UsageError(
"Directory '{}' not found. Check your '--rootdir' option.".format( "Directory '{}' not found. Check your '--rootdir' option.".format(
rootdir rootdir

View File

@ -21,17 +21,27 @@ from _pytest.config.exceptions import UsageError
from _pytest.config.findpaths import determine_setup from _pytest.config.findpaths import determine_setup
from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import locate_config from _pytest.config.findpaths import locate_config
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pathlib import Path from _pytest.pathlib import Path
from _pytest.pytester import Testdir
class TestParseIni: class TestParseIni:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"section, filename", [("pytest", "pytest.ini"), ("tool:pytest", "setup.cfg")] "section, filename", [("pytest", "pytest.ini"), ("tool:pytest", "setup.cfg")]
) )
def test_getcfg_and_config(self, testdir, tmpdir, section, filename): def test_getcfg_and_config(
sub = tmpdir.mkdir("sub") self,
sub.chdir() testdir: Testdir,
tmpdir.join(filename).write( 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( textwrap.dedent(
"""\ """\
[{section}] [{section}]
@ -39,17 +49,14 @@ class TestParseIni:
""".format( """.format(
section=section section=section
) )
) ),
encoding="utf-8",
) )
_, _, cfg = locate_config([sub]) _, _, cfg = locate_config([sub])
assert cfg["name"] == "value" assert cfg["name"] == "value"
config = testdir.parseconfigure(sub) config = testdir.parseconfigure(str(sub))
assert config.inicfg["name"] == "value" 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): def test_setupcfg_uses_toolpytest_with_pytest(self, testdir):
p1 = testdir.makepyfile("def test(): pass") p1 = testdir.makepyfile("def test(): pass")
testdir.makefile( testdir.makefile(
@ -1168,16 +1175,17 @@ def test_collect_pytest_prefix_bug(pytestconfig):
class TestRootdir: class TestRootdir:
def test_simple_noini(self, tmpdir): def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
assert get_common_ancestor([tmpdir]) == tmpdir assert get_common_ancestor([tmp_path]) == tmp_path
a = tmpdir.mkdir("a") a = tmp_path / "a"
assert get_common_ancestor([a, tmpdir]) == tmpdir a.mkdir()
assert get_common_ancestor([tmpdir, a]) == tmpdir assert get_common_ancestor([a, tmp_path]) == tmp_path
with tmpdir.as_cwd(): assert get_common_ancestor([tmp_path, a]) == tmp_path
assert get_common_ancestor([]) == tmpdir monkeypatch.chdir(tmp_path)
no_path = tmpdir.join("does-not-exist") assert get_common_ancestor([]) == tmp_path
assert get_common_ancestor([no_path]) == tmpdir no_path = tmp_path / "does-not-exist"
assert get_common_ancestor([no_path.join("a")]) == tmpdir assert get_common_ancestor([no_path]) == tmp_path
assert get_common_ancestor([no_path / "a"]) == tmp_path
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name, contents", "name, contents",
@ -1190,44 +1198,49 @@ class TestRootdir:
pytest.param("setup.cfg", "[tool:pytest]\nx=10", id="setup.cfg"), 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: def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None:
inifile = tmpdir.join(name) inipath = tmp_path / name
inifile.write(contents) inipath.write_text(contents, "utf-8")
a = tmpdir.mkdir("a") a = tmp_path / "a"
b = a.mkdir("b") a.mkdir()
for args in ([str(tmpdir)], [str(a)], [str(b)]): b = a / "b"
rootdir, parsed_inifile, _ = determine_setup(None, args) b.mkdir()
assert rootdir == tmpdir for args in ([str(tmp_path)], [str(a)], [str(b)]):
assert parsed_inifile == inifile rootpath, parsed_inipath, _ = determine_setup(None, args)
rootdir, parsed_inifile, ini_config = determine_setup(None, [str(b), str(a)]) assert rootpath == tmp_path
assert rootdir == tmpdir assert parsed_inipath == inipath
assert parsed_inifile == inifile 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"} assert ini_config == {"x": "10"}
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split()) @pytest.mark.parametrize("name", ["setup.cfg", "tox.ini"])
def test_pytestini_overrides_empty_other(self, tmpdir: py.path.local, name) -> None: def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> None:
inifile = tmpdir.ensure("pytest.ini") inipath = tmp_path / "pytest.ini"
a = tmpdir.mkdir("a") inipath.touch()
a.ensure(name) a = tmp_path / "a"
rootdir, parsed_inifile, _ = determine_setup(None, [str(a)]) a.mkdir()
assert rootdir == tmpdir (a / name).touch()
assert parsed_inifile == inifile 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: def test_setuppy_fallback(self, tmp_path: Path) -> None:
a = tmpdir.mkdir("a") a = tmp_path / "a"
a.ensure("setup.cfg") a.mkdir()
tmpdir.ensure("setup.py") (a / "setup.cfg").touch()
rootdir, inifile, inicfg = determine_setup(None, [str(a)]) (tmp_path / "setup.py").touch()
assert rootdir == tmpdir rootpath, inipath, inicfg = determine_setup(None, [str(a)])
assert inifile is None assert rootpath == tmp_path
assert inipath is None
assert inicfg == {} assert inicfg == {}
def test_nothing(self, tmpdir: py.path.local, monkeypatch) -> None: def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
monkeypatch.chdir(str(tmpdir)) monkeypatch.chdir(tmp_path)
rootdir, inifile, inicfg = determine_setup(None, [str(tmpdir)]) rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)])
assert rootdir == tmpdir assert rootpath == tmp_path
assert inifile is None assert inipath is None
assert inicfg == {} assert inicfg == {}
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1242,45 +1255,58 @@ class TestRootdir:
], ],
) )
def test_with_specific_inifile( def test_with_specific_inifile(
self, tmpdir: py.path.local, name: str, contents: str self, tmp_path: Path, name: str, contents: str
) -> None: ) -> None:
p = tmpdir.ensure(name) p = tmp_path / name
p.write(contents) p.touch()
rootdir, inifile, ini_config = determine_setup(str(p), [str(tmpdir)]) p.write_text(contents, "utf-8")
assert rootdir == tmpdir rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)])
assert inifile == p assert rootpath == tmp_path
assert inipath == p
assert ini_config == {"x": "10"} assert ini_config == {"x": "10"}
def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch) -> None: def test_with_arg_outside_cwd_without_inifile(
monkeypatch.chdir(str(tmpdir)) self, tmp_path: Path, monkeypatch: MonkeyPatch
a = tmpdir.mkdir("a") ) -> None:
b = tmpdir.mkdir("b") monkeypatch.chdir(tmp_path)
rootdir, inifile, _ = determine_setup(None, [str(a), str(b)]) a = tmp_path / "a"
assert rootdir == tmpdir 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 assert inifile is None
def test_with_arg_outside_cwd_with_inifile(self, tmpdir) -> None: def test_with_arg_outside_cwd_with_inifile(self, tmp_path: Path) -> None:
a = tmpdir.mkdir("a") a = tmp_path / "a"
b = tmpdir.mkdir("b") a.mkdir()
inifile = a.ensure("pytest.ini") b = tmp_path / "b"
rootdir, parsed_inifile, _ = determine_setup(None, [str(a), str(b)]) b.mkdir()
assert rootdir == a inipath = a / "pytest.ini"
assert inifile == parsed_inifile 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"])) @pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"]))
def test_with_non_dir_arg(self, dirs, tmpdir) -> None: def test_with_non_dir_arg(
with tmpdir.ensure(dir=True).as_cwd(): self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch
rootdir, inifile, _ = determine_setup(None, dirs) ) -> None:
assert rootdir == tmpdir monkeypatch.chdir(tmp_path)
assert inifile is None rootpath, inipath, _ = determine_setup(None, dirs)
assert rootpath == tmp_path
assert inipath is None
def test_with_existing_file_in_subdir(self, tmpdir) -> None: def test_with_existing_file_in_subdir(
a = tmpdir.mkdir("a") self, tmp_path: Path, monkeypatch: MonkeyPatch
a.ensure("exist") ) -> None:
with tmpdir.as_cwd(): a = tmp_path / "a"
rootdir, inifile, _ = determine_setup(None, ["a/exist"]) a.mkdir()
assert rootdir == tmpdir (a / "exists").touch()
assert inifile is None monkeypatch.chdir(tmp_path)
rootpath, inipath, _ = determine_setup(None, ["a/exist"])
assert rootpath == tmp_path
assert inipath is None
class TestOverrideIniArgs: class TestOverrideIniArgs:

View File

@ -1,74 +1,74 @@
from textwrap import dedent from textwrap import dedent
import py
import pytest import pytest
from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import load_config_dict_from_file from _pytest.config.findpaths import load_config_dict_from_file
from _pytest.pathlib import Path
class TestLoadConfigDictFromFile: 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""" """pytest.ini files are always considered for configuration, even if empty"""
fn = tmpdir.join("pytest.ini") fn = tmp_path / "pytest.ini"
fn.write("") fn.write_text("", encoding="utf-8")
assert load_config_dict_from_file(fn) == {} 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""" """[pytest] section in pytest.ini files is read correctly"""
fn = tmpdir.join("pytest.ini") fn = tmp_path / "pytest.ini"
fn.write("[pytest]\nx=1") fn.write_text("[pytest]\nx=1", encoding="utf-8")
assert load_config_dict_from_file(fn) == {"x": "1"} 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""" """[pytest] section in any .ini file is read correctly"""
fn = tmpdir.join("custom.ini") fn = tmp_path / "custom.ini"
fn.write("[pytest]\nx=1") fn.write_text("[pytest]\nx=1", encoding="utf-8")
assert load_config_dict_from_file(fn) == {"x": "1"} 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""" """Custom .ini files without [pytest] section are not considered for configuration"""
fn = tmpdir.join("custom.ini") fn = tmp_path / "custom.ini"
fn.write("[custom]") fn.write_text("[custom]", encoding="utf-8")
assert load_config_dict_from_file(fn) is None 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""" """Custom .cfg files without [tool:pytest] section are not considered for configuration"""
fn = tmpdir.join("custom.cfg") fn = tmp_path / "custom.cfg"
fn.write("[custom]") fn.write_text("[custom]", encoding="utf-8")
assert load_config_dict_from_file(fn) is None 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""" """Custom .cfg files with [tool:pytest] section are read correctly"""
fn = tmpdir.join("custom.cfg") fn = tmp_path / "custom.cfg"
fn.write("[tool:pytest]\nx=1") fn.write_text("[tool:pytest]\nx=1", encoding="utf-8")
assert load_config_dict_from_file(fn) == {"x": "1"} 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""" """.cfg files with [pytest] section are no longer supported and should fail to alert users"""
fn = tmpdir.join("custom.cfg") fn = tmp_path / "custom.cfg"
fn.write("[pytest]") fn.write_text("[pytest]", encoding="utf-8")
with pytest.raises(pytest.fail.Exception): with pytest.raises(pytest.fail.Exception):
load_config_dict_from_file(fn) 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.""" """.toml files without [tool.pytest.ini_options] are not considered for configuration."""
fn = tmpdir.join("myconfig.toml") fn = tmp_path / "myconfig.toml"
fn.write( fn.write_text(
dedent( dedent(
""" """
[build_system] [build_system]
x = 1 x = 1
""" """
) ),
encoding="utf-8",
) )
assert load_config_dict_from_file(fn) is None 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 """.toml files with [tool.pytest.ini_options] are read correctly, including changing
data types to str/list for compatibility with other configuration options.""" data types to str/list for compatibility with other configuration options."""
fn = tmpdir.join("myconfig.toml") fn = tmp_path / "myconfig.toml"
fn.write( fn.write_text(
dedent( dedent(
""" """
[tool.pytest.ini_options] [tool.pytest.ini_options]
@ -77,7 +77,8 @@ class TestLoadConfigDictFromFile:
values = ["tests", "integration"] values = ["tests", "integration"]
name = "foo" name = "foo"
""" """
) ),
encoding="utf-8",
) )
assert load_config_dict_from_file(fn) == { assert load_config_dict_from_file(fn) == {
"x": "1", "x": "1",
@ -88,23 +89,22 @@ class TestLoadConfigDictFromFile:
class TestCommonAncestor: class TestCommonAncestor:
def test_has_ancestor(self, tmpdir): def test_has_ancestor(self, tmp_path: Path) -> None:
fn1 = tmpdir.join("foo/bar/test_1.py").ensure(file=1) fn1 = tmp_path / "foo" / "bar" / "test_1.py"
fn2 = tmpdir.join("foo/zaz/test_2.py").ensure(file=1) fn1.parent.mkdir(parents=True)
assert get_common_ancestor([fn1, fn2]) == tmpdir.join("foo") fn1.touch()
assert get_common_ancestor([py.path.local(fn1.dirname), fn2]) == tmpdir.join( fn2 = tmp_path / "foo" / "zaz" / "test_2.py"
"foo" fn2.parent.mkdir(parents=True)
) fn2.touch()
assert get_common_ancestor( assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo"
[py.path.local(fn1.dirname), py.path.local(fn2.dirname)] assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo"
) == tmpdir.join("foo") assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo"
assert get_common_ancestor([fn1, py.path.local(fn2.dirname)]) == tmpdir.join( assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo"
"foo"
)
def test_single_dir(self, tmpdir): def test_single_dir(self, tmp_path: Path) -> None:
assert get_common_ancestor([tmpdir]) == tmpdir assert get_common_ancestor([tmp_path]) == tmp_path
def test_single_file(self, tmpdir): def test_single_file(self, tmp_path: Path) -> None:
fn = tmpdir.join("foo.py").ensure(file=1) fn = tmp_path / "foo.py"
assert get_common_ancestor([fn]) == tmpdir fn.touch()
assert get_common_ancestor([fn]) == tmp_path