zope.pytestlayer 8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zope/pytestlayer/__init__.py +0 -0
- zope/pytestlayer/_compat.py +18 -0
- zope/pytestlayer/doctest.py +31 -0
- zope/pytestlayer/fixture.py +234 -0
- zope/pytestlayer/layered.py +100 -0
- zope/pytestlayer/plugin.py +122 -0
- zope/pytestlayer/testing.py +6 -0
- zope/pytestlayer/tests/__init__.py +0 -0
- zope/pytestlayer/tests/conftest.py +3 -0
- zope/pytestlayer/tests/fixture/bad_layer/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/bad_layer/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/bad_layer/test_core.py +13 -0
- zope/pytestlayer/tests/fixture/custom_fixture_name/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/custom_fixture_name/conftest.py +12 -0
- zope/pytestlayer/tests/fixture/custom_fixture_name/test_core.py +38 -0
- zope/pytestlayer/tests/fixture/keep_layer_across_test_classes/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/keep_layer_across_test_classes/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/keep_layer_across_test_classes/test_core.py +101 -0
- zope/pytestlayer/tests/fixture/layers_with_same_name/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/layers_with_same_name/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/layers_with_same_name/test_core.py +40 -0
- zope/pytestlayer/tests/fixture/no_setup_or_teardown/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/no_setup_or_teardown/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/no_setup_or_teardown/test_core.py +14 -0
- zope/pytestlayer/tests/fixture/order_by_layer/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/order_by_layer/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/order_by_layer/test_core.py +112 -0
- zope/pytestlayer/tests/fixture/order_with_layered_suite/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/order_with_layered_suite/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/order_with_layered_suite/foo.txt +14 -0
- zope/pytestlayer/tests/fixture/order_with_layered_suite/foobar.txt +14 -0
- zope/pytestlayer/tests/fixture/order_with_layered_suite/test_core.py +139 -0
- zope/pytestlayer/tests/fixture/session_fixture/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/session_fixture/conftest.py +13 -0
- zope/pytestlayer/tests/fixture/session_fixture/test_core.py +37 -0
- zope/pytestlayer/tests/fixture/shared_with_layered_suite/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/shared_with_layered_suite/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/shared_with_layered_suite/mydoctest.txt +10 -0
- zope/pytestlayer/tests/fixture/shared_with_layered_suite/test_core.py +53 -0
- zope/pytestlayer/tests/fixture/single_layer/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/single_layer/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/single_layer/test_core.py +33 -0
- zope/pytestlayer/tests/fixture/single_layer_in_two_modules/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/single_layer_in_two_modules/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/single_layer_in_two_modules/test_core.py +33 -0
- zope/pytestlayer/tests/fixture/single_layer_in_two_modules/test_second_module.py +12 -0
- zope/pytestlayer/tests/fixture/single_layer_with_unattached_base_layer/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/single_layer_with_unattached_base_layer/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/single_layer_with_unattached_base_layer/test_core.py +54 -0
- zope/pytestlayer/tests/fixture/single_layered_suite/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/single_layered_suite/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/single_layered_suite/doctest.txt +10 -0
- zope/pytestlayer/tests/fixture/single_layered_suite/test_core.py +44 -0
- zope/pytestlayer/tests/fixture/two_dependent_layered_suites/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/two_dependent_layered_suites/bar.txt +14 -0
- zope/pytestlayer/tests/fixture/two_dependent_layered_suites/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/two_dependent_layered_suites/foo.txt +14 -0
- zope/pytestlayer/tests/fixture/two_dependent_layered_suites/test_core.py +72 -0
- zope/pytestlayer/tests/fixture/two_dependent_layers/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/two_dependent_layers/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/two_dependent_layers/test_core.py +67 -0
- zope/pytestlayer/tests/fixture/two_independent_layers/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/two_independent_layers/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/two_independent_layers/test_core.py +67 -0
- zope/pytestlayer/tests/fixture/with_and_without_layer/__init__.py +0 -0
- zope/pytestlayer/tests/fixture/with_and_without_layer/conftest.py +1 -0
- zope/pytestlayer/tests/fixture/with_and_without_layer/test_core.py +39 -0
- zope/pytestlayer/tests/test_doctest.py +16 -0
- zope/pytestlayer/tests/test_fixture.py +17 -0
- zope/pytestlayer/tests/test_integration.py +524 -0
- zope/pytestlayer/tests/test_layer.py +13 -0
- zope.pytestlayer-8.2-py3.11-nspkg.pth +1 -0
- zope.pytestlayer-8.2.dist-info/LICENSE.txt +43 -0
- zope.pytestlayer-8.2.dist-info/METADATA +318 -0
- zope.pytestlayer-8.2.dist-info/RECORD +79 -0
- zope.pytestlayer-8.2.dist-info/WHEEL +5 -0
- zope.pytestlayer-8.2.dist-info/entry_points.txt +2 -0
- zope.pytestlayer-8.2.dist-info/namespace_packages.txt +1 -0
- zope.pytestlayer-8.2.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# copied from Python 2.7 source code as plone testing uses __bases__
|
|
2
|
+
def _searchbases(cls, accum):
|
|
3
|
+
# Simulate the "classic class" search order.
|
|
4
|
+
if cls in accum:
|
|
5
|
+
return
|
|
6
|
+
accum.append(cls)
|
|
7
|
+
for base in cls.__bases__:
|
|
8
|
+
_searchbases(base, accum)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def getmro(cls):
|
|
12
|
+
"""Return tuple of base classes in method resolution order."""
|
|
13
|
+
if hasattr(cls, "__mro__"):
|
|
14
|
+
return cls.__mro__
|
|
15
|
+
else:
|
|
16
|
+
result = []
|
|
17
|
+
_searchbases(cls, result)
|
|
18
|
+
return tuple(result)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import doctest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NoOpLayer:
|
|
5
|
+
"""Layer needed for zope.pytestlayer to find and run doctests.
|
|
6
|
+
|
|
7
|
+
See https://github.com/zope/zope.pytestlayer/issues/4
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__name__ = 'NoOpLayer'
|
|
11
|
+
__bases__ = ()
|
|
12
|
+
|
|
13
|
+
def setUp(self):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def tearDown(self):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
NOOP_LAYER = NoOpLayer()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def DocTestSuite(*args, **kw):
|
|
24
|
+
"""A DocTestSuite whose tests are detectable by zope.pytestlayer.
|
|
25
|
+
|
|
26
|
+
See https://github.com/zope/zope.pytestlayer/issues/4
|
|
27
|
+
"""
|
|
28
|
+
layer = kw.pop('layer', NOOP_LAYER)
|
|
29
|
+
suite = doctest.DocTestSuite(*args, **kw)
|
|
30
|
+
suite.layer = layer
|
|
31
|
+
return suite
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import re
|
|
3
|
+
import time
|
|
4
|
+
import types
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import zope.dottedname.resolve
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ZopeLayerState(object):
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.current = set()
|
|
14
|
+
self.keep = set()
|
|
15
|
+
self.keep_for_whole_session = set()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@contextlib.contextmanager
|
|
19
|
+
def timer(request, text):
|
|
20
|
+
verbose = request.config.option.verbose > 0
|
|
21
|
+
reporter = request.config.pluginmanager.getplugin('terminalreporter')
|
|
22
|
+
if verbose:
|
|
23
|
+
reporter.ensure_newline()
|
|
24
|
+
reporter.write(text)
|
|
25
|
+
start = time.time()
|
|
26
|
+
yield
|
|
27
|
+
if verbose:
|
|
28
|
+
time_taken = time.time() - start
|
|
29
|
+
reporter.write(f"{time_taken:.3f}", green=1, bold=1)
|
|
30
|
+
reporter.write_line(" seconds.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def setup_layer(layer, request):
|
|
34
|
+
decorate_layer(layer, request)
|
|
35
|
+
state = request.session.zopelayer_state
|
|
36
|
+
layer_name = get_layer_name(layer)
|
|
37
|
+
if hasattr(layer, 'setUp'):
|
|
38
|
+
print(layer_name)
|
|
39
|
+
with timer(request, f"Set up {layer_name} in "):
|
|
40
|
+
layer.setUp()
|
|
41
|
+
state.current.add(layer)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def teardown_layer(layer, request):
|
|
45
|
+
decorate_layer(layer, request)
|
|
46
|
+
state = request.session.zopelayer_state
|
|
47
|
+
layer_name = get_layer_name(layer)
|
|
48
|
+
if hasattr(layer, 'tearDown'):
|
|
49
|
+
with timer(request, f"Tear down {layer_name} in "):
|
|
50
|
+
layer.tearDown()
|
|
51
|
+
state.current.remove(layer)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def session_fixture(request, layer):
|
|
55
|
+
state = request.session.zopelayer_state
|
|
56
|
+
if layer not in state.current:
|
|
57
|
+
setup_layer(layer, request)
|
|
58
|
+
state.keep_for_whole_session.add(layer)
|
|
59
|
+
|
|
60
|
+
def teardown():
|
|
61
|
+
teardown_layer(layer, request)
|
|
62
|
+
request.addfinalizer(teardown)
|
|
63
|
+
return layer
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def class_fixture(request, layer):
|
|
67
|
+
state = request.session.zopelayer_state
|
|
68
|
+
if layer not in state.current:
|
|
69
|
+
setup_layer(layer, request)
|
|
70
|
+
|
|
71
|
+
def maybe_teardown():
|
|
72
|
+
if layer not in (state.keep | state.keep_for_whole_session):
|
|
73
|
+
teardown_layer(layer, request)
|
|
74
|
+
request.addfinalizer(maybe_teardown)
|
|
75
|
+
return layer
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def function_fixture(request, layer):
|
|
79
|
+
decorate_layer(layer, request)
|
|
80
|
+
if hasattr(layer, 'testSetUp'):
|
|
81
|
+
layer.testSetUp()
|
|
82
|
+
|
|
83
|
+
if hasattr(layer, 'testTearDown'):
|
|
84
|
+
|
|
85
|
+
def function_tear_down():
|
|
86
|
+
decorate_layer(layer, request)
|
|
87
|
+
layer.testTearDown()
|
|
88
|
+
|
|
89
|
+
request.addfinalizer(function_tear_down)
|
|
90
|
+
return layer
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def decorate_layer(layer, request):
|
|
94
|
+
setattr(layer, 'pytest_request', request)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_layer_name(layer):
|
|
98
|
+
module = zope.dottedname.resolve.resolve(layer.__module__)
|
|
99
|
+
for key, value in module.__dict__.items():
|
|
100
|
+
if value is layer:
|
|
101
|
+
name = key
|
|
102
|
+
break
|
|
103
|
+
else:
|
|
104
|
+
# As per zope.testrunner conventions, a layer is assumed to have a
|
|
105
|
+
# __name__ even if it's not a class.
|
|
106
|
+
name = layer.__name__
|
|
107
|
+
return '%s.%s' % (layer.__module__, name)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def make_identifier(string):
|
|
111
|
+
# Replaces things between words into underscores:
|
|
112
|
+
return re.sub(r'\W|^(?=\d)', '_', string)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_fixture_name(layer, scope):
|
|
116
|
+
name = make_identifier(get_layer_name(layer))
|
|
117
|
+
layerid = id(layer)
|
|
118
|
+
return 'zope_layer_{scope}_{name}_{layerid}'.format(**locals())
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
LAYERS = {}
|
|
122
|
+
LAYERS[object] = {} # We do not need to create a fixture for `object`
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def create(*layers, **kw):
|
|
126
|
+
"""Create fixtures for given layers and their bases.
|
|
127
|
+
|
|
128
|
+
Fixture names will be generated automatically. For a single layer, you can
|
|
129
|
+
pass in kw arguments ``session_fixture_name, ``class_fixture_name`` and
|
|
130
|
+
``function_fixture_name`` instead.
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
if kw and len(layers) > 1:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
'Overriding layer names is only possible '
|
|
136
|
+
'for a single layer at a time')
|
|
137
|
+
|
|
138
|
+
ns = {}
|
|
139
|
+
for layer in layers:
|
|
140
|
+
if isinstance(layer, str):
|
|
141
|
+
layer = zope.dottedname.resolve.resolve(layer)
|
|
142
|
+
ns.update(_create_single(layer, **kw))
|
|
143
|
+
return ns
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
SCOPES = ('session', 'class', 'function')
|
|
147
|
+
TEMPLATE = """\
|
|
148
|
+
@pytest.fixture(scope='session')
|
|
149
|
+
def {session_fixture_name}(request{session_fixture_dependencies}):
|
|
150
|
+
"Depends on {session_fixture_dependencies}"
|
|
151
|
+
return session_fixture(request, layer)
|
|
152
|
+
|
|
153
|
+
@pytest.fixture(scope='class')
|
|
154
|
+
def {class_fixture_name}(request{class_fixture_dependencies}):
|
|
155
|
+
"Depends on {class_fixture_dependencies}"
|
|
156
|
+
return class_fixture(request, layer)
|
|
157
|
+
|
|
158
|
+
@pytest.fixture(scope='function')
|
|
159
|
+
def {function_fixture_name}(request{function_fixture_dependencies}):
|
|
160
|
+
"Depends on {function_fixture_dependencies}"
|
|
161
|
+
return function_fixture(request, layer)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _create_single(layer, **kw):
|
|
166
|
+
"""Actually create a fixtures for a single layer and its bases."""
|
|
167
|
+
if layer in LAYERS:
|
|
168
|
+
return {}
|
|
169
|
+
|
|
170
|
+
LAYERS[layer] = {}
|
|
171
|
+
dependencies = {}
|
|
172
|
+
for scope in SCOPES:
|
|
173
|
+
LAYERS[layer][scope] = kw.get(
|
|
174
|
+
'%s_fixture_name' % scope, get_fixture_name(layer, scope))
|
|
175
|
+
dependencies[scope] = [
|
|
176
|
+
', ' + LAYERS.get(base, {}).get(
|
|
177
|
+
scope, get_fixture_name(base, scope))
|
|
178
|
+
for base in layer.__bases__ if base is not object]
|
|
179
|
+
dependencies['function'].insert(0, ', ' + LAYERS[layer]['class'])
|
|
180
|
+
|
|
181
|
+
fixtures = {}
|
|
182
|
+
for scope in SCOPES:
|
|
183
|
+
fixtures['%s_fixture_name' % scope] = LAYERS[layer][scope]
|
|
184
|
+
fixtures['%s_fixture_dependencies' % scope] = ''.join(
|
|
185
|
+
dependencies[scope])
|
|
186
|
+
code = TEMPLATE.format(**fixtures)
|
|
187
|
+
|
|
188
|
+
globs = {
|
|
189
|
+
'pytest': pytest,
|
|
190
|
+
'layer': layer,
|
|
191
|
+
}
|
|
192
|
+
for scope in SCOPES:
|
|
193
|
+
globs['%s_fixture' % scope] = globals()['%s_fixture' % scope]
|
|
194
|
+
|
|
195
|
+
ns = {}
|
|
196
|
+
exec(code, globs, ns)
|
|
197
|
+
|
|
198
|
+
# Recurse into bases:
|
|
199
|
+
ns.update(create(*layer.__bases__))
|
|
200
|
+
|
|
201
|
+
return ns
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def parsefactories(collector, layer):
|
|
205
|
+
ns = create(layer)
|
|
206
|
+
if ns:
|
|
207
|
+
name = get_fixture_name(layer, scope='function')
|
|
208
|
+
module = types.ModuleType(name)
|
|
209
|
+
module.__dict__.update(ns)
|
|
210
|
+
collector.session._fixturemanager.parsefactories(module, '')
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def raise_if_bad_layer(layer):
|
|
214
|
+
'complaining about bad layers'
|
|
215
|
+
|
|
216
|
+
if not hasattr(layer, '__bases__'):
|
|
217
|
+
raise RuntimeError(
|
|
218
|
+
"The layer {layer!r} has no __bases__ attribute."
|
|
219
|
+
" Layers may be of two sorts: class or instance with __bases__"
|
|
220
|
+
" attribute."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
KEYWORDS_BY_LAYER = {object: {}}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_keywords(layer):
|
|
228
|
+
if layer in KEYWORDS_BY_LAYER:
|
|
229
|
+
return KEYWORDS_BY_LAYER[layer]
|
|
230
|
+
keywords = {get_layer_name(layer): True}
|
|
231
|
+
for base_layer in layer.__bases__:
|
|
232
|
+
keywords.update(get_keywords(base_layer))
|
|
233
|
+
KEYWORDS_BY_LAYER[layer] = keywords
|
|
234
|
+
return keywords
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import _pytest.unittest
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from zope.pytestlayer import fixture
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LayeredTestSuite(pytest.Class):
|
|
10
|
+
|
|
11
|
+
def collect(self):
|
|
12
|
+
suite = self.obj()
|
|
13
|
+
for item, layer in walk_suite(suite):
|
|
14
|
+
fixture.parsefactories(self.parent, layer)
|
|
15
|
+
yield LayeredTestCaseInstance.from_parent(
|
|
16
|
+
parent=self, obj=item, layer=layer)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LayeredTestCaseInstance(_pytest.unittest.UnitTestCase):
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_parent(cls, parent, obj, layer, **kw):
|
|
23
|
+
testname = repr(obj) # fantastic doctest API :(
|
|
24
|
+
instance = super(pytest.Collector, cls).from_parent(
|
|
25
|
+
parent=parent, name=testname)
|
|
26
|
+
# store testcase instance and layer
|
|
27
|
+
# to pass them to function
|
|
28
|
+
instance.obj = obj
|
|
29
|
+
instance.layer = layer
|
|
30
|
+
instance.extra_keyword_matches.update(fixture.get_keywords(layer))
|
|
31
|
+
return instance
|
|
32
|
+
|
|
33
|
+
def collect(self):
|
|
34
|
+
yield LayeredTestCaseFunction.from_parent(parent=self, name='runTest')
|
|
35
|
+
|
|
36
|
+
def reportinfo(self):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LayeredTestCaseFunction(_pytest.unittest.TestCaseFunction):
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_parent(cls, parent, name, **kw):
|
|
44
|
+
description = get_description(parent)
|
|
45
|
+
keywords = get_keywords(description)
|
|
46
|
+
function = super(LayeredTestCaseFunction, cls).from_parent(
|
|
47
|
+
parent=parent,
|
|
48
|
+
name=name,
|
|
49
|
+
keywords=keywords,
|
|
50
|
+
)
|
|
51
|
+
function.layer = function.parent.layer
|
|
52
|
+
function.tc_description = description
|
|
53
|
+
function._testcase = function.parent.obj
|
|
54
|
+
return function
|
|
55
|
+
|
|
56
|
+
def setup(self):
|
|
57
|
+
# This is actually set in the base class, but as we want to modify
|
|
58
|
+
# `self._request` in our way, we do not make a super call here.
|
|
59
|
+
# It has to be None or a bound method to be called during tearDown.
|
|
60
|
+
self._explicit_tearDown = None
|
|
61
|
+
if hasattr(self, "_request"):
|
|
62
|
+
# call function fixture (testSetUp)
|
|
63
|
+
fixture_name = fixture.get_fixture_name(
|
|
64
|
+
self.layer, scope='function')
|
|
65
|
+
self._request.getfixturevalue(fixture_name)
|
|
66
|
+
|
|
67
|
+
def teardown(self):
|
|
68
|
+
_testcase = self._testcase
|
|
69
|
+
super(LayeredTestCaseFunction, self).teardown()
|
|
70
|
+
# Do not die with a meaningless error message when rerunning doctests:
|
|
71
|
+
self._testcase = _testcase
|
|
72
|
+
|
|
73
|
+
def reportinfo(self):
|
|
74
|
+
return ('test_suite', None, self.tc_description)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_description(collector):
|
|
78
|
+
description = str(collector.obj)
|
|
79
|
+
fspath = collector.session.fspath.strpath
|
|
80
|
+
return description.replace(fspath, '')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_keywords(description):
|
|
84
|
+
words = [word for word in description.split()]
|
|
85
|
+
keywords = {}
|
|
86
|
+
for word in words:
|
|
87
|
+
keywords[word] = True
|
|
88
|
+
return keywords
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def walk_suite(suite):
|
|
92
|
+
if isinstance(suite, unittest.TestSuite):
|
|
93
|
+
has_layer = hasattr(suite, 'layer')
|
|
94
|
+
for item in suite:
|
|
95
|
+
if isinstance(item, unittest.TestCase) and has_layer:
|
|
96
|
+
fixture.raise_if_bad_layer(suite.layer)
|
|
97
|
+
yield item, suite.layer
|
|
98
|
+
else:
|
|
99
|
+
for result in walk_suite(item):
|
|
100
|
+
yield result
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import types
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from zope.pytestlayer import fixture
|
|
7
|
+
from zope.pytestlayer import layered
|
|
8
|
+
|
|
9
|
+
from ._compat import getmro
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.tryfirst
|
|
13
|
+
def pytest_pycollect_makeitem(collector, name, obj):
|
|
14
|
+
# this works because of two things:
|
|
15
|
+
# * this plugin is called before the pytest unittest collector (if it
|
|
16
|
+
# wasn't, it wouldn't be called at all after the pytest collector has
|
|
17
|
+
# detected a unittest test case)
|
|
18
|
+
# * usefixtures works in-place
|
|
19
|
+
|
|
20
|
+
suite = query_testsuite(obj)
|
|
21
|
+
if suite is not None:
|
|
22
|
+
return layered.LayeredTestSuite.from_parent(
|
|
23
|
+
parent=collector, name=name)
|
|
24
|
+
else:
|
|
25
|
+
layer = query_layer(obj)
|
|
26
|
+
if layer is not None:
|
|
27
|
+
fixture.parsefactories(collector, layer)
|
|
28
|
+
return collect_with_layer(collector, name, obj, layer)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def query_testsuite(obj):
|
|
32
|
+
if (isinstance(obj, types.FunctionType) and obj.__name__ == 'test_suite'):
|
|
33
|
+
suite = obj()
|
|
34
|
+
if isinstance(suite, unittest.TestSuite):
|
|
35
|
+
return suite
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def query_layer(obj):
|
|
39
|
+
if has_layer(obj):
|
|
40
|
+
layer = obj.layer
|
|
41
|
+
fixture.raise_if_bad_layer(layer)
|
|
42
|
+
return layer
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def has_layer(obj):
|
|
46
|
+
try:
|
|
47
|
+
isunit = issubclass(obj, unittest.TestCase)
|
|
48
|
+
except TypeError:
|
|
49
|
+
isunit = False
|
|
50
|
+
return isunit and hasattr(obj, 'layer')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def pytest_collection_modifyitems(session, config, items):
|
|
54
|
+
items_by_layer = {}
|
|
55
|
+
layers_in_order = []
|
|
56
|
+
for item in items:
|
|
57
|
+
if hasattr(item, 'cls') and hasattr(item.cls, 'layer'):
|
|
58
|
+
layer = item.cls.layer
|
|
59
|
+
layers_in_order.append(layer)
|
|
60
|
+
elif hasattr(item, 'layer'):
|
|
61
|
+
layer = item.layer
|
|
62
|
+
layers_in_order.append(layer)
|
|
63
|
+
else:
|
|
64
|
+
layer = None
|
|
65
|
+
items_by_layer.setdefault(layer, []).append(item)
|
|
66
|
+
ordered_layers = order_by_bases(layers_in_order)
|
|
67
|
+
items[:] = items_by_layer.get(None, [])
|
|
68
|
+
for layer in ordered_layers:
|
|
69
|
+
items.extend(items_by_layer.get(layer, []))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def order_by_bases(layers):
|
|
73
|
+
"""Order the layers from least to most specific (bottom to top)
|
|
74
|
+
"""
|
|
75
|
+
gathered = []
|
|
76
|
+
for layer in layers:
|
|
77
|
+
gather_layers(layer, gathered)
|
|
78
|
+
seen = set()
|
|
79
|
+
result = []
|
|
80
|
+
for layer in gathered:
|
|
81
|
+
if layer not in seen:
|
|
82
|
+
seen.add(layer)
|
|
83
|
+
if layer in layers:
|
|
84
|
+
result.append(layer)
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def gather_layers(layer, result):
|
|
89
|
+
for b in layer.__bases__:
|
|
90
|
+
gather_layers(b, result)
|
|
91
|
+
if layer is not object:
|
|
92
|
+
result.append(layer)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def pytest_sessionstart(session):
|
|
96
|
+
session.zopelayer_state = fixture.ZopeLayerState()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def pytest_runtest_teardown(item, nextitem):
|
|
100
|
+
state = item.session.zopelayer_state
|
|
101
|
+
|
|
102
|
+
if hasattr(nextitem, 'cls') and hasattr(nextitem.cls, 'layer'):
|
|
103
|
+
state.keep = state.current & set(getmro(nextitem.cls.layer))
|
|
104
|
+
elif hasattr(nextitem, 'layer'):
|
|
105
|
+
state.keep = state.current & set(getmro(nextitem.layer))
|
|
106
|
+
else:
|
|
107
|
+
state.keep.clear()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def collect_with_layer(collector, name, obj, layer):
|
|
111
|
+
fixture_name = fixture.LAYERS.get(layer, {}).get(
|
|
112
|
+
'function', fixture.get_fixture_name(layer, 'function'))
|
|
113
|
+
usefixtures = pytest.mark.usefixtures(fixture_name)
|
|
114
|
+
usefixtures(obj)
|
|
115
|
+
py_unittest = get_py_unittest(collector)
|
|
116
|
+
result = py_unittest.pytest_pycollect_makeitem(collector, name, obj)
|
|
117
|
+
result.extra_keyword_matches.update(fixture.get_keywords(layer))
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_py_unittest(collector):
|
|
122
|
+
return collector.session.config.pluginmanager.getplugin('unittest')
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytest_plugins = ('zopelayer', )
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from custom_fixture_name.test_core import FooLayer
|
|
2
|
+
|
|
3
|
+
import zope.pytestlayer.fixture
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
pytest_plugins = ('zopelayer', )
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
globals().update(zope.pytestlayer.fixture.create(
|
|
10
|
+
FooLayer,
|
|
11
|
+
class_fixture_name='foo_layer_class',
|
|
12
|
+
function_fixture_name='foo_layer'))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from zope.pytestlayer.testing import log_to_terminal
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FooLayer(object):
|
|
7
|
+
|
|
8
|
+
@classmethod
|
|
9
|
+
def setUp(cls):
|
|
10
|
+
cls.layer_foo = 'layer foo'
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def tearDown(cls):
|
|
14
|
+
del cls.layer_foo
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def testSetUp(cls):
|
|
18
|
+
log_to_terminal(cls.pytest_request, 'testSetUp foo')
|
|
19
|
+
cls.test_foo = 'test foo'
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def testTearDown(cls):
|
|
23
|
+
log_to_terminal(cls.pytest_request, 'testTearDown foo')
|
|
24
|
+
del cls.test_foo
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_can_access_layer_via_fixture(foo_layer):
|
|
28
|
+
assert 'layer foo' == foo_layer.layer_foo
|
|
29
|
+
assert 'test foo' == foo_layer.test_foo
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FooTest(unittest.TestCase):
|
|
33
|
+
|
|
34
|
+
layer = FooLayer
|
|
35
|
+
|
|
36
|
+
def test_accesses_fixture_with_generated_name_for_layer(self):
|
|
37
|
+
self.assertEqual('layer foo', self.layer.layer_foo)
|
|
38
|
+
self.assertEqual('test foo', self.layer.test_foo)
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytest_plugins = ('zopelayer', )
|