Skip to content

Commit f68a512

Browse files
JoeScRKrahl
authored andcommitted
Support Different Scopes for Dependencies
By using different scope="" arguments in the dependency mark the user can depend on tests in a different module. Or by using scope="session, depends="all" a test can be skipped if ANY previous test has failed. When depending on a test outside the module its full name must be used i.e. tests/test_a.py::test_a
1 parent 5e97c07 commit f68a512

File tree

2 files changed

+264
-7
lines changed

2 files changed

+264
-7
lines changed

pytest_dependency.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,29 @@ class DependencyManager(object):
4747
"""Dependency manager, stores the results of tests.
4848
"""
4949

50-
ScopeCls = {'module':pytest.Module, 'session':pytest.Session}
50+
ScopeCls = {'class':pytest.Class, 'module':pytest.Module, 'session':pytest.Session}
5151

5252
@classmethod
5353
def getManager(cls, item, scope='module'):
5454
"""Get the DependencyManager object from the node at scope level.
5555
Create it, if not yet present.
5656
"""
5757
node = item.getparent(cls.ScopeCls[scope])
58+
if not node:
59+
return None
5860
if not hasattr(node, 'dependencyManager'):
59-
node.dependencyManager = cls()
61+
node.dependencyManager = cls(scope)
6062
return node.dependencyManager
6163

62-
def __init__(self):
64+
def __init__(self, scope):
6365
self.results = {}
66+
self.scope = scope
6467

6568
def addResult(self, item, name, rep):
6669
if not name:
67-
if item.cls:
70+
if self.scope == "session":
71+
name = item.nodeid.replace("::()::", "::")
72+
elif item.cls and self.scope == "module":
6873
name = "%s::%s" % (item.cls.__name__, item.name)
6974
else:
7075
name = item.name
@@ -132,8 +137,11 @@ def pytest_runtest_makereport(item, call):
132137
if marker is not None or _automark:
133138
rep = outcome.get_result()
134139
name = marker.kwargs.get('name') if marker is not None else None
135-
manager = DependencyManager.getManager(item)
136-
manager.addResult(item, name, rep)
140+
""" Store the test outcome for each scope if it exists"""
141+
for scope in DependencyManager.ScopeCls:
142+
manager = DependencyManager.getManager(item, scope=scope)
143+
if(manager):
144+
manager.addResult(item, name, rep)
137145

138146

139147
def pytest_runtest_setup(item):
@@ -144,5 +152,6 @@ def pytest_runtest_setup(item):
144152
if marker is not None:
145153
depends = marker.kwargs.get('depends')
146154
if depends:
147-
manager = DependencyManager.getManager(item)
155+
scope = marker.kwargs.get('scope', 'module') if marker is not None else 'module'
156+
manager = DependencyManager.getManager(item, scope=scope)
148157
manager.checkDepend(depends, item)

