diff --git a/changelog/3773.bugfix.rst b/changelog/3773.bugfix.rst new file mode 100644 index 000000000..427f6c274 --- /dev/null +++ b/changelog/3773.bugfix.rst @@ -0,0 +1 @@ +Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 2372ea663..4ba428cd8 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -672,7 +672,9 @@ class Testdir(object): example_path.copy(result) return result else: - raise LookupError("example is not found as a file or directory") + raise LookupError( + 'example "{}" is not found as a file or directory'.format(example_path) + ) Session = Session diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 7fb7ff9ef..977b07442 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -201,15 +201,19 @@ def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": if not parent.session.isinitpath(path): - for pat in parent.config.getini("python_files") + ["__init__.py"]: - if path.fnmatch(pat): - break - else: + if not path_matches_patterns( + path, parent.config.getini("python_files") + ["__init__.py"] + ): return ihook = parent.session.gethookproxy(path) return ihook.pytest_pycollect_makemodule(path=path, parent=parent) +def path_matches_patterns(path, patterns): + """Returns True if the given py.path.local matches one of the patterns in the list of globs given""" + return any(path.fnmatch(pattern) for pattern in patterns) + + def pytest_pycollect_makemodule(path, parent): if path.basename == "__init__.py": return Package(path, parent) @@ -590,6 +594,11 @@ class Package(Module): self.session.config.pluginmanager._duplicatepaths.remove(path) this_path = self.fspath.dirpath() + init_module = this_path.join("__init__.py") + if init_module.check(file=1) and path_matches_patterns( + init_module, self.config.getini("python_files") + ): + yield Module(init_module, self) pkg_prefixes = set() for path in this_path.visit(rec=self._recurse, bf=True, sort=True): # we will visit our own __init__.py file, in which case we skip it diff --git a/testing/example_scripts/collect/collect_init_tests/pytest.ini b/testing/example_scripts/collect/collect_init_tests/pytest.ini new file mode 100644 index 000000000..7c4795540 --- /dev/null +++ b/testing/example_scripts/collect/collect_init_tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = *.py diff --git a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py new file mode 100644 index 000000000..9cd366295 --- /dev/null +++ b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -0,0 +1,2 @@ +def test_init(): + pass diff --git a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py new file mode 100644 index 000000000..8f2d73cfa --- /dev/null +++ b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -0,0 +1,2 @@ +def test_foo(): + pass diff --git a/testing/test_collection.py b/testing/test_collection.py index ce0e3a920..fb3860f99 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -938,3 +938,17 @@ def test_fixture_scope_sibling_conftests(testdir): "*1 passed, 1 error*", ] ) + + +def test_collect_init_tests(testdir): + """Check that we collect files from __init__.py files when they patch the 'python_files' (#3773)""" + p = testdir.copy_example("collect/collect_init_tests") + result = testdir.runpytest(p, "--collect-only") + result.stdout.fnmatch_lines( + [ + "*", + "*", + "*", + "*", + ] + )