Merge pull request #11826 from bluetech/no-cwd

Prefer using the invocation dir over CWD
This commit is contained in:
Ran Benita 2024-01-18 19:02:37 +02:00 committed by GitHub
commit 2178ee86d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 133 additions and 59 deletions

View File

@ -541,6 +541,7 @@ class PytestPluginManager(PluginManager):
noconftest: bool, noconftest: bool,
rootpath: Path, rootpath: Path,
confcutdir: Optional[Path], confcutdir: Optional[Path],
invocation_dir: Path,
importmode: Union[ImportMode, str], importmode: Union[ImportMode, str],
) -> None: ) -> None:
"""Load initial conftest files given a preparsed "namespace". """Load initial conftest files given a preparsed "namespace".
@ -550,8 +551,9 @@ class PytestPluginManager(PluginManager):
All builtin and 3rd party plugins will have been loaded, however, so All builtin and 3rd party plugins will have been loaded, however, so
common options will not confuse our logic here. common options will not confuse our logic here.
""" """
current = Path.cwd() self._confcutdir = (
self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None absolutepath(invocation_dir / confcutdir) if confcutdir else None
)
self._noconftest = noconftest self._noconftest = noconftest
self._using_pyargs = pyargs self._using_pyargs = pyargs
foundanchor = False foundanchor = False
@ -561,7 +563,7 @@ class PytestPluginManager(PluginManager):
i = path.find("::") i = path.find("::")
if i != -1: if i != -1:
path = path[:i] path = path[:i]
anchor = absolutepath(current / path) anchor = absolutepath(invocation_dir / path)
# Ensure we do not break if what appears to be an anchor # Ensure we do not break if what appears to be an anchor
# is in fact a very long option (#10169, #11394). # is in fact a very long option (#10169, #11394).
@ -569,7 +571,7 @@ class PytestPluginManager(PluginManager):
self._try_load_conftest(anchor, importmode, rootpath) self._try_load_conftest(anchor, importmode, rootpath)
foundanchor = True foundanchor = True
if not foundanchor: if not foundanchor:
self._try_load_conftest(current, importmode, rootpath) self._try_load_conftest(invocation_dir, importmode, rootpath)
def _is_in_confcutdir(self, path: Path) -> bool: def _is_in_confcutdir(self, path: Path) -> bool:
"""Whether a path is within the confcutdir. """Whether a path is within the confcutdir.
@ -1168,6 +1170,7 @@ class Config:
noconftest=early_config.known_args_namespace.noconftest, noconftest=early_config.known_args_namespace.noconftest,
rootpath=early_config.rootpath, rootpath=early_config.rootpath,
confcutdir=early_config.known_args_namespace.confcutdir, confcutdir=early_config.known_args_namespace.confcutdir,
invocation_dir=early_config.invocation_params.dir,
importmode=early_config.known_args_namespace.importmode, importmode=early_config.known_args_namespace.importmode,
) )
@ -1176,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,
) )
@ -1261,6 +1264,8 @@ class Config:
"""Decide the args (initial paths/nodeids) to use given the relevant inputs. """Decide the args (initial paths/nodeids) to use given the relevant inputs.
:param warn: Whether can issue warnings. :param warn: Whether can issue warnings.
:returns: The args and the args source. Guaranteed to be non-empty.
""" """
if args: if args:
source = Config.ArgsSource.ARGS source = Config.ArgsSource.ARGS

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

@ -109,10 +109,11 @@ def pytest_cmdline_parse() -> Generator[None, Config, Config]:
debugfile = open(path, "w", encoding="utf-8") debugfile = open(path, "w", encoding="utf-8")
debugfile.write( debugfile.write(
"versions pytest-%s, " "versions pytest-%s, "
"python-%s\ncwd=%s\nargs=%s\n\n" "python-%s\ninvocation_dir=%s\ncwd=%s\nargs=%s\n\n"
% ( % (
pytest.__version__, pytest.__version__,
".".join(map(str, sys.version_info)), ".".join(map(str, sys.version_info)),
config.invocation_params.dir,
os.getcwd(), os.getcwd(),
config.invocation_params.args, config.invocation_params.args,
) )

View File

@ -1525,14 +1525,13 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
return val if escape_option else ascii_escaped(val) # type: ignore return val if escape_option else ascii_escaped(val) # type: ignore
def _pretty_fixture_path(func) -> str: def _pretty_fixture_path(invocation_dir: Path, func) -> str:
cwd = Path.cwd() loc = Path(getlocation(func, invocation_dir))
loc = Path(getlocation(func, str(cwd)))
prefix = Path("...", "_pytest") prefix = Path("...", "_pytest")
try: try:
return str(prefix / loc.relative_to(_PYTEST_DIR)) return str(prefix / loc.relative_to(_PYTEST_DIR))
except ValueError: except ValueError:
return bestrelpath(cwd, loc) return bestrelpath(invocation_dir, loc)
def show_fixtures_per_test(config): def show_fixtures_per_test(config):
@ -1545,19 +1544,19 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
import _pytest.config import _pytest.config
session.perform_collect() session.perform_collect()
curdir = Path.cwd() invocation_dir = config.invocation_params.dir
tw = _pytest.config.create_terminal_writer(config) tw = _pytest.config.create_terminal_writer(config)
verbose = config.getvalue("verbose") verbose = config.getvalue("verbose")
def get_best_relpath(func) -> str: def get_best_relpath(func) -> str:
loc = getlocation(func, str(curdir)) loc = getlocation(func, invocation_dir)
return bestrelpath(curdir, Path(loc)) return bestrelpath(invocation_dir, Path(loc))
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
argname = fixture_def.argname argname = fixture_def.argname
if verbose <= 0 and argname.startswith("_"): if verbose <= 0 and argname.startswith("_"):
return return
prettypath = _pretty_fixture_path(fixture_def.func) prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
tw.write(f"{argname}", green=True) tw.write(f"{argname}", green=True)
tw.write(f" -- {prettypath}", yellow=True) tw.write(f" -- {prettypath}", yellow=True)
tw.write("\n") tw.write("\n")
@ -1601,7 +1600,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
import _pytest.config import _pytest.config
session.perform_collect() session.perform_collect()
curdir = Path.cwd() invocation_dir = config.invocation_params.dir
tw = _pytest.config.create_terminal_writer(config) tw = _pytest.config.create_terminal_writer(config)
verbose = config.getvalue("verbose") verbose = config.getvalue("verbose")
@ -1615,7 +1614,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
if not fixturedefs: if not fixturedefs:
continue continue
for fixturedef in fixturedefs: for fixturedef in fixturedefs:
loc = getlocation(fixturedef.func, str(curdir)) loc = getlocation(fixturedef.func, invocation_dir)
if (fixturedef.argname, loc) in seen: if (fixturedef.argname, loc) in seen:
continue continue
seen.add((fixturedef.argname, loc)) seen.add((fixturedef.argname, loc))
@ -1623,7 +1622,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
( (
len(fixturedef.baseid), len(fixturedef.baseid),
fixturedef.func.__module__, fixturedef.func.__module__,
_pretty_fixture_path(fixturedef.func), _pretty_fixture_path(invocation_dir, fixturedef.func),
fixturedef.argname, fixturedef.argname,
fixturedef, fixturedef,
) )

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

@ -35,6 +35,7 @@ def conftest_setinitial(
noconftest=False, noconftest=False,
rootpath=Path(args[0]), rootpath=Path(args[0]),
confcutdir=confcutdir, confcutdir=confcutdir,
invocation_dir=Path.cwd(),
importmode="prepend", importmode="prepend",
) )

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