Skip to content

Commit 676f38d

Browse files
committed
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.
1 parent 212c552 commit 676f38d

File tree

4 files changed

+111
-43
lines changed

4 files changed

+111
-43
lines changed

src/_pytest/config/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,8 +1179,8 @@ def _initini(self, args: Sequence[str]) -> None:
11791179
args, namespace=copy.copy(self.option)
11801180
)
11811181
rootpath, inipath, inicfg = determine_setup(
1182-
ns.inifilename,
1183-
ns.file_or_dir + unknown_args,
1182+
inifile=ns.inifilename,
1183+
args=ns.file_or_dir + unknown_args,
11841184
rootdir_cmd_arg=ns.rootdir or None,
11851185
invocation_dir=self.invocation_params.dir,
11861186
)

src/_pytest/config/findpaths.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def make_scalar(v: object) -> Union[str, List[str]]:
8787

8888

8989
def locate_config(
90+
invocation_dir: Path,
9091
args: Iterable[Path],
9192
) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]:
9293
"""Search in the list of arguments for a valid ini-file for pytest,
@@ -100,7 +101,7 @@ def locate_config(
100101
]
101102
args = [x for x in args if not str(x).startswith("-")]
102103
if not args:
103-
args = [Path.cwd()]
104+
args = [invocation_dir]
104105
for arg in args:
105106
argpath = absolutepath(arg)
106107
for base in (argpath, *argpath.parents):
@@ -113,7 +114,10 @@ def locate_config(
113114
return None, None, {}
114115

115116

116-
def get_common_ancestor(paths: Iterable[Path]) -> Path:
117+
def get_common_ancestor(
118+
invocation_dir: Path,
119+
paths: Iterable[Path],
120+
) -> Path:
117121
common_ancestor: Optional[Path] = None
118122
for path in paths:
119123
if not path.exists():
@@ -130,7 +134,7 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path:
130134
if shared is not None:
131135
common_ancestor = shared
132136
if common_ancestor is None:
133-
common_ancestor = Path.cwd()
137+
common_ancestor = invocation_dir
134138
elif common_ancestor.is_file():
135139
common_ancestor = common_ancestor.parent
136140
return common_ancestor
@@ -162,10 +166,11 @@ def get_dir_from_path(path: Path) -> Path:
162166

163167

164168
def determine_setup(
169+
*,
165170
inifile: Optional[str],
166171
args: Sequence[str],
167-
rootdir_cmd_arg: Optional[str] = None,
168-
invocation_dir: Optional[Path] = None,
172+
rootdir_cmd_arg: Optional[str],
173+
invocation_dir: Path,
169174
) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
170175
"""Determine the rootdir, inifile and ini configuration values from the
171176
command line arguments.
@@ -177,8 +182,7 @@ def determine_setup(
177182
:param rootdir_cmd_arg:
178183
The `--rootdir` command line argument, if given.
179184
:param invocation_dir:
180-
The working directory when pytest was invoked, if known.
181-
If not known, the current working directory is used.
185+
The working directory when pytest was invoked.
182186
"""
183187
rootdir = None
184188
dirs = get_dirs_from_args(args)
@@ -189,22 +193,20 @@ def determine_setup(
189193
if rootdir_cmd_arg is None:
190194
rootdir = inipath_.parent
191195
else:
192-
ancestor = get_common_ancestor(dirs)
193-
rootdir, inipath, inicfg = locate_config([ancestor])
196+
ancestor = get_common_ancestor(invocation_dir, dirs)
197+
rootdir, inipath, inicfg = locate_config(invocation_dir, [ancestor])
194198
if rootdir is None and rootdir_cmd_arg is None:
195199
for possible_rootdir in (ancestor, *ancestor.parents):
196200
if (possible_rootdir / "setup.py").is_file():
197201
rootdir = possible_rootdir
198202
break
199203
else:
200204
if dirs != [ancestor]:
201-
rootdir, inipath, inicfg = locate_config(dirs)
205+
rootdir, inipath, inicfg = locate_config(invocation_dir, dirs)
202206
if rootdir is None:
203-
if invocation_dir is not None:
204-
cwd = invocation_dir
205-
else:
206-
cwd = Path.cwd()
207-
rootdir = get_common_ancestor([cwd, ancestor])
207+
rootdir = get_common_ancestor(
208+
invocation_dir, [invocation_dir, ancestor]
209+
)
208210
if is_fs_root(rootdir):
209211
rootdir = ancestor
210212
if rootdir_cmd_arg:

testing/test_config.py

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_getcfg_and_config(
5959
),
6060
encoding="utf-8",
6161
)
62-
_, _, cfg = locate_config([sub])
62+
_, _, cfg = locate_config(Path.cwd(), [sub])
6363
assert cfg["name"] == "value"
6464
config = pytester.parseconfigure(str(sub))
6565
assert config.inicfg["name"] == "value"
@@ -1436,16 +1436,16 @@ class pytest_something:
14361436

14371437
class TestRootdir:
14381438
def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
1439-
assert get_common_ancestor([tmp_path]) == tmp_path
1439+
assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path
14401440
a = tmp_path / "a"
14411441
a.mkdir()
1442-
assert get_common_ancestor([a, tmp_path]) == tmp_path
1443-
assert get_common_ancestor([tmp_path, a]) == tmp_path
1442+
assert get_common_ancestor(Path.cwd(), [a, tmp_path]) == tmp_path
1443+
assert get_common_ancestor(Path.cwd(), [tmp_path, a]) == tmp_path
14441444
monkeypatch.chdir(tmp_path)
1445-
assert get_common_ancestor([]) == tmp_path
1445+
assert get_common_ancestor(Path.cwd(), []) == tmp_path
14461446
no_path = tmp_path / "does-not-exist"
1447-
assert get_common_ancestor([no_path]) == tmp_path
1448-
assert get_common_ancestor([no_path / "a"]) == tmp_path
1447+
assert get_common_ancestor(Path.cwd(), [no_path]) == tmp_path
1448+
assert get_common_ancestor(Path.cwd(), [no_path / "a"]) == tmp_path
14491449

14501450
@pytest.mark.parametrize(
14511451
"name, contents",
@@ -1467,10 +1467,20 @@ def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None:
14671467
b = a / "b"
14681468
b.mkdir()
14691469
for args in ([str(tmp_path)], [str(a)], [str(b)]):
1470-
rootpath, parsed_inipath, _ = determine_setup(None, args)
1470+
rootpath, parsed_inipath, _ = determine_setup(
1471+
inifile=None,
1472+
args=args,
1473+
rootdir_cmd_arg=None,
1474+
invocation_dir=Path.cwd(),
1475+
)
14711476
assert rootpath == tmp_path
14721477
assert parsed_inipath == inipath
1473-
rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)])
1478+
rootpath, parsed_inipath, ini_config = determine_setup(
1479+
inifile=None,
1480+
args=[str(b), str(a)],
1481+
rootdir_cmd_arg=None,
1482+
invocation_dir=Path.cwd(),
1483+
)
14741484
assert rootpath == tmp_path
14751485
assert parsed_inipath == inipath
14761486
assert ini_config == {"x": "10"}
@@ -1482,7 +1492,12 @@ def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> Non
14821492
a = tmp_path / "a"
14831493
a.mkdir()
14841494
(a / name).touch()
1485-
rootpath, parsed_inipath, _ = determine_setup(None, [str(a)])
1495+
rootpath, parsed_inipath, _ = determine_setup(
1496+
inifile=None,
1497+
args=[str(a)],
1498+
rootdir_cmd_arg=None,
1499+
invocation_dir=Path.cwd(),
1500+
)
14861501
assert rootpath == tmp_path
14871502
assert parsed_inipath == inipath
14881503

@@ -1491,14 +1506,24 @@ def test_setuppy_fallback(self, tmp_path: Path) -> None:
14911506
a.mkdir()
14921507
(a / "setup.cfg").touch()
14931508
(tmp_path / "setup.py").touch()
1494-
rootpath, inipath, inicfg = determine_setup(None, [str(a)])
1509+
rootpath, inipath, inicfg = determine_setup(
1510+
inifile=None,
1511+
args=[str(a)],
1512+
rootdir_cmd_arg=None,
1513+
invocation_dir=Path.cwd(),
1514+
)
14951515
assert rootpath == tmp_path
14961516
assert inipath is None
14971517
assert inicfg == {}
14981518

14991519
def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
15001520
monkeypatch.chdir(tmp_path)
1501-
rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)])
1521+
rootpath, inipath, inicfg = determine_setup(
1522+
inifile=None,
1523+
args=[str(tmp_path)],
1524+
rootdir_cmd_arg=None,
1525+
invocation_dir=Path.cwd(),
1526+
)
15021527
assert rootpath == tmp_path
15031528
assert inipath is None
15041529
assert inicfg == {}
@@ -1520,7 +1545,12 @@ def test_with_specific_inifile(
15201545
p = tmp_path / name
15211546
p.touch()
15221547
p.write_text(contents, encoding="utf-8")
1523-
rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)])
1548+
rootpath, inipath, ini_config = determine_setup(
1549+
inifile=str(p),
1550+
args=[str(tmp_path)],
1551+
rootdir_cmd_arg=None,
1552+
invocation_dir=Path.cwd(),
1553+
)
15241554
assert rootpath == tmp_path
15251555
assert inipath == p
15261556
assert ini_config == {"x": "10"}
@@ -1534,14 +1564,24 @@ def test_explicit_config_file_sets_rootdir(
15341564
monkeypatch.chdir(tmp_path)
15351565

15361566
# No config file is explicitly given: rootdir is determined to be cwd.
1537-
rootpath, found_inipath, *_ = determine_setup(None, [str(tests_dir)])
1567+
rootpath, found_inipath, *_ = determine_setup(
1568+
inifile=None,
1569+
args=[str(tests_dir)],
1570+
rootdir_cmd_arg=None,
1571+
invocation_dir=Path.cwd(),
1572+
)
15381573
assert rootpath == tmp_path
15391574
assert found_inipath is None
15401575

15411576
# Config file is explicitly given: rootdir is determined to be inifile's directory.
15421577
inipath = tmp_path / "pytest.ini"
15431578
inipath.touch()
1544-
rootpath, found_inipath, *_ = determine_setup(str(inipath), [str(tests_dir)])
1579+
rootpath, found_inipath, *_ = determine_setup(
1580+
inifile=str(inipath),
1581+
args=[str(tests_dir)],
1582+
rootdir_cmd_arg=None,
1583+
invocation_dir=Path.cwd(),
1584+
)
15451585
assert rootpath == tmp_path
15461586
assert found_inipath == inipath
15471587

@@ -1553,7 +1593,12 @@ def test_with_arg_outside_cwd_without_inifile(
15531593
a.mkdir()
15541594
b = tmp_path / "b"
15551595
b.mkdir()
1556-
rootpath, inifile, _ = determine_setup(None, [str(a), str(b)])
1596+
rootpath, inifile, _ = determine_setup(
1597+
inifile=None,
1598+
args=[str(a), str(b)],
1599+
rootdir_cmd_arg=None,
1600+
invocation_dir=Path.cwd(),
1601+
)
15571602
assert rootpath == tmp_path
15581603
assert inifile is None
15591604

@@ -1564,7 +1609,12 @@ def test_with_arg_outside_cwd_with_inifile(self, tmp_path: Path) -> None:
15641609
b.mkdir()
15651610
inipath = a / "pytest.ini"
15661611
inipath.touch()
1567-
rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)])
1612+
rootpath, parsed_inipath, _ = determine_setup(
1613+
inifile=None,
1614+
args=[str(a), str(b)],
1615+
rootdir_cmd_arg=None,
1616+
invocation_dir=Path.cwd(),
1617+
)
15681618
assert rootpath == a
15691619
assert inipath == parsed_inipath
15701620

@@ -1573,7 +1623,12 @@ def test_with_non_dir_arg(
15731623
self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch
15741624
) -> None:
15751625
monkeypatch.chdir(tmp_path)
1576-
rootpath, inipath, _ = determine_setup(None, dirs)
1626+
rootpath, inipath, _ = determine_setup(
1627+
inifile=None,
1628+
args=dirs,
1629+
rootdir_cmd_arg=None,
1630+
invocation_dir=Path.cwd(),
1631+
)
15771632
assert rootpath == tmp_path
15781633
assert inipath is None
15791634

@@ -1584,7 +1639,12 @@ def test_with_existing_file_in_subdir(
15841639
a.mkdir()
15851640
(a / "exists").touch()
15861641
monkeypatch.chdir(tmp_path)
1587-
rootpath, inipath, _ = determine_setup(None, ["a/exist"])
1642+
rootpath, inipath, _ = determine_setup(
1643+
inifile=None,
1644+
args=["a/exist"],
1645+
rootdir_cmd_arg=None,
1646+
invocation_dir=Path.cwd(),
1647+
)
15881648
assert rootpath == tmp_path
15891649
assert inipath is None
15901650

@@ -1598,7 +1658,12 @@ def test_with_config_also_in_parent_directory(
15981658
(tmp_path / "myproject" / "tests").mkdir()
15991659
monkeypatch.chdir(tmp_path / "myproject")
16001660

1601-
rootpath, inipath, _ = determine_setup(None, ["tests/"])
1661+
rootpath, inipath, _ = determine_setup(
1662+
inifile=None,
1663+
args=["tests/"],
1664+
rootdir_cmd_arg=None,
1665+
invocation_dir=Path.cwd(),
1666+
)
16021667

16031668
assert rootpath == tmp_path / "myproject"
16041669
assert inipath == tmp_path / "myproject" / "setup.cfg"

testing/test_findpaths.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,19 @@ def test_has_ancestor(self, tmp_path: Path) -> None:
109109
fn2 = tmp_path / "foo" / "zaz" / "test_2.py"
110110
fn2.parent.mkdir(parents=True)
111111
fn2.touch()
112-
assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo"
113-
assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo"
114-
assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo"
115-
assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo"
112+
cwd = Path.cwd()
113+
assert get_common_ancestor(cwd, [fn1, fn2]) == tmp_path / "foo"
114+
assert get_common_ancestor(cwd, [fn1.parent, fn2]) == tmp_path / "foo"
115+
assert get_common_ancestor(cwd, [fn1.parent, fn2.parent]) == tmp_path / "foo"
116+
assert get_common_ancestor(cwd, [fn1, fn2.parent]) == tmp_path / "foo"
116117

117118
def test_single_dir(self, tmp_path: Path) -> None:
118-
assert get_common_ancestor([tmp_path]) == tmp_path
119+
assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path
119120

120121
def test_single_file(self, tmp_path: Path) -> None:
121122
fn = tmp_path / "foo.py"
122123
fn.touch()
123-
assert get_common_ancestor([fn]) == tmp_path
124+
assert get_common_ancestor(Path.cwd(), [fn]) == tmp_path
124125

125126

126127
def test_get_dirs_from_args(tmp_path):

0 commit comments

Comments
 (0)