Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ Vlad Radziuk
Vladyslav Rachek
Volodymyr Kochetkov
Volodymyr Piskun
Wan Xiankai
Warren Markham
Wei Lin
Wil Cooley
Expand Down
1 change: 1 addition & 0 deletions changelog/14716.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Explicit ``-c``/``--config-file`` paths now raise a clean usage error when the file is missing or uses an unsupported extension.
14 changes: 14 additions & 0 deletions src/_pytest/config/findpaths.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ConfigValue:
ConfigDict: TypeAlias = dict[str, ConfigValue]


SUPPORTED_CONFIG_FILE_EXTENSIONS = {".cfg", ".ini", ".toml"}


def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
"""Parse the given generic '.ini' file using legacy IniConfig parser, returning
the parsed object.
Expand Down Expand Up @@ -313,6 +316,17 @@ def determine_setup(

if inifile:
inipath_ = absolutepath(inifile)
if not inipath_.is_file():
raise UsageError(
f"Config file '{inipath_}' not found. "
"Check your '-c/--config-file' option."
)
if inipath_.suffix not in SUPPORTED_CONFIG_FILE_EXTENSIONS:
supported = ", ".join(sorted(SUPPORTED_CONFIG_FILE_EXTENSIONS))
raise UsageError(
f"Config file '{inipath_}' has unsupported extension "
f"{inipath_.suffix!r}. Supported extensions are: {supported}."
)
inipath: Path | None = inipath_
inicfg = load_config_dict_from_file(inipath_) or {}
if rootdir_cmd_arg is None:
Expand Down
66 changes: 66 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,40 @@ def test_absolute_win32_path(self, pytester: Pytester) -> None:
ret = pytest.main(["--config-file", temp_ini_file_norm])
assert ret == ExitCode.NO_TESTS_COLLECTED

@pytest.mark.parametrize(
("filename", "contents", "expected_error"),
[
pytest.param(
"custom.in",
"[pytest]",
"unsupported extension '.in'",
id="unsupported-extension",
),
pytest.param(
"missing.ini",
None,
"not found",
id="missing-file",
),
],
)
@pytest.mark.parametrize("option", ("-c", "--config-file"))
def test_explicitly_specified_invalid_config_file_errors(
self,
pytester: Pytester,
option: str,
filename: str,
contents: str | None,
expected_error: str,
) -> None:
if contents is not None:
pytester.path.joinpath(filename).write_text(contents, encoding="utf-8")

result = pytester.runpytest(option, filename)

assert result.ret == ExitCode.USAGE_ERROR
result.stderr.fnmatch_lines([f"ERROR: *{expected_error}*"])


class TestConfigAPI:
def test_config_trace(self, pytester: Pytester) -> None:
Expand Down Expand Up @@ -2058,6 +2092,38 @@ def test_with_specific_inifile(
assert inipath == p
assert ini_config["x"] == ConfigValue("10", origin="file", mode="ini")

@pytest.mark.parametrize(
("filename", "contents", "expected_error"),
[
pytest.param(
"config.in",
"[pytest]\nx=10",
"unsupported extension '.in'",
id="unsupported-extension",
),
pytest.param("missing.ini", None, "not found", id="missing-file"),
],
)
def test_with_invalid_specific_inifile(
self,
tmp_path: Path,
filename: str,
contents: str | None,
expected_error: str,
) -> None:
p = tmp_path / filename
if contents is not None:
p.write_text(contents, encoding="utf-8")

with pytest.raises(UsageError, match=re.escape(expected_error)):
determine_setup(
inifile=str(p),
override_ini=None,
args=[str(tmp_path)],
rootdir_cmd_arg=None,
invocation_dir=Path.cwd(),
)

def test_explicit_config_file_sets_rootdir(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
Expand Down