[Checkins] SVN: transaction/branches/sphinx/ Kill of forked sample module.

Tres Seaver cvs-admin at zope.org
Mon Dec 17 20:28:55 UTC 2012


Log message for revision 128707:
  Kill of forked sample module.

Changed:
  _U  transaction/branches/sphinx/
  A   transaction/branches/sphinx/docs/hooks.rst
  U   transaction/branches/sphinx/docs/index.rst
  U   transaction/branches/sphinx/transaction/_transaction.py
  D   transaction/branches/sphinx/transaction/tests/sampledm.py
  U   transaction/branches/sphinx/transaction/tests/test_transaction.py

-=-
Added: transaction/branches/sphinx/docs/hooks.rst
===================================================================
--- transaction/branches/sphinx/docs/hooks.rst	                        (rev 0)
+++ transaction/branches/sphinx/docs/hooks.rst	2012-12-17 20:28:55 UTC (rev 128707)
@@ -0,0 +1,384 @@
+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()

Modified: transaction/branches/sphinx/docs/index.rst
===================================================================
--- transaction/branches/sphinx/docs/index.rst	2012-12-17 20:28:54 UTC (rev 128706)
+++ transaction/branches/sphinx/docs/index.rst	2012-12-17 20:28:55 UTC (rev 128707)
@@ -9,6 +9,7 @@
    convenience
    doom
    savepoint
+   hooks
    datamanager
    resourcemanager
    api

Modified: transaction/branches/sphinx/transaction/_transaction.py
===================================================================
--- transaction/branches/sphinx/transaction/_transaction.py	2012-12-17 20:28:54 UTC (rev 128706)
+++ transaction/branches/sphinx/transaction/_transaction.py	2012-12-17 20:28:55 UTC (rev 128707)
@@ -116,6 +116,12 @@
 
 _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
 # hasattr() does).
 def myhasattr(obj, attr):
@@ -364,7 +370,7 @@
     def _saveAndGetCommitishError(self):
         self.status = Status.COMMITFAILED
         # Save the traceback for TransactionFailedError.
-        ft = self._failure_traceback = StringIO()
+        ft = self._failure_traceback = _makeTracebackBuffer()
         t = None
         v = None
         tb = None

Deleted: transaction/branches/sphinx/transaction/tests/sampledm.py
===================================================================
--- transaction/branches/sphinx/transaction/tests/sampledm.py	2012-12-17 20:28:54 UTC (rev 128706)
+++ transaction/branches/sphinx/transaction/tests/sampledm.py	2012-12-17 20:28:55 UTC (rev 128707)
@@ -1,412 +0,0 @@
-##############################################################################
-#
-# 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()

Modified: transaction/branches/sphinx/transaction/tests/test_transaction.py
===================================================================
--- transaction/branches/sphinx/transaction/tests/test_transaction.py	2012-12-17 20:28:54 UTC (rev 128706)
+++ transaction/branches/sphinx/transaction/tests/test_transaction.py	2012-12-17 20:28:55 UTC (rev 128707)
@@ -39,6 +39,36 @@
 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):
     """Return id(obj) as a non-negative integer."""
     import struct
@@ -50,41 +80,45 @@
         assert result > 0
     return result
 
-class TransactionTests(unittest.TestCase):
+class TransactionManagerTests(unittest.TestCase):
 
-    def setUp(self):
+    def _makeDM(self):
         from transaction import TransactionManager
-        mgr = self.transaction_manager = TransactionManager()
-        self.sub1 = DataObject(mgr)
-        self.sub2 = DataObject(mgr)
-        self.sub3 = DataObject(mgr)
-        self.nosub1 = DataObject(mgr, nost=1)
+        mgr = TransactionManager()
+        sub1 = DataObject(mgr)
+        sub2 = DataObject(mgr)
+        sub3 = DataObject(mgr)
+        nosub1 = DataObject(mgr, nost=1)
+        return mgr, sub1, sub2, sub3, nosub1
 
     # basic tests with two sub trans jars
     # really we only need one, so tests for
     # sub1 should identical to tests for sub2
     def testTransactionCommit(self):
 
-        self.sub1.modify()
-        self.sub2.modify()
+        mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
+        sub1.modify()
+        sub2.modify()
 
-        self.transaction_manager.commit()
+        mgr.commit()
 
-        assert self.sub1._p_jar.ccommit_sub == 0
-        assert self.sub1._p_jar.ctpc_finish == 1
+        assert sub1._p_jar.ccommit_sub == 0
+        assert sub1._p_jar.ctpc_finish == 1
 
     def testTransactionAbort(self):
 
-        self.sub1.modify()
-        self.sub2.modify()
+        mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
+        sub1.modify()
+        sub2.modify()
 
-        self.transaction_manager.abort()
+        mgr.abort()
 
-        assert self.sub2._p_jar.cabort == 1
+        assert sub2._p_jar.cabort == 1
 
     def testTransactionNote(self):
 
-        t = self.transaction_manager.get()
+        mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
+        t = mgr.get()
 
         t.note('This is a note.')
         self.assertEqual(t.description, 'This is a note.')
@@ -98,20 +132,22 @@
 
     def testNSJTransactionCommit(self):
 
-        self.nosub1.modify()
+        mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
+        nosub1.modify()
 
-        self.transaction_manager.commit()
+        mgr.commit()
 
-        assert self.nosub1._p_jar.ctpc_finish == 1
+        assert nosub1._p_jar.ctpc_finish == 1
 
     def testNSJTransactionAbort(self):
 
