Skip to content

Commit b5bb06b

Browse files
committed
Handle dependency markers with the same name
- the same name may be used in different modules
1 parent 0ae920f commit b5bb06b

3 files changed

Lines changed: 92 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# pytest-order Release Notes
22

3+
## Fixes
4+
- handle dependency markers with the same alias name (see [#71](https://github.com/pytest-dev/pytest-order/issues/71))
5+
36
## Infrastructure
47
- avoid unknown marker warning in tests (see [#101](https://github.com/pytest-dev/pytest-order/issues/101))
58
- added pytest 8 to CI tests

pytest_order/sorter.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import re
12
import sys
2-
from warnings import warn
3+
from collections import OrderedDict
34
from contextlib import suppress
45
from typing import Optional, List, Dict, Tuple, cast
6+
from warnings import warn
57

68
from _pytest.config import Config
79
from _pytest.mark import Mark
@@ -10,20 +12,6 @@
1012
from .item import Item, ItemList, ItemGroup, filter_marks, move_item, RelativeMark
1113
from .settings import Settings, Scope
1214

13-
try:
14-
from typing import OrderedDict
15-
except ImportError:
16-
# In Python <3.7.2, we need to stub it
17-
from collections import OrderedDict as OrderedDict_cls
18-
from typing import MutableMapping, TypeVar
19-
20-
KT = TypeVar("KT")
21-
VT = TypeVar("VT")
22-
23-
class OrderedDict(OrderedDict_cls, MutableMapping[KT, VT]): # type: ignore
24-
pass
25-
26-
2715
orders_map = {
2816
"first": 0,
2917
"second": 1,
@@ -112,7 +100,7 @@ def mark_binning(
112100
self,
113101
item: Item,
114102
dep_marks: Dict[Tuple[str, Scope, str], List[Item]],
115-
aliases: Dict[str, Item],
103+
aliases: Dict[str, List[Item]],
116104
) -> None:
117105
"""
118106
Collect relevant markers for the given item.
@@ -138,7 +126,7 @@ def handle_dependency_mark(
138126
item: Item,
139127
has_order: bool,
140128
dep_marks: Dict[Tuple[str, Scope, str], List[Item]],
141-
aliases: Dict[str, Item],
129+
aliases: Dict[str, List[Item]],
142130
) -> None:
143131
# always order dependencies if an order mark is present
144132
# otherwise only if order-dependencies is set
@@ -159,7 +147,7 @@ def handle_dependency_mark(
159147
# of the nodeid, depending on the scope
160148
if not name_mark:
161149
name_mark = item.node_id
162-
aliases[name_mark] = item
150+
aliases.setdefault(name_mark, []).append(item)
163151

164152
def handle_order_marks(self, item: Item) -> None:
165153
marks = item.item.iter_markers("order")
@@ -289,7 +277,7 @@ def warn_about_unknown_test(item: Item, rel_mark: str) -> None:
289277
)
290278

291279
def collect_markers(self) -> None:
292-
aliases: Dict[str, Item] = {}
280+
aliases: Dict[str, List[Item]] = {}
293281
dep_marks: Dict[Tuple[str, Scope, str], List[Item]] = {}
294282
for item in self.items:
295283
self.mark_binning(item, dep_marks, aliases)
@@ -298,27 +286,49 @@ def collect_markers(self) -> None:
298286
def resolve_dependency_markers(
299287
self,
300288
dep_marks: Dict[Tuple[str, Scope, str], List[Item]],
301-
aliases: Dict[str, Item],
289+
aliases: Dict[str, List[Item]],
302290
) -> None:
303291
for (name, _, prefix), items in dep_marks.items():
304292
if name in aliases:
305293
for item in items:
306-
self.dep_marks.append(
307-
RelativeMark(aliases[name], item, move_after=True)
308-
)
294+
alias = self.matching_alias(aliases[name], item)
295+
self.dep_marks.append(RelativeMark(alias, item, move_after=True))
309296
else:
310297
label = "::".join((prefix, name))
311298
if label in aliases:
312299
for item in items:
300+
alias = self.matching_alias(aliases[label], item)
313301
self.dep_marks.append(
314-
RelativeMark(aliases[label], item, move_after=True)
302+
RelativeMark(alias, item, move_after=True)
315303
)
316304
else:
317305
sys.stdout.write(
318306
"\nWARNING: Cannot resolve the dependency marker '{}' "
319307
"- ignoring it.".format(name)
320308
)
321309

310+
@staticmethod
311+
def matching_alias(aliases: List[Item], item: Item) -> Item:
312+
if len(aliases) == 1:
313+
return aliases[0]
314+
315+
# handle the rare case that several tests have the same alias name
316+
# we use the item that best matches the node id of the dependent item
317+
max_matching_parts = 0
318+
node_id_parts = re.split("(::|/)", item.node_id)
319+
matching_item = aliases[0]
320+
for alias_item in aliases:
321+
alias_node_id_parts = re.split("(::|/)", alias_item.node_id)
322+
nr_matching_parts = 0
323+
for n, a in zip(node_id_parts, alias_node_id_parts):
324+
if n != a:
325+
break
326+
nr_matching_parts += 1
327+
if nr_matching_parts > max_matching_parts:
328+
max_matching_parts = nr_matching_parts
329+
matching_item = alias_item
330+
return matching_item
331+
322332

323333
def module_item_groups(items: List[Item]) -> Dict[str, List[Item]]:
324334
"""

tests/test_dependency.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,61 @@ def test_two():
399399
)
400400

401401

402+
def test_equally_named_dependency_in_modules(test_path):
403+
# regression test for #71
404+
test_path.makepyfile(
405+
test_ndep1=(
406+
"""
407+
import pytest
408+
409+
class TestOne:
410+
411+
@pytest.mark.dependency(name="one", depends=["zero"])
412+
def test_one(self):
413+
assert True
414+
415+
@pytest.mark.dependency(name="two", depends=["one"])
416+
def test_two(self):
417+
assert True
418+
419+
@pytest.mark.dependency(name="three", depends=["two"])
420+
def test_three(self):
421+
assert True
422+
423+
@pytest.mark.dependency(name="zero")
424+
def test_zero(self):
425+
assert True
426+
"""
427+
),
428+
test_ndep2=(
429+
"""
430+
import pytest
431+
432+
class TestTwo:
433+
434+
@pytest.mark.dependency(name="one", depends=["zero"])
435+
def test_one(self):
436+
assert True
437+
438+
@pytest.mark.dependency(name="two", depends=["one"])
439+
def test_two(self):
440+
assert True
441+
442+
@pytest.mark.dependency(name="three", depends=["two"])
443+
def test_three(self):
444+
assert True
445+
446+
@pytest.mark.dependency(name="zero")
447+
def test_zero(self):
448+
assert True
449+
"""
450+
),
451+
)
452+
453+
result = test_path.runpytest("-v", "--order-dependencies")
454+
result.assert_outcomes(passed=8, failed=0, skipped=0)
455+
456+
402457
@pytest.mark.skipif(
403458
pytest.__version__.startswith("3.7."),
404459
reason="pytest-dependency < 0.5 does not support session scope",

0 commit comments

Comments
 (0)