summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog/4310.bugfix.rst1
-rw-r--r--src/_pytest/main.py29
-rw-r--r--src/_pytest/python.py15
-rw-r--r--testing/test_collection.py32
4 files changed, 54 insertions, 23 deletions
diff --git a/changelog/4310.bugfix.rst b/changelog/4310.bugfix.rst
new file mode 100644
index 000000000..24e177c2e
--- /dev/null
+++ b/changelog/4310.bugfix.rst
@@ -0,0 +1 @@
+Fix duplicate collection due to multiple args matching the same packages.
diff --git a/src/_pytest/main.py b/src/_pytest/main.py
index 3c908ec4c..46228f824 100644
--- a/src/_pytest/main.py
+++ b/src/_pytest/main.py
@@ -387,6 +387,7 @@ class Session(nodes.FSCollector):
self._initialpaths = frozenset()
# Keep track of any collected nodes in here, so we don't duplicate fixtures
self._node_cache = {}
+ self._collect_seen_pkgdirs = set()
self.config.pluginmanager.register(self, name="session")
@@ -496,18 +497,19 @@ class Session(nodes.FSCollector):
# and stack all Packages found on the way.
# No point in finding packages when collecting doctests
if not self.config.option.doctestmodules:
+ pm = self.config.pluginmanager
for parent in argpath.parts():
- pm = self.config.pluginmanager
if pm._confcutdir and pm._confcutdir.relto(parent):
continue
if parent.isdir():
pkginit = parent.join("__init__.py")
if pkginit.isfile():
+ self._collect_seen_pkgdirs.add(parent)
if pkginit in self._node_cache:
root = self._node_cache[pkginit][0]
else:
- col = root._collectfile(pkginit)
+ col = root._collectfile(pkginit, handle_dupes=False)
if col:
if isinstance(col[0], Package):
root = col[0]
@@ -529,13 +531,12 @@ class Session(nodes.FSCollector):
def filter_(f):
return f.check(file=1)
- seen_dirs = set()
for path in argpath.visit(
fil=filter_, rec=self._recurse, bf=True, sort=True
):
dirpath = path.dirpath()
- if dirpath not in seen_dirs:
- seen_dirs.add(dirpath)
+ if dirpath not in self._collect_seen_pkgdirs:
+ self._collect_seen_pkgdirs.add(dirpath)
pkginit = dirpath.join("__init__.py")
if pkginit.exists() and parts(pkginit.strpath).isdisjoint(paths):
for x in root._collectfile(pkginit):
@@ -570,20 +571,20 @@ class Session(nodes.FSCollector):
for y in m:
yield y
- def _collectfile(self, path):
+ def _collectfile(self, path, handle_dupes=True):
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
return ()
- # Skip duplicate paths.
- keepduplicates = self.config.getoption("keepduplicates")
- if not keepduplicates:
- duplicate_paths = self.config.pluginmanager._duplicatepaths
- if path in duplicate_paths:
- return ()
- else:
- duplicate_paths.add(path)
+ if handle_dupes:
+ keepduplicates = self.config.getoption("keepduplicates")
+ if not keepduplicates:
+ duplicate_paths = self.config.pluginmanager._duplicatepaths
+ if path in duplicate_paths:
+ return ()
+ else:
+ duplicate_paths.add(path)
return ihook.pytest_collect_file(path=path, parent=self)
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index 03a9fe031..d360e2c8f 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -545,11 +545,24 @@ class Package(Module):
proxy = self.config.hook
return proxy
- def _collectfile(self, path):
+ def _collectfile(self, path, handle_dupes=True):
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
return ()
+
+ if handle_dupes:
+ keepduplicates = self.config.getoption("keepduplicates")
+ if not keepduplicates:
+ duplicate_paths = self.config.pluginmanager._duplicatepaths
+ if path in duplicate_paths:
+ return ()
+ else:
+ duplicate_paths.add(path)
+
+ if self.fspath == path: # __init__.py
+ return [self]
+
return ihook.pytest_collect_file(path=path, parent=self)
def isinitpath(self, path):
diff --git a/testing/test_collection.py b/testing/test_collection.py
index 3860cf9f9..dd8ecb1af 100644
--- a/testing/test_collection.py
+++ b/testing/test_collection.py
@@ -951,19 +951,35 @@ def test_collect_init_tests(testdir):
result = testdir.runpytest(p, "--collect-only")
result.stdout.fnmatch_lines(
[
- "*<Module '__init__.py'>",
- "*<Function 'test_init'>",
- "*<Module 'test_foo.py'>",
- "*<Function 'test_foo'>",
+ "collected 2 items",
+ "<Package *",
+ " <Module '__init__.py'>",
+ " <Function 'test_init'>",
+ " <Module 'test_foo.py'>",
+ " <Function 'test_foo'>",
]
)
result = testdir.runpytest("./tests", "--collect-only")
result.stdout.fnmatch_lines(
[
- "*<Module '__init__.py'>",
- "*<Function 'test_init'>",
- "*<Module 'test_foo.py'>",
- "*<Function 'test_foo'>",
+ "collected 2 items",
+ "<Package *",
+ " <Module '__init__.py'>",
+ " <Function 'test_init'>",
+ " <Module 'test_foo.py'>",
+ " <Function 'test_foo'>",
+ ]
+ )
+ # Ignores duplicates with "." and pkginit (#4310).
+ result = testdir.runpytest("./tests", ".", "--collect-only")
+ result.stdout.fnmatch_lines(
+ [
+ "collected 2 items",
+ "<Package *",
+ " <Module '__init__.py'>",
+ " <Function 'test_init'>",
+ " <Module 'test_foo.py'>",
+ " <Function 'test_foo'>",
]
)
result = testdir.runpytest("./tests/test_foo.py", "--collect-only")