diff --git a/changelog/13913.bugfix.rst b/changelog/13913.bugfix.rst new file mode 100644 index 00000000000..a538c6518f9 --- /dev/null +++ b/changelog/13913.bugfix.rst @@ -0,0 +1 @@ +Fixed a regression where a conftest.py that registers command line options was sometimes not loaded as an initial conftest. This happened when a command line option's value was the path of an existing file (e.g. ``pytest --write-idents idents.txt``): due to intermixed argument parsing the value ended up among the positional arguments during pre-parsing and was treated as a test path, which suppressed the conftest discovery and made the option report as "unrecognized". diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 96b3dd6a86a..7ff2fc0b22a 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -650,10 +650,17 @@ def _set_initial_conftests( if not safe_exists(anchor): continue + # With intermixed argument parsing, the path *value* of a command + # line option (e.g. ``--write-idents idents.txt``) can end up among + # the positional arguments. Such a file anchor must not prevent the + # conftest discovery for its directory (and its ``test*`` + # sub-directories), otherwise conftests that register the option are + # not loaded as initial conftests and the option becomes + # "unrecognized" (#13913). Resolve file anchors to their directory. + anchor = self._get_directory(anchor) anchors.append(anchor) # Let's also consider test* subdirs. - if anchor.is_dir(): - anchors.extend(x for x in anchor.glob("test*") if x.is_dir()) + anchors.extend(x for x in anchor.glob("test*") if x.is_dir()) if not anchors: anchors.append(invocation_dir) anchors.extend(x for x in invocation_dir.glob("test*") if x.is_dir()) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 23a0db81c39..47d8baeeee6 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -462,6 +462,36 @@ def test_it(request): result.assert_outcomes(passed=1) +def test_initial_conftest_loaded_when_option_value_is_existing_file( + pytester: Pytester, +) -> None: + """An initial conftest registering a command line option is still loaded + when the option's value happens to be the path of an existing file. + + With intermixed argument parsing, such a value ends up among the + positional arguments during pre-parsing and used to be treated as a test + path anchor, which suppressed the ``test*`` conftest discovery and left + the option "unrecognized". Regression test for #13913. + """ + pytester.makepyfile( + **{ + "tests/conftest.py": """ + def pytest_addoption(parser): + parser.addoption("--write-idents", action="store", default=None) + """, + "test_it.py": """ + def test_it(request): + assert request.config.getoption("--write-idents") == "idents.txt" + """, + } + ) + # The option value is a path to an existing file on disk. + pytester.makefile(".txt", idents="") + result = pytester.runpytest("--write-idents", "idents.txt") + assert result.ret == ExitCode.OK + result.assert_outcomes(passed=1) + + def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: ct1 = pytester.makeconftest("") sub = pytester.mkdir("sub")