findpaths: rely on invocation_dir instead of cwd

We should aim to remove all `cwd()` calls except one, otherwise things
will go bad if the working directory changes. Use the invocation dir
instead.
This commit is contained in:
Ran Benita 2024-01-16 20:56:32 +02:00
parent 212c55218b
commit 676f38d04a
4 changed files with 111 additions and 43 deletions

View File

@ -1179,8 +1179,8 @@ class Config:
args, namespace=copy.copy(self.option) args, namespace=copy.copy(self.option)
) )
rootpath, inipath, inicfg = determine_setup( rootpath, inipath, inicfg = determine_setup(
ns.inifilename, inifile=ns.inifilename,
ns.file_or_dir + unknown_args, args=ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None, rootdir_cmd_arg=ns.rootdir or None,
invocation_dir=self.invocation_params.dir, invocation_dir=self.invocation_params.dir,
) )

View File

@ -87,6 +87,7 @@ def load_config_dict_from_file(
def locate_config( def locate_config(
invocation_dir: Path,
args: Iterable[Path], args: Iterable[Path],
) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: ) -> Tuple[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,
@ -100,7 +101,7 @@ 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 = [Path.cwd()] args = [invocation_dir]
for arg in args: for arg in args:
argpath = absolutepath(arg) argpath = absolutepath(arg)
for base in (argpath, *argpath.parents): for base in (argpath, *argpath.parents):
@ -113,7 +114,10 @@ def locate_config(
return None, None, {} return None, None, {}
def get_common_ancestor(paths: Iterable[Path]) -> Path: def get_common_ancestor(
invocation_dir: Path,
paths: Iterable[Path],
) -> Path:
common_ancestor: Optional[Path] = None common_ancestor: Optional[Path] = None
for path in paths: for path in paths:
if not path.exists(): if not path.exists():
@ -130,7 +134,7 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path:
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 = Path.cwd() common_ancestor = invocation_dir
elif common_ancestor.is_file(): elif common_ancestor.is_file():
common_ancestor = common_ancestor.parent common_ancestor = common_ancestor.parent
return common_ancestor return common_ancestor
@ -162,10 +166,11 @@ 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: Sequence[str], args: Sequence[str],
rootdir_cmd_arg: Optional[str] = None, rootdir_cmd_arg: Optional[str],
invocation_dir: Optional[Path] = None, invocation_dir: Path,
) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: ) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
"""Determine the rootdir, inifile and ini configuration values from the """Determine the rootdir, inifile and ini configuration values from the
command line arguments. command line arguments.
@ -177,8 +182,7 @@ def determine_setup(
:param rootdir_cmd_arg: :param rootdir_cmd_arg:
The `--rootdir` command line argument, if given. The `--rootdir` command line argument, if given.
:param invocation_dir: :param invocation_dir:
The working directory when pytest was invoked, if known. The working directory when pytest was invoked.
If not known, the current working directory is used.
""" """
rootdir = None rootdir = None
dirs = get_dirs_from_args(args) dirs = get_dirs_from_args(args)
@ -189,8 +193,8 @@ def determine_setup(
if rootdir_cmd_arg is None: if rootdir_cmd_arg is None:
rootdir = inipath_.parent rootdir = inipath_.parent
else: else:
ancestor = get_common_ancestor(dirs) ancestor = get_common_ancestor(invocation_dir, dirs)
rootdir, inipath, inicfg = locate_config([ancestor]) rootdir, inipath, inicfg = locate_config(invocation_dir, [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, *ancestor.parents): for possible_rootdir in (ancestor, *ancestor.parents):
if (possible_rootdir / "setup.py").is_file(): if (possible_rootdir / "setup.py").is_file():
@ -198,13 +202,11 @@ def determine_setup(
break break
else: else:
if dirs != [ancestor]: if dirs != [ancestor]:
rootdir, inipath, inicfg = locate_config(dirs) rootdir, inipath, inicfg = locate_config(invocation_dir, dirs)
if rootdir is None: if rootdir is None:
if invocation_dir is not None: rootdir = get_common_ancestor(
cwd = invocation_dir invocation_dir, [invocation_dir, ancestor]
else: )
cwd = Path.cwd()
rootdir = get_common_ancestor([cwd, ancestor])
if is_fs_root(rootdir): if is_fs_root(rootdir):
rootdir = ancestor rootdir = ancestor
if rootdir_cmd_arg: if rootdir_cmd_arg:

View File

@ -59,7 +59,7 @@ class TestParseIni:
), ),
encoding="utf-8", encoding="utf-8",
) )
_, _, cfg = locate_config([sub]) _, _, cfg = locate_config(Path.cwd(), [sub])
assert cfg["name"] == "value" assert cfg["name"] == "value"
config = pytester.parseconfigure(str(sub)) config = pytester.parseconfigure(str(sub))
assert config.inicfg["name"] == "value" assert config.inicfg["name"] == "value"
@ -1436,16 +1436,16 @@ def test_collect_pytest_prefix_bug(pytestconfig):
class TestRootdir: class TestRootdir:
def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
assert get_common_ancestor([tmp_path]) == tmp_path assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path
a = tmp_path / "a" a = tmp_path / "a"
a.mkdir() a.mkdir()
assert get_common_ancestor([a, tmp_path]) == tmp_path assert get_common_ancestor(Path.cwd(), [a, tmp_path]) == tmp_path
assert get_common_ancestor([tmp_path, a]) == tmp_path assert get_common_ancestor(Path.cwd(), [tmp_path, a]) == tmp_path
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
assert get_common_ancestor([]) == tmp_path assert get_common_ancestor(Path.cwd(), []) == tmp_path
no_path = tmp_path / "does-not-exist" no_path = tmp_path / "does-not-exist"
assert get_common_ancestor([no_path]) == tmp_path assert get_common_ancestor(Path.cwd(), [no_path]) == tmp_path
assert get_common_ancestor([no_path / "a"]) == tmp_path assert get_common_ancestor(Path.cwd(), [no_path / "a"]) == tmp_path
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name, contents", "name, contents",
@ -1467,10 +1467,20 @@ class TestRootdir:
b = a / "b" b = a / "b"
b.mkdir() b.mkdir()
for args in ([str(tmp_path)], [str(a)], [str(b)]): for args in ([str(tmp_path)], [str(a)], [str(b)]):
rootpath, parsed_inipath, _ = determine_setup(None, args) rootpath, parsed_inipath, _ = determine_setup(
inifile=None,
args=args,
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert parsed_inipath == inipath assert parsed_inipath == inipath
rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)]) rootpath, parsed_inipath, ini_config = determine_setup(
inifile=None,
args=[str(b), str(a)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert parsed_inipath == inipath assert parsed_inipath == inipath
assert ini_config == {"x": "10"} assert ini_config == {"x": "10"}
@ -1482,7 +1492,12 @@ class TestRootdir:
a = tmp_path / "a" a = tmp_path / "a"
a.mkdir() a.mkdir()
(a / name).touch() (a / name).touch()
rootpath, parsed_inipath, _ = determine_setup(None, [str(a)]) rootpath, parsed_inipath, _ = determine_setup(
inifile=None,
args=[str(a)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert parsed_inipath == inipath assert parsed_inipath == inipath
@ -1491,14 +1506,24 @@ class TestRootdir:
a.mkdir() a.mkdir()
(a / "setup.cfg").touch() (a / "setup.cfg").touch()
(tmp_path / "setup.py").touch() (tmp_path / "setup.py").touch()
rootpath, inipath, inicfg = determine_setup(None, [str(a)]) rootpath, inipath, inicfg = determine_setup(
inifile=None,
args=[str(a)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert inipath is None assert inipath is None
assert inicfg == {} assert inicfg == {}
def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)]) rootpath, inipath, inicfg = determine_setup(
inifile=None,
args=[str(tmp_path)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert inipath is None assert inipath is None
assert inicfg == {} assert inicfg == {}
@ -1520,7 +1545,12 @@ class TestRootdir:
p = tmp_path / name p = tmp_path / name
p.touch() p.touch()
p.write_text(contents, encoding="utf-8") p.write_text(contents, encoding="utf-8")
rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)]) rootpath, inipath, ini_config = determine_setup(
inifile=str(p),
args=[str(tmp_path)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert inipath == p assert inipath == p
assert ini_config == {"x": "10"} assert ini_config == {"x": "10"}
@ -1534,14 +1564,24 @@ class TestRootdir:
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
# No config file is explicitly given: rootdir is determined to be cwd. # No config file is explicitly given: rootdir is determined to be cwd.
rootpath, found_inipath, *_ = determine_setup(None, [str(tests_dir)]) rootpath, found_inipath, *_ = determine_setup(
inifile=None,
args=[str(tests_dir)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert found_inipath is None assert found_inipath is None
# Config file is explicitly given: rootdir is determined to be inifile's directory. # Config file is explicitly given: rootdir is determined to be inifile's directory.
inipath = tmp_path / "pytest.ini" inipath = tmp_path / "pytest.ini"
inipath.touch() inipath.touch()
rootpath, found_inipath, *_ = determine_setup(str(inipath), [str(tests_dir)]) rootpath, found_inipath, *_ = determine_setup(
inifile=str(inipath),
args=[str(tests_dir)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert found_inipath == inipath assert found_inipath == inipath
@ -1553,7 +1593,12 @@ class TestRootdir:
a.mkdir() a.mkdir()
b = tmp_path / "b" b = tmp_path / "b"
b.mkdir() b.mkdir()
rootpath, inifile, _ = determine_setup(None, [str(a), str(b)]) rootpath, inifile, _ = determine_setup(
inifile=None,
args=[str(a), str(b)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert inifile is None assert inifile is None
@ -1564,7 +1609,12 @@ class TestRootdir:
b.mkdir() b.mkdir()
inipath = a / "pytest.ini" inipath = a / "pytest.ini"
inipath.touch() inipath.touch()
rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)]) rootpath, parsed_inipath, _ = determine_setup(
inifile=None,
args=[str(a), str(b)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == a assert rootpath == a
assert inipath == parsed_inipath assert inipath == parsed_inipath
@ -1573,7 +1623,12 @@ class TestRootdir:
self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch
) -> None: ) -> None:
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
rootpath, inipath, _ = determine_setup(None, dirs) rootpath, inipath, _ = determine_setup(
inifile=None,
args=dirs,
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert inipath is None assert inipath is None
@ -1584,7 +1639,12 @@ class TestRootdir:
a.mkdir() a.mkdir()
(a / "exists").touch() (a / "exists").touch()
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
rootpath, inipath, _ = determine_setup(None, ["a/exist"]) rootpath, inipath, _ = determine_setup(
inifile=None,
args=["a/exist"],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path assert rootpath == tmp_path
assert inipath is None assert inipath is None
@ -1598,7 +1658,12 @@ class TestRootdir:
(tmp_path / "myproject" / "tests").mkdir() (tmp_path / "myproject" / "tests").mkdir()
monkeypatch.chdir(tmp_path / "myproject") monkeypatch.chdir(tmp_path / "myproject")
rootpath, inipath, _ = determine_setup(None, ["tests/"]) rootpath, inipath, _ = determine_setup(
inifile=None,
args=["tests/"],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)
assert rootpath == tmp_path / "myproject" assert rootpath == tmp_path / "myproject"
assert inipath == tmp_path / "myproject" / "setup.cfg" assert inipath == tmp_path / "myproject" / "setup.cfg"

View File

@ -109,18 +109,19 @@ class TestCommonAncestor:
fn2 = tmp_path / "foo" / "zaz" / "test_2.py" fn2 = tmp_path / "foo" / "zaz" / "test_2.py"
fn2.parent.mkdir(parents=True) fn2.parent.mkdir(parents=True)
fn2.touch() fn2.touch()
assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo" cwd = Path.cwd()
assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo" assert get_common_ancestor(cwd, [fn1, fn2]) == tmp_path / "foo"
assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo" assert get_common_ancestor(cwd, [fn1.parent, fn2]) == tmp_path / "foo"
assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo" assert get_common_ancestor(cwd, [fn1.parent, fn2.parent]) == tmp_path / "foo"
assert get_common_ancestor(cwd, [fn1, fn2.parent]) == tmp_path / "foo"
def test_single_dir(self, tmp_path: Path) -> None: def test_single_dir(self, tmp_path: Path) -> None:
assert get_common_ancestor([tmp_path]) == tmp_path assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path
def test_single_file(self, tmp_path: Path) -> None: def test_single_file(self, tmp_path: Path) -> None:
fn = tmp_path / "foo.py" fn = tmp_path / "foo.py"
fn.touch() fn.touch()
assert get_common_ancestor([fn]) == tmp_path assert get_common_ancestor(Path.cwd(), [fn]) == tmp_path
def test_get_dirs_from_args(tmp_path): def test_get_dirs_from_args(tmp_path):