-        self.nosub1.modify()
+        mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
+        nosub1.modify()
 
-        self.transaction_manager.abort()
+        mgr.abort()
 
-        assert self.nosub1._p_jar.ctpc_finish == 0
-        assert self.nosub1._p_jar.cabort == 1
+        assert nosub1._p_jar.ctpc_finish == 0
+        assert nosub1._p_jar.cabort == 1
 
 
     ### Failure Mode Tests
@@ -126,87 +162,90 @@
 
     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()
-        self.sub1.modify(nojar=1)
-        self.sub2.modify()
+        nosub1.modify()
+        sub1.modify(nojar=1)
+        sub2.modify()
 
         try:
-            self.transaction_manager.abort()
+            mgr.abort()
         except TestTxnException: pass
 
-        assert self.nosub1._p_jar.cabort == 1
-        assert self.sub2._p_jar.cabort == 1
+        assert nosub1._p_jar.cabort == 1
+        assert sub2._p_jar.cabort == 1
 
     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()
-        self.sub1.modify(nojar=1)
+        nosub1.modify()
+        sub1.modify(nojar=1)
 
         try:
-            self.transaction_manager.commit()
+            mgr.commit()
         except TestTxnException: pass
 
-        assert self.nosub1._p_jar.ctpc_finish == 0
-        assert self.nosub1._p_jar.ccommit == 1
-        assert self.nosub1._p_jar.ctpc_abort == 1
+        assert nosub1._p_jar.ctpc_finish == 0
+        assert nosub1._p_jar.ccommit == 1
+        assert nosub1._p_jar.ctpc_abort == 1
 
     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()
-        self.sub1.modify(nojar=1)
+        nosub1.modify()
+        sub1.modify(nojar=1)
 
         try:
-            self.transaction_manager.commit()
+            mgr.commit()
         except TestTxnException: pass
 
-        assert self.nosub1._p_jar.ctpc_finish == 0
-        assert self.nosub1._p_jar.ccommit == 1
-        assert self.nosub1._p_jar.ctpc_abort == 1
-        assert self.sub1._p_jar.ctpc_abort == 1
+        assert nosub1._p_jar.ctpc_finish == 0
+        assert nosub1._p_jar.ccommit == 1
+        assert nosub1._p_jar.ctpc_abort == 1
+        assert sub1._p_jar.ctpc_abort == 1
 
     def testExceptionInTpcBegin(self):
-        """
-        ok this test reveals a bug in the TM.py
-        as the nosub tpc_abort there is ignored.
+        # ok this test reveals a bug in the TM.py
+        # as the nosub tpc_abort there is ignored.
 
-        nosub calling method tpc_begin
-        nosub calling method commit
-        sub calling method tpc_begin
-        sub calling method abort
-        sub calling method tpc_abort
-        nosub calling method tpc_abort
-        """
-        self.sub1._p_jar = BasicJar(errors='tpc_begin')
+        # nosub calling method tpc_begin
+        # nosub calling method commit
+        # sub calling method tpc_begin
+        # sub calling method abort
+        # sub calling method tpc_abort
+        # nosub calling method tpc_abort
+        mgr, sub1, sub2, sub3, nosub1 = self._makeDM()
+        sub1._p_jar = BasicJar(errors='tpc_begin')
 
-        self.nosub1.modify()
-        self.sub1.modify(nojar=1)
+        nosub1.modify()
+        sub1.modify(nojar=1)
 
         try:
-            self.transaction_manager.commit()
+            mgr.commit()
         except TestTxnException:
             pass
 
-        assert self.nosub1._p_jar.ctpc_abort == 1
-        assert self.sub1._p_jar.ctpc_abort == 1
+        assert nosub1._p_jar.ctpc_abort == 1
+        assert sub1._p_jar.ctpc_abort == 1
 
     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()
-        self.sub1.modify(nojar=1)
+        nosub1.modify()
+        sub1.modify(nojar=1)
 
         try:
-            self.transaction_manager.commit()
+            mgr.commit()
         except TestTxnException:
             pass
 
-        assert self.nosub1._p_jar.ctpc_abort == 1
+        assert nosub1._p_jar.ctpc_abort == 1
 
     # last test, check the hosing mechanism
 
@@ -217,7 +256,8 @@
 ##        # recover from such an error if it occurs during the very first
 ##        # 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.reset()
 ##            obj._p_jar = j
@@ -230,7 +270,7 @@
 
 ##        self.assert_(Transaction.hosed)
 
-##        self.sub2.modify()
+##        sub2.modify()
 
 ##        try:
 ##            transaction.commit()
@@ -241,6 +281,7 @@
 
 
 class Test_oid_repr(unittest.TestCase):
+
     def _callFUT(self, oid):
         from transaction._transaction import oid_repr
         return oid_repr(oid)
@@ -259,6 +300,76 @@
         s = '\1'*8
         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:
 
     def __init__(self, transaction_manager, nost=0):
@@ -358,421 +469,17 @@
         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():
     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():
     from doctest import DocTestSuite
     suite = unittest.TestSuite((
         DocTestSuite(),
-        unittest.makeSuite(TransactionTests),
+        unittest.makeSuite(TransactionManagerTests),
         unittest.makeSuite(Test_oid_repr),
+        unittest.makeSuite(MiscellaneousTests),
         ))
 
     return suite



More information about the checkins mailing list