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,137 @@
1
+ When zope.component.hooks is loaded, it installs a zope.testing
2
+ cleanup function. However, prior to version 4.2.0, this cleanup
3
+ function isn't adequote and leaves stale data around, leading
4
+ zope.interface adapters to improperly adapt things (a particular
5
+ problem when running test suites that load different adapter
6
+ configurations) This problem goes away if zope.site.site is imported
7
+ and installs its cleanup hooks.
8
+
9
+ Although the bug has been fixed, it was an interesting problem, and
10
+ this file remains to provide an example of working with ZCA internals.
11
+ Tests that are now fixed are marked as skipped.
12
+
13
+ Demonstration
14
+ =============
15
+
16
+ We can demonstrate this with some code.
17
+
18
+ First we import the basic modules::
19
+
20
+ >>> from zope.testing import cleanup
21
+ >>> from zope import interface
22
+ >>> from zope import component
23
+ >>> from zope.component import hooks
24
+
25
+ Next, we define an interface and two different adapters::
26
+
27
+ >>> class II(interface.Interface): pass
28
+ >>> class O(object): pass
29
+ >>> class A1(object):
30
+ ... def __init__( self, *args):
31
+ ... pass
32
+ ... def __repr__( self ):
33
+ ... return "<A1>"
34
+ >>> class A2(object):
35
+ ... def __init__( self, *args):
36
+ ... pass
37
+ ... def __repr__( self ):
38
+ ... return "<A2>"
39
+
40
+ We can now proceed to run our "unit tests". Each of these unit tests
41
+ will begin by installing the component hooks and providing some base
42
+ configuration in the form of registering the A1 adapter::
43
+
44
+ >>> hooks.setHooks()
45
+ >>> component.provideAdapter( A1, adapts=(O,), provides=II )
46
+
47
+ If we do nothing further, we can get get back the A1 adapter when we
48
+ ask for it from both zope.component, and thanks to the adapter hook, zope.interface::
49
+
50
+ >>> component.getAdapter( O(), II )
51
+ <A1>
52
+ >>> II( O() )
53
+ <A1>
54
+
55
+ Lets suppose some tests start with the base configuration and override
56
+ it, installing the second adapter::
57
+
58
+ >>> component.provideAdapter( A2, adapts=(O,), provides=II )
59
+
60
+ This adapter can now be accessed in both of those places::
61
+
62
+ >>> component.getAdapter( O(), II )
63
+ <A2>
64
+ >>> II( O() )
65
+ <A2>
66
+
67
+ Finally, our test case shuts down and the cleanup is run::
68
+
69
+ >>> cleanup.cleanUp()
70
+
71
+ To demonstrate the problem, let's begin the next test case run with the
72
+ same basic setup as before:::
73
+
74
+ >>> hooks.setHooks()
75
+ >>> component.provideAdapter( A1, adapts=(O,), provides=II )
76
+
77
+ At this point, we would expect to get back A1 when we ask for
78
+ adapters. If we ask the global site manager directly for it, we're
79
+ alright::
80
+
81
+ >>> component.getGlobalSiteManager().queryAdapter( O(), II )
82
+ <A1>
83
+
84
+ But if we ask the (hooked) global API, we have a problem::
85
+
86
+ >>> component.queryAdapter( O(), II ) # doctest: +SKIP
87
+ <A2>
88
+
89
+ And we have the same problem if we ask zope.interface::
90
+
91
+ >>> II( O() ) # doctest: +SKIP
92
+ <A2>
93
+
94
+ You can see that we get back the A2 registration, which should no
95
+ longer be here, as the cleanup hooks have run. zope.interface's
96
+ cleanup hooks reset the entire global registry, in fact, by re-running
97
+ its __init__ method. What's causing this?
98
+
99
+ A clue comes in the form of realizing that this doesn't happen all the
100
+ time. In fact, as soon as zope.site.site is imported, it no longer
101
+ happens at all::
102
+
103
+ >>> from zope.site import site
104
+
105
+ zope.site.site installs a cleanup function that calls
106
+ zope.component.hooks.setSite to clear the site::
107
+
108
+ >>> cleanup.cleanUp()
109
+
110
+ Now the next time a test runs, the ancient A2 registration is truly gone::
111
+
112
+ >>> hooks.setHooks()
113
+ >>> component.provideAdapter( A1, adapts=(O,), provides=II )
114
+
115
+ both from zope.component::
116
+
117
+ >>> component.queryAdapter( O(), II )
118
+ <A1>
119
+
120
+ and zope.interface::
121
+
122
+ >>> II( O() )
123
+ <A1>
124
+
125
+ Analysis
126
+ ========
127
+
128
+ In a nutshell, what appears to be happening is that
129
+ zope.component.hooks.SiteInfo caches the adapter_hook of the current
130
+ site manager's `adapters` property the first time it is accessed.
131
+ However, the cleanup for the globalSiteManager completely *replaces*
132
+ its `adapters` property with a new object, leaving SiteInfo holding a
133
+ dangling reference to an adapter registry that is no longer installed
134
+ anywhere. Importing zope.site.site causes the
135
+ zope.component.hooks.setSite to be called to clear out the site, which
136
+ causes the cached adapter_hook to be deleted, thus letting it get a
137
+ new reference to the current adapter registry.
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Test for layers.py.
5
+ """
6
+
7
+ from __future__ import absolute_import
8
+ from __future__ import division
9
+ from __future__ import print_function
10
+
11
+ # stdlib imports
12
+ import unittest
13
+
14
+ from nti.testing import layers
15
+
16
+ from hamcrest import assert_that
17
+ from hamcrest import is_
18
+
19
+ __docformat__ = "restructuredtext en"
20
+
21
+
22
+ class TestLayers(unittest.TestCase):
23
+
24
+ def test_find_test(self):
25
+
26
+ def func():
27
+ assert_that(layers.find_test(), is_(self))
28
+
29
+ func()
30
+
31
+ def via_test(test): # pylint:disable=unused-argument
32
+ func()
33
+
34
+ via_test(self)
35
+
36
+ def _check_methods(self, layer, extra_methods=()):
37
+ default_methods = ('setUp', 'tearDown', 'testSetUp', 'testTearDown')
38
+ for meth in default_methods + extra_methods:
39
+ getattr(layer, meth)()
40
+
41
+ def test_gc(self):
42
+
43
+ gcm = layers.GCLayerMixin()
44
+ self._check_methods(gcm, ('setUpGC', 'tearDownGC'))
45
+
46
+ def test_shared_cleanup(self):
47
+ self._check_methods(layers.SharedCleanupLayer)
48
+
49
+ def test_zcl(self):
50
+ self._check_methods(layers.ZopeComponentLayer)
51
+
52
+ def test_configuring_layer_mixin(self):
53
+ class Layer(layers.ConfiguringLayerMixin):
54
+ set_up_packages = ('zope.component',)
55
+
56
+ self._check_methods(Layer, ('setUpPackages', 'tearDownPackages'))
@@ -0,0 +1,47 @@
1
+ from __future__ import absolute_import
2
+ from __future__ import division
3
+ from __future__ import print_function
4
+
5
+ # stdlib imports
6
+ import doctest
7
+ import os
8
+ import unittest
9
+
10
+
11
+ class TestImport(unittest.TestCase):
12
+ def test_import(self):
13
+ for name in ('base', 'layers', 'matchers', 'time'):
14
+ __import__('nti.testing.' + name)
15
+
16
+ def test_mock_direct_import(self):
17
+ from nti.testing.mock import Mock
18
+ from nti.testing import mock
19
+ self.assertIs(mock.Mock, Mock)
20
+
21
+ def test_mock_module_identity(self):
22
+ from nti.testing import mock
23
+ try:
24
+ from unittest import mock as stdmock
25
+ except ImportError: # pragma: no cover
26
+ # Python 2
27
+ import mock as stdmock
28
+
29
+ self.assertIs(mock, stdmock)
30
+
31
+ def test_suite():
32
+ here = os.path.dirname(__file__)
33
+
34
+ suite = unittest.defaultTestLoader.loadTestsFromName(__name__)
35
+ suite.addTest(doctest.DocFileSuite(
36
+ 'test_component_cleanup_broken.txt'))
37
+
38
+ readmedir = here
39
+ while not os.path.exists(os.path.join(readmedir, 'setup.py')):
40
+ readmedir = os.path.dirname(readmedir)
41
+ readme = os.path.join(readmedir, 'README.rst')
42
+ suite.addTest(doctest.DocFileSuite(
43
+ readme,
44
+ module_relative=False,
45
+ optionflags=doctest.ELLIPSIS,
46
+ ))
47
+ return suite
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Tests for matchers.py.
5
+ """
6
+
7
+ from __future__ import absolute_import
8
+ from __future__ import division
9
+ from __future__ import print_function
10
+
11
+ # stdlib imports
12
+ import unittest
13
+
14
+ from hamcrest import assert_that
15
+ from hamcrest import calling
16
+
17
+ from hamcrest import raises
18
+
19
+ from nti.testing import matchers
20
+
21
+ __docformat__ = "restructuredtext en"
22
+
23
+
24
+ # pylint:disable=inherit-non-class,redundant-u-string-prefix
25
+
26
+
27
+ class TestMatchers(unittest.TestCase):
28
+
29
+ def test_bool(self):
30
+ assert_that(True, matchers.is_true())
31
+ assert_that(1, matchers.is_true())
32
+
33
+ assert_that(False, matchers.is_false())
34
+ assert_that(0, matchers.is_false())
35
+
36
+
37
+ assert_that(calling(assert_that).with_args(1, matchers.is_false()),
38
+ raises(AssertionError))
39
+
40
+ assert_that(calling(assert_that).with_args(u'', matchers.is_true()),
41
+ raises(AssertionError))
42
+ assert_that(calling(assert_that).with_args(b'', matchers.is_true()),
43
+ raises(AssertionError))
44
+
45
+ def test_provides(self):
46
+
47
+ from zope import interface
48
+
49
+ class IThing(interface.Interface):
50
+ pass
51
+
52
+ @interface.implementer(IThing)
53
+ class Thing(object):
54
+ pass
55
+
56
+ assert_that(Thing(), matchers.provides(IThing))
57
+ assert_that(calling(assert_that).with_args(self, matchers.provides(IThing)),
58
+ raises(AssertionError, "object providing"))
59
+
60
+ def test_implements(self):
61
+
62
+ from zope import interface
63
+
64
+ class IThing(interface.Interface):
65
+ pass
66
+
67
+ @interface.implementer(IThing)
68
+ class Thing(object):
69
+ pass
70
+
71
+ assert_that(Thing, matchers.implements(IThing))
72
+ assert_that(calling(assert_that).with_args(self, matchers.implements(IThing)),
73
+ raises(AssertionError, "object implementing"))
74
+
75
+
76
+ def test_verifiably_provides(self):
77
+
78
+ from zope import interface
79
+
80
+ class IThing(interface.Interface):
81
+ thing = interface.Attribute("thing")
82
+
83
+ def method(): # pylint:disable=no-method-argument
84
+ """Does nothing."""
85
+
86
+ @interface.implementer(IThing)
87
+ class Thing(object):
88
+ thing = 1
89
+
90
+ def method(self):
91
+ raise AssertionError("Not called")
92
+
93
+ assert_that(Thing(), matchers.verifiably_provides(IThing, IThing))
94
+ # We have a nice multi-line error message, but we can only match one line at a time
95
+ for line in (
96
+ 'verifiably providing .*IThing',
97
+ 'Using class.*has failed to implement',
98
+ 'Does not declaratively implement',
99
+ 'thing attribute was not provided',
100
+ r'method\(\) attribute was not provided',
101
+ ):
102
+ assert_that(calling(assert_that).with_args(self, matchers.verifiably_provides(IThing)),
103
+ raises(AssertionError, line))
104
+
105
+ broken_thing = Thing()
106
+ broken_thing.method = None
107
+ assert_that(calling(assert_that).with_args(broken_thing,
108
+ matchers.verifiably_provides(IThing)),
109
+ raises(AssertionError, r"The contract of.*method\(\) is violated"))
110
+
111
+ del Thing.thing
112
+ assert_that(calling(assert_that).with_args(Thing(), matchers.verifiably_provides(IThing)),
113
+ raises(AssertionError, "thing attribute was not provided"))
114
+
115
+ def test_validly_provides(self):
116
+
117
+ from zope import interface
118
+ from zope.schema import Int
119
+
120
+ class IThing(interface.Interface):
121
+ thing = Int()
122
+
123
+ @interface.implementer(IThing)
124
+ class Thing(object):
125
+ thing = 1
126
+
127
+ assert_that(Thing(), matchers.validly_provides(IThing, IThing))
128
+ # We have a nice multi-line error message, but we can only match one line at a time
129
+ for line in (
130
+ 'verifiably providing .*IThing.*validly providing.*IThing',
131
+ 'Using class.*has failed to implement',
132
+ 'Does not declaratively implement',
133
+ 'thing attribute was not provided'
134
+ ):
135
+ assert_that(calling(assert_that).with_args(self, matchers.validly_provides(IThing)),
136
+ raises(AssertionError, line))
137
+
138
+ broken_thing = Thing()
139
+ broken_thing.thing = "not an int"
140
+
141
+ assert_that(calling(assert_that).with_args(broken_thing, matchers.validly_provides(IThing)),
142
+ raises(AssertionError, "has attribute"))
143
+
144
+ def test_validated_by(self):
145
+ from zope.schema import Int
146
+ from zope.schema.interfaces import WrongType
147
+
148
+ assert_that(1, matchers.validated_by(Int()))
149
+ assert_that('', matchers.not_validated_by(Int()))
150
+ with self.assertRaises(WrongType):
151
+ assert_that('', matchers.not_validated_by(Int(), invalid=NameError))
152
+ assert_that(calling(assert_that).with_args('', matchers.validated_by(Int())),
153
+ raises(AssertionError, "failed to validate"))
154
+
155
+ def test_validated_by_defaults_to_Invalid(self):
156
+ from zope.interface.exceptions import Invalid
157
+ class Validator(object):
158
+ def validate(self, o):
159
+ raise o
160
+
161
+ validator = Validator()
162
+ class Arbitrary(Exception):
163
+ pass
164
+
165
+ with self.assertRaises(Arbitrary):
166
+ assert_that(Arbitrary(), matchers.validated_by(validator))
167
+
168
+ assert_that(calling(assert_that).with_args(Invalid(),
169
+ matchers.validated_by(validator)),
170
+ raises(AssertionError, "failed to validate"))
171
+
172
+ assert_that(calling(assert_that).with_args(Arbitrary,
173
+ matchers.validated_by(validator,
174
+ invalid=Exception)),
175
+ raises(AssertionError, "failed to validate"))
176
+
177
+
178
+ def test_dict(self):
179
+
180
+ d = matchers.TypeCheckedDict(key_class=int, notify=lambda k, v: None)
181
+
182
+ d[1] = 'abc'
183
+
184
+ assert_that(calling(d.__setitem__).with_args('abc', 'def'),
185
+ raises(AssertionError, "instance of int"))
186
+
187
+ def test_in_context(self):
188
+ # pylint:disable=attribute-defined-outside-init
189
+ class Thing(object):
190
+ def __init__(self, name, parent=None):
191
+ self.__name__ = name
192
+ if parent:
193
+ self.__parent__ = parent
194
+ def __repr__(self):
195
+ return '<Thing %s>' % (self.__name__,)
196
+
197
+ parent = Thing('parent')
198
+ child = Thing('child', parent)
199
+ sibling = Thing('sibling', parent)
200
+
201
+ assert_that(child, matchers.aq_inContextOf(parent))
202
+
203
+ with self.assertRaises(AssertionError) as exc:
204
+ assert_that(parent, matchers.aq_inContextOf('ROOT'))
205
+ ex = str(exc.exception)
206
+ del exc
207
+ self.assertIn('object in context of', ex)
208
+ self.assertIn('<Thing parent> was not in the context of \'ROOT\'\n; its lineage is []', ex)
209
+
210
+ with self.assertRaises(AssertionError) as exc:
211
+ assert_that(sibling, matchers.aq_inContextOf(child))
212
+ ex = str(exc.exception)
213
+ del exc
214
+ self.assertIn('object in context of', ex)
215
+ self.assertIn('<Thing sibling> was not in the context of <Thing child>', ex)
216
+ self.assertIn('its lineage is [<Thing parent>]', ex)
217
+
218
+
219
+ # fake wrapper
220
+ child.aq_inContextOf = lambda thing: thing == child.__parent__
221
+ assert_that(child, matchers.aq_inContextOf(parent))
222
+
223
+ def test_in_context_no_Acquisition(self):
224
+ # pylint:disable=protected-access
225
+ orig_in_context = matchers._aq_inContextOf
226
+ matchers._aq_inContextOf = matchers._aq_inContextOf_NotImplemented
227
+ try:
228
+ with self.assertRaises(AssertionError) as exc:
229
+ assert_that(self, matchers.aq_inContextOf(self))
230
+
231
+ finally:
232
+ matchers._aq_inContextOf = orig_in_context
233
+
234
+ ex = str(exc.exception)
235
+ del exc
236
+ self.assertIn('object in context of', ex)
237
+ self.assertIn('Acquisition was not installed', ex)
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from __future__ import absolute_import
5
+ from __future__ import division
6
+ from __future__ import print_function
7
+
8
+ # stdlib imports
9
+ import unittest
10
+
11
+ from nti.testing.time import reset_monotonic_time
12
+ from nti.testing.time import time_monotonically_increases
13
+
14
+ from hamcrest import assert_that
15
+ from hamcrest import greater_than
16
+ from hamcrest import greater_than_or_equal_to
17
+ from hamcrest import is_
18
+ from hamcrest import less_than
19
+
20
+ __docformat__ = "restructuredtext en"
21
+
22
+ class TestTime(unittest.TestCase):
23
+
24
+ def _check_time(self, granularity=1.0):
25
+ import time
26
+ for _ in range(10):
27
+ before = time.time()
28
+ after = time.time()
29
+ assert_that(after, is_(greater_than_or_equal_to(before + granularity)))
30
+
31
+
32
+ assert_that(time.gmtime(after), is_(time.gmtime(after)))
33
+ assert_that(time.gmtime(after), is_(greater_than_or_equal_to(time.gmtime(before))))
34
+
35
+ if granularity >= 1.0:
36
+ gm_before = time.gmtime()
37
+ gm_after = time.gmtime()
38
+ assert_that(gm_after, is_(greater_than(gm_before)))
39
+ return after
40
+
41
+ @time_monotonically_increases
42
+ def test_increases_in_method(self):
43
+ self._check_time()
44
+
45
+ def test_increases_in_func(self):
46
+
47
+ @time_monotonically_increases
48
+ def f():
49
+ self._check_time()
50
+
51
+ f()
52
+
53
+ def test_func_takes_args(self):
54
+
55
+ @time_monotonically_increases
56
+ def f(a, b, c=42):
57
+ return a, b, c
58
+
59
+ assert_that(f(1, 2), is_((1, 2, 42)))
60
+ assert_that(f(1, 2, 3), is_((1, 2, 3)))
61
+ assert_that(f(1, 2, c=9), is_((1, 2, 9)))
62
+
63
+ def test_increases_relative_to_real(self):
64
+ import time
65
+ before_all = time.time()
66
+
67
+ @time_monotonically_increases
68
+ def f():
69
+ return self._check_time()
70
+
71
+ after_first = f()
72
+ assert_that(after_first, is_(greater_than(before_all)))
73
+
74
+
75
+ def test_increases_across_funcs(self):
76
+ reset_monotonic_time()
77
+
78
+ granularity = 0.1
79
+ import time
80
+ before_all = time.time()
81
+
82
+
83
+ @time_monotonically_increases(granularity)
84
+ def f1():
85
+ return self._check_time(granularity)
86
+
87
+ @time_monotonically_increases(granularity)
88
+ def f2():
89
+ return self._check_time(granularity)
90
+
91
+ @time_monotonically_increases(granularity)
92
+ def f3():
93
+ return time.time()
94
+
95
+ self._check_across_funcs(before_all, f1, f2, f3)
96
+
97
+ def _check_across_funcs(self, before_all, f1, f2, f3):
98
+ from nti.testing.time import _real_time
99
+ after_first = f1()
100
+
101
+ assert_that(after_first, is_(greater_than(before_all)))
102
+
103
+ after_second = f2()
104
+ current_real = _real_time()
105
+
106
+ # The loop in self._check_time incremented the clock by a full second.
107
+ # That function should have taken far less time to actually run than that, though,
108
+ # so the real time function should be *behind*
109
+ assert_that(current_real, is_(less_than(after_second)))
110
+
111
+
112
+ # And immediately running it again will continue to produce values that
113
+ # are ever larger.
114
+ after_second = f2()
115
+ current_real = _real_time()
116
+
117
+ assert_that(current_real, is_(less_than(after_second)))
118
+
119
+ assert_that(after_second, is_(greater_than(before_all)))
120
+ assert_that(after_second, is_(greater_than(after_first)))
121
+
122
+ # We are now some number of seconds ahead. If we're the only ones calling
123
+ # time.time(), it would be about 3. But we're not, various parts of the lib
124
+ # apparently are calling time.time() too, which makes it unpredictable.
125
+ # We don't want to sleep for a long time, so we
126
+ # reset the clock again to prove that we can go back to real time.
127
+
128
+ # Use a value in the distant past to account for the calls that get made.
129
+ reset_monotonic_time(-50)
130
+
131
+ after_third = f3()
132
+ current_real = _real_time()
133
+ assert_that(current_real, is_(greater_than_or_equal_to(after_third)))
134
+
135
+
136
+ def test_layer(self):
137
+ from nti.testing.time import MonotonicallyIncreasingTimeLayerMixin
138
+ import time
139
+ granularity = 0.1
140
+
141
+ def f1():
142
+ return self._check_time(granularity)
143
+
144
+ def f2():
145
+ return self._check_time(granularity)
146
+
147
+ def f3():
148
+ return time.time()
149
+
150
+ before_all = time.time()
151
+
152
+ layer = MonotonicallyIncreasingTimeLayerMixin(granularity)
153
+ layer.testSetUp()
154
+ try:
155
+ self._check_across_funcs(before_all, f1, f2, f3)
156
+ finally:
157
+ layer.testTearDown()