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,326 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Tests for zodb.py
4
+
5
+ """
6
+ from __future__ import absolute_import
7
+ from __future__ import division
8
+ from __future__ import print_function
9
+
10
+ import io
11
+ import gc
12
+ import unittest
13
+
14
+ import transaction
15
+ from transaction.interfaces import NoTransaction
16
+ from zope import interface
17
+ from nti.testing import zodb
18
+
19
+ # pylint:disable=protected-access,pointless-string-statement
20
+
21
+ NativeStringIO = io.BytesIO if str is bytes else io.StringIO
22
+
23
+ class MockDB(object):
24
+
25
+ def __init__(self):
26
+ self.pool = []
27
+
28
+ def open(self):
29
+ conn = MockConn()
30
+ self.pool.append(conn)
31
+ return conn
32
+
33
+
34
+ class MockConn(object):
35
+ closed = False
36
+ minimized = False
37
+ def close(self):
38
+ self.closed = True
39
+
40
+ def cacheMinimize(self):
41
+ self.minimized = True
42
+
43
+
44
+ class MockDBTrans(zodb.mock_db_trans):
45
+
46
+ def __init__(self, db=None):
47
+ if db is None:
48
+ db = MockDB()
49
+ super().__init__(db)
50
+ self.exc_file = NativeStringIO()
51
+
52
+ class TestMockDBTrans(unittest.TestCase):
53
+
54
+ def setUp(self):
55
+ self._was_explicit = transaction.manager.explicit
56
+ transaction.manager.explicit = False
57
+
58
+ def tearDown(self):
59
+ transaction.manager.explicit = self._was_explicit
60
+ transaction.abort()
61
+
62
+ def test_begins_ends_transaction(self):
63
+ transaction.manager.explicit = True
64
+ with self.assertRaises(NoTransaction):
65
+ transaction.get()
66
+
67
+ with MockDBTrans():
68
+ self.assertIsNotNone(transaction.get())
69
+
70
+ transaction.manager.explicit = True
71
+ with self.assertRaises(NoTransaction):
72
+ transaction.get()
73
+
74
+ def test_sets_txm_explicit(self):
75
+ with MockDBTrans():
76
+ self.assertTrue(transaction.manager.explicit)
77
+ self.assertFalse(transaction.manager.explicit)
78
+
79
+ def test_error_if_mode_changed(self):
80
+ with self.assertRaises(zodb._TransactionManagerModeChanged):
81
+ with MockDBTrans():
82
+ transaction.manager.explicit = False
83
+
84
+ def test_error_if_mode_changed_and_error_in_body(self):
85
+ with self.assertRaises(zodb._TransactionManagerModeChanged) as exc:
86
+ with MockDBTrans():
87
+ transaction.manager.explicit = False
88
+ raise Exception("BodyError") # pylint:disable=broad-exception-raised
89
+ # The backing exception is included
90
+ self.assertIn('BodyError', str(exc.exception))
91
+
92
+ def test_error_if_tx_ended(self):
93
+ with self.assertRaises(zodb._TransactionChanged):
94
+ with MockDBTrans():
95
+ transaction.commit()
96
+
97
+ def test_error_if_tx_changed(self):
98
+ with self.assertRaises(zodb._TransactionChanged):
99
+ with MockDBTrans():
100
+ transaction.commit()
101
+ transaction.begin()
102
+
103
+ def test_error_if_tx_changed_aborts_new(self):
104
+ transaction.manager.explicit = True
105
+ with self.assertRaises(zodb._TransactionChanged):
106
+ with MockDBTrans():
107
+ transaction.commit()
108
+ transaction.begin()
109
+ with self.assertRaises(NoTransaction):
110
+ transaction.get()
111
+
112
+ def test_returns_connection_it_closes(self):
113
+ with MockDBTrans() as conn:
114
+ self.assertIsInstance(conn, MockConn)
115
+
116
+ self.assertTrue(conn.closed)
117
+ self.assertTrue(conn.minimized)
118
+
119
+ def test_aborts_doomed_tx(self):
120
+ aborted = []
121
+ with MockDBTrans():
122
+ transaction.doom()
123
+ tx = transaction.get()
124
+ abort = tx.abort
125
+
126
+ def _abort():
127
+ abort()
128
+ aborted.append(1)
129
+ tx.abort = _abort
130
+
131
+ self.assertEqual(aborted, [1])
132
+
133
+ def test_closing_conn_error_not_shadow_body_error(self):
134
+ class ConnEx(Exception):
135
+ pass
136
+
137
+ class BodyEx(Exception):
138
+ pass
139
+
140
+ def e():
141
+ raise ConnEx
142
+
143
+ mock_db_trans = MockDBTrans()
144
+ with self.assertRaises(BodyEx):
145
+ with mock_db_trans as conn:
146
+ conn.close = e
147
+ raise BodyEx
148
+
149
+ msg = mock_db_trans.exc_file.getvalue()
150
+ self.assertIn('Unexpected error closing connection', msg)
151
+ self.assertIn('Module nti.testing.zodb', msg)
152
+
153
+
154
+ def test_aborts_on_abort_error(self):
155
+ aborted = []
156
+ class AbortEx(Exception):
157
+ pass
158
+ with self.assertRaises(AbortEx):
159
+ with MockDBTrans():
160
+ transaction.doom()
161
+ tx = transaction.get()
162
+ abort = tx.abort
163
+
164
+ def _abort():
165
+ abort()
166
+ aborted.append(1)
167
+ del tx.abort
168
+ raise AbortEx
169
+ tx.abort = _abort
170
+
171
+ self.assertEqual(aborted, [1])
172
+
173
+ def test_aborts_on_commit_error(self):
174
+ committed = []
175
+ class CommitEx(Exception):
176
+ pass
177
+ with self.assertRaises(CommitEx):
178
+ with MockDBTrans():
179
+ tx = transaction.get()
180
+ commit = tx.commit
181
+
182
+ def _commit():
183
+ commit()
184
+ committed.append(1)
185
+ del tx.commit
186
+ raise CommitEx
187
+ tx.commit = _commit
188
+
189
+ self.assertEqual(committed, [1])
190
+
191
+ def test_abort_error_does_not_shadow_body_error(self):
192
+ aborted = []
193
+ class AbortEx(Exception):
194
+ pass
195
+ class BodyEx(Exception):
196
+ pass
197
+ mock_db_trans = MockDBTrans()
198
+ with self.assertRaises(BodyEx):
199
+ with mock_db_trans:
200
+ transaction.doom()
201
+ tx = transaction.get()
202
+ abort = tx.abort
203
+
204
+ def _abort():
205
+ abort()
206
+ aborted.append(1)
207
+ del tx.abort
208
+ raise AbortEx
209
+ tx.abort = _abort
210
+ raise BodyEx
211
+
212
+ self.assertEqual(aborted, [1])
213
+ msg = mock_db_trans.exc_file.getvalue()
214
+ self.assertIn('Failed to cleanup trans', msg)
215
+ self.assertIn('Module nti.testing.zodb', msg)
216
+
217
+ def test_error_in_on_opened(self):
218
+ class OpenEx(Exception):
219
+ pass
220
+
221
+ class MyFalse(object):
222
+ def __bool__(self):
223
+ return False
224
+
225
+ transaction.manager.explicit = MyFalse()
226
+ class MyMock(MockDBTrans):
227
+ seen_tx = None
228
+ aborted = False
229
+ def on_connection_opened(self, conn):
230
+ # pylint:disable=no-member
231
+ self.seen_tx = self._mock_db_trans__current_transaction
232
+ abort = self.seen_tx.abort
233
+ def _abort():
234
+ abort()
235
+ self.aborted = True
236
+ del self.seen_tx.abort
237
+ self.seen_tx.abort = _abort
238
+ raise OpenEx
239
+
240
+ db = MockDB()
241
+ my_mock = MyMock(db)
242
+ with self.assertRaises(OpenEx):
243
+ with my_mock:
244
+ "We never get here"
245
+
246
+ # The txm mode was restored
247
+ self.assertFalse(transaction.manager.explicit)
248
+ self.assertIsInstance(transaction.manager.explicit, MyFalse)
249
+ # the conn was opened, and then closed
250
+ self.assertTrue(db.pool[0].closed)
251
+ self.assertIsNone(my_mock.conn)
252
+ # The transaction that was opened is cleaned up
253
+ self.assertTrue(my_mock.aborted)
254
+
255
+
256
+ class TestZODBLayer(unittest.TestCase):
257
+
258
+ layer = zodb.ZODBLayer
259
+
260
+ def test_registration(self):
261
+ from ZODB.interfaces import IDatabase
262
+ from zope import component
263
+ gsm = component.getGlobalSiteManager()
264
+ reg_db = gsm.getUtility(IDatabase)
265
+ self.assertIsNotNone(reg_db)
266
+ self.assertIs(reg_db, self.layer.db)
267
+
268
+ def test_premature_teardown(self):
269
+ self.layer.tearDown()
270
+ from ZODB.interfaces import IDatabase
271
+ from zope import component
272
+ gsm = component.getGlobalSiteManager()
273
+ reg_db = gsm.queryUtility(IDatabase)
274
+ self.assertIsNone(reg_db)
275
+
276
+ self.layer.setUp()
277
+
278
+
279
+ class IExtra(interface.Interface): # pylint:disable=inherit-non-class
280
+ """An extra interface."""
281
+
282
+ class IExtra2(interface.Interface): # pylint:disable=inherit-non-class
283
+ """Another extra interface."""
284
+
285
+ class Object(object):
286
+
287
+ def __init__(self, *args):
288
+ pass
289
+
290
+ class TestResetDbCaches(unittest.TestCase):
291
+ layer = zodb.ZODBLayer
292
+
293
+ def setUp(self):
294
+ super().setUp()
295
+ gc.disable()
296
+
297
+ def tearDown(self):
298
+ gc.enable()
299
+ super().tearDown()
300
+
301
+ def test_persistent_site_closed(self):
302
+ from zope.site.site import LocalSiteManager
303
+ from ZODB import DB
304
+ from ZODB.DemoStorage import DemoStorage
305
+
306
+ interface.classImplements(LocalSiteManager, IExtra)
307
+
308
+ db = DB(DemoStorage())
309
+ transaction.begin()
310
+ conn = db.open()
311
+ site_man = LocalSiteManager(None)
312
+ conn.root()[0] = site_man
313
+ site_man.registerAdapter(Object, [IExtra], IExtra2)
314
+ site_man.getAdapter(LocalSiteManager(None), IExtra2)
315
+ transaction.commit()
316
+ conn.close()
317
+
318
+ zodb.reset_db_caches(db)
319
+ IExtra.changed(None) # pylint:disable=no-value-for-parameter
320
+
321
+ def test_arguments(self):
322
+ # Don't pass the DB, let it get the one from the layer.
323
+ # But do ask for collect, so we get something non-negative on
324
+ # all platforms.
325
+ collected = zodb.reset_db_caches(collect=True)
326
+ self.assertNotEqual(collected, -1)
nti/testing/time.py ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Test support for working with clocks and time.
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 functools
13
+ from threading import Lock
14
+ import time
15
+ from time import time as _real_time
16
+ from time import gmtime as _real_gmtime
17
+
18
+ import six
19
+
20
+ from nti.testing.mock import Mock
21
+
22
+ __docformat__ = "restructuredtext en"
23
+
24
+ _current_time = _real_time()
25
+
26
+
27
+ class _TimeWrapper(object):
28
+
29
+ def __init__(self, granularity=1.0):
30
+ self._granularity = granularity
31
+ self._lock = Lock()
32
+ self.fake_gmtime = Mock()
33
+ self.fake_time = Mock()
34
+ self._configure_fakes()
35
+
36
+ def _configure_fakes(self):
37
+ def incr():
38
+ global _current_time # pylint:disable=global-statement
39
+ with self._lock:
40
+ _current_time = max(_real_time(), _current_time + self._granularity)
41
+ return _current_time
42
+ self.fake_time.side_effect = incr
43
+
44
+ def incr_gmtime(*seconds):
45
+ if seconds:
46
+ assert len(seconds) == 1
47
+ now = seconds[0]
48
+ else:
49
+ now = incr()
50
+ return _real_gmtime(now)
51
+ self.fake_gmtime.side_effect = incr_gmtime
52
+
53
+ def install_fakes(self):
54
+ time.time = self.fake_time
55
+ time.gmtime = self.fake_gmtime
56
+
57
+ __enter__ = install_fakes
58
+
59
+ def close(self, *_args):
60
+ time.time = _real_time
61
+ time.gmtime = _real_gmtime
62
+
63
+ __exit__ = close
64
+
65
+ def __call__(self, func):
66
+ @functools.wraps(func)
67
+ def wrapper(*args, **kwargs):
68
+ with self:
69
+ return func(*args, **kwargs)
70
+ return wrapper
71
+
72
+
73
+
74
+ def time_monotonically_increases(func_or_granularity):
75
+ """
76
+ Decorate a unittest method with this function to cause the value
77
+ of :func:`time.time` (and :func:`time.gmtime`) to monotonically
78
+ increase by one each time it is called. This ensures things like
79
+ last modified dates always increase.
80
+
81
+ We make three guarantees about the value of :func:`time.time`
82
+ returned while the decorated function is running:
83
+
84
+ 1. It is always *at least* the value of the *real*
85
+ :func:`time.time`;
86
+
87
+ 2. Each call returns a value greater than the previous call;
88
+
89
+ 3. Those two constraints hold across different invocations of
90
+ functions decorated.
91
+
92
+ This decorator can be applied to a method in a test case::
93
+
94
+ class TestThing(unittest.TestCase)
95
+ @time_monotonically_increases
96
+ def test_method(self):
97
+ t = time.time()
98
+ ...
99
+
100
+ It can also be applied to a bare function taking any number of
101
+ arguments::
102
+
103
+ @time_monotonically_increases
104
+ def utility_function(a, b, c=1):
105
+ t = time.time()
106
+ ...
107
+
108
+ By default, the time will be incremented in 1.0 second intervals.
109
+ You can specify a particular granularity as an argument; this is
110
+ useful to keep from running too far ahead of the real clock::
111
+
112
+ @time_monotonically_increases(0.1)
113
+ def smaller_increment():
114
+ t1 = time.time()
115
+ t2 = time.time()
116
+ assrt t2 == t1 + 0.1
117
+
118
+ .. seealso:: `nti.testing.time.reset_monotonic_time`
119
+
120
+ .. versionchanged:: 2.1
121
+ Add support for ``time.gmtime``.
122
+ .. versionchanged:: 2.1
123
+ Now thread safe.
124
+ .. versionchanged:: 2.0
125
+ The decorated function returns whatever the passed function returns.
126
+ .. versionchanged:: 2.0
127
+ Properly handle more than one argument to the underlying function.
128
+ .. versionchanged:: 2.0
129
+ Ensure that the values returned are at least the real time. Previously
130
+ they were integers starting at 0.
131
+ .. versionchanged:: 2.0
132
+ Ensure that the values returned are increasing even across different
133
+ invocations of a decorated function or different decorated functions. Previously
134
+ different functions would return the same sequence.
135
+ .. versionchanged:: 2.0
136
+ Allow specifying the granularity of clock increments. Keep at
137
+ 1.0 seconds for backwards compatibility by default.
138
+ """
139
+ if isinstance(func_or_granularity, (six.integer_types, float)):
140
+ # We're being used as a factory.
141
+ wrapper_factory = _TimeWrapper(func_or_granularity)
142
+ return wrapper_factory
143
+
144
+ # We're being used bare
145
+ wrapper_factory = _TimeWrapper()
146
+ return wrapper_factory(func_or_granularity)
147
+
148
+
149
+ def reset_monotonic_time(value=0.0):
150
+ """
151
+ Make the monotonic clock return the real time on its next
152
+ call.
153
+
154
+ .. versionadded:: 2.0
155
+ """
156
+
157
+ global _current_time # pylint:disable=global-statement
158
+ _current_time = value
159
+
160
+
161
+ class MonotonicallyIncreasingTimeLayerMixin(object):
162
+ """
163
+ A helper for layers that need time to increase monotonically.
164
+
165
+ You can either mix this in to a layer object, or instantiate it
166
+ and call the methods directly.
167
+
168
+ .. versionadded:: 2.2
169
+ """
170
+
171
+ def __init__(self, granularity=1.0):
172
+ self.time_manager = _TimeWrapper(granularity)
173
+
174
+ def testSetUp(self):
175
+ self.time_manager.install_fakes()
176
+
177
+ def testTearDown(self):
178
+ self.time_manager.close()
179
+ reset_monotonic_time()