tests/test_06_scope.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
2+
3+
4+
def test_class_scope(ctestdir):
5+
""" test_a fails, however the scope of the dependency of TestClass::test_b
6+
causes it to only depend on other tests within its class. test_d failing
7+
shows that the class scope is actually detecting failures within its own
8+
class. test_f should skip since test_c within the depends on test_c
9+
within the class failed """
10+
ctestdir.makepyfile("""
11+
import pytest
12+
13+
@pytest.mark.dependency()
14+
def test_a():
15+
assert False
16+
17+
class TestClass(object):
18+
19+
@pytest.mark.dependency()
20+
def test_a(self):
21+
pass
22+
23+
@pytest.mark.dependency(scope="class", depends="all")
24+
def test_b(self):
25+
pass
26+
27+
@pytest.mark.dependency()
28+
def test_c(self):
29+
assert False
30+
31+
@pytest.mark.dependency(scope="class", depends="all")
32+
def test_d(self):
33+
pass
34+
35+
@pytest.mark.dependency()
36+
def test_e():
37+
pass
38+
39+
@pytest.mark.dependency(depends=["TestClass::test_c"])
40+
def test_f():
41+
pass
42+
43+
""")
44+
45+
result = ctestdir.runpytest("--verbose", "test_class_scope.py")
46+
result.assert_outcomes(passed=3, skipped=2, failed=2)
47+
result.stdout.fnmatch_lines("""
48+
*::test_a FAILED
49+
*::TestClass::test_a PASSED
50+
*::TestClass::test_b PASSED
51+
*::TestClass::test_c FAILED
52+
*::TestClass::test_d SKIPPED
53+
*::test_e PASSED
54+
*::test_f SKIPPED
55+
""")
56+
57+
58+
def test_session_scope(ctestdir):
59+
""" test_1 is marked dependent on test_a in the test_a.py file, that test
60+
failed and causes test_1 to be skipped. Also test using session scope to
61+
select a test dependency within the same module"""
62+
test_file1 = """
63+
import pytest
64+
65+
@pytest.mark.dependency()
66+
def test_a():
67+
assert False
68+
69+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_a"])
70+
def test_b():
71+
pass
72+
"""
73+
74+
test_file2 = """
75+
import pytest
76+
77+
@pytest.mark.dependency()
78+
def test_0():
79+
pass
80+
81+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_a"])
82+
def test_1():
83+
pass
84+
"""
85+
86+
ctestdir.makepyfile(test_a=test_file1, test_b=test_file2)
87+
88+
result = ctestdir.runpytest("--verbose", "test_a.py", "test_b.py")
89+
result.assert_outcomes(passed=1, skipped=2, failed=1)
90+
result.stdout.fnmatch_lines("""
91+
*::test_a FAILED
92+
*::test_b SKIPPED
93+
*::test_0 PASSED
94+
*::test_1 SKIPPED
95+
""")
96+
97+
98+
def test_session_scope_three_files(ctestdir):
99+
""" Test using 3 files with the third file depending on functions from either
100+
the first or second file """
101+
test_file1 = """
102+
import pytest
103+
104+
@pytest.mark.dependency()
105+
def test_fail():
106+
assert False
107+
108+
@pytest.mark.dependency()
109+
def test_pass():
110+
pass
111+
"""
112+
113+
test_file2 = """
114+
import pytest
115+
116+
@pytest.mark.dependency()
117+
def test_pass():
118+
pass
119+
120+
@pytest.mark.dependency()
121+
def test_fail():
122+
assert False
123+
"""
124+
125+
test_file3 = """
126+
import pytest
127+
128+
@pytest.mark.dependency()
129+
def test_0():
130+
pass
131+
132+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_fail", "test_b.py::test_pass"])
133+
def test_1():
134+
pass
135+
136+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_pass", "test_b.py::test_fail"])
137+
def test_2():
138+
pass
139+
140+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_pass", "test_b.py::test_pass"])
141+
def test_3():
142+
pass
143+
"""
144+
145+
ctestdir.makepyfile(test_a=test_file1, test_b=test_file2, test_c=test_file3)
146+
147+
result = ctestdir.runpytest("--verbose", "test_a.py", "test_b.py", "test_c.py")
148+
result.assert_outcomes(passed=4, skipped=2, failed=2)
149+
result.stdout.fnmatch_lines("""
150+
test_a.py::test_fail FAILED
151+
test_a.py::test_pass PASSED
152+
test_b.py::test_pass PASSED
153+
test_b.py::test_fail FAILED
154+
*::test_0 PASSED
155+
*::test_1 SKIPPED
156+
*::test_2 SKIPPED
157+
*::test_3 PASSED
158+
""")
159+
160+
161+
162+
163+
def test_complex_scope(ctestdir):
164+
""" A complex test of scope utilizing module, class and session scopes. Also utilizing the
165+
depends="all" modifier """
166+
test_file1 = """
167+
import pytest
168+
169+
@pytest.mark.dependency()
170+
def test_a():
171+
assert False
172+
173+
@pytest.mark.dependency()
174+
def test_b():
175+
pass
176+
177+
@pytest.mark.dependency(depends="all")
178+
def test_c():
179+
pass
180+
181+
@pytest.mark.dependency(depends=["test_c"])
182+
def test_d():
183+
pass
184+
185+
class TestFile1():
186+
@pytest.mark.dependency()
187+
def test_0(self):
188+
pass
189+
"""
190+
191+
test_file2 = """
192+
import pytest
193+
194+
def test_v():
195+
pass
196+
197+
@pytest.mark.dependency(scope="session", depends="all")
198+
def test_w():
199+
pass
200+
201+
@pytest.mark.dependency(scope="session", depends=["test_a.py::TestFile1::test_0"])
202+
def test_x():
203+
pass
204+
205+
class TestFile2():
206+
def test_0(self):
207+
pass
208+
209+
@pytest.mark.dependency(scope="class", depends="all")
210+
def test_1(self):
211+
pass
212+
213+
@pytest.mark.dependency(scope="module", depends="all")
214+
def test_2(self):
215+
pass
216+
217+
@pytest.mark.dependency(scope="session", depends="all")
218+
def test_3(self):
219+
pass
220+
221+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_b"])
222+
def test_y():
223+
pass
224+
225+
@pytest.mark.dependency(scope="session", depends=["test_a.py::test_a"])
226+
def test_z():
227+
pass
228+
"""
229+
ctestdir.makepyfile(test_a=test_file1, test_b=test_file2)
230+
231+
result = ctestdir.runpytest("--verbose", "test_a.py", "test_b.py")
232+
result.assert_outcomes(passed=7, skipped=6, failed=1)
233+
result.stdout.fnmatch_lines("""
234+
*::test_a FAILED
235+
*::test_b PASSED
236+
*::test_c SKIPPED
237+
*::test_d SKIPPED
238+
*::TestFile1::test_0 PASSED
239+
*::test_v PASSED
240+
*::test_w SKIPPED
241+
*::test_x PASSED
242+
*::TestFile2::test_0 PASSED
243+
*::TestFile2::test_1 PASSED
244+
*::TestFile2::test_2 SKIPPED
245+
*::TestFile2::test_3 SKIPPED
246+
*::test_y PASSED
247+
*::test_z SKIPPED
248+
""")

0 commit comments

Comments
 (0)