Skip to content

Commit 7cf2b51

Browse files
authored
Merge branch 'pytest-dev:main' into remove-eq-format
2 parents 2d7905b + dd609e1 commit 7cf2b51

File tree

4 files changed

+62
-9
lines changed

4 files changed

+62
-9
lines changed

src/_pytest/hookspec.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
from _pytest.nodes import Collector
3434
from _pytest.nodes import Item
3535
from _pytest.outcomes import Exit
36+
from _pytest.python import Class
3637
from _pytest.python import Function
3738
from _pytest.python import Metafunc
3839
from _pytest.python import Module
39-
from _pytest.python import PyCollector
4040
from _pytest.reports import CollectReport
4141
from _pytest.reports import TestReport
4242
from _pytest.runner import CallInfo
@@ -359,7 +359,7 @@ def pytest_pycollect_makemodule(
359359

360360
@hookspec(firstresult=True)
361361
def pytest_pycollect_makeitem(
362-
collector: "PyCollector", name: str, obj: object
362+
collector: Union["Module", "Class"], name: str, obj: object
363363
) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]:
364364
"""Return a custom item/collector for a Python object in a module, or None.
365365

src/_pytest/python.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
224224

225225

226226
@hookimpl(trylast=True)
227-
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
227+
def pytest_pycollect_makeitem(
228+
collector: Union["Module", "Class"], name: str, obj: object
229+
) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]:
230+
assert isinstance(collector, (Class, Module)), type(collector)
228231
# Nothing was collected elsewhere, let's do it here.
229232
if safe_isclass(obj):
230233
if collector.istestclass(obj, name):
231-
return Class.from_parent(collector, name=name, obj=obj)
234+
klass: Class = Class.from_parent(collector, name=name, obj=obj)
235+
return klass
232236
elif collector.istestfunction(obj, name):
233237
# mock seems to store unbound methods (issue473), normalize it.
234238
obj = getattr(obj, "__func__", obj)
@@ -247,15 +251,16 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
247251
)
248252
elif getattr(obj, "__test__", True):
249253
if is_generator(obj):
250-
res = Function.from_parent(collector, name=name)
254+
res: Function = Function.from_parent(collector, name=name)
251255
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
252256
name=name
253257
)
254258
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
255259
res.warn(PytestCollectionWarning(reason))
260+
return res
256261
else:
257-
res = list(collector._genfunctions(name, obj))
258-
return res
262+
return list(collector._genfunctions(name, obj))
263+
return None
259264

260265

261266
class PyobjMixin(nodes.Node):

src/_pytest/unittest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from _pytest.outcomes import xfail
2828
from _pytest.python import Class
2929
from _pytest.python import Function
30-
from _pytest.python import PyCollector
30+
from _pytest.python import Module
3131
from _pytest.runner import CallInfo
3232
from _pytest.scope import Scope
3333

@@ -42,7 +42,7 @@
4242

4343

4444
def pytest_pycollect_makeitem(
45-
collector: PyCollector, name: str, obj: object
45+
collector: Union[Module, Class], name: str, obj: object
4646
) -> Optional["UnitTestCase"]:
4747
# Has unittest been imported and is obj a subclass of its TestCase?
4848
try:

testing/test_session.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,54 @@ def pytest_sessionfinish():
335335
assert res.ret == ExitCode.NO_TESTS_COLLECTED
336336

337337

338+
def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None:
339+
"""Test that when multiple collection args are specified on the command line
340+
for the same module, only a single Module collector is created.
341+
342+
Regression test for #723, #3358.
343+
"""
344+
pytester.makepyfile(
345+
**{
346+
"d/test_it": """
347+
def test_1(): pass
348+
def test_2(): pass
349+
"""
350+
}
351+
)
352+
353+
result = pytester.runpytest(
354+
"--collect-only",
355+
"d/test_it.py::test_1",
356+
"d/test_it.py::test_2",
357+
)
358+
result.stdout.fnmatch_lines(
359+
[
360+
"<Module d/test_it.py>",
361+
" <Function test_1>",
362+
" <Function test_2>",
363+
],
364+
consecutive=True,
365+
)
366+
367+
# Different, but related case.
368+
result = pytester.runpytest(
369+
"--collect-only",
370+
"--keep-duplicates",
371+
"d",
372+
"d",
373+
)
374+
result.stdout.fnmatch_lines(
375+
[
376+
"<Module d/test_it.py>",
377+
" <Function test_1>",
378+
" <Function test_2>",
379+
" <Function test_1>",
380+
" <Function test_2>",
381+
],
382+
consecutive=True,
383+
)
384+
385+
338386
@pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"])
339387
def test_rootdir_option_arg(
340388
pytester: Pytester, monkeypatch: MonkeyPatch, path: str

0 commit comments

Comments
 (0)