diff --git a/changelog/7124.bugfix.rst b/changelog/7124.bugfix.rst new file mode 100644 index 000000000..191925d7a --- /dev/null +++ b/changelog/7124.bugfix.rst @@ -0,0 +1 @@ +Fixed an issue where ``__main__.py`` would raise an ``ImportError`` when ``--doctest-modules`` was provided. diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 870920f5a..f799f5f9c 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -125,7 +125,9 @@ def pytest_collect_file( ) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config if fspath.suffix == ".py": - if config.option.doctestmodules and not _is_setup_py(fspath): + if config.option.doctestmodules and not any( + (_is_setup_py(fspath), _is_main_py(fspath)) + ): mod: DoctestModule = DoctestModule.from_parent(parent, path=fspath) return mod elif _is_doctest(config, fspath, parent): @@ -148,6 +150,10 @@ def _is_doctest(config: Config, path: Path, parent: Collector) -> bool: return any(fnmatch_ex(glob, path) for glob in globs) +def _is_main_py(path: Path) -> bool: + return path.name == "__main__.py" + + class ReprFailDoctest(TerminalRepr): def __init__( self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] diff --git a/testing/example_scripts/__init__.py b/testing/example_scripts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/doctest/main_py/__main__.py b/testing/example_scripts/doctest/main_py/__main__.py new file mode 100644 index 000000000..e471d06d6 --- /dev/null +++ b/testing/example_scripts/doctest/main_py/__main__.py @@ -0,0 +1,2 @@ +def test_this_is_ignored(): + assert True diff --git a/testing/example_scripts/doctest/main_py/test_normal_module.py b/testing/example_scripts/doctest/main_py/test_normal_module.py new file mode 100644 index 000000000..700cc9750 --- /dev/null +++ b/testing/example_scripts/doctest/main_py/test_normal_module.py @@ -0,0 +1,6 @@ +def test_doc(): + """ + >>> 10 > 5 + True + """ + assert False diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 1d33e7378..47b3d6217 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -6,6 +6,7 @@ from typing import Optional import pytest from _pytest.doctest import _get_checker +from _pytest.doctest import _is_main_py from _pytest.doctest import _is_mocked from _pytest.doctest import _is_setup_py from _pytest.doctest import _patch_unwrap_mock_aware @@ -811,6 +812,11 @@ class TestDoctests: result = pytester.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines(["*collected 0 items*"]) + def test_main_py_does_not_cause_import_errors(self, pytester: Pytester): + p = pytester.copy_example("doctest/main_py") + result = pytester.runpytest(p, "--doctest-modules") + result.stdout.fnmatch_lines(["*collected 2 items*", "*1 failed, 1 passed*"]) + def test_invalid_setup_py(self, pytester: Pytester): """ Test to make sure that pytest reads setup.py files that are not used @@ -1518,3 +1524,11 @@ def test_is_setup_py_different_encoding(tmp_path: Path, mod: str) -> None: ) setup_py.write_bytes(contents.encode("cp1252")) assert _is_setup_py(setup_py) + + +@pytest.mark.parametrize( + "name, expected", [("__main__.py", True), ("__init__.py", False)] +) +def test_is_main_py(tmp_path: Path, name: str, expected: bool) -> None: + dunder_main = tmp_path.joinpath(name) + assert _is_main_py(dunder_main) == expected