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,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()
|