nti.testing 4.2.0__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.
- nti/__init__.py +0 -0
- nti/testing/__init__.py +107 -0
- nti/testing/base.py +574 -0
- nti/testing/layers/__init__.py +53 -0
- nti/testing/layers/cleanup.py +106 -0
- nti/testing/layers/postgres.py +693 -0
- nti/testing/layers/tests/__init__.py +0 -0
- nti/testing/layers/tests/test_postgres.py +17 -0
- nti/testing/layers/zope.py +122 -0
- nti/testing/matchers.py +370 -0
- nti/testing/mock.py +22 -0
- nti/testing/tests/__init__.py +0 -0
- nti/testing/tests/test_base.py +160 -0
- nti/testing/tests/test_component_cleanup_broken.txt +137 -0
- nti/testing/tests/test_layers.py +56 -0
- nti/testing/tests/test_main.py +47 -0
- nti/testing/tests/test_matchers.py +237 -0
- nti/testing/tests/test_time.py +157 -0
- nti/testing/tests/test_zodb.py +326 -0
- nti/testing/time.py +179 -0
- nti/testing/zodb.py +245 -0
- nti.testing-4.2.0.dist-info/LICENSE +11 -0
- nti.testing-4.2.0.dist-info/METADATA +423 -0
- nti.testing-4.2.0.dist-info/RECORD +27 -0
- nti.testing-4.2.0.dist-info/WHEEL +5 -0
- nti.testing-4.2.0.dist-info/top_level.txt +1 -0
- nti.testing-4.2.0.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Test layers for working with Zope libraries.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import gc
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from zope import component
|
|
13
|
+
from zope.component import eventtesting
|
|
14
|
+
from zope.component.hooks import setHooks
|
|
15
|
+
|
|
16
|
+
from .. import transactionCleanUp
|
|
17
|
+
from ..base import AbstractConfiguringObject
|
|
18
|
+
from .cleanup import SharedCleanupLayer
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
class ZopeComponentLayer(SharedCleanupLayer):
|
|
23
|
+
"""
|
|
24
|
+
Test layer that can be subclassed when zope.component will be used.
|
|
25
|
+
|
|
26
|
+
This does nothing but set up the hooks and the event handlers.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def setUp(cls):
|
|
31
|
+
setHooks() # zope.component.hooks registers a zope.testing.cleanup to reset these
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def tearDown(cls):
|
|
36
|
+
# always safe to clear events
|
|
37
|
+
eventtesting.clearEvents() # redundant with zope.testing.cleanup
|
|
38
|
+
# we never actually want to do this, it's not needed and can mess up other fixtures
|
|
39
|
+
# resetHooks()
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def testSetUp(cls):
|
|
43
|
+
setHooks() # ensure these are still here; cheap and easy
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def testTearDown(cls):
|
|
47
|
+
# Some tear down needs to happen always
|
|
48
|
+
eventtesting.clearEvents()
|
|
49
|
+
transactionCleanUp()
|
|
50
|
+
|
|
51
|
+
_marker = object()
|
|
52
|
+
|
|
53
|
+
class ConfiguringLayerMixin(AbstractConfiguringObject):
|
|
54
|
+
"""
|
|
55
|
+
Inherit from this layer *at the leaf level* to perform configuration.
|
|
56
|
+
You should have already inherited from :class:`ZopeComponentLayer`.
|
|
57
|
+
|
|
58
|
+
To use this layer, subclass it and define a set of packages. This
|
|
59
|
+
should be done EXACTLY ONCE for each set of packages; things that
|
|
60
|
+
add to the set of packages should generally extend that layer
|
|
61
|
+
class. You must call :meth:`setUpPackages` and :meth:`tearDownPackages`
|
|
62
|
+
from your ``setUp`` and ``tearDown`` methods.
|
|
63
|
+
|
|
64
|
+
See :class:`~.AbstractConfiguringObject` for documentation on
|
|
65
|
+
the class attributes to configure.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def setUp(cls):
|
|
70
|
+
# You MUST implement this, otherwise zope.testrunner
|
|
71
|
+
# will call the super-class again
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def tearDown(cls):
|
|
76
|
+
# You MUST implement this, otherwise zope.testrunner
|
|
77
|
+
# will call the super-class again
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def testSetUp(cls):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def testTearDown(cls):
|
|
86
|
+
# Must implement
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
#: .. seealso:: :meth:`~.AbstractConfiguringObject.get_configuration_package_for_class`
|
|
90
|
+
#: .. versionadded:: 2.1.0
|
|
91
|
+
get_configuration_package = classmethod(
|
|
92
|
+
AbstractConfiguringObject.get_configuration_package_for_class
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def setUpPackages(cls):
|
|
97
|
+
"""
|
|
98
|
+
Set up the configured packages.
|
|
99
|
+
"""
|
|
100
|
+
logger.info('Setting up packages %s for layer %s', cls.set_up_packages, cls)
|
|
101
|
+
gc.collect()
|
|
102
|
+
cls.configuration_context = cls.configure_packages(
|
|
103
|
+
set_up_packages=cls.set_up_packages,
|
|
104
|
+
features=cls.features,
|
|
105
|
+
context=cls.configuration_context,
|
|
106
|
+
package=cls.get_configuration_package())
|
|
107
|
+
component.provideHandler(eventtesting.events.append, (None,))
|
|
108
|
+
gc.collect()
|
|
109
|
+
|
|
110
|
+
configure_packages = classmethod(AbstractConfiguringObject._do_configure_packages)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def tearDownPackages(cls):
|
|
114
|
+
"""
|
|
115
|
+
Tear down all configured packages in the global site manager.
|
|
116
|
+
"""
|
|
117
|
+
# This is a duplicate of zope.component.globalregistry
|
|
118
|
+
logger.info('Tearing down packages %s for layer %s', cls.set_up_packages, cls)
|
|
119
|
+
gc.collect()
|
|
120
|
+
component.getGlobalSiteManager().__init__('base') # pylint:disable=unnecessary-dunder-call
|
|
121
|
+
gc.collect()
|
|
122
|
+
cls.configuration_context = None
|
nti/testing/matchers.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Hamcrest matchers for testing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Sequence, Mapping
|
|
8
|
+
|
|
9
|
+
import pprint
|
|
10
|
+
|
|
11
|
+
import six
|
|
12
|
+
from zope.interface.exceptions import Invalid
|
|
13
|
+
from zope.interface.verify import verifyObject
|
|
14
|
+
from zope.schema import ValidationError
|
|
15
|
+
from zope.schema import getValidationErrors
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import hamcrest
|
|
19
|
+
from hamcrest import assert_that
|
|
20
|
+
from hamcrest import has_length
|
|
21
|
+
from hamcrest import is_
|
|
22
|
+
from hamcrest import is_not
|
|
23
|
+
from hamcrest.core.base_description import BaseDescription
|
|
24
|
+
from hamcrest.core.base_matcher import BaseMatcher
|
|
25
|
+
from hamcrest.library.collection.is_empty import empty
|
|
26
|
+
|
|
27
|
+
__docformat__ = "restructuredtext en"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
'has_length',
|
|
31
|
+
'has_attr',
|
|
32
|
+
'is_true',
|
|
33
|
+
'is_false',
|
|
34
|
+
'provides',
|
|
35
|
+
'verifiably_provides',
|
|
36
|
+
'validly_provides',
|
|
37
|
+
'implements',
|
|
38
|
+
'validated_by',
|
|
39
|
+
'not_validated_by',
|
|
40
|
+
'aq_inContextOf',
|
|
41
|
+
'TypeCheckedDict',
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
is_empty = empty # bwc
|
|
45
|
+
|
|
46
|
+
has_attr = hamcrest.library.has_property
|
|
47
|
+
|
|
48
|
+
class BoolMatcher(BaseMatcher):
|
|
49
|
+
def __init__(self, value):
|
|
50
|
+
super().__init__()
|
|
51
|
+
self.value = value
|
|
52
|
+
|
|
53
|
+
def _matches(self, item):
|
|
54
|
+
return bool(item) == self.value
|
|
55
|
+
|
|
56
|
+
def describe_to(self, description):
|
|
57
|
+
description.append_text('object with bool() value ').append(str(self.value))
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
return 'object with bool() value ' + str(self.value)
|
|
61
|
+
|
|
62
|
+
def is_true():
|
|
63
|
+
"""
|
|
64
|
+
Matches an object with a true boolean value.
|
|
65
|
+
"""
|
|
66
|
+
return BoolMatcher(True)
|
|
67
|
+
|
|
68
|
+
def is_false():
|
|
69
|
+
"""
|
|
70
|
+
Matches an object with a false boolean value.
|
|
71
|
+
"""
|
|
72
|
+
return BoolMatcher(False)
|
|
73
|
+
|
|
74
|
+
class Provides(BaseMatcher):
|
|
75
|
+
|
|
76
|
+
def __init__(self, iface):
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.iface = iface
|
|
79
|
+
|
|
80
|
+
def _matches(self, item):
|
|
81
|
+
return self.iface.providedBy(item)
|
|
82
|
+
|
|
83
|
+
def describe_to(self, description):
|
|
84
|
+
description.append_text('object providing') \
|
|
85
|
+
.append(str(self.iface))
|
|
86
|
+
|
|
87
|
+
def __repr__(self):
|
|
88
|
+
return 'object providing' + str(self.iface)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def provides(iface):
|
|
92
|
+
"""
|
|
93
|
+
Matches an object that provides the given interface.
|
|
94
|
+
"""
|
|
95
|
+
return Provides(iface)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class VerifyProvides(BaseMatcher):
|
|
99
|
+
|
|
100
|
+
def __init__(self, iface):
|
|
101
|
+
super().__init__()
|
|
102
|
+
self.iface = iface
|
|
103
|
+
|
|
104
|
+
def _matches(self, item):
|
|
105
|
+
try:
|
|
106
|
+
verifyObject(self.iface, item)
|
|
107
|
+
except Invalid:
|
|
108
|
+
return False
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
def describe_to(self, description):
|
|
112
|
+
description.append_text('object verifiably providing ').append_description_of(self.iface)
|
|
113
|
+
|
|
114
|
+
def describe_mismatch(self, item, mismatch_description):
|
|
115
|
+
md = mismatch_description
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
verifyObject(self.iface, item)
|
|
119
|
+
except Invalid as x:
|
|
120
|
+
# Beginning in zope.interface 5, the Invalid exception subclasses
|
|
121
|
+
# like BrokenImplementation, DoesNotImplement, etc, all typically
|
|
122
|
+
# have a much nicer error message than they used to, better than we
|
|
123
|
+
# were producing. This is especially true now that MultipleInvalid
|
|
124
|
+
# is a thing.
|
|
125
|
+
x = str(x).strip()
|
|
126
|
+
|
|
127
|
+
md.append_text("Using class ").append_description_of(type(item)).append_text(' ')
|
|
128
|
+
if x.startswith('The object '):
|
|
129
|
+
x = x[len("The object "):]
|
|
130
|
+
x = 'the object ' + x
|
|
131
|
+
x = x.replace('\n ', '\n ')
|
|
132
|
+
md.append_text(x)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def verifiably_provides(*ifaces):
|
|
136
|
+
"""
|
|
137
|
+
Matches if the object verifiably provides the correct interface(s),
|
|
138
|
+
as defined by :func:`zope.interface.verify.verifyObject`. This means having the right attributes
|
|
139
|
+
and methods with the right signatures.
|
|
140
|
+
|
|
141
|
+
.. note:: This does **not** test schema compliance. For that
|
|
142
|
+
(stricter) test, see :func:`validly_provides`.
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
if len(ifaces) == 1:
|
|
146
|
+
return VerifyProvides(ifaces[0])
|
|
147
|
+
|
|
148
|
+
return hamcrest.all_of(*[VerifyProvides(x) for x in ifaces])
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class VerifyValidSchema(BaseMatcher):
|
|
152
|
+
def __init__(self, iface):
|
|
153
|
+
super().__init__()
|
|
154
|
+
self.iface = iface
|
|
155
|
+
|
|
156
|
+
def _matches(self, item):
|
|
157
|
+
errors = getValidationErrors(self.iface, item)
|
|
158
|
+
return not errors
|
|
159
|
+
|
|
160
|
+
def describe_to(self, description):
|
|
161
|
+
description.append_text('object validly providing ').append(str(self.iface))
|
|
162
|
+
|
|
163
|
+
def describe_mismatch(self, item, mismatch_description):
|
|
164
|
+
x = None
|
|
165
|
+
md = mismatch_description
|
|
166
|
+
md.append_text(str(type(item)))
|
|
167
|
+
|
|
168
|
+
errors = getValidationErrors(self.iface, item)
|
|
169
|
+
|
|
170
|
+
for attr, exc in errors:
|
|
171
|
+
try:
|
|
172
|
+
raise exc
|
|
173
|
+
except ValidationError:
|
|
174
|
+
md.append_text(' has attribute "')
|
|
175
|
+
md.append_text(attr)
|
|
176
|
+
md.append_text('" with error "')
|
|
177
|
+
md.append_text(repr(exc))
|
|
178
|
+
md.append_text('"\n\t ')
|
|
179
|
+
except Invalid as x: # pragma: no cover
|
|
180
|
+
md.append_text(str(x))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def validly_provides(*ifaces):
|
|
184
|
+
"""
|
|
185
|
+
Matches if the object verifiably and validly provides the given
|
|
186
|
+
schema (interface(s)).
|
|
187
|
+
|
|
188
|
+
Verification is done with :mod:`zope.interface` and
|
|
189
|
+
:func:`verifiably_provides`, while validation is done with
|
|
190
|
+
:func:`zope.schema.getValidationErrors`.
|
|
191
|
+
"""
|
|
192
|
+
if len(ifaces) == 1:
|
|
193
|
+
the_schema = ifaces[0]
|
|
194
|
+
return hamcrest.all_of(verifiably_provides(the_schema), VerifyValidSchema(the_schema))
|
|
195
|
+
|
|
196
|
+
prov = verifiably_provides(*ifaces)
|
|
197
|
+
valid = [VerifyValidSchema(x) for x in ifaces]
|
|
198
|
+
|
|
199
|
+
return hamcrest.all_of(prov, *valid)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class Implements(BaseMatcher):
|
|
204
|
+
|
|
205
|
+
def __init__(self, iface):
|
|
206
|
+
super().__init__()
|
|
207
|
+
self.iface = iface
|
|
208
|
+
|
|
209
|
+
def _matches(self, item):
|
|
210
|
+
return self.iface.implementedBy(item)
|
|
211
|
+
|
|
212
|
+
def describe_to(self, description):
|
|
213
|
+
description.append_text('object implementing')
|
|
214
|
+
description.append_description_of(self.iface)
|
|
215
|
+
|
|
216
|
+
def implements(iface):
|
|
217
|
+
"""
|
|
218
|
+
Matches if the object implements (is a factory for) the given
|
|
219
|
+
interface.
|
|
220
|
+
|
|
221
|
+
.. seealso:: :meth:`zope.interface.interfaces.ISpecification.implementedBy`
|
|
222
|
+
"""
|
|
223
|
+
return Implements(iface)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ValidatedBy(BaseMatcher):
|
|
227
|
+
|
|
228
|
+
def __init__(self, field, invalid=Invalid):
|
|
229
|
+
super().__init__()
|
|
230
|
+
self.field = field
|
|
231
|
+
self.invalid = invalid
|
|
232
|
+
|
|
233
|
+
def _matches(self, item):
|
|
234
|
+
try:
|
|
235
|
+
self.field.validate(item)
|
|
236
|
+
except self.invalid:
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
def describe_to(self, description):
|
|
242
|
+
description.append_text('data validated by').append_description_of(self.field)
|
|
243
|
+
|
|
244
|
+
def describe_mismatch(self, item, mismatch_description):
|
|
245
|
+
ex = None
|
|
246
|
+
try:
|
|
247
|
+
self.field.validate(item)
|
|
248
|
+
except self.invalid as e:
|
|
249
|
+
ex = e
|
|
250
|
+
|
|
251
|
+
mismatch_description.append_description_of(self.field)
|
|
252
|
+
mismatch_description.append_text(' failed to validate ')
|
|
253
|
+
mismatch_description.append_description_of(item)
|
|
254
|
+
mismatch_description.append_text(' with ')
|
|
255
|
+
mismatch_description.append_description_of(ex)
|
|
256
|
+
|
|
257
|
+
def validated_by(field, invalid=Invalid):
|
|
258
|
+
"""
|
|
259
|
+
Matches if the data is validated by the given ``IField``.
|
|
260
|
+
|
|
261
|
+
:keyword exception invalid: The types of exceptions that are considered
|
|
262
|
+
invalid. Anything other than this is allowed to be raised.
|
|
263
|
+
|
|
264
|
+
.. versionchanged:: 2.0.1
|
|
265
|
+
Add ``invalid`` and change it from ``Exception`` to
|
|
266
|
+
:class:`zope.interface.interfaces.Invalid`
|
|
267
|
+
"""
|
|
268
|
+
return ValidatedBy(field, invalid=invalid)
|
|
269
|
+
|
|
270
|
+
def not_validated_by(field, invalid=Invalid):
|
|
271
|
+
"""
|
|
272
|
+
Matches if the data is NOT validated by the given IField.
|
|
273
|
+
|
|
274
|
+
:keyword exception invalid: The types of exceptions that are considered
|
|
275
|
+
invalid. Anything other than this is allowed to be raised.
|
|
276
|
+
|
|
277
|
+
.. versionchanged:: 2.0.1
|
|
278
|
+
Add ``invalid`` and change it from ``Exception`` to
|
|
279
|
+
:class:`zope.interface.interfaces.Invalid`
|
|
280
|
+
"""
|
|
281
|
+
return is_not(validated_by(field, invalid=invalid))
|
|
282
|
+
|
|
283
|
+
def _aq_inContextOf_NotImplemented(child, parent): # pylint:disable=unused-argument
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
from Acquisition import aq_inContextOf as _aq_inContextOf
|
|
288
|
+
except ImportError: # pragma: no cover
|
|
289
|
+
# acquisition not installed
|
|
290
|
+
_aq_inContextOf = _aq_inContextOf_NotImplemented
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class AqInContextOf(BaseMatcher):
|
|
294
|
+
def __init__(self, parent):
|
|
295
|
+
super().__init__()
|
|
296
|
+
self.parent = parent
|
|
297
|
+
|
|
298
|
+
def _matches(self, item):
|
|
299
|
+
if hasattr(item, 'aq_inContextOf'): # wrappers
|
|
300
|
+
return item.aq_inContextOf(self.parent)
|
|
301
|
+
return _aq_inContextOf(item, self.parent) # not wrapped, but maybe __parent__ chain
|
|
302
|
+
|
|
303
|
+
def describe_to(self, description):
|
|
304
|
+
description.append_text('object in context of ')
|
|
305
|
+
description.append_description_of(self.parent)
|
|
306
|
+
|
|
307
|
+
def describe_mismatch(self, item, mismatch_description):
|
|
308
|
+
if _aq_inContextOf is _aq_inContextOf_NotImplemented:
|
|
309
|
+
mismatch_description.append_text('Acquisition was not installed.')
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
mismatch_description.append_description_of(item)
|
|
313
|
+
mismatch_description.append_text(' was not in the context of ')
|
|
314
|
+
mismatch_description.append_description_of(self.parent)
|
|
315
|
+
mismatch_description.append_text('; its lineage is ')
|
|
316
|
+
lineage = []
|
|
317
|
+
while item is not None:
|
|
318
|
+
try:
|
|
319
|
+
item = item.__parent__
|
|
320
|
+
except AttributeError:
|
|
321
|
+
item = None
|
|
322
|
+
if item is not None:
|
|
323
|
+
lineage.append(item)
|
|
324
|
+
mismatch_description.append_description_of(lineage)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def aq_inContextOf(parent):
|
|
328
|
+
"""
|
|
329
|
+
Matches if the data is in the acquisition context of
|
|
330
|
+
the given object.
|
|
331
|
+
"""
|
|
332
|
+
return AqInContextOf(parent)
|
|
333
|
+
|
|
334
|
+
# Patch hamcrest for better descriptions of maps (json data)
|
|
335
|
+
# and sequences
|
|
336
|
+
if six.PY3:
|
|
337
|
+
from io import StringIO
|
|
338
|
+
else: # pragma: no cover
|
|
339
|
+
from cStringIO import StringIO # pylint:disable=import-error
|
|
340
|
+
|
|
341
|
+
_orig_append_description_of = BaseDescription.append_description_of
|
|
342
|
+
def _append_description_of_map(self, value):
|
|
343
|
+
if not hasattr(value, 'describe_to'):
|
|
344
|
+
if isinstance(value, (Mapping, Sequence)):
|
|
345
|
+
sio = StringIO()
|
|
346
|
+
pprint.pprint(value, sio)
|
|
347
|
+
self.append(sio.getvalue())
|
|
348
|
+
return self
|
|
349
|
+
return _orig_append_description_of(self, value)
|
|
350
|
+
|
|
351
|
+
BaseDescription.append_description_of = _append_description_of_map
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TypeCheckedDict(dict):
|
|
355
|
+
"""
|
|
356
|
+
A dictionary that ensures keys and values are of the required type when set
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
def __init__(self, key_class=object, val_class=object, notify=None):
|
|
360
|
+
dict.__init__(self)
|
|
361
|
+
self.key_class = key_class
|
|
362
|
+
self.val_class = val_class
|
|
363
|
+
self.notify = notify
|
|
364
|
+
|
|
365
|
+
def __setitem__(self, key, val):
|
|
366
|
+
assert_that(key, is_(self.key_class))
|
|
367
|
+
assert_that(val, is_(self.val_class))
|
|
368
|
+
dict.__setitem__(self, key, val)
|
|
369
|
+
if self.notify:
|
|
370
|
+
self.notify(key, val)
|
nti/testing/mock.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from __future__ import absolute_import
|
|
4
|
+
from __future__ import division
|
|
5
|
+
from __future__ import print_function
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# This module exists on disk to satisfy static linters;
|
|
10
|
+
# it replaces itself when loaded.
|
|
11
|
+
try:
|
|
12
|
+
from unittest import mock
|
|
13
|
+
except ImportError: # pragma: no cover
|
|
14
|
+
# Python 2
|
|
15
|
+
import mock
|
|
16
|
+
|
|
17
|
+
# More for static linters
|
|
18
|
+
Mock = mock.Mock
|
|
19
|
+
MagicMock = mock.MagicMock
|
|
20
|
+
patch = mock.patch
|
|
21
|
+
|
|
22
|
+
sys.modules[__name__] = mock
|
|
File without changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Tests for base.py.
|
|
5
|
+
|
|
6
|
+
.. $Id$
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import absolute_import
|
|
10
|
+
from __future__ import division
|
|
11
|
+
from __future__ import print_function
|
|
12
|
+
|
|
13
|
+
# stdlib imports
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
from nti.testing import base
|
|
17
|
+
from nti.testing.matchers import has_attr
|
|
18
|
+
|
|
19
|
+
from hamcrest import assert_that
|
|
20
|
+
from hamcrest import is_
|
|
21
|
+
|
|
22
|
+
__docformat__ = "restructuredtext en"
|
|
23
|
+
|
|
24
|
+
logger = __import__('logging').getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
#disable: accessing protected members, too many methods
|
|
27
|
+
#pylint: disable=W0212,R0904
|
|
28
|
+
|
|
29
|
+
class TestBase(unittest.TestCase):
|
|
30
|
+
|
|
31
|
+
def test_package(self):
|
|
32
|
+
import nti.testing
|
|
33
|
+
|
|
34
|
+
class MyTest(base.AbstractTestBase):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
b = MyTest('get_configuration_package')
|
|
38
|
+
assert_that(b.get_configuration_package(),
|
|
39
|
+
is_(nti.testing))
|
|
40
|
+
|
|
41
|
+
def test_cleanup(self):
|
|
42
|
+
called = [0]
|
|
43
|
+
def func():
|
|
44
|
+
called[0] += 1
|
|
45
|
+
|
|
46
|
+
base.addSharedCleanUp(func)
|
|
47
|
+
base.sharedCleanup()
|
|
48
|
+
assert_that(called[0], is_(1))
|
|
49
|
+
|
|
50
|
+
import zope.testing
|
|
51
|
+
|
|
52
|
+
zope.testing.cleanup.cleanUp()
|
|
53
|
+
assert_that(called[0], is_(2))
|
|
54
|
+
|
|
55
|
+
base._shared_cleanups.remove((func, (), {}))
|
|
56
|
+
|
|
57
|
+
def test_explicit_transaction_cleanup(self):
|
|
58
|
+
import transaction
|
|
59
|
+
import zope.testing
|
|
60
|
+
transaction.manager.explicit = True
|
|
61
|
+
transaction.begin()
|
|
62
|
+
|
|
63
|
+
zope.testing.cleanup.cleanUp()
|
|
64
|
+
assert_that(transaction.manager, has_attr('explicit', False))
|
|
65
|
+
|
|
66
|
+
def test_explicit_transaction_cleanup_no_transaction(self):
|
|
67
|
+
import transaction
|
|
68
|
+
import zope.testing
|
|
69
|
+
transaction.manager.explicit = True
|
|
70
|
+
zope.testing.cleanup.cleanUp()
|
|
71
|
+
assert_that(transaction.manager, has_attr('explicit', False))
|
|
72
|
+
|
|
73
|
+
def test_shared_test_base_cover(self):
|
|
74
|
+
# Just coverage.
|
|
75
|
+
import gc
|
|
76
|
+
class MyTest(base.AbstractSharedTestBase):
|
|
77
|
+
HANDLE_GC = True
|
|
78
|
+
def test_thing(self):
|
|
79
|
+
raise AssertionError("Not called")
|
|
80
|
+
|
|
81
|
+
MyTest.setUpClass()
|
|
82
|
+
# pylint:disable-next=simplifiable-if-expression
|
|
83
|
+
assert_that(gc.isenabled(), is_(False if not base._is_pypy else True))
|
|
84
|
+
MyTest.tearDownClass()
|
|
85
|
+
assert_that(gc.isenabled(), is_(True))
|
|
86
|
+
|
|
87
|
+
MyTest('test_thing').setUp()
|
|
88
|
+
MyTest('test_thing').tearDown()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_configuring_base(self):
|
|
92
|
+
import zope.traversing.tests.test_traverser
|
|
93
|
+
class MyTest(base.ConfiguringTestBase):
|
|
94
|
+
set_up_packages = ('zope.component',
|
|
95
|
+
('configure.zcml', 'zope.component'),
|
|
96
|
+
zope.traversing.tests.test_traverser)
|
|
97
|
+
|
|
98
|
+
def test_thing(self):
|
|
99
|
+
raise AssertionError("Not called")
|
|
100
|
+
|
|
101
|
+
mt = MyTest('test_thing')
|
|
102
|
+
mt.setUp() # pylint:disable=no-value-for-parameter
|
|
103
|
+
mt.configure_string('<configure xmlns="http://namespaces.zope.org/zope" />')
|
|
104
|
+
mt.tearDown() # pylint:disable=no-value-for-parameter
|
|
105
|
+
|
|
106
|
+
def test_shared_configuring_base(self):
|
|
107
|
+
import zope.traversing.tests.test_traverser
|
|
108
|
+
class MyTest(base.SharedConfiguringTestBase):
|
|
109
|
+
layer = None # replaced by metaclass
|
|
110
|
+
set_up_packages = ('zope.component',
|
|
111
|
+
('configure.zcml', 'zope.component'),
|
|
112
|
+
zope.traversing.tests.test_traverser)
|
|
113
|
+
|
|
114
|
+
def test_thing(self):
|
|
115
|
+
raise AssertionError("Not called")
|
|
116
|
+
|
|
117
|
+
MyTest.setUpClass()
|
|
118
|
+
MyTest.tearDownClass()
|
|
119
|
+
|
|
120
|
+
# It should have a layer automatically
|
|
121
|
+
assert_that(MyTest, has_attr('layer', has_attr('__name__', 'MyTest')))
|
|
122
|
+
|
|
123
|
+
MyTest.layer.setUp()
|
|
124
|
+
MyTest.layer.testSetUp()
|
|
125
|
+
MyTest.layer.testTearDown()
|
|
126
|
+
MyTest.layer.tearDown()
|
|
127
|
+
|
|
128
|
+
MyTest('test_thing').tearDown()
|
|
129
|
+
|
|
130
|
+
def test_module_setup(self):
|
|
131
|
+
base.module_setup()
|
|
132
|
+
base.module_setup(set_up_packages=('zope.component',))
|
|
133
|
+
base.module_teardown()
|
|
134
|
+
|
|
135
|
+
def test_calling_super_linting(self):
|
|
136
|
+
# Deriving a class from our bases and calling their setup
|
|
137
|
+
# methods should NOT produce linter warnings.
|
|
138
|
+
# So this section should not have pylint disable commands.
|
|
139
|
+
class X(base.AbstractTestBase):
|
|
140
|
+
def setUp(self):
|
|
141
|
+
super().setUp()
|
|
142
|
+
self.thing = 1
|
|
143
|
+
|
|
144
|
+
def tearDown(self):
|
|
145
|
+
super().tearDown()
|
|
146
|
+
self.thing = 2
|
|
147
|
+
|
|
148
|
+
class Y(base.ConfiguringTestBase):
|
|
149
|
+
def setUp(self):
|
|
150
|
+
super().setUp()
|
|
151
|
+
self.thing = 1
|
|
152
|
+
|
|
153
|
+
def tearDown(self):
|
|
154
|
+
super().tearDown()
|
|
155
|
+
self.thing = 2
|
|
156
|
+
|
|
157
|
+
X().setUp()
|
|
158
|
+
X().tearDown()
|
|
159
|
+
Y().setUp()
|
|
160
|
+
Y().tearDown()
|