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.
@@ -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
@@ -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()