Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
transaction
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
transaction
Commits
de6a7506
Commit
de6a7506
authored
Dec 17, 2012
by
Tres Seaver
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Kill of forked sample module.
parent
ea586b78
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
576 additions
and
890 deletions
+576
-890
docs/hooks.rst
docs/hooks.rst
+384
-0
docs/index.rst
docs/index.rst
+1
-0
transaction/_transaction.py
transaction/_transaction.py
+7
-1
transaction/tests/sampledm.py
transaction/tests/sampledm.py
+0
-412
transaction/tests/test_transaction.py
transaction/tests/test_transaction.py
+184
-477
No files found.
docs/hooks.rst
0 → 100644
View file @
de6a7506
Hooking the Transaction Machinery
=================================
The :meth:`addBeforeCommitHook` Method
--------------------------------------
Let's define a hook to call, and a way to see that it was called.
.. doctest::
>>> log = []
>>> def reset_log():
... del log[:]
>>> def hook(arg='no_arg', kw1='no_kw1', kw2='no_kw2'):
... log.append("arg %r kw1 %r kw2 %r" % (arg, kw1, kw2))
Now register the hook with a transaction.
.. doctest::
>>> from transaction import begin
>>> from transaction.compat import func_name
>>> import transaction
>>> t = begin()
>>> t.addBeforeCommitHook(hook, '1')
We can see that the hook is indeed registered.
.. doctest::
>>> [(func_name(hook), args, kws)
... for hook, args, kws in t.getBeforeCommitHooks()]
[('hook', ('1',), {})]
When transaction commit starts, the hook is called, with its
arguments.
.. doctest::
>>> log
[]
>>> t.commit()
>>> log
["arg '1' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
A hook's registration is consumed whenever the hook is called. Since
the hook above was called, it's no longer registered:
.. doctest::
>>> from transaction import commit
>>> len(list(t.getBeforeCommitHooks()))
0
>>> commit()
>>> log
[]
The hook is only called for a full commit, not for a savepoint.
.. doctest::
>>> t = begin()
>>> t.addBeforeCommitHook(hook, 'A', dict(kw1='B'))
>>> dummy = t.savepoint()
>>> log
[]
>>> t.commit()
>>> log
["arg 'A' kw1 'B' kw2 'no_kw2'"]
>>> reset_log()
If a transaction is aborted, no hook is called.
.. doctest::
>>> from transaction import abort
>>> t = begin()
>>> t.addBeforeCommitHook(hook, ["OOPS!"])
>>> abort()
>>> log
[]
>>> commit()
>>> log
[]
The hook is called before the commit does anything, so even if the
commit fails the hook will have been called. To provoke failures in
commit, we'll add failing resource manager to the transaction.
.. doctest::
>>> class CommitFailure(Exception):
... pass
>>> class FailingDataManager:
... def tpc_begin(self, txn, sub=False):
... raise CommitFailure('failed')
... def abort(self, txn):
... pass
>>> t = begin()
>>> t.join(FailingDataManager())
>>> t.addBeforeCommitHook(hook, '2')
>>> from transaction.tests.test_transaction import DummyFile
>>> from transaction.tests.test_transaction import Monkey
>>> from transaction.tests.test_transaction import assertRaisesEx
>>> from transaction import _transaction
>>> buffer = DummyFile()
>>> with Monkey(_transaction, _TB_BUFFER=buffer):
... err = assertRaisesEx(CommitFailure, t.commit)
>>> log
["arg '2' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
Let's register several hooks.
.. doctest::
>>> t = begin()
>>> t.addBeforeCommitHook(hook, '4', dict(kw1='4.1'))
>>> t.addBeforeCommitHook(hook, '5', dict(kw2='5.2'))
They are returned in the same order by getBeforeCommitHooks.
.. doctest::
>>> [(func_name(hook), args, kws) #doctest: +NORMALIZE_WHITESPACE
... for hook, args, kws in t.getBeforeCommitHooks()]
[('hook', ('4',), {'kw1': '4.1'}),
('hook', ('5',), {'kw2': '5.2'})]
And commit also calls them in this order.
.. doctest::
>>> t.commit()
>>> len(log)
2
>>> log #doctest: +NORMALIZE_WHITESPACE
["arg '4' kw1 '4.1' kw2 'no_kw2'",
"arg '5' kw1 'no_kw1' kw2 '5.2'"]
>>> reset_log()
While executing, a hook can itself add more hooks, and they will all
be called before the real commit starts.
.. doctest::
>>> def recurse(txn, arg):
... log.append('rec' + str(arg))
... if arg:
... txn.addBeforeCommitHook(hook, '-')
... txn.addBeforeCommitHook(recurse, (txn, arg-1))
>>> t = begin()
>>> t.addBeforeCommitHook(recurse, (t, 3))
>>> commit()
>>> log #doctest: +NORMALIZE_WHITESPACE
['rec3',
"arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec2',
"arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec1',
"arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec0']
>>> reset_log()
The :meth:`addAfterCommitHook` Method
--------------------------------------
Let's define a hook to call, and a way to see that it was called.
.. doctest::
>>> log = []
>>> def reset_log():
... del log[:]
>>> def hook(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'):
... log.append("%r arg %r kw1 %r kw2 %r" % (status, arg, kw1, kw2))
Now register the hook with a transaction.
.. doctest::
>>> from transaction import begin
>>> from transaction.compat import func_name
>>> t = begin()
>>> t.addAfterCommitHook(hook, '1')
We can see that the hook is indeed registered.
.. doctest::
>>> [(func_name(hook), args, kws)
... for hook, args, kws in t.getAfterCommitHooks()]
[('hook', ('1',), {})]
When transaction commit is done, the hook is called, with its
arguments.
.. doctest::
>>> log
[]
>>> t.commit()
>>> log
["True arg '1' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
A hook's registration is consumed whenever the hook is called. Since
the hook above was called, it's no longer registered:
.. doctest::
>>> from transaction import commit
>>> len(list(t.getAfterCommitHooks()))
0
>>> commit()
>>> log
[]
The hook is only called after a full commit, not for a savepoint.
.. doctest::
>>> t = begin()
>>> t.addAfterCommitHook(hook, 'A', dict(kw1='B'))
>>> dummy = t.savepoint()
>>> log
[]
>>> t.commit()
>>> log
["True arg 'A' kw1 'B' kw2 'no_kw2'"]
>>> reset_log()
If a transaction is aborted, no hook is called.
.. doctest::
>>> from transaction import abort
>>> t = begin()
>>> t.addAfterCommitHook(hook, ["OOPS!"])
>>> abort()
>>> log
[]
>>> commit()
>>> log
[]
The hook is called after the commit is done, so even if the
commit fails the hook will have been called. To provoke failures in
commit, we'll add failing resource manager to the transaction.
.. doctest::
>>> class CommitFailure(Exception):
... pass
>>> class FailingDataManager:
... def tpc_begin(self, txn):
... raise CommitFailure('failed')
... def abort(self, txn):
... pass
>>> t = begin()
>>> t.join(FailingDataManager())
>>> t.addAfterCommitHook(hook, '2')
>>> from transaction.tests.test_transaction import DummyFile
>>> from transaction.tests.test_transaction import Monkey
>>> from transaction.tests.test_transaction import assertRaisesEx
>>> from transaction import _transaction
>>> buffer = DummyFile()
>>> with Monkey(_transaction, _TB_BUFFER=buffer):
... err = assertRaisesEx(CommitFailure, t.commit)
>>> log
["False arg '2' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
Let's register several hooks.
.. doctest::
>>> t = begin()
>>> t.addAfterCommitHook(hook, '4', dict(kw1='4.1'))
>>> t.addAfterCommitHook(hook, '5', dict(kw2='5.2'))
They are returned in the same order by getAfterCommitHooks.
.. doctest::
>>> [(func_name(hook), args, kws) #doctest: +NORMALIZE_WHITESPACE
... for hook, args, kws in t.getAfterCommitHooks()]
[('hook', ('4',), {'kw1': '4.1'}),
('hook', ('5',), {'kw2': '5.2'})]
And commit also calls them in this order.
.. doctest::
>>> t.commit()
>>> len(log)
2
>>> log #doctest: +NORMALIZE_WHITESPACE
["True arg '4' kw1 '4.1' kw2 'no_kw2'",
"True arg '5' kw1 'no_kw1' kw2 '5.2'"]
>>> reset_log()
While executing, a hook can itself add more hooks, and they will all
be called before the real commit starts.
.. doctest::
>>> def recurse(status, txn, arg):
... log.append('rec' + str(arg))
... if arg:
... txn.addAfterCommitHook(hook, '-')
... txn.addAfterCommitHook(recurse, (txn, arg-1))
>>> t = begin()
>>> t.addAfterCommitHook(recurse, (t, 3))
>>> commit()
>>> log #doctest: +NORMALIZE_WHITESPACE
['rec3',
"True arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec2',
"True arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec1',
"True arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec0']
>>> reset_log()
If an after commit hook is raising an exception then it will log a
message at error level so that if other hooks are registered they
can be executed. We don't support execution dependencies at this level.
.. doctest::
>>> from transaction import TransactionManager
>>> from transaction.tests.test_transaction import DataObject
>>> mgr = TransactionManager()
>>> do = DataObject(mgr)
>>> def hookRaise(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'):
... raise TypeError("Fake raise")
>>> t = begin()
>>> t.addAfterCommitHook(hook, ('-', 1))
>>> t.addAfterCommitHook(hookRaise, ('-', 2))
>>> t.addAfterCommitHook(hook, ('-', 3))
>>> commit()
>>> log
["True arg '-' kw1 1 kw2 'no_kw2'", "True arg '-' kw1 3 kw2 'no_kw2'"]
>>> reset_log()
Test that the associated transaction manager has been cleanup when
after commit hooks are registered
.. doctest::
>>> mgr = TransactionManager()
>>> do = DataObject(mgr)
>>> t = begin()
>>> t._manager._txn is not None
True
>>> t.addAfterCommitHook(hook, ('-', 1))
>>> commit()
>>> log
["True arg '-' kw1 1 kw2 'no_kw2'"]
>>> t._manager._txn is not None
False
>>> reset_log()
docs/index.rst
View file @
de6a7506
...
@@ -9,6 +9,7 @@ Contents:
...
@@ -9,6 +9,7 @@ Contents:
convenience
convenience
doom
doom
savepoint
savepoint
hooks
datamanager
datamanager
resourcemanager
resourcemanager
api
api
...
...
transaction/_transaction.py
View file @
de6a7506
...
@@ -116,6 +116,12 @@ from transaction import interfaces
...
@@ -116,6 +116,12 @@ from transaction import interfaces
_marker
=
object
()
_marker
=
object
()
_TB_BUFFER
=
None
def
_makeTracebackBuffer
():
#unittests may hook
if
_TB_BUFFER
is
not
None
:
return
_TB_BUFFER
return
StringIO
()
# The point of this is to avoid hiding exceptions (which the builtin
# The point of this is to avoid hiding exceptions (which the builtin
# hasattr() does).
# hasattr() does).
def
myhasattr
(
obj
,
attr
):
def
myhasattr
(
obj
,
attr
):
...
@@ -364,7 +370,7 @@ class Transaction(object):
...
@@ -364,7 +370,7 @@ class Transaction(object):
def
_saveAndGetCommitishError
(
self
):
def
_saveAndGetCommitishError
(
self
):
self
.
status
=
Status
.
COMMITFAILED
self
.
status
=
Status
.
COMMITFAILED
# Save the traceback for TransactionFailedError.
# Save the traceback for TransactionFailedError.
ft
=
self
.
_failure_traceback
=
StringIO
()
ft
=
self
.
_failure_traceback
=
_makeTracebackBuffer
()
t
=
None
t
=
None
v
=
None
v
=
None
tb
=
None
tb
=
None
...
...
transaction/tests/sampledm.py
deleted
100644 → 0
View file @
ea586b78
##############################################################################
#
# Copyright (c) 2004 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Sample objects for use in tests
$Id: sampledm.py 29896 2005-04-07 04:48:06Z tim_one $
"""
class
DataManager
(
object
):
"""Sample data manager
This class provides a trivial data-manager implementation and doc
strings to illustrate the the protocol and to provide a tool for
writing tests.
Our sample data manager has state that is updated through an inc
method and through transaction operations.
When we create a sample data manager:
>>> dm = DataManager()
It has two bits of state, state:
>>> dm.state
0
and delta:
>>> dm.delta
0
Both of which are initialized to 0. state is meant to model
committed state, while delta represents tentative changes within a
transaction. We change the state by calling inc:
>>> dm.inc()
which updates delta:
>>> dm.delta
1
but state isn't changed until we commit the transaction:
>>> dm.state
0
To commit the changes, we use 2-phase commit. We execute the first
stage by calling prepare. We need to pass a transation. Our
sample data managers don't really use the transactions for much,
so we'll be lazy and use strings for transactions:
>>> t1 = '1'
>>> dm.prepare(t1)
The sample data manager updates the state when we call prepare:
>>> dm.state
1
>>> dm.delta
1
This is mainly so we can detect some affect of calling the methods.
Now if we call commit:
>>> dm.commit(t1)
Our changes are"permanent". The state reflects the changes and the
delta has been reset to 0.
>>> dm.state
1
>>> dm.delta
0
"""
def
__init__
(
self
):
self
.
state
=
0
self
.
sp
=
0
self
.
transaction
=
None
self
.
delta
=
0
self
.
prepared
=
False
def
inc
(
self
,
n
=
1
):
self
.
delta
+=
n
def
prepare
(
self
,
transaction
):
"""Prepare to commit data
>>> dm = DataManager()
>>> dm.inc()
>>> t1 = '1'
>>> dm.prepare(t1)
>>> dm.commit(t1)
>>> dm.state
1
>>> dm.inc()
>>> t2 = '2'
>>> dm.prepare(t2)
>>> dm.abort(t2)
>>> dm.state
1
It is en error to call prepare more than once without an intervening
commit or abort:
>>> dm.prepare(t1)
>>> dm.prepare(t1)
Traceback (most recent call last):
...
TypeError: Already prepared
>>> dm.prepare(t2)
Traceback (most recent call last):
...
TypeError: Already prepared
>>> dm.abort(t1)
If there was a preceeding savepoint, the transaction must match:
>>> rollback = dm.savepoint(t1)
>>> dm.prepare(t2)
Traceback (most recent call last):
,,,
TypeError: ('Transaction missmatch', '2', '1')
>>> dm.prepare(t1)
"""
if
self
.
prepared
:
raise
TypeError
(
'Already prepared'
)
self
.
_checkTransaction
(
transaction
)
self
.
prepared
=
True
self
.
transaction
=
transaction
self
.
state
+=
self
.
delta
def
_checkTransaction
(
self
,
transaction
):
if
(
transaction
is
not
self
.
transaction
and
self
.
transaction
is
not
None
):
raise
TypeError
(
"Transaction missmatch"
,
transaction
,
self
.
transaction
)
def
abort
(
self
,
transaction
):
"""Abort a transaction
The abort method can be called before two-phase commit to
throw away work done in the transaction:
>>> dm = DataManager()
>>> dm.inc()
>>> dm.state, dm.delta
(0, 1)
>>> t1 = '1'
>>> dm.abort(t1)
>>> dm.state, dm.delta
(0, 0)
The abort method also throws away work done in savepoints:
>>> dm.inc()
>>> r = dm.savepoint(t1)
>>> dm.inc()
>>> r = dm.savepoint(t1)
>>> dm.state, dm.delta
(0, 2)
>>> dm.abort(t1)
>>> dm.state, dm.delta
(0, 0)
If savepoints are used, abort must be passed the same
transaction:
>>> dm.inc()
>>> r = dm.savepoint(t1)
>>> t2 = '2'
>>> dm.abort(t2)
Traceback (most recent call last):
...
TypeError: ('Transaction missmatch', '2', '1')
>>> dm.abort(t1)
The abort method is also used to abort a two-phase commit:
>>> dm.inc()
>>> dm.state, dm.delta
(0, 1)
>>> dm.prepare(t1)
>>> dm.state, dm.delta
(1, 1)
>>> dm.abort(t1)
>>> dm.state, dm.delta
(0, 0)
Of course, the transactions passed to prepare and abort must
match:
>>> dm.prepare(t1)
>>> dm.abort(t2)
Traceback (most recent call last):
...
TypeError: ('Transaction missmatch', '2', '1')
>>> dm.abort(t1)
"""
self
.
_checkTransaction
(
transaction
)
if
self
.
transaction
is
not
None
:
self
.
transaction
=
None
if
self
.
prepared
:
self
.
state
-=
self
.
delta
self
.
prepared
=
False
self
.
delta
=
0
def
commit
(
self
,
transaction
):
"""Complete two-phase commit
>>> dm = DataManager()
>>> dm.state
0
>>> dm.inc()
We start two-phase commit by calling prepare:
>>> t1 = '1'
>>> dm.prepare(t1)
We complete it by calling commit:
>>> dm.commit(t1)
>>> dm.state
1
It is an error ro call commit without calling prepare first:
>>> dm.inc()
>>> t2 = '2'
>>> dm.commit(t2)
Traceback (most recent call last):
...
TypeError: Not prepared to commit
>>> dm.prepare(t2)
>>> dm.commit(t2)
If course, the transactions given to prepare and commit must
be the same:
>>> dm.inc()
>>> t3 = '3'
>>> dm.prepare(t3)
>>> dm.commit(t2)
Traceback (most recent call last):
...
TypeError: ('Transaction missmatch', '2', '3')
"""
if
not
self
.
prepared
:
raise
TypeError
(
'Not prepared to commit'
)
self
.
_checkTransaction
(
transaction
)
self
.
delta
=
0
self
.
transaction
=
None
self
.
prepared
=
False
def
savepoint
(
self
,
transaction
):
"""Provide the ability to rollback transaction state
Savepoints provide a way to:
- Save partial transaction work. For some data managers, this
could allow resources to be used more efficiently.
- Provide the ability to revert state to a point in a
transaction without aborting the entire transaction. In
other words, savepoints support partial aborts.
Savepoints don't use two-phase commit. If there are errors in
setting or rolling back to savepoints, the application should
abort the containing transaction. This is *not* the
responsibility of the data manager.
Savepoints are always associated with a transaction. Any work
done in a savepoint's transaction is tentative until the
transaction is committed using two-phase commit.
>>> dm = DataManager()
>>> dm.inc()
>>> t1 = '1'
>>> r = dm.savepoint(t1)
>>> dm.state, dm.delta
(0, 1)
>>> dm.inc()
>>> dm.state, dm.delta
(0, 2)
>>> r.rollback()
>>> dm.state, dm.delta
(0, 1)
>>> dm.prepare(t1)
>>> dm.commit(t1)
>>> dm.state, dm.delta
(1, 0)
Savepoints must have the same transaction:
>>> r1 = dm.savepoint(t1)
>>> dm.state, dm.delta
(1, 0)
>>> dm.inc()
>>> dm.state, dm.delta
(1, 1)
>>> t2 = '2'
>>> r2 = dm.savepoint(t2)
Traceback (most recent call last):
...
TypeError: ('Transaction missmatch', '2', '1')
>>> r2 = dm.savepoint(t1)
>>> dm.inc()
>>> dm.state, dm.delta
(1, 2)
If we rollback to an earlier savepoint, we discard all work
done later:
>>> r1.rollback()
>>> dm.state, dm.delta
(1, 0)
and we can no longer rollback to the later savepoint:
>>> r2.rollback()
Traceback (most recent call last):
...
TypeError: ('Attempt to roll back to invalid save point', 3, 2)
We can roll back to a savepoint as often as we like:
>>> r1.rollback()
>>> r1.rollback()
>>> r1.rollback()
>>> dm.state, dm.delta
(1, 0)
>>> dm.inc()
>>> dm.inc()
>>> dm.inc()
>>> dm.state, dm.delta
(1, 3)
>>> r1.rollback()
>>> dm.state, dm.delta
(1, 0)
But we can't rollback to a savepoint after it has been
committed:
>>> dm.prepare(t1)
>>> dm.commit(t1)
>>> r1.rollback()
Traceback (most recent call last):
...
TypeError: Attempt to rollback stale rollback
"""
if
self
.
prepared
:
raise
TypeError
(
"Can't get savepoint during two-phase commit"
)
self
.
_checkTransaction
(
transaction
)
self
.
transaction
=
transaction
self
.
sp
+=
1
return
Rollback
(
self
)
class
Rollback
(
object
):
def
__init__
(
self
,
dm
):
self
.
dm
=
dm
self
.
sp
=
dm
.
sp
self
.
delta
=
dm
.
delta
self
.
transaction
=
dm
.
transaction
def
rollback
(
self
):
if
self
.
transaction
is
not
self
.
dm
.
transaction
:
raise
TypeError
(
"Attempt to rollback stale rollback"
)
if
self
.
dm
.
sp
<
self
.
sp
:
raise
TypeError
(
"Attempt to roll back to invalid save point"
,
self
.
sp
,
self
.
dm
.
sp
)
self
.
dm
.
sp
=
self
.
sp
self
.
dm
.
delta
=
self
.
delta
def
test_suite
():
from
doctest
import
DocTestSuite
return
DocTestSuite
()
if
__name__
==
'__main__'
:
unittest
.
main
()
transaction/tests/test_transaction.py
View file @
de6a7506
...
@@ -39,6 +39,36 @@ TODO
...
@@ -39,6 +39,36 @@ TODO
import
unittest
import
unittest
class
DummyFile
(
object
):
def
__init__
(
self
):
self
.
_lines
=
[]
def
write
(
self
,
text
):
self
.
_lines
.
append
(
text
)
def
writelines
(
self
,
lines
):
self
.
_lines
.
extend
(
lines
)
class
Monkey
(
object
):
# context-manager for replacing module names in the scope of a test.
def
__init__
(
self
,
module
,
**
kw
):
self
.
module
=
module
self
.
to_restore
=
dict
([(
key
,
getattr
(
module
,
key
))
for
key
in
kw
])
for
key
,
value
in
kw
.
items
():
setattr
(
module
,
key
,
value
)
def
__enter__
(
self
):
return
self
def
__exit__
(
self
,
exc_type
,
exc_val
,
exc_tb
):
for
key
,
value
in
self
.
to_restore
.
items
():
setattr
(
self
.
module
,
key
,
value
)
def
assertRaisesEx
(
e_type
,
checked
,
*
args
,
**
kw
):
try
:
checked
(
*
args
,
**
kw
)
except
e_type
as
e
:
return
e
assert
(
0
,
"Didn't raise: %s"
%
e_type
.
__name__
)
def
positive_id
(
obj
):
def
positive_id
(
obj
):
"""Return id(obj) as a non-negative integer."""
"""Return id(obj) as a non-negative integer."""
import
struct
import
struct
...
@@ -50,41 +80,45 @@ def positive_id(obj):
...
@@ -50,41 +80,45 @@ def positive_id(obj):
assert
result
>
0
assert
result
>
0
return
result
return
result
class
TransactionTests
(
unittest
.
TestCase
):
class
Transaction
Manager
Tests
(
unittest
.
TestCase
):
def
setUp
(
self
):
def
_makeDM
(
self
):
from
transaction
import
TransactionManager
from
transaction
import
TransactionManager
mgr
=
self
.
transaction_manager
=
TransactionManager
()
mgr
=
TransactionManager
()
self
.
sub1
=
DataObject
(
mgr
)
sub1
=
DataObject
(
mgr
)
self
.
sub2
=
DataObject
(
mgr
)
sub2
=
DataObject
(
mgr
)
self
.
sub3
=
DataObject
(
mgr
)
sub3
=
DataObject
(
mgr
)
self
.
nosub1
=
DataObject
(
mgr
,
nost
=
1
)
nosub1
=
DataObject
(
mgr
,
nost
=
1
)
return
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
# basic tests with two sub trans jars
# basic tests with two sub trans jars
# really we only need one, so tests for
# really we only need one, so tests for
# sub1 should identical to tests for sub2
# sub1 should identical to tests for sub2
def
testTransactionCommit
(
self
):
def
testTransactionCommit
(
self
):
self
.
sub1
.
modify
()
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
self
.
sub2
.
modify
()
sub1
.
modify
()
sub2
.
modify
()
self
.
transaction_manage
r
.
commit
()
mg
r
.
commit
()
assert
s
elf
.
s
ub1
.
_p_jar
.
ccommit_sub
==
0
assert
sub1
.
_p_jar
.
ccommit_sub
==
0
assert
s
elf
.
s
ub1
.
_p_jar
.
ctpc_finish
==
1
assert
sub1
.
_p_jar
.
ctpc_finish
==
1
def
testTransactionAbort
(
self
):
def
testTransactionAbort
(
self
):
self
.
sub1
.
modify
()
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
self
.
sub2
.
modify
()
sub1
.
modify
()
sub2
.
modify
()
self
.
transaction_manage
r
.
abort
()
mg
r
.
abort
()
assert
s
elf
.
s
ub2
.
_p_jar
.
cabort
==
1
assert
sub2
.
_p_jar
.
cabort
==
1
def
testTransactionNote
(
self
):
def
testTransactionNote
(
self
):
t
=
self
.
transaction_manager
.
get
()
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
t
=
mgr
.
get
()
t
.
note
(
'This is a note.'
)
t
.
note
(
'This is a note.'
)
self
.
assertEqual
(
t
.
description
,
'This is a note.'
)
self
.
assertEqual
(
t
.
description
,
'This is a note.'
)
...
@@ -98,20 +132,22 @@ class TransactionTests(unittest.TestCase):
...
@@ -98,20 +132,22 @@ class TransactionTests(unittest.TestCase):
def
testNSJTransactionCommit
(
self
):
def
testNSJTransactionCommit
(
self
):
self
.
nosub1
.
modify
()
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
nosub1
.
modify
()
self
.
transaction_manage
r
.
commit
()
mg
r
.
commit
()
assert
self
.
nosub1
.
_p_jar
.
ctpc_finish
==
1
assert
nosub1
.
_p_jar
.
ctpc_finish
==
1
def
testNSJTransactionAbort
(
self
):
def
testNSJTransactionAbort
(
self
):
self
.
nosub1
.
modify
()
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
nosub1
.
modify
()
self
.
transaction_manage
r
.
abort
()
mg
r
.
abort
()
assert
self
.
nosub1
.
_p_jar
.
ctpc_finish
==
0
assert
nosub1
.
_p_jar
.
ctpc_finish
==
0
assert
self
.
nosub1
.
_p_jar
.
cabort
==
1
assert
nosub1
.
_p_jar
.
cabort
==
1
### Failure Mode Tests
### Failure Mode Tests
...
@@ -126,87 +162,90 @@ class TransactionTests(unittest.TestCase):
...
@@ -126,87 +162,90 @@ class TransactionTests(unittest.TestCase):
def
testExceptionInAbort
(
self
):
def
testExceptionInAbort
(
self
):
self
.
sub1
.
_p_jar
=
BasicJar
(
errors
=
'abort'
)
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
sub1
.
_p_jar
=
BasicJar
(
errors
=
'abort'
)
self
.
nosub1
.
modify
()
nosub1
.
modify
()
s
elf
.
s
ub1
.
modify
(
nojar
=
1
)
sub1
.
modify
(
nojar
=
1
)
s
elf
.
s
ub2
.
modify
()
sub2
.
modify
()
try
:
try
:
self
.
transaction_manage
r
.
abort
()
mg
r
.
abort
()
except
TestTxnException
:
pass
except
TestTxnException
:
pass
assert
self
.
nosub1
.
_p_jar
.
cabort
==
1
assert
nosub1
.
_p_jar
.
cabort
==
1
assert
s
elf
.
s
ub2
.
_p_jar
.
cabort
==
1
assert
sub2
.
_p_jar
.
cabort
==
1
def
testExceptionInCommit
(
self
):
def
testExceptionInCommit
(
self
):
self
.
sub1
.
_p_jar
=
BasicJar
(
errors
=
'commit'
)
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
sub1
.
_p_jar
=
BasicJar
(
errors
=
'commit'
)
self
.
nosub1
.
modify
()
nosub1
.
modify
()
s
elf
.
s
ub1
.
modify
(
nojar
=
1
)
sub1
.
modify
(
nojar
=
1
)
try
:
try
:
self
.
transaction_manage
r
.
commit
()
mg
r
.
commit
()
except
TestTxnException
:
pass
except
TestTxnException
:
pass
assert
self
.
nosub1
.
_p_jar
.
ctpc_finish
==
0
assert
nosub1
.
_p_jar
.
ctpc_finish
==
0
assert
self
.
nosub1
.
_p_jar
.
ccommit
==
1
assert
nosub1
.
_p_jar
.
ccommit
==
1
assert
self
.
nosub1
.
_p_jar
.
ctpc_abort
==
1
assert
nosub1
.
_p_jar
.
ctpc_abort
==
1
def
testExceptionInTpcVote
(
self
):
def
testExceptionInTpcVote
(
self
):
self
.
sub1
.
_p_jar
=
BasicJar
(
errors
=
'tpc_vote'
)
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
sub1
.
_p_jar
=
BasicJar
(
errors
=
'tpc_vote'
)
self
.
nosub1
.
modify
()
nosub1
.
modify
()
s
elf
.
s
ub1
.
modify
(
nojar
=
1
)
sub1
.
modify
(
nojar
=
1
)
try
:
try
:
self
.
transaction_manage
r
.
commit
()
mg
r
.
commit
()
except
TestTxnException
:
pass
except
TestTxnException
:
pass
assert
self
.
nosub1
.
_p_jar
.
ctpc_finish
==
0
assert
nosub1
.
_p_jar
.
ctpc_finish
==
0
assert
self
.
nosub1
.
_p_jar
.
ccommit
==
1
assert
nosub1
.
_p_jar
.
ccommit
==
1
assert
self
.
nosub1
.
_p_jar
.
ctpc_abort
==
1
assert
nosub1
.
_p_jar
.
ctpc_abort
==
1
assert
s
elf
.
s
ub1
.
_p_jar
.
ctpc_abort
==
1
assert
sub1
.
_p_jar
.
ctpc_abort
==
1
def
testExceptionInTpcBegin
(
self
):
def
testExceptionInTpcBegin
(
self
):
"""
# ok this test reveals a bug in the TM.py
ok this test reveals a bug in the TM.py
# as the nosub tpc_abort there is ignored.
as the nosub tpc_abort there is ignored.
# nosub calling method tpc_begin
nosub calling method tpc_begin
# nosub calling method commit
nosub calling method commit
# sub calling method tpc_begin
sub calling method tpc_begin
# sub calling method abort
sub calling method abort
# sub calling method tpc_abort
sub calling method tpc_abort
# nosub calling method tpc_abort
nosub calling method tpc_abort
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
"""
sub1
.
_p_jar
=
BasicJar
(
errors
=
'tpc_begin'
)
self
.
sub1
.
_p_jar
=
BasicJar
(
errors
=
'tpc_begin'
)
nosub1
.
modify
()
self
.
nosub1
.
modify
()
sub1
.
modify
(
nojar
=
1
)
self
.
sub1
.
modify
(
nojar
=
1
)
try
:
try
:
self
.
transaction_manage
r
.
commit
()
mg
r
.
commit
()
except
TestTxnException
:
except
TestTxnException
:
pass
pass
assert
self
.
nosub1
.
_p_jar
.
ctpc_abort
==
1
assert
nosub1
.
_p_jar
.
ctpc_abort
==
1
assert
s
elf
.
s
ub1
.
_p_jar
.
ctpc_abort
==
1
assert
sub1
.
_p_jar
.
ctpc_abort
==
1
def
testExceptionInTpcAbort
(
self
):
def
testExceptionInTpcAbort
(
self
):
self
.
sub1
.
_p_jar
=
BasicJar
(
errors
=
(
'tpc_abort'
,
'tpc_vote'
))
mgr
,
sub1
,
sub2
,
sub3
,
nosub1
=
self
.
_makeDM
()
sub1
.
_p_jar
=
BasicJar
(
errors
=
(
'tpc_abort'
,
'tpc_vote'
))
self
.
nosub1
.
modify
()
nosub1
.
modify
()
s
elf
.
s
ub1
.
modify
(
nojar
=
1
)
sub1
.
modify
(
nojar
=
1
)
try
:
try
:
self
.
transaction_manage
r
.
commit
()
mg
r
.
commit
()
except
TestTxnException
:
except
TestTxnException
:
pass
pass
assert
self
.
nosub1
.
_p_jar
.
ctpc_abort
==
1
assert
nosub1
.
_p_jar
.
ctpc_abort
==
1
# last test, check the hosing mechanism
# last test, check the hosing mechanism
...
@@ -217,7 +256,8 @@ class TransactionTests(unittest.TestCase):
...
@@ -217,7 +256,8 @@ class TransactionTests(unittest.TestCase):
## # recover from such an error if it occurs during the very first
## # recover from such an error if it occurs during the very first
## # tpc_finish() call of the second phase.
## # tpc_finish() call of the second phase.
## for obj in self.sub1, self.sub2:
## mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
## for obj in sub1, sub2:
## j = HoserJar(errors='tpc_finish')
## j = HoserJar(errors='tpc_finish')
## j.reset()
## j.reset()
## obj._p_jar = j
## obj._p_jar = j
...
@@ -230,7 +270,7 @@ class TransactionTests(unittest.TestCase):
...
@@ -230,7 +270,7 @@ class TransactionTests(unittest.TestCase):
## self.assert_(Transaction.hosed)
## self.assert_(Transaction.hosed)
## s
elf.s
ub2.modify()
## sub2.modify()
## try:
## try:
## transaction.commit()
## transaction.commit()
...
@@ -241,6 +281,7 @@ class TransactionTests(unittest.TestCase):
...
@@ -241,6 +281,7 @@ class TransactionTests(unittest.TestCase):
class
Test_oid_repr
(
unittest
.
TestCase
):
class
Test_oid_repr
(
unittest
.
TestCase
):
def
_callFUT
(
self
,
oid
):
def
_callFUT
(
self
,
oid
):
from
transaction._transaction
import
oid_repr
from
transaction._transaction
import
oid_repr
return
oid_repr
(
oid
)
return
oid_repr
(
oid
)
...
@@ -259,6 +300,76 @@ class Test_oid_repr(unittest.TestCase):
...
@@ -259,6 +300,76 @@ class Test_oid_repr(unittest.TestCase):
s
=
'
\
1
'
*
8
s
=
'
\
1
'
*
8
self
.
assertEqual
(
self
.
_callFUT
(
s
),
'0x0101010101010101'
)
self
.
assertEqual
(
self
.
_callFUT
(
s
),
'0x0101010101010101'
)
class
MiscellaneousTests
(
unittest
.
TestCase
):
def
test_BBB_join
(
self
):
# The join method is provided for "backward-compatability" with ZODB 4
# data managers.
from
transaction
import
Transaction
from
transaction.tests.SampleDataManager
import
DataManager
from
transaction._transaction
import
DataManagerAdapter
# The argument to join must be a zodb4 data manager,
# transaction.interfaces.IDataManager.
t
=
Transaction
()
dm
=
DataManager
()
t
.
join
(
dm
)
# The end result is that a data manager adapter is one of the
# transaction's objects:
self
.
assertTrue
(
isinstance
(
t
.
_resources
[
0
],
DataManagerAdapter
))
self
.
assertTrue
(
t
.
_resources
[
0
].
_datamanager
is
dm
)
def
test_bug239086
(
self
):
# The original implementation of thread transaction manager made
# invalid assumptions about thread ids.
import
threading
import
transaction
import
transaction.tests.savepointsample
as
SPS
dm
=
SPS
.
SampleSavepointDataManager
()
self
.
assertEqual
(
list
(
dm
.
keys
()),
[])
class
Sync
:
def
__init__
(
self
,
label
):
self
.
label
=
label
self
.
log
=
[]
def
beforeCompletion
(
self
,
t
):
self
.
log
.
append
(
'%s %s'
%
(
self
.
label
,
'before'
))
def
afterCompletion
(
self
,
t
):
self
.
log
.
append
(
'%s %s'
%
(
self
.
label
,
'after'
))
def
newTransaction
(
self
,
t
):
self
.
log
.
append
(
'%s %s'
%
(
self
.
label
,
'new'
))
def
run_in_thread
(
f
):
t
=
threading
.
Thread
(
target
=
f
)
t
.
start
()
t
.
join
()
sync
=
Sync
(
1
)
@
run_in_thread
def
first
():
transaction
.
manager
.
registerSynch
(
sync
)
transaction
.
manager
.
begin
()
dm
[
'a'
]
=
1
self
.
assertEqual
(
sync
.
log
,
[
'1 new'
])
@
run_in_thread
def
second
():
transaction
.
abort
()
# should do nothing.
self
.
assertEqual
(
sync
.
log
,
[
'1 new'
])
self
.
assertEqual
(
list
(
dm
.
keys
()),
[
'a'
])
dm
=
SPS
.
SampleSavepointDataManager
()
self
.
assertEqual
(
list
(
dm
.
keys
()),
[])
@
run_in_thread
def
first
():
dm
[
'a'
]
=
1
self
.
assertEqual
(
sync
.
log
,
[
'1 new'
])
transaction
.
abort
()
# should do nothing
self
.
assertEqual
(
list
(
dm
.
keys
()),
[
'a'
])
class
DataObject
:
class
DataObject
:
def
__init__
(
self
,
transaction_manager
,
nost
=
0
):
def
__init__
(
self
,
transaction_manager
,
nost
=
0
):
...
@@ -358,421 +469,17 @@ class HoserJar(BasicJar):
...
@@ -358,421 +469,17 @@ class HoserJar(BasicJar):
HoserJar
.
committed
+=
1
HoserJar
.
committed
+=
1
def
test_join
():
"""White-box test of the join method
The join method is provided for "backward-compatability" with ZODB 4
data managers.
The argument to join must be a zodb4 data manager,
transaction.interfaces.IDataManager.
>>> from transaction import Transaction
>>> from transaction.tests.sampledm import DataManager
>>> from transaction._transaction import DataManagerAdapter
>>> t = Transaction()
>>> dm = DataManager()
>>> t.join(dm)
The end result is that a data manager adapter is one of the
transaction's objects:
>>> isinstance(t._resources[0], DataManagerAdapter)
True
>>> t._resources[0]._datamanager is dm
True
"""
def
hook
():
def
hook
():
pass
pass
def
test_addBeforeCommitHook
():
"""Test addBeforeCommitHook.
Let's define a hook to call, and a way to see that it was called.
>>> log = []
>>> def reset_log():
... del log[:]
>>> def hook(arg='no_arg', kw1='no_kw1', kw2='no_kw2'):
... log.append("arg %r kw1 %r kw2 %r" % (arg, kw1, kw2))
Now register the hook with a transaction.
>>> from transaction import begin
>>> from transaction.compat import func_name
>>> import transaction
>>> t = begin()
>>> t.addBeforeCommitHook(hook, '1')
We can see that the hook is indeed registered.
>>> [(func_name(hook), args, kws)
... for hook, args, kws in t.getBeforeCommitHooks()]
[('hook', ('1',), {})]
When transaction commit starts, the hook is called, with its
arguments.
>>> log
[]
>>> t.commit()
>>> log
["arg '1' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
A hook's registration is consumed whenever the hook is called. Since
the hook above was called, it's no longer registered:
>>> from transaction import commit
>>> len(list(t.getBeforeCommitHooks()))
0
>>> commit()
>>> log
[]
The hook is only called for a full commit, not for a savepoint.
>>> t = begin()
>>> t.addBeforeCommitHook(hook, 'A', dict(kw1='B'))
>>> dummy = t.savepoint()
>>> log
[]
>>> t.commit()
>>> log
["arg 'A' kw1 'B' kw2 'no_kw2'"]
>>> reset_log()
If a transaction is aborted, no hook is called.
>>> from transaction import abort
>>> t = begin()
>>> t.addBeforeCommitHook(hook, ["OOPS!"])
>>> abort()
>>> log
[]
>>> commit()
>>> log
[]
The hook is called before the commit does anything, so even if the
commit fails the hook will have been called. To provoke failures in
commit, we'll add failing resource manager to the transaction.
>>> class CommitFailure(Exception):
... pass
>>> class FailingDataManager:
... def tpc_begin(self, txn, sub=False):
... raise CommitFailure('failed')
... def abort(self, txn):
... pass
>>> t = begin()
>>> t.join(FailingDataManager())
>>> t.addBeforeCommitHook(hook, '2')
>>> t.commit() #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
CommitFailure: failed
>>> log
["arg '2' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
Let's register several hooks.
>>> t = begin()
>>> t.addBeforeCommitHook(hook, '4', dict(kw1='4.1'))
>>> t.addBeforeCommitHook(hook, '5', dict(kw2='5.2'))
They are returned in the same order by getBeforeCommitHooks.
>>> [(func_name(hook), args, kws) #doctest: +NORMALIZE_WHITESPACE
... for hook, args, kws in t.getBeforeCommitHooks()]
[('hook', ('4',), {'kw1': '4.1'}),
('hook', ('5',), {'kw2': '5.2'})]
And commit also calls them in this order.
>>> t.commit()
>>> len(log)
2
>>> log #doctest: +NORMALIZE_WHITESPACE
["arg '4' kw1 '4.1' kw2 'no_kw2'",
"arg '5' kw1 'no_kw1' kw2 '5.2'"]
>>> reset_log()
While executing, a hook can itself add more hooks, and they will all
be called before the real commit starts.
>>> def recurse(txn, arg):
... log.append('rec' + str(arg))
... if arg:
... txn.addBeforeCommitHook(hook, '-')
... txn.addBeforeCommitHook(recurse, (txn, arg-1))
>>> t = begin()
>>> t.addBeforeCommitHook(recurse, (t, 3))
>>> commit()
>>> log #doctest: +NORMALIZE_WHITESPACE
['rec3',
"arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec2',
"arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec1',
"arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec0']
>>> reset_log()
"""
def
test_addAfterCommitHook
():
"""Test addAfterCommitHook.
Let's define a hook to call, and a way to see that it was called.
>>> log = []
>>> def reset_log():
... del log[:]
>>> def hook(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'):
... log.append("%r arg %r kw1 %r kw2 %r" % (status, arg, kw1, kw2))
Now register the hook with a transaction.
>>> from transaction import begin
>>> from transaction.compat import func_name
>>> t = begin()
>>> t.addAfterCommitHook(hook, '1')
We can see that the hook is indeed registered.
>>> [(func_name(hook), args, kws)
... for hook, args, kws in t.getAfterCommitHooks()]
[('hook', ('1',), {})]
When transaction commit is done, the hook is called, with its
arguments.
>>> log
[]
>>> t.commit()
>>> log
["True arg '1' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
A hook's registration is consumed whenever the hook is called. Since
the hook above was called, it's no longer registered:
>>> from transaction import commit
>>> len(list(t.getAfterCommitHooks()))
0
>>> commit()
>>> log
[]
The hook is only called after a full commit, not for a savepoint.
>>> t = begin()
>>> t.addAfterCommitHook(hook, 'A', dict(kw1='B'))
>>> dummy = t.savepoint()
>>> log
[]
>>> t.commit()
>>> log
["True arg 'A' kw1 'B' kw2 'no_kw2'"]
>>> reset_log()
If a transaction is aborted, no hook is called.
>>> from transaction import abort
>>> t = begin()
>>> t.addAfterCommitHook(hook, ["OOPS!"])
>>> abort()
>>> log
[]
>>> commit()
>>> log
[]
The hook is called after the commit is done, so even if the
commit fails the hook will have been called. To provoke failures in
commit, we'll add failing resource manager to the transaction.
>>> class CommitFailure(Exception):
... pass
>>> class FailingDataManager:
... def tpc_begin(self, txn):
... raise CommitFailure('failed')
... def abort(self, txn):
... pass
>>> t = begin()
>>> t.join(FailingDataManager())
>>> t.addAfterCommitHook(hook, '2')
>>> t.commit() #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
CommitFailure: failed
>>> log
["False arg '2' kw1 'no_kw1' kw2 'no_kw2'"]
>>> reset_log()
Let's register several hooks.
>>> t = begin()
>>> t.addAfterCommitHook(hook, '4', dict(kw1='4.1'))
>>> t.addAfterCommitHook(hook, '5', dict(kw2='5.2'))
They are returned in the same order by getAfterCommitHooks.
>>> [(func_name(hook), args, kws) #doctest: +NORMALIZE_WHITESPACE
... for hook, args, kws in t.getAfterCommitHooks()]
[('hook', ('4',), {'kw1': '4.1'}),
('hook', ('5',), {'kw2': '5.2'})]
And commit also calls them in this order.
>>> t.commit()
>>> len(log)
2
>>> log #doctest: +NORMALIZE_WHITESPACE
["True arg '4' kw1 '4.1' kw2 'no_kw2'",
"True arg '5' kw1 'no_kw1' kw2 '5.2'"]
>>> reset_log()
While executing, a hook can itself add more hooks, and they will all
be called before the real commit starts.
>>> def recurse(status, txn, arg):
... log.append('rec' + str(arg))
... if arg:
... txn.addAfterCommitHook(hook, '-')
... txn.addAfterCommitHook(recurse, (txn, arg-1))
>>> t = begin()
>>> t.addAfterCommitHook(recurse, (t, 3))
>>> commit()
>>> log #doctest: +NORMALIZE_WHITESPACE
['rec3',
"True arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec2',
"True arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec1',
"True arg '-' kw1 'no_kw1' kw2 'no_kw2'",
'rec0']
>>> reset_log()
If an after commit hook is raising an exception then it will log a
message at error level so that if other hooks are registered they
can be executed. We don't support execution dependencies at this level.
>>> from transaction import TransactionManager
>>> mgr = TransactionManager()
>>> do = DataObject(mgr)
>>> def hookRaise(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'):
... raise TypeError("Fake raise")
>>> t = begin()
>>> t.addAfterCommitHook(hook, ('-', 1))
>>> t.addAfterCommitHook(hookRaise, ('-', 2))
>>> t.addAfterCommitHook(hook, ('-', 3))
>>> commit()
>>> log
["True arg '-' kw1 1 kw2 'no_kw2'", "True arg '-' kw1 3 kw2 'no_kw2'"]
>>> reset_log()
Test that the associated transaction manager has been cleanup when
after commit hooks are registered
>>> mgr = TransactionManager()
>>> do = DataObject(mgr)
>>> t = begin()
>>> t._manager._txn is not None
True
>>> t.addAfterCommitHook(hook, ('-', 1))
>>> commit()
>>> log
["True arg '-' kw1 1 kw2 'no_kw2'"]
>>> t._manager._txn is not None
False
>>> reset_log()
"""
def
bug239086
():
"""
The original implementation of thread transaction manager made
invalid assumptions about thread ids.
>>> import transaction
>>> import transaction.tests.savepointsample as SPS
>>> dm = SPS.SampleSavepointDataManager()
>>> list(dm.keys())
[]
>>> class Sync:
... def __init__(self, label):
... self.label = label
... def beforeCompletion(self, t):
... print('%s %s' % (self.label, 'before'))
... def afterCompletion(self, t):
... print('%s %s' % (self.label, 'after'))
... def newTransaction(self, t):
... print('%s %s' % (self.label, 'new'))
>>> sync = Sync(1)
>>> import threading
>>> def run_in_thread(f):
... t = threading.Thread(target=f)
... t.start()
... t.join()
>>> @run_in_thread
... def first():
... transaction.manager.registerSynch(sync)
... transaction.manager.begin()
... dm['a'] = 1
1 new
>>> @run_in_thread
... def second():
... transaction.abort() # should do nothing.
>>> list(dm.keys())
['a']
>>> dm = SPS.SampleSavepointDataManager()
>>> list(dm.keys())
[]
>>> @run_in_thread
... def first():
... dm['a'] = 1
>>> transaction.abort() # should do nothing
>>> list(dm.keys())
['a']
"""
def
test_suite
():
def
test_suite
():
from
doctest
import
DocTestSuite
from
doctest
import
DocTestSuite
suite
=
unittest
.
TestSuite
((
suite
=
unittest
.
TestSuite
((
DocTestSuite
(),
DocTestSuite
(),
unittest
.
makeSuite
(
TransactionTests
),
unittest
.
makeSuite
(
Transaction
Manager
Tests
),
unittest
.
makeSuite
(
Test_oid_repr
),
unittest
.
makeSuite
(
Test_oid_repr
),
unittest
.
makeSuite
(
MiscellaneousTests
),
))
))
return
suite
return
suite
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment