• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / transaction / 16399678488

18 Sep 2024 07:25AM UTC coverage: 99.793% (+0.1%) from 99.696%
16399678488

push

github

dataflake
- vb [ci skip]

299 of 306 branches covered (97.71%)

Branch coverage included in aggregate %.

3083 of 3083 relevant lines covered (100.0%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.93
/src/transaction/tests/test__transaction.py
1
##############################################################################
2
#
3
# Copyright (c) 2001, 2002, 2005 Zope Foundation and Contributors.
4
# All Rights Reserved.
5
#
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE
12
#
13
##############################################################################
14
"""Test transaction behavior for variety of cases.
15

16
I wrote these unittests to investigate some odd transaction
17
behavior when doing unittests of integrating non sub transaction
18
aware objects, and to insure proper txn behavior. these
19
tests test the transaction system independent of the rest of the
20
zodb.
21

22
you can see the method calls to a jar by passing the
23
keyword arg tracing to the modify method of a dataobject.
24
the value of the arg is a prefix used for tracing print calls
25
to that objects jar.
26

27
the number of times a jar method was called can be inspected
28
by looking at an attribute of the jar that is the method
29
name prefixed with a c (count/check).
30

31
i've included some tracing examples for tests that i thought
32
were illuminating as doc strings below.
33

34
TODO
35

36
    add in tests for objects which are modified multiple times,
37
    for example an object that gets modified in multiple sub txns.
38
"""
39
import os
1✔
40
import unittest
1✔
41
import warnings
1✔
42

43

44
class TransactionTests(unittest.TestCase):
1✔
45

46
    def _getTargetClass(self):
1✔
47
        from transaction._transaction import Transaction
1✔
48
        return Transaction
1✔
49

50
    def _makeOne(self, synchronizers=None, manager=None):
1✔
51
        return self._getTargetClass()(synchronizers, manager)
1✔
52

53
    def test_verifyImplements_ITransaction(self):
1✔
54
        from zope.interface.verify import verifyClass
1✔
55

56
        from transaction.interfaces import ITransaction
1✔
57
        verifyClass(ITransaction, self._getTargetClass())
1✔
58

59
    def test_verifyProvides_ITransaction(self):
1✔
60
        from zope.interface.verify import verifyObject
1✔
61

62
        from transaction.interfaces import ITransaction
1✔
63
        verifyObject(ITransaction, self._makeOne())
1✔
64

65
    def test_ctor_defaults(self):
1✔
66
        from transaction import _transaction
1✔
67
        from transaction.tests.common import DummyLogger
1✔
68
        from transaction.tests.common import Monkey
1✔
69
        from transaction.weakset import WeakSet
1✔
70
        logger = DummyLogger()
1✔
71
        with Monkey(_transaction, _LOGGER=logger):
1✔
72
            txn = self._makeOne()
1✔
73
        self.assertIsInstance(txn._synchronizers, WeakSet)
1✔
74
        self.assertEqual(len(txn._synchronizers), 0)
1✔
75
        self.assertIsNone(txn._manager)
1✔
76
        self.assertEqual(txn.user, "")
1✔
77
        self.assertEqual(txn.description, "")
1✔
78
        self.assertIsNone(txn._savepoint2index)
1✔
79
        self.assertEqual(txn._savepoint_index, 0)
1✔
80
        self.assertEqual(txn._resources, [])
1✔
81
        self.assertEqual(txn._adapters, {})
1✔
82
        self.assertEqual(txn._voted, {})
1✔
83
        self.assertEqual(txn.extension, {})
1✔
84
        self.assertIs(txn._extension, txn.extension)  # legacy
1✔
85
        self.assertIs(txn.log, logger)
1✔
86
        self.assertEqual(len(logger._log), 1)
1✔
87
        self.assertEqual(logger._log[0][0], 'debug')
1✔
88
        self.assertEqual(logger._log[0][1], 'new transaction')
1✔
89
        self.assertIsNone(txn._failure_traceback)
1✔
90
        self.assertEqual(txn._before_commit, [])
1✔
91
        self.assertEqual(txn._after_commit, [])
1✔
92

93
    def test_ctor_w_syncs(self):
1✔
94
        from transaction.weakset import WeakSet
1✔
95
        synchs = WeakSet()
1✔
96
        txn = self._makeOne(synchronizers=synchs)
1✔
97
        self.assertIs(txn._synchronizers, synchs)
1✔
98

99
    def test_isDoomed(self):
1✔
100
        from transaction._transaction import Status
1✔
101
        txn = self._makeOne()
1✔
102
        self.assertFalse(txn.isDoomed())
1✔
103
        txn.status = Status.DOOMED
1✔
104
        self.assertTrue(txn.isDoomed())
1✔
105

106
    def test_doom_active(self):
1✔
107
        from transaction._transaction import Status
1✔
108
        txn = self._makeOne()
1✔
109
        txn.doom()
1✔
110
        self.assertTrue(txn.isDoomed())
1✔
111
        self.assertEqual(txn.status, Status.DOOMED)
1✔
112

113
    def test_doom_invalid(self):
1✔
114
        from transaction._transaction import Status
1✔
115
        txn = self._makeOne()
1✔
116
        for status in Status.COMMITTING, Status.COMMITTED, Status.COMMITFAILED:
1✔
117
            txn.status = status
1✔
118
            self.assertRaises(ValueError, txn.doom)
1✔
119

120
    def test_doom_already_doomed(self):
1✔
121
        from transaction._transaction import Status
1✔
122
        txn = self._makeOne()
1✔
123
        txn.status = Status.DOOMED
1✔
124
        txn.doom()
1✔
125
        self.assertTrue(txn.isDoomed())
1✔
126
        self.assertEqual(txn.status, Status.DOOMED)
1✔
127

128
    def test__prior_operation_failed(self):
1✔
129
        from transaction.interfaces import TransactionFailedError
1✔
130

131
        class _Traceback:
1✔
132
            def getvalue(self):
1✔
133
                return 'TRACEBACK'
1✔
134
        txn = self._makeOne()
1✔
135
        txn._failure_traceback = _Traceback()
1✔
136
        with self.assertRaises(TransactionFailedError) as exc:
1✔
137
            txn._prior_operation_failed()
1✔
138
        err = exc.exception
1✔
139
        self.assertTrue(str(err).startswith('An operation previously failed'))
1✔
140
        self.assertTrue(str(err).endswith("with traceback:\n\nTRACEBACK"))
1✔
141

142
    def test_join_COMMITFAILED(self):
1✔
143
        from transaction._transaction import Status
1✔
144
        from transaction.interfaces import TransactionFailedError
1✔
145

146
        class _Traceback:
1✔
147
            def getvalue(self):
1✔
148
                return 'TRACEBACK'
1✔
149
        txn = self._makeOne()
1✔
150
        txn.status = Status.COMMITFAILED
1✔
151
        txn._failure_traceback = _Traceback()
1✔
152
        self.assertRaises(TransactionFailedError, txn.join, object())
1✔
153

154
    def test_join_COMMITTING(self):
1✔
155
        from transaction._transaction import Status
1✔
156
        txn = self._makeOne()
1✔
157
        txn.status = Status.COMMITTING
1✔
158
        self.assertRaises(ValueError, txn.join, object())
1✔
159

160
    def test_join_COMMITTED(self):
1✔
161
        from transaction._transaction import Status
1✔
162
        txn = self._makeOne()
1✔
163
        txn.status = Status.COMMITTED
1✔
164
        self.assertRaises(ValueError, txn.join, object())
1✔
165

166
    def test_join_DOOMED_non_preparing_wo_sp2index(self):
1✔
167
        from transaction._transaction import Status
1✔
168
        txn = self._makeOne()
1✔
169
        txn.status = Status.DOOMED
1✔
170
        resource = object()
1✔
171
        txn.join(resource)
1✔
172
        self.assertEqual(txn._resources, [resource])
1✔
173

174
    def test__unjoin_miss(self):
1✔
175
        txn = self._makeOne()
1✔
176
        txn._unjoin(object())  # no raise
1✔
177

178
    def test__unjoin_hit(self):
1✔
179
        txn = self._makeOne()
1✔
180
        resource = object()
1✔
181
        txn._resources.append(resource)
1✔
182
        txn._unjoin(resource)
1✔
183
        self.assertEqual(txn._resources, [])
1✔
184

185
    def test_savepoint_COMMITFAILED(self):
1✔
186
        from transaction._transaction import Status
1✔
187
        from transaction.interfaces import TransactionFailedError
1✔
188

189
        class _Traceback:
1✔
190
            def getvalue(self):
1✔
191
                return 'TRACEBACK'
1✔
192
        txn = self._makeOne()
1✔
193
        txn.status = Status.COMMITFAILED
1✔
194
        txn._failure_traceback = _Traceback()
1✔
195
        self.assertRaises(TransactionFailedError, txn.savepoint)
1✔
196

197
    def test_savepoint_empty(self):
1✔
198
        from weakref import WeakKeyDictionary
1✔
199

200
        from transaction import _transaction
1✔
201
        from transaction._transaction import Savepoint
1✔
202
        from transaction.tests.common import DummyLogger
1✔
203
        from transaction.tests.common import Monkey
1✔
204
        logger = DummyLogger()
1✔
205
        with Monkey(_transaction, _LOGGER=logger):
1✔
206
            txn = self._makeOne()
1✔
207
        sp = txn.savepoint()
1✔
208
        self.assertIsInstance(sp, Savepoint)
1✔
209
        self.assertIs(sp.transaction, txn)
1✔
210
        self.assertEqual(sp._savepoints, [])
1✔
211
        self.assertEqual(txn._savepoint_index, 1)
1✔
212
        self.assertIsInstance(txn._savepoint2index, WeakKeyDictionary)
1✔
213
        self.assertEqual(txn._savepoint2index[sp], 1)
1✔
214

215
    def test_savepoint_non_optimistc_resource_wo_support(self):
1✔
216
        from io import StringIO
1✔
217

218
        from transaction import _transaction
1✔
219
        from transaction._transaction import Status
1✔
220
        from transaction.tests.common import DummyLogger
1✔
221
        from transaction.tests.common import Monkey
1✔
222
        logger = DummyLogger()
1✔
223
        with Monkey(_transaction, _LOGGER=logger):
1✔
224
            txn = self._makeOne()
1✔
225
        logger._clear()
1✔
226
        resource = object()
1✔
227
        txn._resources.append(resource)
1✔
228
        self.assertRaises(TypeError, txn.savepoint)
1✔
229
        self.assertEqual(txn.status, Status.COMMITFAILED)
1✔
230
        self.assertIsInstance(txn._failure_traceback, StringIO)
1✔
231
        self.assertIn('TypeError', txn._failure_traceback.getvalue())
1✔
232
        self.assertEqual(len(logger._log), 2)
1✔
233
        self.assertEqual(logger._log[0][0], 'error')
1✔
234
        self.assertTrue(logger._log[0][1].startswith('Error in abort'))
1✔
235
        self.assertEqual(logger._log[1][0], 'error')
1✔
236
        self.assertTrue(logger._log[1][1].startswith('Error in tpc_abort'))
1✔
237

238
    def test__remove_and_invalidate_after_miss(self):
1✔
239
        from weakref import WeakKeyDictionary
1✔
240
        txn = self._makeOne()
1✔
241
        txn._savepoint2index = WeakKeyDictionary()
1✔
242

243
        class _SP:
1✔
244
            def __init__(self, txn):
1✔
245
                self.transaction = txn
1✔
246
        holdme = []
1✔
247
        for i in range(10):
1✔
248
            sp = _SP(txn)
1✔
249
            holdme.append(sp)  # prevent gc
1✔
250
            txn._savepoint2index[sp] = i
1✔
251
        self.assertEqual(len(txn._savepoint2index), 10)
1✔
252
        self.assertRaises(KeyError, txn._remove_and_invalidate_after, _SP(txn))
1✔
253
        self.assertEqual(len(txn._savepoint2index), 10)
1✔
254

255
    def test__remove_and_invalidate_after_hit(self):
1✔
256
        from weakref import WeakKeyDictionary
1✔
257
        txn = self._makeOne()
1✔
258
        txn._savepoint2index = WeakKeyDictionary()
1✔
259

260
        class _SP:
1✔
261
            def __init__(self, txn, index):
1✔
262
                self.transaction = txn
1✔
263
                self._index = index
1✔
264

265
            def __lt__(self, other):
1✔
266
                return self._index < other._index
1✔
267

268
            def __repr__(self):  # pragma: no cover
269
                return '_SP: %d' % self._index
270
        holdme = []
1✔
271
        for i in range(10):
1✔
272
            sp = _SP(txn, i)
1✔
273
            holdme.append(sp)  # prevent gc
1✔
274
            txn._savepoint2index[sp] = i
1✔
275
        self.assertEqual(len(txn._savepoint2index), 10)
1✔
276
        txn._remove_and_invalidate_after(holdme[1])
1✔
277
        self.assertEqual(sorted(txn._savepoint2index), sorted(holdme[:2]))
1✔
278

279
    def test__invalidate_all_savepoints(self):
1✔
280
        from weakref import WeakKeyDictionary
1✔
281
        txn = self._makeOne()
1✔
282
        txn._savepoint2index = WeakKeyDictionary()
1✔
283

284
        class _SP:
1✔
285
            def __init__(self, txn, index):
1✔
286
                self.transaction = txn
1✔
287
                self._index = index
1✔
288

289
            def __repr__(self):  # pragma: no cover
290
                return '_SP: %d' % self._index
291
        holdme = []
1✔
292
        for i in range(10):
1✔
293
            sp = _SP(txn, i)
1✔
294
            holdme.append(sp)  # prevent gc
1✔
295
            txn._savepoint2index[sp] = i
1✔
296
        self.assertEqual(len(txn._savepoint2index), 10)
1✔
297
        txn._invalidate_all_savepoints()
1✔
298
        self.assertEqual(list(txn._savepoint2index), [])
1✔
299

300
    def test_commit_DOOMED(self):
1✔
301
        from transaction._transaction import Status
1✔
302
        from transaction.interfaces import DoomedTransaction
1✔
303
        txn = self._makeOne()
1✔
304
        txn.status = Status.DOOMED
1✔
305
        self.assertRaises(DoomedTransaction, txn.commit)
1✔
306

307
    def test_commit_COMMITFAILED(self):
1✔
308
        from transaction._transaction import Status
1✔
309
        from transaction.interfaces import TransactionFailedError
1✔
310

311
        class _Traceback:
1✔
312
            def getvalue(self):
1✔
313
                return 'TRACEBACK'
1✔
314
        txn = self._makeOne()
1✔
315
        txn.status = Status.COMMITFAILED
1✔
316
        txn._failure_traceback = _Traceback()
1✔
317
        self.assertRaises(TransactionFailedError, txn.commit)
1✔
318

319
    def test_commit_wo_savepoints_wo_hooks_wo_synchronizers(self):
1✔
320
        from transaction import _transaction
1✔
321
        from transaction._transaction import Status
1✔
322
        from transaction.tests.common import DummyLogger
1✔
323
        from transaction.tests.common import Monkey
1✔
324

325
        class _Mgr:
1✔
326
            def __init__(self, txn):
1✔
327
                self._txn = txn
1✔
328

329
            def free(self, txn):
1✔
330
                assert txn is self._txn
1✔
331
                self._txn = None
1✔
332
        logger = DummyLogger()
1✔
333
        with Monkey(_transaction, _LOGGER=logger):
1✔
334
            txn = self._makeOne()
1✔
335
            logger._clear()
1✔
336
            mgr = txn._manager = _Mgr(txn)
1✔
337
            txn.commit()
1✔
338
        self.assertEqual(txn.status, Status.COMMITTED)
1✔
339
        self.assertIsNone(mgr._txn)
1✔
340
        self.assertEqual(logger._log[0][0], 'debug')
1✔
341
        self.assertEqual(logger._log[0][1], 'commit')
1✔
342

343
    def test_commit_w_savepoints(self):
1✔
344
        from weakref import WeakKeyDictionary
1✔
345

346
        from transaction import _transaction
1✔
347
        from transaction.tests.common import DummyLogger
1✔
348
        from transaction.tests.common import Monkey
1✔
349

350
        class _SP:
1✔
351
            def __init__(self, txn, index):
1✔
352
                self.transaction = txn
1✔
353
                self._index = index
1✔
354

355
            def __repr__(self):  # pragma: no cover
356
                return '_SP: %d' % self._index
357
        logger = DummyLogger()
1✔
358
        with Monkey(_transaction, _LOGGER=logger):
1✔
359
            txn = self._makeOne()
1✔
360
            txn._savepoint2index = WeakKeyDictionary()
1✔
361
            holdme = []
1✔
362
            for i in range(10):
1✔
363
                sp = _SP(txn, i)
1✔
364
                holdme.append(sp)  # prevent gc
1✔
365
                txn._savepoint2index[sp] = i
1✔
366
            logger._clear()
1✔
367
            txn.commit()
1✔
368
        self.assertEqual(list(txn._savepoint2index), [])
1✔
369

370
    def test_commit_w_beforeCommitHooks(self):
1✔
371
        from transaction import _transaction
1✔
372
        from transaction.tests.common import DummyLogger
1✔
373
        from transaction.tests.common import Monkey
1✔
374
        _hooked1, _hooked2 = [], []
1✔
375

376
        def _hook1(*args, **kw):
1✔
377
            _hooked1.append((args, kw))
1✔
378

379
        def _hook2(*args, **kw):
1✔
380
            _hooked2.append((args, kw))
1✔
381
        logger = DummyLogger()
1✔
382
        with Monkey(_transaction, _LOGGER=logger):
1✔
383
            txn = self._makeOne()
1✔
384
            txn._before_commit.append((_hook1, ('one',), {'uno': 1}))
1✔
385
            txn._before_commit.append((_hook2, (), {}))
1✔
386
            logger._clear()
1✔
387
            txn.commit()
1✔
388
        self.assertEqual(_hooked1, [(('one',), {'uno': 1})])
1✔
389
        self.assertEqual(_hooked2, [((), {})])
1✔
390
        self.assertEqual(txn._before_commit, [])
1✔
391

392
    def test_commit_w_synchronizers(self):
1✔
393
        from transaction import _transaction
1✔
394
        from transaction.tests.common import DummyLogger
1✔
395
        from transaction.tests.common import Monkey
1✔
396
        from transaction.weakset import WeakSet
1✔
397

398
        class _Synch:
1✔
399
            _before = _after = False
1✔
400

401
            def beforeCompletion(self, txn):
1✔
402
                self._before = txn
1✔
403

404
            def afterCompletion(self, txn):
1✔
405
                self._after = txn
1✔
406
        synchs = [_Synch(), _Synch(), _Synch()]
1✔
407
        ws = WeakSet()
1✔
408
        for synch in synchs:
1✔
409
            ws.add(synch)
1✔
410
        logger = DummyLogger()
1✔
411
        with Monkey(_transaction, _LOGGER=logger):
1✔
412
            txn = self._makeOne(synchronizers=ws)
1✔
413
            logger._clear()
1✔
414
            txn.commit()
1✔
415
        for synch in synchs:
1✔
416
            self.assertIs(synch._before, txn)
1✔
417
            self.assertIs(synch._after, txn)
1✔
418

419
    def test_commit_w_afterCommitHooks(self):
1✔
420
        from transaction import _transaction
1✔
421
        from transaction.tests.common import DummyLogger
1✔
422
        from transaction.tests.common import Monkey
1✔
423
        _hooked1, _hooked2 = [], []
1✔
424

425
        def _hook1(*args, **kw):
1✔
426
            _hooked1.append((args, kw))
1✔
427

428
        def _hook2(*args, **kw):
1✔
429
            _hooked2.append((args, kw))
1✔
430
        logger = DummyLogger()
1✔
431
        with Monkey(_transaction, _LOGGER=logger):
1✔
432
            txn = self._makeOne()
1✔
433
            txn._after_commit.append((_hook1, ('one',), {'uno': 1}))
1✔
434
            txn._after_commit.append((_hook2, (), {}))
1✔
435
            logger._clear()
1✔
436
            txn.commit()
1✔
437
        self.assertEqual(_hooked1, [((True, 'one',), {'uno': 1})])
1✔
438
        self.assertEqual(_hooked2, [((True,), {})])
1✔
439
        self.assertEqual(txn._after_commit, [])
1✔
440
        self.assertEqual(txn._resources, [])
1✔
441

442
    def test_commit_error_w_afterCompleteHooks(self):
1✔
443
        from transaction import _transaction
1✔
444
        from transaction.tests.common import DummyLogger
1✔
445
        from transaction.tests.common import Monkey
1✔
446

447
        class BrokenResource:
1✔
448
            def sortKey(self):
1✔
449
                return 'zzz'
1✔
450

451
            def tpc_begin(self, txn):
1✔
452
                raise ValueError('test')
1✔
453
        broken = BrokenResource()
1✔
454
        resource = Resource('aaa')
1✔
455
        _hooked1, _hooked2 = [], []
1✔
456

457
        def _hook1(*args, **kw):
1✔
458
            _hooked1.append((args, kw))
1✔
459

460
        def _hook2(*args, **kw):
1✔
461
            _hooked2.append((args, kw))
1✔
462
        logger = DummyLogger()
1✔
463
        with Monkey(_transaction, _LOGGER=logger):
1✔
464
            txn = self._makeOne()
1✔
465
            txn._after_commit.append((_hook1, ('one',), {'uno': 1}))
1✔
466
            txn._after_commit.append((_hook2, (), {}))
1✔
467
            txn._resources.append(broken)
1✔
468
            txn._resources.append(resource)
1✔
469
            logger._clear()
1✔
470
            self.assertRaises(ValueError, txn.commit)
1✔
471
        self.assertEqual(_hooked1, [((False, 'one',), {'uno': 1})])
1✔
472
        self.assertEqual(_hooked2, [((False,), {})])
1✔
473
        self.assertEqual(txn._after_commit, [])
1✔
474
        self.assertTrue(resource._b)
1✔
475
        self.assertFalse(resource._c)
1✔
476
        self.assertFalse(resource._v)
1✔
477
        self.assertFalse(resource._f)
1✔
478
        self.assertTrue(resource._a)
1✔
479
        self.assertTrue(resource._x)
1✔
480

481
    def test_commit_error_w_synchronizers(self):
1✔
482
        from transaction import _transaction
1✔
483
        from transaction.tests.common import DummyLogger
1✔
484
        from transaction.tests.common import Monkey
1✔
485
        from transaction.weakset import WeakSet
1✔
486

487
        class _Synch:
1✔
488
            _before = _after = False
1✔
489

490
            def beforeCompletion(self, txn):
1✔
491
                self._before = txn
1✔
492

493
            def afterCompletion(self, txn):
1✔
494
                self._after = txn
1✔
495
        synchs = [_Synch(), _Synch(), _Synch()]
1✔
496
        ws = WeakSet()
1✔
497
        for synch in synchs:
1✔
498
            ws.add(synch)
1✔
499

500
        class BrokenResource:
1✔
501
            def sortKey(self):
1✔
502
                return 'zzz'
1✔
503

504
            def tpc_begin(self, txn):
1✔
505
                raise ValueError('test')
1✔
506
        broken = BrokenResource()
1✔
507
        logger = DummyLogger()
1✔
508
        with Monkey(_transaction, _LOGGER=logger):
1✔
509
            txn = self._makeOne(synchronizers=ws)
1✔
510
            logger._clear()
1✔
511
            txn._resources.append(broken)
1✔
512
            self.assertRaises(ValueError, txn.commit)
1✔
513
        for synch in synchs:
1✔
514
            self.assertIs(synch._before, txn)
1✔
515
            self.assertIs(synch._after, txn)  # called in _cleanup
1✔
516

517
    def test_commit_clears_resources(self):
1✔
518
        class DM:
1✔
519
            tpc_begin = commit = tpc_finish = tpc_vote = lambda s, txn: True
1✔
520

521
        dm = DM()
1✔
522
        txn = self._makeOne()
1✔
523
        txn.join(dm)
1✔
524
        self.assertEqual(txn._resources, [dm])
1✔
525
        txn.commit()
1✔
526
        self.assertEqual(txn._resources, [])
1✔
527

528
    def test_getBeforeCommitHooks_empty(self):
1✔
529
        txn = self._makeOne()
1✔
530
        self.assertEqual(list(txn.getBeforeCommitHooks()), [])
1✔
531

532
    def test_addBeforeCommitHook(self):
1✔
533
        def _hook(*args, **kw):
1✔
534
            raise AssertionError("Not called")
535
        txn = self._makeOne()
1✔
536
        txn.addBeforeCommitHook(_hook, ('one',), dict(uno=1))
1✔
537
        self.assertEqual(list(txn.getBeforeCommitHooks()),
1✔
538
                         [(_hook, ('one',), {'uno': 1})])
539

540
    def test_callBeforeCommitHook_w_error(self):
1✔
541
        from transaction import _transaction
1✔
542
        from transaction.tests.common import DummyLogger
1✔
543
        from transaction.tests.common import Monkey
1✔
544
        _calls = []
1✔
545

546
        def _hook(*args, **kw):
1✔
547
            _calls.append((args, kw))
1✔
548

549
        def _hook_err(*args, **kw):
1✔
550
            raise ValueError()
1✔
551

552
        logger = DummyLogger()
1✔
553
        with Monkey(_transaction, _LOGGER=logger):
1✔
554
            txn = self._makeOne()
1✔
555
        logger._clear()
1✔
556
        txn.addBeforeCommitHook(_hook, ('one',), dict(uno=1))
1✔
557
        txn.addBeforeCommitHook(_hook_err, ('two',), dict(dos=2))
1✔
558
        txn.addBeforeCommitHook(_hook, ('three',), dict(tres=3))
1✔
559
        # only first hook gets called, and instead of logging the error,
560
        # the exception is raised
561
        self.assertRaises(ValueError, txn._callBeforeCommitHooks)
1✔
562
        self.assertEqual(_calls, [(('one',), {'uno': 1})])
1✔
563
        self.assertEqual(len(logger._log), 0)
1✔
564

565
    def test_addBeforeCommitHook_w_kws(self):
1✔
566
        def _hook(*args, **kw):
1✔
567
            raise AssertionError("Not called")
568
        txn = self._makeOne()
1✔
569
        txn.addBeforeCommitHook(_hook, ('one',))
1✔
570
        self.assertEqual(list(txn.getBeforeCommitHooks()),
1✔
571
                         [(_hook, ('one',), {})])
572

573
    def test_getAfterCommitHooks_empty(self):
1✔
574
        txn = self._makeOne()
1✔
575
        self.assertEqual(list(txn.getAfterCommitHooks()), [])
1✔
576

577
    def test_addAfterCommitHook(self):
1✔
578
        def _hook(*args, **kw):
1✔
579
            raise AssertionError("Not called")
580
        txn = self._makeOne()
1✔
581
        txn.addAfterCommitHook(_hook, ('one',), dict(uno=1))
1✔
582
        self.assertEqual(list(txn.getAfterCommitHooks()),
1✔
583
                         [(_hook, ('one',), {'uno': 1})])
584

585
    def test_addAfterCommitHook_wo_kws(self):
1✔
586
        def _hook(*args, **kw):
1✔
587
            raise AssertionError("Not called")
588
        txn = self._makeOne()
1✔
589
        txn.addAfterCommitHook(_hook, ('one',))
1✔
590
        self.assertEqual(list(txn.getAfterCommitHooks()),
1✔
591
                         [(_hook, ('one',), {})])
592

593
    def test_callAfterCommitHook_w_error(self):
1✔
594
        from transaction import _transaction
1✔
595
        from transaction.tests.common import DummyLogger
1✔
596
        from transaction.tests.common import Monkey
1✔
597
        _hooked2 = []
1✔
598

599
        def _hook1(*args, **kw):
1✔
600
            raise ValueError()
1✔
601

602
        def _hook2(*args, **kw):
1✔
603
            _hooked2.append((args, kw))
1✔
604
        logger = DummyLogger()
1✔
605
        with Monkey(_transaction, _LOGGER=logger):
1✔
606
            txn = self._makeOne()
1✔
607
        logger._clear()
1✔
608
        txn.addAfterCommitHook(_hook1, ('one',))
1✔
609
        txn.addAfterCommitHook(_hook2, ('two',), dict(dos=2))
1✔
610
        txn._callAfterCommitHooks()
1✔
611
        # second hook gets called even if first raises
612
        self.assertEqual(_hooked2, [((True, 'two',), {'dos': 2})])
1✔
613
        self.assertEqual(len(logger._log), 1)
1✔
614
        self.assertEqual(logger._log[0][0], 'error')
1✔
615
        self.assertTrue(logger._log[0][1].startswith("Error in hook"))
1✔
616

617
    def test_callAfterCommitHook_w_abort(self):
1✔
618
        from transaction import _transaction
1✔
619
        from transaction.tests.common import DummyLogger
1✔
620
        from transaction.tests.common import Monkey
1✔
621
        _hooked2 = []
1✔
622

623
        def _hook1(*args, **kw):
1✔
624
            raise ValueError()
1✔
625

626
        def _hook2(*args, **kw):
1✔
627
            _hooked2.append((args, kw))
1✔
628
        logger = DummyLogger()
1✔
629
        with Monkey(_transaction, _LOGGER=logger):
1✔
630
            txn = self._makeOne()
1✔
631
        logger._clear()
1✔
632
        txn.addAfterCommitHook(_hook1, ('one',))
1✔
633
        txn.addAfterCommitHook(_hook2, ('two',), dict(dos=2))
1✔
634
        txn._callAfterCommitHooks()
1✔
635
        self.assertEqual(logger._log[0][0], 'error')
1✔
636
        self.assertTrue(logger._log[0][1].startswith("Error in hook"))
1✔
637

638
    def test__commitResources_normal(self):
1✔
639
        from transaction import _transaction
1✔
640
        from transaction.tests.common import DummyLogger
1✔
641
        from transaction.tests.common import Monkey
1✔
642
        resources = [Resource('bbb'), Resource('aaa')]
1✔
643
        logger = DummyLogger()
1✔
644
        with Monkey(_transaction, _LOGGER=logger):
1✔
645
            txn = self._makeOne()
1✔
646
        logger._clear()
1✔
647
        txn._resources.extend(resources)
1✔
648
        txn._commitResources()
1✔
649
        self.assertEqual(len(txn._voted), 2)
1✔
650
        for r in resources:
1✔
651
            self.assertTrue(r._b and r._c and r._v and r._f)
1✔
652
            self.assertFalse(r._a and r._x)
1✔
653
            self.assertIn(id(r), txn._voted)
1✔
654
        self.assertEqual(len(logger._log), 2)
1✔
655
        self.assertEqual(logger._log[0][0], 'debug')
1✔
656
        self.assertEqual(logger._log[0][1], 'commit Resource: aaa')
1✔
657
        self.assertEqual(logger._log[1][0], 'debug')
1✔
658
        self.assertEqual(logger._log[1][1], 'commit Resource: bbb')
1✔
659

660
    def test__commitResources_error_in_tpc_begin(self):
1✔
661
        from transaction import _transaction
1✔
662
        from transaction.tests.common import DummyLogger
1✔
663
        from transaction.tests.common import Monkey
1✔
664
        resources = [Resource('bbb', 'tpc_begin'), Resource('aaa')]
1✔
665
        logger = DummyLogger()
1✔
666
        with Monkey(_transaction, _LOGGER=logger):
1✔
667
            txn = self._makeOne()
1✔
668
        logger._clear()
1✔
669
        txn._resources.extend(resources)
1✔
670
        self.assertRaises(ValueError, txn._commitResources)
1✔
671
        for r in resources:
1✔
672
            if r._key == 'aaa':
1✔
673
                self.assertTrue(r._b)
1✔
674
            else:
675
                self.assertFalse(r._b)
1✔
676
            self.assertFalse(r._c and r._v and r._f)
1✔
677
            self.assertTrue(r._a and r._x)
1✔
678
        self.assertEqual(len(logger._log), 0)
1✔
679

680
    def test__commitResources_error_in_afterCompletion(self):
1✔
681
        from transaction import _transaction
1✔
682
        from transaction.tests.common import DummyLogger
1✔
683
        from transaction.tests.common import Monkey
1✔
684

685
        class _Synchronizers:
1✔
686
            def __init__(self, res):
1✔
687
                self._res = res
1✔
688

689
            def map(self, func):
1✔
690
                for res in self._res:
1!
691
                    func(res)
1✔
692
        resources = [Resource('bbb', 'tpc_begin'),
1✔
693
                     Resource('aaa', 'afterCompletion')]
694
        sync = _Synchronizers(resources)
1✔
695
        logger = DummyLogger()
1✔
696
        with Monkey(_transaction, _LOGGER=logger):
1✔
697
            txn = self._makeOne(sync)
1✔
698
        logger._clear()
1✔
699
        txn._resources.extend(resources)
1✔
700
        self.assertRaises(ValueError, txn._commitResources)
1✔
701
        for r in resources:
1✔
702
            if r._key == 'aaa':
1✔
703
                self.assertTrue(r._b)
1✔
704
            else:
705
                self.assertFalse(r._b)
1✔
706
            self.assertFalse(r._c and r._v and r._f)
1✔
707
            self.assertTrue(r._a and r._x)
1✔
708
        self.assertEqual(len(logger._log), 0)
1✔
709
        self.assertTrue(resources[0]._after)
1✔
710
        self.assertFalse(resources[1]._after)
1✔
711

712
    def test__commitResources_error_in_commit(self):
1✔
713
        from transaction import _transaction
1✔
714
        from transaction.tests.common import DummyLogger
1✔
715
        from transaction.tests.common import Monkey
1✔
716
        resources = [Resource('bbb', 'commit'), Resource('aaa')]
1✔
717
        logger = DummyLogger()
1✔
718
        with Monkey(_transaction, _LOGGER=logger):
1✔
719
            txn = self._makeOne()
1✔
720
        logger._clear()
1✔
721
        txn._resources.extend(resources)
1✔
722
        self.assertRaises(ValueError, txn._commitResources)
1✔
723
        for r in resources:
1✔
724
            self.assertTrue(r._b)
1✔
725
            if r._key == 'aaa':
1✔
726
                self.assertTrue(r._c)
1✔
727
            else:
728
                self.assertFalse(r._c)
1✔
729
            self.assertFalse(r._v and r._f)
1✔
730
            self.assertTrue(r._a and r._x)
1✔
731
        self.assertEqual(len(logger._log), 1)
1✔
732
        self.assertEqual(logger._log[0][0], 'debug')
1✔
733
        self.assertEqual(logger._log[0][1], 'commit Resource: aaa')
1✔
734

735
    def test__commitResources_error_in_tpc_vote(self):
1✔
736
        from transaction import _transaction
1✔
737
        from transaction.tests.common import DummyLogger
1✔
738
        from transaction.tests.common import Monkey
1✔
739
        resources = [Resource('bbb', 'tpc_vote'), Resource('aaa')]
1✔
740
        logger = DummyLogger()
1✔
741
        with Monkey(_transaction, _LOGGER=logger):
1✔
742
            txn = self._makeOne()
1✔
743
        logger._clear()
1✔
744
        txn._resources.extend(resources)
1✔
745
        self.assertRaises(ValueError, txn._commitResources)
1✔
746
        self.assertEqual(len(txn._voted), 1)
1✔
747
        for r in resources:
1✔
748
            self.assertTrue(r._b and r._c)
1✔
749
            if r._key == 'aaa':
1✔
750
                self.assertIn(id(r), txn._voted)
1✔
751
                self.assertTrue(r._v)
1✔
752
                self.assertFalse(r._f)
1✔
753
                self.assertFalse(r._a)
1✔
754
                self.assertTrue(r._x)
1✔
755
            else:
756
                self.assertNotIn(id(r), txn._voted)
1✔
757
                self.assertFalse(r._v)
1✔
758
                self.assertFalse(r._f)
1✔
759
                self.assertTrue(r._a and r._x)
1✔
760
        self.assertEqual(len(logger._log), 2)
1✔
761
        self.assertEqual(logger._log[0][0], 'debug')
1✔
762
        self.assertEqual(logger._log[0][1], 'commit Resource: aaa')
1✔
763
        self.assertEqual(logger._log[1][0], 'debug')
1✔
764
        self.assertEqual(logger._log[1][1], 'commit Resource: bbb')
1✔
765

766
    def test__commitResources_error_in_tpc_finish(self):
1✔
767
        from transaction import _transaction
1✔
768
        from transaction.tests.common import DummyLogger
1✔
769
        from transaction.tests.common import Monkey
1✔
770
        resources = [Resource('bbb', 'tpc_finish'), Resource('aaa')]
1✔
771
        logger = DummyLogger()
1✔
772
        with Monkey(_transaction, _LOGGER=logger):
1✔
773
            txn = self._makeOne()
1✔
774
        logger._clear()
1✔
775
        txn._resources.extend(resources)
1✔
776
        self.assertRaises(ValueError, txn._commitResources)
1✔
777
        for r in resources:
1✔
778
            self.assertTrue(r._b and r._c and r._v)
1✔
779
            self.assertIn(id(r), txn._voted)
1✔
780
            if r._key == 'aaa':
1✔
781
                self.assertTrue(r._f)
1✔
782
            else:
783
                self.assertFalse(r._f)
1✔
784
            self.assertFalse(r._a and r._x)  # no cleanup if tpc_finish raises
1✔
785
        self.assertEqual(len(logger._log), 3)
1✔
786
        self.assertEqual(logger._log[0][0], 'debug')
1✔
787
        self.assertEqual(logger._log[0][1], 'commit Resource: aaa')
1✔
788
        self.assertEqual(logger._log[1][0], 'debug')
1✔
789
        self.assertEqual(logger._log[1][1], 'commit Resource: bbb')
1✔
790
        self.assertEqual(logger._log[2][0], 'critical')
1✔
791
        self.assertTrue(logger._log[2][1].startswith(
1✔
792
                        'A storage error occurred'))
793

794
    def test_abort_wo_savepoints_wo_hooks_wo_synchronizers(self):
1✔
795
        from transaction import _transaction
1✔
796
        from transaction._transaction import Status
1✔
797
        from transaction.tests.common import DummyLogger
1✔
798
        from transaction.tests.common import Monkey
1✔
799

800
        class _Mgr:
1✔
801
            def __init__(self, txn):
1✔
802
                self._txn = txn
1✔
803

804
            def free(self, txn):
1✔
805
                assert txn is self._txn
1✔
806
                self._txn = None
1✔
807
        logger = DummyLogger()
1✔
808
        with Monkey(_transaction, _LOGGER=logger):
1✔
809
            txn = self._makeOne()
1✔
810
            logger._clear()
1✔
811
            mgr = txn._manager = _Mgr(txn)
1✔
812
            txn.abort()
1✔
813
        self.assertEqual(txn.status, Status.ACTIVE)
1✔
814
        self.assertIsNone(mgr._txn)
1✔
815
        self.assertEqual(logger._log[0][0], 'debug')
1✔
816
        self.assertEqual(logger._log[0][1], 'abort')
1✔
817

818
    def test_abort_w_savepoints(self):
1✔
819
        from weakref import WeakKeyDictionary
1✔
820

821
        from transaction import _transaction
1✔
822
        from transaction.tests.common import DummyLogger
1✔
823
        from transaction.tests.common import Monkey
1✔
824

825
        class _SP:
1✔
826
            def __init__(self, txn, index):
1✔
827
                self.transaction = txn
1✔
828
                self._index = index
1✔
829

830
            def __repr__(self):  # pragma: no cover
831
                return '_SP: %d' % self._index
832
        logger = DummyLogger()
1✔
833
        with Monkey(_transaction, _LOGGER=logger):
1✔
834
            txn = self._makeOne()
1✔
835
            txn._savepoint2index = WeakKeyDictionary()
1✔
836
            holdme = []
1✔
837
            for i in range(10):
1✔
838
                sp = _SP(txn, i)
1✔
839
                holdme.append(sp)  # prevent gc
1✔
840
                txn._savepoint2index[sp] = i
1✔
841
            logger._clear()
1✔
842
            txn.abort()
1✔
843
        self.assertEqual(list(txn._savepoint2index), [])
1✔
844

845
    def test_abort_w_beforeCommitHooks(self):
1✔
846
        from transaction import _transaction
1✔
847
        from transaction.tests.common import DummyLogger
1✔
848
        from transaction.tests.common import Monkey
1✔
849
        _hooked1, _hooked2 = [], []
1✔
850

851
        def _hook1(*args, **kw):
1✔
852
            raise AssertionError("Not called")
853

854
        def _hook2(*args, **kw):
1✔
855
            raise AssertionError("Not called")
856
        logger = DummyLogger()
1✔
857
        with Monkey(_transaction, _LOGGER=logger):
1✔
858
            txn = self._makeOne()
1✔
859
            txn._before_commit.append((_hook1, ('one',), {'uno': 1}))
1✔
860
            txn._before_commit.append((_hook2, (), {}))
1✔
861
            logger._clear()
1✔
862
            txn.abort()
1✔
863
        self.assertEqual(_hooked1, [])
1✔
864
        self.assertEqual(_hooked2, [])
1✔
865
        # Hooks are not called but cleared on abort
866
        self.assertEqual(list(txn.getBeforeCommitHooks()), [])
1✔
867
        self.assertIsNone(txn._manager)
1✔
868

869
    def test_abort_w_synchronizers(self):
1✔
870
        from transaction import _transaction
1✔
871
        from transaction.tests.common import DummyLogger
1✔
872
        from transaction.tests.common import Monkey
1✔
873
        test = self
1✔
874

875
        class _Synch:
1✔
876
            _before = _after = None
1✔
877

878
            def beforeCompletion(self, txn):
1✔
879
                self._before = txn
1✔
880
                txn.set_data(self, 42)
1✔
881
                test.assertIsNotNone(txn._manager)
1✔
882

883
            def afterCompletion(self, txn):
1✔
884
                self._after = txn
1✔
885
                # data is accessible afterCompletion,
886
                # but the transaction is not current anymore.
887
                test.assertEqual(42, txn.data(self))
1✔
888
                test.assertIsNone(txn._manager)
1✔
889

890
        class _BadSynch(_Synch):
1✔
891
            def afterCompletion(self, txn):
1✔
892
                _Synch.afterCompletion(self, txn)
1✔
893
                raise SystemExit
1✔
894

895
        # Ensure iteration order
896
        class Synchs:
1✔
897
            synchs = [_Synch(), _Synch(), _Synch(), _BadSynch()]
1✔
898

899
            def map(self, func):
1✔
900
                for s in self.synchs:
1✔
901
                    func(s)
1✔
902

903
        logger = DummyLogger()
1✔
904

905
        class Manager:
1✔
906
            txn = None
1✔
907

908
            def free(self, txn):
1✔
909
                test.assertIs(txn, self.txn)
1✔
910
                self.txn = None
1✔
911

912
        manager = Manager()
1✔
913
        synchs = Synchs()
1✔
914

915
        with Monkey(_transaction, _LOGGER=logger):
1✔
916
            txn = self._makeOne(synchronizers=synchs, manager=manager)
1✔
917
            manager.txn = txn
1✔
918
            logger._clear()
1✔
919
            with self.assertRaises(SystemExit):
1✔
920
                txn.abort()
1✔
921

922
        for synch in synchs.synchs:
1✔
923
            self.assertIs(synch._before, txn)
1✔
924
            self.assertIs(synch._after, txn)
1✔
925

926
        # And everything was cleaned up despite raising the bad
927
        # exception
928
        self.assertIsNone(txn._manager)
1✔
929
        self.assertIsNot(txn._synchronizers, synchs)
1✔
930
        self.assertIsNone(manager.txn)
1✔
931

932
    def test_abort_w_afterCommitHooks(self):
1✔
933
        from transaction import _transaction
1✔
934
        from transaction.tests.common import DummyLogger
1✔
935
        from transaction.tests.common import Monkey
1✔
936
        _hooked1, _hooked2 = [], []
1✔
937

938
        def _hook1(*args, **kw):
1✔
939
            raise AssertionError("Not called")
940

941
        def _hook2(*args, **kw):
1✔
942
            raise AssertionError("Not called")
943
        logger = DummyLogger()
1✔
944
        with Monkey(_transaction, _LOGGER=logger):
1✔
945
            txn = self._makeOne()
1✔
946
            txn._after_commit.append((_hook1, ('one',), {'uno': 1}))
1✔
947
            txn._after_commit.append((_hook2, (), {}))
1✔
948
            logger._clear()
1✔
949
            txn.abort()
1✔
950
        # Hooks are not called but cleared on abort
951
        self.assertEqual(_hooked1, [])
1✔
952
        self.assertEqual(_hooked2, [])
1✔
953
        self.assertEqual(list(txn.getAfterCommitHooks()), [])
1✔
954
        self.assertEqual(txn._resources, [])
1✔
955
        self.assertIsNone(txn._manager)
1✔
956

957
    def test_abort_error_w_afterCommitHooks(self):
1✔
958
        from transaction import _transaction
1✔
959
        from transaction.tests.common import DummyLogger
1✔
960
        from transaction.tests.common import Monkey
1✔
961

962
        class BrokenResource:
1✔
963
            def sortKey(self):
1✔
964
                raise AssertionError("Not called")
965

966
            def abort(self, txn):
1✔
967
                raise ValueError('test')
1✔
968
        broken = BrokenResource()
1✔
969
        aaa = Resource('aaa')
1✔
970
        broken2 = BrokenResource()
1✔
971
        _hooked1, _hooked2 = [], []
1✔
972

973
        def _hook1(*args, **kw):
1✔
974
            raise AssertionError("Not called")
975

976
        def _hook2(*args, **kw):
1✔
977
            raise AssertionError("Not called")
978
        logger = DummyLogger()
1✔
979
        with Monkey(_transaction, _LOGGER=logger):
1✔
980
            txn = self._makeOne()
1✔
981
            txn._after_commit.append((_hook1, ('one',), {'uno': 1}))
1✔
982
            txn._after_commit.append((_hook2, (), {}))
1✔
983
            txn._resources.append(aaa)
1✔
984
            txn._resources.append(broken)
1✔
985
            txn._resources.append(broken2)
1✔
986
            logger._clear()
1✔
987
            self.assertRaises(ValueError, txn.abort)
1✔
988
        # Hooks are not called but cleared on abort
989
        self.assertEqual(_hooked1, [])
1✔
990
        self.assertEqual(_hooked2, [])
1✔
991
        self.assertEqual(list(txn.getAfterCommitHooks()), [])
1✔
992
        self.assertTrue(aaa._a)
1✔
993
        self.assertFalse(aaa._x)
1✔
994
        self.assertIsNone(txn._manager)
1✔
995

996
    def test_abort_error_w_synchronizers(self):
1✔
997
        from transaction import _transaction
1✔
998
        from transaction.tests.common import DummyLogger
1✔
999
        from transaction.tests.common import Monkey
1✔
1000
        from transaction.weakset import WeakSet
1✔
1001

1002
        class _Synch:
1✔
1003
            _before = _after = False
1✔
1004

1005
            def beforeCompletion(self, txn):
1✔
1006
                self._before = txn
1✔
1007

1008
            def afterCompletion(self, txn):
1✔
1009
                self._after = txn
1✔
1010
        synchs = [_Synch(), _Synch(), _Synch()]
1✔
1011
        ws = WeakSet()
1✔
1012
        for synch in synchs:
1✔
1013
            ws.add(synch)
1✔
1014

1015
        class BrokenResource:
1✔
1016
            def sortKey(self):
1✔
1017
                raise AssertionError("Should not be called")
1018

1019
            def abort(self, txn):
1✔
1020
                raise ValueError('test')
1✔
1021
        broken = BrokenResource()
1✔
1022
        logger = DummyLogger()
1✔
1023
        with Monkey(_transaction, _LOGGER=logger):
1✔
1024
            t = self._makeOne(synchronizers=ws)
1✔
1025
            logger._clear()
1✔
1026
            t._resources.append(broken)
1✔
1027
            self.assertRaises(ValueError, t.abort)
1✔
1028
        for synch in synchs:
1✔
1029
            self.assertIs(synch._before, t)
1✔
1030
            self.assertIs(synch._after, t)  # called in _cleanup
1✔
1031
        self.assertIsNot(t._synchronizers, ws)
1✔
1032

1033
    def test_abort_synchronizer_error_w_resources(self):
1✔
1034
        from transaction import _transaction
1✔
1035
        from transaction.tests.common import DummyLogger
1✔
1036
        from transaction.tests.common import Monkey
1✔
1037

1038
        class _Synch:
1✔
1039
            _before = _after = False
1✔
1040

1041
            def beforeCompletion(self, txn):
1✔
1042
                self._before = txn
1✔
1043

1044
            def afterCompletion(self, txn):
1✔
1045
                self._after = txn
1✔
1046

1047
        class _BadSynch(_Synch):
1✔
1048
            def beforeCompletion(self, txn):
1✔
1049
                _Synch.beforeCompletion(self, txn)
1✔
1050
                raise SystemExit
1✔
1051

1052
        # Ensure iteration order
1053
        class Synchs:
1✔
1054
            synchs = [_Synch(), _Synch(), _Synch(), _BadSynch()]
1✔
1055

1056
            def map(self, func):
1✔
1057
                for s in self.synchs:
1✔
1058
                    func(s)
1✔
1059

1060
        resource = Resource('a')
1✔
1061
        logger = DummyLogger()
1✔
1062
        synchs = Synchs()
1✔
1063
        with Monkey(_transaction, _LOGGER=logger):
1✔
1064
            t = self._makeOne(synchronizers=synchs)
1✔
1065
            logger._clear()
1✔
1066
            t._resources.append(resource)
1✔
1067
            with self.assertRaises(SystemExit):
1✔
1068
                t.abort()
1✔
1069

1070
        for synch in synchs.synchs:
1✔
1071
            self.assertIs(synch._before, t)
1✔
1072
            self.assertIs(synch._after, t)  # called in _cleanup
1✔
1073
        self.assertIsNot(t._synchronizers, synchs)
1✔
1074
        self.assertTrue(resource._a)
1✔
1075

1076
    def test_abort_clears_resources(self):
1✔
1077
        class DM:
1✔
1078
            def abort(self, txn):
1✔
1079
                return True
1✔
1080

1081
        dm = DM()
1✔
1082
        txn = self._makeOne()
1✔
1083
        txn.join(dm)
1✔
1084
        self.assertEqual(txn._resources, [dm])
1✔
1085
        txn.abort()
1✔
1086
        self.assertEqual(txn._resources, [])
1✔
1087

1088
    def test_getBeforeAbortHooks_empty(self):
1✔
1089
        txn = self._makeOne()
1✔
1090
        self.assertEqual(list(txn.getBeforeAbortHooks()), [])
1✔
1091

1092
    def test_addBeforeAbortHook(self):
1✔
1093
        def _hook(*args, **kw):
1✔
1094
            raise AssertionError("Not called")
1095
        txn = self._makeOne()
1✔
1096
        txn.addBeforeAbortHook(_hook, ('one',), dict(uno=1))
1✔
1097
        self.assertEqual(list(txn.getBeforeAbortHooks()),
1✔
1098
                         [(_hook, ('one',), {'uno': 1})])
1099

1100
    def test_addBeforeAbortHook_w_kws(self):
1✔
1101
        def _hook(*args, **kw):
1✔
1102
            raise AssertionError("Not called")
1103
        txn = self._makeOne()
1✔
1104
        txn.addBeforeAbortHook(_hook, ('one',))
1✔
1105
        self.assertEqual(list(txn.getBeforeAbortHooks()),
1✔
1106
                         [(_hook, ('one',), {})])
1107

1108
    def test_getAfterAbortHooks_empty(self):
1✔
1109
        txn = self._makeOne()
1✔
1110
        self.assertEqual(list(txn.getAfterAbortHooks()), [])
1✔
1111

1112
    def test_addAfterAbortHook(self):
1✔
1113
        def _hook(*args, **kw):
1✔
1114
            raise AssertionError("Not called")
1115
        txn = self._makeOne()
1✔
1116
        txn.addAfterAbortHook(_hook, ('one',), dict(uno=1))
1✔
1117
        self.assertEqual(list(txn.getAfterAbortHooks()),
1✔
1118
                         [(_hook, ('one',), {'uno': 1})])
1119

1120
    def test_addAfterAbortHook_wo_kws(self):
1✔
1121
        def _hook(*args, **kw):
1✔
1122
            raise AssertionError("Not called")
1123
        txn = self._makeOne()
1✔
1124
        txn.addAfterAbortHook(_hook, ('one',))
1✔
1125
        self.assertEqual(list(txn.getAfterAbortHooks()),
1✔
1126
                         [(_hook, ('one',), {})])
1127

1128
    def test_callBeforeAbortHook_w_error(self):
1✔
1129
        from transaction import _transaction
1✔
1130
        from transaction.tests.common import DummyLogger
1✔
1131
        from transaction.tests.common import Monkey
1✔
1132
        _hooked2 = []
1✔
1133

1134
        def _hook1(*args, **kw):
1✔
1135
            raise ValueError()
1✔
1136

1137
        def _hook2(*args, **kw):
1✔
1138
            _hooked2.append((args, kw))
1✔
1139
        logger = DummyLogger()
1✔
1140
        with Monkey(_transaction, _LOGGER=logger):
1✔
1141
            txn = self._makeOne()
1✔
1142
        logger._clear()
1✔
1143
        txn.addBeforeAbortHook(_hook1, ('one',))
1✔
1144
        txn.addBeforeAbortHook(_hook2, ('two',), dict(dos=2))
1✔
1145
        txn._callBeforeAbortHooks()
1✔
1146
        # second hook gets called even if first raises
1147
        self.assertEqual(_hooked2, [(('two',), {'dos': 2})])
1✔
1148
        self.assertEqual(len(logger._log), 1)
1✔
1149
        self.assertEqual(logger._log[0][0], 'error')
1✔
1150
        self.assertTrue(logger._log[0][1].startswith("Error in hook"))
1✔
1151

1152
    def test_callBeforeAbortHook_w_abort(self):
1✔
1153
        from transaction import _transaction
1✔
1154
        from transaction.tests.common import DummyLogger
1✔
1155
        from transaction.tests.common import Monkey
1✔
1156
        _hooked2 = []
1✔
1157

1158
        def _hook1(*args, **kw):
1✔
1159
            raise ValueError()
1✔
1160

1161
        def _hook2(*args, **kw):
1✔
1162
            _hooked2.append((args, kw))
1✔
1163
        logger = DummyLogger()
1✔
1164
        with Monkey(_transaction, _LOGGER=logger):
1✔
1165
            txn = self._makeOne()
1✔
1166
        logger._clear()
1✔
1167
        txn.addBeforeAbortHook(_hook1, ('one',))
1✔
1168
        txn.addBeforeAbortHook(_hook2, ('two',), dict(dos=2))
1✔
1169
        txn._callBeforeAbortHooks()
1✔
1170
        self.assertEqual(logger._log[0][0], 'error')
1✔
1171
        self.assertTrue(logger._log[0][1].startswith("Error in hook"))
1✔
1172

1173
    def test_callAfterAbortHook_w_abort_error(self):
1✔
1174
        from transaction import _transaction
1✔
1175
        from transaction.tests.common import DummyLogger
1✔
1176
        from transaction.tests.common import Monkey
1✔
1177
        _hooked2 = []
1✔
1178

1179
        def _hook2(*args, **kw):
1✔
1180
            _hooked2.append((args, kw))
1✔
1181
        logger = DummyLogger()
1✔
1182
        with Monkey(_transaction, _LOGGER=logger):
1✔
1183
            txn = self._makeOne()
1✔
1184
        logger._clear()
1✔
1185
        r = Resource("r", "abort")
1✔
1186
        txn.join(r)
1✔
1187
        txn.addAfterAbortHook(_hook2, ('two',), dict(dos=2))
1✔
1188
        txn._callAfterAbortHooks()
1✔
1189
        self.assertEqual(logger._log[0][0], 'error')
1✔
1190
        self.assertTrue(
1✔
1191
            logger._log[0][1].startswith("Error in abort() on manager"))
1192

1193
    def test_callAfterAbortHook_w_error_w_abort_error(self):
1✔
1194
        from transaction import _transaction
1✔
1195
        from transaction.tests.common import DummyLogger
1✔
1196
        from transaction.tests.common import Monkey
1✔
1197
        _hooked2 = []
1✔
1198

1199
        def _hook1(*args, **kw):
1✔
1200
            raise ValueError()
1✔
1201

1202
        def _hook2(*args, **kw):
1✔
1203
            _hooked2.append((args, kw))  # pragma: no cover
1204
        logger = DummyLogger()
1✔
1205
        with Monkey(_transaction, _LOGGER=logger):
1✔
1206
            txn = self._makeOne()
1✔
1207
        logger._clear()
1✔
1208
        r = Resource("r", "abort")
1✔
1209
        txn.join(r)
1✔
1210
        txn.addAfterAbortHook(_hook1, ('one',), dict(dos=1))
1✔
1211
        txn.addAfterAbortHook(_hook2, ('two',), dict(dos=2))
1✔
1212
        with self.assertRaises(ValueError):
1✔
1213
            txn._callAfterAbortHooks()
1✔
1214
        self.assertEqual(logger._log[0][0], 'error')
1✔
1215
        self.assertTrue(
1✔
1216
            logger._log[0][1].startswith("Error in abort() on manager"))
1217

1218
    def test_abort_w_abortHooks(self):
1✔
1219
        comm = []
1✔
1220
        txn = self._makeOne()
1✔
1221

1222
        def bah():
1✔
1223
            comm.append("before")
1✔
1224

1225
        def aah():
1✔
1226
            comm.append("after")
1✔
1227
        txn.addAfterAbortHook(aah)
1✔
1228
        txn.addBeforeAbortHook(bah)
1✔
1229
        txn.abort()
1✔
1230
        self.assertEqual(comm, ["before", "after"])
1✔
1231
        self.assertEqual(list(txn.getBeforeAbortHooks()), [])
1✔
1232
        self.assertEqual(list(txn.getAfterAbortHooks()), [])
1✔
1233

1234
    def test_commit_w_abortHooks(self):
1✔
1235
        comm = []
1✔
1236
        txn = self._makeOne()
1✔
1237

1238
        def bah():
1✔
1239
            comm.append("before")  # pragma: no cover
1240

1241
        def aah():
1✔
1242
            comm.append("after")  # pragma: no cover
1243
        txn.addAfterAbortHook(aah)
1✔
1244
        txn.addBeforeAbortHook(bah)
1✔
1245
        txn.commit()
1✔
1246
        self.assertEqual(comm, [])  # not called
1✔
1247
        # but cleared
1248
        self.assertEqual(list(txn.getBeforeAbortHooks()), [])
1✔
1249
        self.assertEqual(list(txn.getAfterAbortHooks()), [])
1✔
1250

1251
    def test_commit_w_error_w_abortHooks(self):
1✔
1252
        comm = []
1✔
1253
        txn = self._makeOne()
1✔
1254

1255
        def bah():
1✔
1256
            comm.append("before")  # pragma: no cover
1257

1258
        def aah():
1✔
1259
            comm.append("after")  # pragma: no cover
1260
        txn.addAfterAbortHook(aah)
1✔
1261
        txn.addBeforeAbortHook(bah)
1✔
1262
        r = Resource("aaa", "tpc_vote")
1✔
1263
        txn.join(r)
1✔
1264
        with self.assertRaises(ValueError):
1✔
1265
            txn.commit()
1✔
1266
        self.assertEqual(comm, [])  # not called
1✔
1267
        # not cleared
1268
        self.assertEqual(list(txn.getBeforeAbortHooks()), [(bah, (), {})])
1✔
1269
        self.assertEqual(list(txn.getAfterAbortHooks()), [(aah, (), {})])
1✔
1270

1271
    def test_note(self):
1✔
1272
        txn = self._makeOne()
1✔
1273
        try:
1✔
1274
            txn.note('This is a note.')
1✔
1275
            self.assertEqual(txn.description, 'This is a note.')
1✔
1276
            txn.note('Another.')
1✔
1277
            self.assertEqual(txn.description, 'This is a note.\nAnother.')
1✔
1278
        finally:
1279
            txn.abort()
1✔
1280

1281
    def test_note_bytes(self):
1✔
1282
        txn = self._makeOne()
1✔
1283
        with warnings.catch_warnings(record=True) as w:
1✔
1284
            warnings.simplefilter("always")
1✔
1285
            txn.note(b'haha')
1✔
1286
            self.assertNonTextDeprecationWarning(w)
1✔
1287
            self.assertEqual(txn.description, 'haha')
1✔
1288

1289
    def test_note_None(self):
1✔
1290
        txn = self._makeOne()
1✔
1291
        self.assertEqual('', txn.description)
1✔
1292
        with warnings.catch_warnings(record=True) as w:
1✔
1293
            warnings.simplefilter("always")
1✔
1294
            txn.note(None)
1✔
1295
            self.assertFalse(w)
1✔
1296
        self.assertEqual(txn.description, '')
1✔
1297

1298
    def test_note_42(self):
1✔
1299
        txn = self._makeOne()
1✔
1300
        with warnings.catch_warnings(record=True) as w:
1✔
1301
            warnings.simplefilter("always")
1✔
1302
            txn.note(42)
1✔
1303
            self.assertNonTextDeprecationWarning(w)
1✔
1304
            self.assertEqual(txn.description, '42')
1✔
1305

1306
    def assertNonTextDeprecationWarning(self, w):
1✔
1307
        [w] = w
1✔
1308
        self.assertEqual(
1✔
1309
            (DeprecationWarning, "Expected text",
1310
             os.path.splitext(__file__)[0]),
1311
            (w.category, str(w.message), os.path.splitext(w.filename)[0]),
1312
        )
1313

1314
    def test_description_bytes(self):
1✔
1315
        txn = self._makeOne()
1✔
1316
        with warnings.catch_warnings(record=True) as w:
1✔
1317
            warnings.simplefilter("always")
1✔
1318
            txn.description = b'haha'
1✔
1319
            self.assertNonTextDeprecationWarning(w)
1✔
1320
            self.assertEqual(txn.description, 'haha')
1✔
1321

1322
    def test_description_42(self):
1✔
1323
        txn = self._makeOne()
1✔
1324
        with warnings.catch_warnings(record=True) as w:
1✔
1325
            warnings.simplefilter("always")
1✔
1326
            txn.description = 42
1✔
1327
            self.assertNonTextDeprecationWarning(w)
1✔
1328
            self.assertEqual(txn.description, '42')
1✔
1329

1330
    def test_description_None(self):
1✔
1331
        txn = self._makeOne()
1✔
1332
        self.assertEqual('', txn.description)
1✔
1333
        with warnings.catch_warnings(record=True) as w:
1✔
1334
            warnings.simplefilter("always")
1✔
1335
            txn.description = None
1✔
1336
            self.assertFalse(w)
1✔
1337
        self.assertEqual(txn.description, '')
1✔
1338

1339
    def test_setUser_default_path(self):
1✔
1340
        txn = self._makeOne()
1✔
1341
        txn.setUser('phreddy')
1✔
1342
        self.assertEqual(txn.user, '/ phreddy')
1✔
1343

1344
    def test_setUser_explicit_path(self):
1✔
1345
        txn = self._makeOne()
1✔
1346
        txn.setUser('phreddy', '/bedrock')
1✔
1347
        self.assertEqual(txn.user, '/bedrock phreddy')
1✔
1348

1349
    def test_user_w_none(self):
1✔
1350
        txn = self._makeOne()
1✔
1351
        txn.user = 'phreddy'
1✔
1352
        with self.assertRaises(ValueError):
1✔
1353
            txn.user = None  # raises
1✔
1354
        self.assertEqual(txn.user, 'phreddy')
1✔
1355

1356
    def _test_user_non_text(self, user, path, expect, both=False):
1✔
1357
        txn = self._makeOne()
1✔
1358
        with warnings.catch_warnings(record=True) as w:
1✔
1359
            warnings.simplefilter("always")
1✔
1360
            if path:
1✔
1361
                txn.setUser(user, path)
1✔
1362
            else:
1363
                if path is None:
1✔
1364
                    txn.setUser(user)
1✔
1365
                else:
1366
                    txn.user = user
1✔
1367

1368
            if both:
1✔
1369
                self.assertNonTextDeprecationWarning(w[:1])
1✔
1370
                self.assertNonTextDeprecationWarning(w[1:])
1✔
1371
            else:
1372
                self.assertNonTextDeprecationWarning(w)
1✔
1373

1374
        self.assertEqual(expect, txn.user)
1✔
1375

1376
    def test_user_non_text(self, user=b'phreddy', path=b'/bedrock',
1✔
1377
                           expect="/bedrock phreddy", both=True):
1378
        self._test_user_non_text(b'phreddy', b'/bedrock',
1✔
1379
                                 "/bedrock phreddy", True)
1380
        self._test_user_non_text(b'phreddy', None, '/ phreddy')
1✔
1381
        self._test_user_non_text(b'phreddy', False, 'phreddy')
1✔
1382
        self._test_user_non_text(b'phreddy', '/bedrock', '/bedrock phreddy')
1✔
1383
        self._test_user_non_text('phreddy', b'/bedrock', '/bedrock phreddy')
1✔
1384
        self._test_user_non_text('phreddy', 2, '2 phreddy')
1✔
1385
        self._test_user_non_text(1, '/bedrock', '/bedrock 1')
1✔
1386
        self._test_user_non_text(1, 2, '2 1', True)
1✔
1387

1388
    def test_setExtendedInfo_single(self):
1✔
1389
        txn = self._makeOne()
1✔
1390
        txn.setExtendedInfo('frob', 'qux')
1✔
1391
        self.assertEqual(txn.extension, {'frob': 'qux'})
1✔
1392
        self.assertIs(txn._extension, txn._extension)  # legacy
1✔
1393

1394
    def test_setExtendedInfo_multiple(self):
1✔
1395
        txn = self._makeOne()
1✔
1396
        txn.setExtendedInfo('frob', 'qux')
1✔
1397
        txn.setExtendedInfo('baz', 'spam')
1✔
1398
        txn.setExtendedInfo('frob', 'quxxxx')
1✔
1399
        self.assertEqual(txn._extension, {'frob': 'quxxxx', 'baz': 'spam'})
1✔
1400
        self.assertIs(txn._extension, txn._extension)  # legacy
1✔
1401

1402
    def test__extension_settable(self):
1✔
1403
        # Because ZEO sets it. I'll fix ZEO, but maybe something else will
1404
        # break
1405
        txn = self._makeOne()
1✔
1406
        txn._extension = dict(baz='spam')
1✔
1407
        txn.setExtendedInfo('frob', 'qux')
1✔
1408
        self.assertEqual(txn.extension, {'frob': 'qux', 'baz': 'spam'})
1✔
1409

1410
    def test_data(self):
1✔
1411
        txn = self._makeOne()
1✔
1412

1413
        # Can't get data that wasn't set:
1414
        with self.assertRaises(KeyError) as c:
1✔
1415
            txn.data(self)
1✔
1416
        self.assertEqual(c.exception.args, (self,))
1✔
1417

1418
        data = dict(a=1)
1✔
1419
        txn.set_data(self, data)
1✔
1420
        self.assertEqual(txn.data(self), data)
1✔
1421

1422
        # Can't get something we haven't stored.
1423
        with self.assertRaises(KeyError) as c:
1✔
1424
            txn.data(data)
1✔
1425
        self.assertEqual(c.exception.args, (data,))
1✔
1426

1427
        # When the transaction ends, data are discarded:
1428
        txn.commit()
1✔
1429
        with self.assertRaises(KeyError) as c:
1✔
1430
            txn.data(self)
1✔
1431
        self.assertEqual(c.exception.args, (self,))
1✔
1432

1433
    def test_isRetryableError_w_transient_error(self):
1✔
1434
        from transaction._manager import TransactionManager
1✔
1435
        from transaction.interfaces import TransientError
1✔
1436
        txn = self._makeOne(manager=TransactionManager())
1✔
1437
        txn._manager._txn = txn
1✔
1438
        self.assertTrue(txn.isRetryableError(TransientError()))
1✔
1439

1440
    def test_isRetryableError_w_transient_subclass(self):
1✔
1441
        from transaction._manager import TransactionManager
1✔
1442
        from transaction.interfaces import TransientError
1✔
1443

1444
        class _Derived(TransientError):
1✔
1445
            pass
1✔
1446
        txn = self._makeOne(manager=TransactionManager())
1✔
1447
        txn._manager._txn = txn
1✔
1448
        self.assertTrue(txn.isRetryableError(_Derived()))
1✔
1449

1450
    def test_isRetryableError_w_normal_exception_no_resources(self):
1✔
1451
        from transaction._manager import TransactionManager
1✔
1452
        txn = self._makeOne(manager=TransactionManager())
1✔
1453
        txn._manager._txn = txn
1✔
1454
        self.assertFalse(txn.isRetryableError(Exception()))
1✔
1455

1456
    def test_isRetryableError_w_normal_exception_w_resource_voting_yes(self):
1✔
1457
        from transaction._manager import TransactionManager
1✔
1458

1459
        class _Resource:
1✔
1460
            def should_retry(self, err):
1✔
1461
                return True
1✔
1462
        txn = self._makeOne(manager=TransactionManager())
1✔
1463
        txn._manager._txn = txn
1✔
1464
        txn._resources.append(_Resource())
1✔
1465
        self.assertTrue(txn.isRetryableError(Exception()))
1✔
1466

1467
    def test_isRetryableError_w_multiple(self):
1✔
1468
        from transaction._manager import TransactionManager
1✔
1469

1470
        class _Resource:
1✔
1471
            _should = True
1✔
1472

1473
            def should_retry(self, err):
1✔
1474
                return self._should
1✔
1475
        txn = self._makeOne(manager=TransactionManager())
1✔
1476
        txn._manager._txn = txn
1✔
1477
        res1 = _Resource()
1✔
1478
        res1._should = False
1✔
1479
        res2 = _Resource()
1✔
1480
        txn._resources.append(res1)
1✔
1481
        txn._resources.append(res2)
1✔
1482
        self.assertTrue(txn.isRetryableError(Exception()))
1✔
1483

1484

1485
class Test_rm_key(unittest.TestCase):
1✔
1486

1487
    def _callFUT(self, oid):
1✔
1488
        from transaction._transaction import rm_key
1✔
1489
        return rm_key(oid)
1✔
1490

1491
    def test_miss(self):
1✔
1492
        self.assertIsNone(self._callFUT(object()))
1✔
1493

1494
    def test_hit(self):
1✔
1495
        self.assertEqual(self._callFUT(Resource('zzz')), 'zzz')
1✔
1496

1497

1498
class SavepointTests(unittest.TestCase):
1✔
1499

1500
    def _getTargetClass(self):
1✔
1501
        from transaction._transaction import Savepoint
1✔
1502
        return Savepoint
1✔
1503

1504
    def _makeOne(self, txn, optimistic, *resources):
1✔
1505
        return self._getTargetClass()(txn, optimistic, *resources)
1✔
1506

1507
    def test_ctor_w_savepoint_oblivious_resource_non_optimistic(self):
1✔
1508
        txn = object()
1✔
1509
        resource = object()
1✔
1510
        self.assertRaises(TypeError, self._makeOne, txn, False, resource)
1✔
1511

1512
    def test_ctor_w_savepoint_oblivious_resource_optimistic(self):
1✔
1513
        from transaction._transaction import NoRollbackSavepoint
1✔
1514
        txn = object()
1✔
1515
        resource = object()
1✔
1516
        sp = self._makeOne(txn, True, resource)
1✔
1517
        self.assertEqual(len(sp._savepoints), 1)
1✔
1518
        self.assertIsInstance(sp._savepoints[0], NoRollbackSavepoint)
1✔
1519
        self.assertIs(sp._savepoints[0].datamanager, resource)
1✔
1520

1521
    def test_ctor_w_savepoint_aware_resources(self):
1✔
1522
        class _Aware:
1✔
1523
            def savepoint(self):
1✔
1524
                return self
1✔
1525
        txn = object()
1✔
1526
        one = _Aware()
1✔
1527
        another = _Aware()
1✔
1528
        sp = self._makeOne(txn, True, one, another)
1✔
1529
        self.assertEqual(len(sp._savepoints), 2)
1✔
1530
        self.assertIsInstance(sp._savepoints[0], _Aware)
1✔
1531
        self.assertIs(sp._savepoints[0], one)
1✔
1532
        self.assertIsInstance(sp._savepoints[1], _Aware)
1✔
1533
        self.assertIs(sp._savepoints[1], another)
1✔
1534

1535
    def test_valid_wo_transacction(self):
1✔
1536
        sp = self._makeOne(None, True, object())
1✔
1537
        self.assertFalse(sp.valid)
1✔
1538

1539
    def test_valid_w_transacction(self):
1✔
1540
        sp = self._makeOne(object(), True, object())
1✔
1541
        self.assertTrue(sp.valid)
1✔
1542

1543
    def test_rollback_w_txn_None(self):
1✔
1544
        from transaction.interfaces import InvalidSavepointRollbackError
1✔
1545
        txn = None
1✔
1546

1547
        class _Aware:
1✔
1548
            def savepoint(self):
1✔
1549
                return self
1✔
1550
        resource = _Aware()
1✔
1551
        sp = self._makeOne(txn, False, resource)
1✔
1552
        self.assertRaises(InvalidSavepointRollbackError, sp.rollback)
1✔
1553

1554
    def test_rollback_w_sp_error(self):
1✔
1555
        class _TXN:
1✔
1556
            _sarce = False
1✔
1557
            _raia = None
1✔
1558

1559
            def _saveAndRaiseCommitishError(self):
1✔
1560
                import sys
1✔
1561

1562
                self._sarce = True
1✔
1563
                _, v, tb = sys.exc_info()
1✔
1564
                raise v.with_traceback(tb)
1✔
1565

1566
            def _remove_and_invalidate_after(self, sp):
1✔
1567
                self._raia = sp
1✔
1568

1569
        class _Broken:
1✔
1570
            def rollback(self):
1✔
1571
                raise ValueError()
1✔
1572
        _broken = _Broken()
1✔
1573

1574
        class _GonnaRaise:
1✔
1575
            def savepoint(self):
1✔
1576
                return _broken
1✔
1577
        txn = _TXN()
1✔
1578
        resource = _GonnaRaise()
1✔
1579
        sp = self._makeOne(txn, False, resource)
1✔
1580
        self.assertRaises(ValueError, sp.rollback)
1✔
1581
        self.assertIs(txn._raia, sp)
1✔
1582
        self.assertTrue(txn._sarce)
1✔
1583

1584

1585
class AbortSavepointTests(unittest.TestCase):
1✔
1586

1587
    def _getTargetClass(self):
1✔
1588
        from transaction._transaction import AbortSavepoint
1✔
1589
        return AbortSavepoint
1✔
1590

1591
    def _makeOne(self, datamanager, transaction):
1✔
1592
        return self._getTargetClass()(datamanager, transaction)
1✔
1593

1594
    def test_ctor(self):
1✔
1595
        dm = object()
1✔
1596
        txn = object()
1✔
1597
        asp = self._makeOne(dm, txn)
1✔
1598
        self.assertIs(asp.datamanager, dm)
1✔
1599
        self.assertIs(asp.transaction, txn)
1✔
1600

1601
    def test_rollback(self):
1✔
1602
        class _DM:
1✔
1603
            _aborted = None
1✔
1604

1605
            def abort(self, txn):
1✔
1606
                self._aborted = txn
1✔
1607

1608
        class _TXN:
1✔
1609
            _unjoined = None
1✔
1610

1611
            def _unjoin(self, datamanager):
1✔
1612
                self._unjoin = datamanager
1✔
1613
        dm = _DM()
1✔
1614
        txn = _TXN()
1✔
1615
        asp = self._makeOne(dm, txn)
1✔
1616
        asp.rollback()
1✔
1617
        self.assertIs(dm._aborted, txn)
1✔
1618
        self.assertIs(txn._unjoin, dm)
1✔
1619

1620

1621
class NoRollbackSavepointTests(unittest.TestCase):
1✔
1622

1623
    def _getTargetClass(self):
1✔
1624
        from transaction._transaction import NoRollbackSavepoint
1✔
1625
        return NoRollbackSavepoint
1✔
1626

1627
    def _makeOne(self, datamanager):
1✔
1628
        return self._getTargetClass()(datamanager)
1✔
1629

1630
    def test_ctor(self):
1✔
1631
        dm = object()
1✔
1632
        nrsp = self._makeOne(dm)
1✔
1633
        self.assertIs(nrsp.datamanager, dm)
1✔
1634

1635
    def test_rollback(self):
1✔
1636
        dm = object()
1✔
1637
        nrsp = self._makeOne(dm)
1✔
1638
        self.assertRaises(TypeError, nrsp.rollback)
1✔
1639

1640

1641
class MiscellaneousTests(unittest.TestCase):
1✔
1642

1643
    def test_bug239086(self):
1✔
1644
        # The original implementation of thread transaction manager made
1645
        # invalid assumptions about thread ids.
1646
        import threading
1✔
1647

1648
        import transaction
1✔
1649
        import transaction.tests.savepointsample as SPS
1✔
1650
        dm = SPS.SampleSavepointDataManager()
1✔
1651
        self.assertEqual(list(dm.keys()), [])
1✔
1652

1653
        class Sync:
1✔
1654
            def __init__(self, label):
1✔
1655
                self.label = label
1✔
1656
                self.log = []
1✔
1657

1658
            def beforeCompletion(self, txn):
1✔
1659
                raise AssertionError("Not called")
1660

1661
            def afterCompletion(self, txn):
1✔
1662
                raise AssertionError("Not called")
1663

1664
            def newTransaction(self, txn):
1✔
1665
                self.log.append('{} {}'.format(self.label, 'new'))
1✔
1666

1667
        def run_in_thread(f):
1✔
1668
            txn = threading.Thread(target=f)
1✔
1669
            txn.start()
1✔
1670
            txn.join()
1✔
1671

1672
        sync = Sync(1)
1✔
1673

1674
        @run_in_thread
1✔
1675
        def _():
1✔
1676
            transaction.manager.registerSynch(sync)
1✔
1677
            transaction.manager.begin()
1✔
1678
            dm['a'] = 1
1✔
1679
        self.assertEqual(sync.log, ['1 new'])
1✔
1680

1681
        @run_in_thread
1✔
1682
        def _2():
1✔
1683
            transaction.abort()  # should do nothing.
1✔
1684
        self.assertEqual(sync.log, ['1 new'])
1✔
1685
        self.assertEqual(list(dm.keys()), ['a'])
1✔
1686

1687
        dm = SPS.SampleSavepointDataManager()
1✔
1688
        self.assertEqual(list(dm.keys()), [])
1✔
1689

1690
        @run_in_thread
1✔
1691
        def _3():
1✔
1692
            dm['a'] = 1
1✔
1693
        self.assertEqual(sync.log, ['1 new'])
1✔
1694

1695
        transaction.abort()  # should do nothing
1✔
1696
        self.assertEqual(list(dm.keys()), ['a'])
1✔
1697

1698
    def test_gh5(self):
1✔
1699
        from transaction import _transaction
1✔
1700

1701
        buffer = _transaction._makeTracebackBuffer()
1✔
1702

1703
        s = 'ąčę'
1✔
1704
        buffer.write(s)
1✔
1705

1706
        buffer.seek(0)
1✔
1707
        self.assertEqual(buffer.read(), s)
1✔
1708

1709

1710
class Resource:
1✔
1711
    _b = _c = _v = _f = _a = _x = _after = False
1✔
1712

1713
    def __init__(self, key, error=None):
1✔
1714
        self._key = key
1✔
1715
        self._error = error
1✔
1716

1717
    def __repr__(self):
1✔
1718
        return 'Resource: %s' % self._key
1✔
1719

1720
    def sortKey(self):
1✔
1721
        return self._key
1✔
1722

1723
    def tpc_begin(self, txn):
1✔
1724
        if self._error == 'tpc_begin':
1✔
1725
            raise ValueError()
1✔
1726
        self._b = True
1✔
1727

1728
    def commit(self, txn):
1✔
1729
        if self._error == 'commit':
1✔
1730
            raise ValueError()
1✔
1731
        self._c = True
1✔
1732

1733
    def tpc_vote(self, txn):
1✔
1734
        if self._error == 'tpc_vote':
1✔
1735
            raise ValueError()
1✔
1736
        self._v = True
1✔
1737

1738
    def tpc_finish(self, txn):
1✔
1739
        if self._error == 'tpc_finish':
1✔
1740
            raise ValueError()
1✔
1741
        self._f = True
1✔
1742

1743
    def abort(self, txn):
1✔
1744
        if self._error == 'abort':
1✔
1745
            raise AssertionError("Not called in that state")
1746
        self._a = True
1✔
1747

1748
    def tpc_abort(self, txn):
1✔
1749
        if self._error == 'tpc_abort':
1✔
1750
            raise AssertionError("Not called in that state")
1751
        self._x = True
1✔
1752

1753
    def afterCompletion(self, txn):
1✔
1754
        if self._error == 'afterCompletion':
1✔
1755
            raise ValueError()
1✔
1756
        self._after = True
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc