Ñò 4äKc@s+dZddkZddkZddkZddkZddklZdeidƒZd„Z dei fd„ƒYZ d dd „ƒYZ d e fd „ƒYZd dd„ƒYZdefd„ƒYZd„Zd„Zd„Zd„Zd„ZeZedjoeiƒieƒƒndS(s½Test transaction behavior for variety of cases. I wrote these unittests to investigate some odd transaction behavior when doing unittests of integrating non sub transaction aware objects, and to insure proper txn behavior. these tests test the transaction system independent of the rest of the zodb. you can see the method calls to a jar by passing the keyword arg tracing to the modify method of a dataobject. the value of the arg is a prefix used for tracing print calls to that objects jar. the number of times a jar method was called can be inspected by looking at an attribute of the jar that is the method name prefixed with a c (count/check). i've included some tracing examples for tests that i thought were illuminating as doc strings below. TODO add in tests for objects which are modified multiple times, for example an object that gets modified in multiple sub txns. $Id: test_transaction.py 112144 2010-05-07 15:35:15Z tseaver $ iÿÿÿÿN(t WarningsHookitPcCs?t|ƒ}|djo"|t7}|djpt‚n|S(s)Return id(obj) as a non-negative integer.i(tidt _ADDRESS_MASKtAssertionError(tobjtresult((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyt positive_id2s    tTransactionTestscBskeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z d „Z RS( cCsYtiƒ}|_t|ƒ|_t|ƒ|_t|ƒ|_t|ddƒ|_dS(Ntnosti(t transactiontTransactionManagerttransaction_managert DataObjecttsub1tsub2tsub3tnosub1(tselftmgr((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pytsetUp=s cCse|iiƒ|iiƒ|iiƒ|iiidjpt‚|iiidjpt‚dS(Nii( RtmodifyRR tcommitt_p_jart ccommit_subRt ctpc_finish(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestTransactionCommitGs    cCsH|iiƒ|iiƒ|iiƒ|iiidjpt‚dS(Ni(RRRR tabortRtcabortR(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestTransactionAbortQs   cCs]|iiƒ}|idƒ|i|idƒ|idƒ|i|idƒ|iƒdS(NsThis is a note.sAnother.sThis is a note. Another.(R tgettnotet assertEqualt descriptionR(Rtt((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestTransactionNoteZs   cCs;|iiƒ|iiƒ|iiidjpt‚dS(Ni(RRR RRRR(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestNSJTransactionCommiths  cCsX|iiƒ|iiƒ|iiidjpt‚|iiidjpt‚dS(Nii(RRR RRRRR(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestNSJTransactionAbortps  cCs§tddƒ|i_|iiƒ|iiddƒ|iiƒy|iiƒWntj onX|iii djpt ‚|iii djpt ‚dS(NterrorsRtnojari( tBasicJarRRRRRR RtTestTxnExceptionRR(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestExceptionInAbort„s  cCs·tddƒ|i_|iiƒ|iiddƒy|iiƒWntj onX|iiidjpt ‚|iii djpt ‚|iii djpt ‚dS(NR&RR'ii( R(RRRRR RR)RRtccommitt ctpc_abort(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestExceptionInCommit“s cCsÔtddƒ|i_|iiƒ|iiddƒy|iiƒWntj onX|iiidjpt ‚|iii djpt ‚|iii djpt ‚|iii djpt ‚dS(NR&ttpc_voteR'ii( R(RRRRR RR)RRR+R,(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestExceptionInTpcVote¢s cCsštddƒ|i_|iiƒ|iiddƒy|iiƒWntj onX|iiidjpt ‚|iiidjpt ‚dS(sH 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 R&t tpc_beginR'iN( R(RRRRR RR)R,R(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestExceptionInTpcBegin²s  cCs}tddƒ|i_|iiƒ|iiddƒy|iiƒWntj onX|iiidjpt ‚dS(NR&t tpc_abortR.R'i(s tpc_abortstpc_vote( R(RRRRR RR)R,R(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttestExceptionInTpcAbortËs ( t__name__t __module__RRRR#R$R%R*R-R/R1R3(((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR;s       R cBs#eZdd„Zddd„ZRS(icCs||_||_d|_dS(N(R R tNoneR(RR R ((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyt__init__ús  cCsZ|p6|iotd|ƒ|_q=td|ƒ|_n|iiƒi|iƒdS(Nttracing(R R(RR Rtjoin(RR'R8((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyRÿs  (R4R5R7R(((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR øs R)cBseZRS((R4R5(((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR)sR(cBskeZd dd„Zd„Zd„Zd„Zd„Zd„Zdd„Zd„Z d „Z d „Z RS( icCs{t|tƒp |f}n||_||_d|_d|_d|_d|_d|_d|_ d|_ d|_ dS(Ni( t isinstancettupleR&R8RR+t ctpc_beginR,t ctpc_voteRt cabort_subR(RR&R8((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR7 s          cCs d|iit|ƒ|ifS(Ns <%s %X %s>(t __class__R4RR&(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyt__repr__s  cCs |iiS(N(R?R4(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pytsortKeyscCsN|iodt|iƒ|fGHn||ijotd|ƒ‚ndS(Ns%s calling method %sserror %s(R8tstrR&R)(Rtmethod((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pytcheck$s cGs |idƒ|id7_dS(NRi(RDR(Rtargs((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR-s cGs |idƒ|id7_dS(NRi(RDR+(RRE((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR1s cCs |idƒ|id7_dS(NR0i(RDR<(Rttxntsub((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR05s cGs |idƒ|id7_dS(NR.i(RDR=(RRE((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR.9s cGs |idƒ|id7_dS(NR2i(RDR,(RRE((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR2=s cGs |idƒ|id7_dS(Nt tpc_finishi(RDR(RRE((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyRHAs (( R4R5R7R@RARDRRR0R.R2RH(((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyR( s       tHoserJarcBs)eZdZd„Zd„Zd„ZRS(icCs dt_dS(Ni(RIt committed(R((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pytresetMscCs(tidjoti||ƒndS(Ni(RIRJR(RD(RRC((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyRDQscGs/|idƒ|id7_tid7_dS(NRHi(RDRRIRJ(RRE((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyRHUs (R4R5RJRKRDRH(((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyRIEs  cCsdS(s‡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.tests.sampledm import DataManager >>> from transaction._transaction import DataManagerAdapter >>> t = transaction.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 N((((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyt test_join[scCsdS(N((((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pythooktscCsdS(s 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. >>> import transaction >>> t = transaction.begin() >>> t.addBeforeCommitHook(hook, '1') We can see that the hook is indeed registered. >>> [(hook.func_name, 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: >>> len(list(t.getBeforeCommitHooks())) 0 >>> transaction.commit() >>> log [] The hook is only called for a full commit, not for a savepoint. >>> t = transaction.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. >>> t = transaction.begin() >>> t.addBeforeCommitHook(hook, ["OOPS!"]) >>> transaction.abort() >>> log [] >>> transaction.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 ... def abort(self, txn): ... pass >>> t = transaction.begin() >>> t.join(FailingDataManager()) >>> t.addBeforeCommitHook(hook, '2') >>> t.commit() Traceback (most recent call last): ... CommitFailure >>> log ["arg '2' kw1 'no_kw1' kw2 'no_kw2'"] >>> reset_log() Let's register several hooks. >>> t = transaction.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. >>> [(hook.func_name, 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 = transaction.begin() >>> t.addBeforeCommitHook(recurse, (t, 3)) >>> transaction.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() N((((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttest_addBeforeCommitHookwscCsdS(sì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. >>> import transaction >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, '1') We can see that the hook is indeed registered. >>> [(hook.func_name, 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: >>> len(list(t.getAfterCommitHooks())) 0 >>> transaction.commit() >>> log [] The hook is only called after a full commit, not for a savepoint. >>> t = transaction.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. >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, ["OOPS!"]) >>> transaction.abort() >>> log [] >>> transaction.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 ... def abort(self, txn): ... pass >>> t = transaction.begin() >>> t.join(FailingDataManager()) >>> t.addAfterCommitHook(hook, '2') >>> t.commit() Traceback (most recent call last): ... CommitFailure >>> log ["False arg '2' kw1 'no_kw1' kw2 'no_kw2'"] >>> reset_log() Let's register several hooks. >>> t = transaction.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. >>> [(hook.func_name, 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 = transaction.begin() >>> t.addAfterCommitHook(recurse, (t, 3)) >>> transaction.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. >>> mgr = transaction.TransactionManager() >>> do = DataObject(mgr) >>> def hookRaise(status, arg='no_arg', kw1='no_kw1', kw2='no_kw2'): ... raise TypeError("Fake raise") >>> t = transaction.begin() >>> t.addAfterCommitHook(hook, ('-', 1)) >>> t.addAfterCommitHook(hookRaise, ('-', 2)) >>> t.addAfterCommitHook(hook, ('-', 3)) >>> transaction.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 = transaction.TransactionManager() >>> do = DataObject(mgr) >>> t = transaction.begin() >>> len(t._manager._txns) 1 >>> t.addAfterCommitHook(hook, ('-', 1)) >>> transaction.commit() >>> log ["True arg '-' kw1 1 kw2 'no_kw2'"] >>> len(t._manager._txns) 0 >>> reset_log() N((((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyttest_addAfterCommitHookscCs>ddkl}l}ti|dƒ|ƒtitƒfƒS(Niÿÿÿÿ(t DocTestSuitet DocFileSuitesdoom.txt(tdoctestRPRQtunittestt TestSuitet makeSuiteR(RPRQ((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyt test_suite³s  t__main__(((t__doc__tstructRStwarningsR ttransaction.tests.warnhookRtcalcsizeRRtTestCaseRR t ExceptionR)R(RIRLRMRNRORVtadditional_testsR4tTextTestRunnertrun(((sF/usr/lib/python2.6/site-packages/transaction/tests/test_transaction.pyt(s(     ½;   ‰ ³