Skip to content

Commit 5fc3e35

Browse files
authored
Merge pull request #9144 from bluetech/py36_order_by_definition
py36+ tests are definition ordered [v2]
1 parent d8831c6 commit 5fc3e35

File tree

3 files changed

+47
-10
lines changed

3 files changed

+47
-10
lines changed

changelog/5196.feature.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Tests are now ordered by definition order in more cases.
2+
3+
In a class hierarchy, tests from base classes are now consistently ordered before tests defined on their subclasses (reverse MRO order).

src/_pytest/python.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,18 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
407407
if not getattr(self.obj, "__test__", True):
408408
return []
409409

410-
# NB. we avoid random getattrs and peek in the __dict__ instead
411-
# (XXX originally introduced from a PyPy need, still true?)
410+
# Avoid random getattrs and peek in the __dict__ instead.
412411
dicts = [getattr(self.obj, "__dict__", {})]
413412
for basecls in self.obj.__class__.__mro__:
414413
dicts.append(basecls.__dict__)
414+
415+
# In each class, nodes should be definition ordered. Since Python 3.6,
416+
# __dict__ is definition ordered.
415417
seen: Set[str] = set()
416-
values: List[Union[nodes.Item, nodes.Collector]] = []
418+
dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = []
417419
ihook = self.ihook
418420
for dic in dicts:
421+
values: List[Union[nodes.Item, nodes.Collector]] = []
419422
# Note: seems like the dict can change during iteration -
420423
# be careful not to remove the list() without consideration.
421424
for name, obj in list(dic.items()):
@@ -433,13 +436,14 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
433436
values.extend(res)
434437
else:
435438
values.append(res)
436-
437-
def sort_key(item):
438-
fspath, lineno, _ = item.reportinfo()
439-
return (str(fspath), lineno)
440-
441-
values.sort(key=sort_key)
442-
return values
439+
dict_values.append(values)
440+
441+
# Between classes in the class hierarchy, reverse-MRO order -- nodes
442+
# inherited from base classes should come before subclasses.
443+
result = []
444+
for values in reversed(dict_values):
445+
result.extend(values)
446+
return result
443447

444448
def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
445449
modulecol = self.getparent(Module)

testing/python/collect.py

+30
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,36 @@ def test_a(y):
770770
assert len(colitems) == 2
771771
assert [item.name for item in colitems] == ["test_b", "test_a"]
772772

773+
def test_ordered_by_definition_order(self, pytester: Pytester) -> None:
774+
pytester.makepyfile(
775+
"""\
776+
class Test1:
777+
def test_foo(): pass
778+
def test_bar(): pass
779+
class Test2:
780+
def test_foo(): pass
781+
test_bar = Test1.test_bar
782+
class Test3(Test2):
783+
def test_baz(): pass
784+
"""
785+
)
786+
result = pytester.runpytest("--collect-only")
787+
result.stdout.fnmatch_lines(
788+
[
789+
"*Class Test1*",
790+
"*Function test_foo*",
791+
"*Function test_bar*",
792+
"*Class Test2*",
793+
# previously the order was flipped due to Test1.test_bar reference
794+
"*Function test_foo*",
795+
"*Function test_bar*",
796+
"*Class Test3*",
797+
"*Function test_foo*",
798+
"*Function test_bar*",
799+
"*Function test_baz*",
800+
]
801+
)
802+
773803

774804
class TestConftestCustomization:
775805
def test_pytest_pycollect_module(self, pytester: Pytester) -> None:

0 commit comments

Comments
 (0)