diff --git a/changelog/3768.bugfix.rst b/changelog/3768.bugfix.rst new file mode 100644 index 00000000000..b824853c0c8 --- /dev/null +++ b/changelog/3768.bugfix.rst @@ -0,0 +1 @@ +Fix test collection from packages mixed with normal directories. diff --git a/changelog/3789.bugfix.rst b/changelog/3789.bugfix.rst new file mode 100644 index 00000000000..b824853c0c8 --- /dev/null +++ b/changelog/3789.bugfix.rst @@ -0,0 +1 @@ +Fix test collection from packages mixed with normal directories. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 2657bff638a..6282c13cf40 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -216,18 +216,6 @@ def pytest_pycollect_makemodule(path, parent): return Module(path, parent) -def pytest_ignore_collect(path, config): - # Skip duplicate packages. - keepduplicates = config.getoption("keepduplicates") - if keepduplicates: - duplicate_paths = config.pluginmanager._duplicatepaths - if path.basename == "__init__.py": - if path in duplicate_paths: - return True - else: - duplicate_paths.add(path) - - @hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield @@ -554,9 +542,7 @@ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None): self.name = fspath.dirname self.trace = session.trace self._norecursepatterns = session._norecursepatterns - for path in list(session.config.pluginmanager._duplicatepaths): - if path.dirname == fspath.dirname and path != fspath: - session.config.pluginmanager._duplicatepaths.remove(path) + self.fspath = fspath def _recurse(self, path): ihook = self.gethookproxy(path.dirpath()) @@ -594,6 +580,15 @@ def isinitpath(self, path): return path in self.session._initialpaths def collect(self): + # XXX: HACK! + # Before starting to collect any files from this package we need + # to cleanup the duplicate paths added by the session's collect(). + # Proper fix is to not track these as duplicates in the first place. + for path in list(self.session.config.pluginmanager._duplicatepaths): + # if path.parts()[:len(self.fspath.dirpath().parts())] == self.fspath.dirpath().parts(): + if path.dirname.startswith(self.name): + self.session.config.pluginmanager._duplicatepaths.remove(path) + this_path = self.fspath.dirpath() pkg_prefix = None for path in this_path.visit(rec=self._recurse, bf=True, sort=True): diff --git a/testing/python/collect.py b/testing/python/collect.py index 907b368ebfd..c040cc09e68 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1583,3 +1583,43 @@ def test_package_collection_infinite_recursion(testdir): testdir.copy_example("collect/package_infinite_recursion") result = testdir.runpytest() result.stdout.fnmatch_lines("*1 passed*") + + +def test_package_with_modules(testdir): + """ + . + └── root + ├── __init__.py + ├── sub1 + │ ├── __init__.py + │ └── sub1_1 + │ ├── __init__.py + │ └── test_in_sub1.py + └── sub2 + └── test + └── test_in_sub2.py + + """ + root = testdir.mkpydir("root") + sub1 = root.mkdir("sub1") + sub1.ensure("__init__.py") + sub1_test = sub1.mkdir("sub1_1") + sub1_test.ensure("__init__.py") + sub2 = root.mkdir("sub2") + sub2_test = sub2.mkdir("sub2") + + sub1_test.join("test_in_sub1.py").write("def test_1(): pass") + sub2_test.join("test_in_sub2.py").write("def test_2(): pass") + + # Execute from . + result = testdir.runpytest("-v", "-s") + result.assert_outcomes(passed=2) + + # Execute from . with one argument "root" + result = testdir.runpytest("-v", "-s", "root") + result.assert_outcomes(passed=2) + + # Chdir into package's root and execute with no args + root.chdir() + result = testdir.runpytest("-v", "-s") + result.assert_outcomes(passed=2)