mò Sã¸Ec@s–dZdklZdkZdkZdklZdkZdkl Z dk l Z l Z dk lZdklZeidd eeƒd eifd „ƒYZd eifd „ƒYZdeifd„ƒYZd„Zdefd„ƒYZdfd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdfd„ƒYZdeifd„ƒYZ dfd „ƒYZ!d!„Z"dS("s$Unit tests for the Connection class.(sdoctestN(s Persistent(sdatabaseFromString(sp64su64(s WarningsHook(s verifyObjecttignores!.* subtransactions are deprecatedtConnectionDotAddcBsYtZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d „Z RS( NcCsHdkl}tƒ|_||iƒ|_|iiƒtƒ|_dS(N(s Connection( tZODB.Connectiont Connectiont StubDatabasetselftdbtdatamgrtopentStubTransactiont transaction(RR((t8/data/zmath/zope/lib/python/ZODB/tests/testConnection.pytsetUp#s    cCstiƒdS(N(R tabort(R((R ttearDown*scCsdkl}tƒ}|i|idjƒ|i|idjƒ|i i |ƒ|i|idj ƒ|i|i|i jƒ|i|i i |iƒ|jƒ|i t |i i tƒƒ|i}|i i |ƒ|i|i|ƒtƒ}tƒ|_|i ||i i |ƒdS(N(sInvalidObjectReference(tZODB.POSExceptiontInvalidObjectReferencet StubObjecttobjRtassert_t_p_oidtNonet_p_jarRtaddtgett assertRaisest TypeErrortobjecttoidt assertEqualtobj2(RRRRR((R t check_add-s  "   cCs~tƒ}|ii|ƒ|i}|ii|iƒ|i |idjƒ|i |i djƒ|i t |ii|ƒdS(N(RRRRRRRR R RRRRtKeyErrorR(RRR((R tcheckResetOnAbortEs  cCs‘tƒ}|ii|ƒ|i}|ii|iƒ|ii |iƒ|i |idjƒ|i |i djƒ|i t|ii|ƒdS(N(RRRRRRRt tpc_beginR t tpc_abortRRRRR R(RRR((R tcheckResetOnTpcAbortPs  cCsÀtƒ}|ii|ƒ|i}|ii|iƒ|ii |iƒ|ii |iƒ|i |idjƒ|i |i djƒ|it|ii|ƒ|i|iii|gƒdS(N(RRRRRRRR"R tcommitR#RRRRR Rt assertEqualsRt_storaget_stored(RRR((R tcheckTpcAbortAfterCommit_s  cCsÆtƒ}|ii|ƒ|i}|ii|iƒ|ii |iƒ|ii |iƒ|i |i|jƒ|i |i |ijƒ|i |iii|gƒ|i |iii|gƒdS(N(RRRRRRRR"R R%t tpc_finishRRR&RR'R(t _finished(RRR((R t checkCommitls  cCsítƒ}tƒ}||_t|ƒ}|ii|ƒ|ii|i ƒ|ii |i ƒ|ii |i ƒ|i i }|i|i|ijdƒ|i|i|ijdƒ|i|i|ijdƒ|i|iidjƒdS(Nsobject was not storedssubobject was not storedsmember was not stored(RtmembertsubobjtModifyOnGetStateObjectRRRRR"R R%R*RR'tstorageRRR(t_added_during_commitR(RRR0R-R.((R tcheckModifyOnGetstate|s     cCsbtƒ}|ii|ƒ|ii|iƒ|ii|iƒ|i|i |ii i jƒdS(N( RRRRRR"R R*RRR'R((RR((R tcheckUnusedAddWorksŒs  ( t__name__t __module__R RRR!R$R)R,R2R3(((R R!s      tUserMethodTestscBsPtZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z RS( NcCsdS(sdoctest of root() method The root() method is simple, and the tests are pretty minimal. Ensure that a new database has a root and that it is a PersistentMapping. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> root = cn.root() >>> type(root).__name__ 'PersistentMapping' >>> root._p_oid '\x00\x00\x00\x00\x00\x00\x00\x00' >>> root._p_jar is cn True >>> db.close() N((R((R t test_rootscCsdS(sØdoctest of get() method The get() method return the persistent object corresponding to an oid. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> obj = cn.get(p64(0)) >>> obj._p_oid '\x00\x00\x00\x00\x00\x00\x00\x00' The object is a ghost. >>> obj._p_state -1 And multiple calls with the same oid, return the same object. >>> obj2 = cn.get(p64(0)) >>> obj is obj2 True If all references to the object are released, then a new object will be returned. The cache doesn't keep unreferenced ghosts alive. (The next object returned my still have the same id, because Python may re-use the same memory.) >>> del obj, obj2 >>> cn._cache.get(p64(0), None) If the object is unghosted, then it will stay in the cache after the last reference is released. (This is true only if there is room in the cache and the object is recently used.) >>> obj = cn.get(p64(0)) >>> obj._p_activate() >>> y = id(obj) >>> del obj >>> obj = cn.get(p64(0)) >>> id(obj) == y True >>> obj._p_state 0 A request for an object that doesn't exist will raise a KeyError. >>> cn.get(p64(1)) Traceback (most recent call last): ... KeyError: '\x00\x00\x00\x00\x00\x00\x00\x01' N((R((R ttest_get°s3cCsdS(sGdoctest of close() method This is a minimal test, because most of the interesting effects on closing a connection involve its interaction with the database and the transaction. >>> db = databaseFromString("\n\n") >>> cn = db.open() It's safe to close a connection multiple times. >>> cn.close() >>> cn.close() >>> cn.close() It's not possible to load or store objects once the storage is closed. >>> cn.get(p64(0)) Traceback (most recent call last): ... ConnectionStateError: The database connection is closed >>> p = Persistent() >>> cn.add(p) Traceback (most recent call last): ... ConnectionStateError: The database connection is closed N((R((R t test_closeåscCsdS(sdoctest to ensure close() w/ pending changes complains >>> import transaction Just opening and closing is fine. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> cn.close() Opening, making a change, committing, and closing is fine. >>> cn = db.open() >>> cn.root()['a'] = 1 >>> transaction.commit() >>> cn.close() Opening, making a change, and aborting is fine. >>> cn = db.open() >>> cn.root()['a'] = 1 >>> transaction.abort() >>> cn.close() But trying to close with a change pending complains. >>> cn = db.open() >>> cn.root()['a'] = 10 >>> cn.close() Traceback (most recent call last): ... ConnectionStateError: Cannot close a connection joined to a transaction This leaves the connection as it was, so we can still commit the change. >>> transaction.commit() >>> cn2 = db.open() >>> cn2.root()['a'] 10 >>> cn.close(); cn2.close() Bug: We weren't catching the case where the only changes pending were in a subtransaction. >>> cn = db.open() >>> cn.root()['a'] = 100 >>> transaction.commit(True) >>> cn.close() # this was succeeding Traceback (most recent call last): ... ConnectionStateError: Cannot close a connection joined to a transaction Again this leaves the connection as it was. >>> transaction.commit() >>> cn2 = db.open() >>> cn2.root()['a'] 100 >>> cn.close(); cn2.close() Make sure we can still close a connection after aborting a pending subtransaction. >>> cn = db.open() >>> cn.root()['a'] = 1000 >>> transaction.commit(True) >>> cn.root()['a'] 1000 >>> transaction.abort() >>> cn.root()['a'] 100 >>> cn.close() >>> db.close() N((R((R ttest_close_with_pending_changessDcCsdS(s+doctest of onCloseCallback() method >>> db = databaseFromString("\n\n") >>> cn = db.open() Every function registered is called, even if it raises an exception. They are only called once. >>> L = [] >>> def f(): ... L.append("f") >>> def g(): ... L.append("g") ... return 1 / 0 >>> cn.onCloseCallback(g) >>> cn.onCloseCallback(f) >>> cn.close() >>> L ['g', 'f'] >>> del L[:] >>> cn.close() >>> L [] The implementation keeps a list of callbacks that is reset to a class variable (which is bound to None) after the connection is closed. >>> cn._Connection__onCloseCallbacks N((R((R ttest_onCloseCallbacksGscCsdS(sêdoctest of db() method >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> cn.db() is db True >>> cn.close() >>> cn.db() is db True N((R((R ttest_dbgs cCsdS(sdoctest of isReadOnly() method >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> cn.isReadOnly() False >>> cn.close() >>> cn.isReadOnly() Traceback (most recent call last): ... ConnectionStateError: The database connection is closed An expedient way to create a read-only storage: >>> db._storage._is_read_only = True >>> cn = db.open() >>> cn.isReadOnly() True N((R((R ttest_isReadOnlysscCsdS(s0doctest of cacheMinimize(). Thus test us minimal, just verifying that the method can be called and has some effect. We need other tests that verify the cache works as intended. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> r = cn.root() >>> cn.cacheMinimize() >>> r._p_state -1 >>> r._p_activate() >>> r._p_state # up to date 0 >>> cn.cacheMinimize() >>> r._p_state # ghost again -1 N((R((R t test_cacheˆs( R4R5R7R8R9R:R;R<R=R>(((R R6•s  5  F tInvalidationTestscBstZd„ZRS(NcCsdS(s This test initializes the database with several persistent objects, then manually delivers invalidations and verifies that they have the expected effect. >>> db = databaseFromString("\n\n") >>> cn = db.open() >>> p1 = Persistent() >>> p2 = Persistent() >>> p3 = Persistent() >>> r = cn.root() >>> r.update(dict(p1=p1, p2=p2, p3=p3)) >>> transaction.commit() Transaction ids are 8-byte strings, just like oids; p64() will create one from an int. >>> cn.invalidate(p64(1), {p1._p_oid: 1}) >>> cn._txn_time '\x00\x00\x00\x00\x00\x00\x00\x01' >>> p1._p_oid in cn._invalidated True >>> p2._p_oid in cn._invalidated False >>> cn.invalidate(p64(10), {p2._p_oid: 1, p64(76): 1}) >>> cn._txn_time '\x00\x00\x00\x00\x00\x00\x00\x01' >>> p1._p_oid in cn._invalidated True >>> p2._p_oid in cn._invalidated True Calling invalidate() doesn't affect the object state until a transaction boundary. >>> p1._p_state 0 >>> p2._p_state 0 >>> p3._p_state 0 The sync() method will abort the current transaction and process any pending invalidations. >>> cn.sync() >>> p1._p_state -1 >>> p2._p_state -1 >>> p3._p_state 0 >>> cn._invalidated {} N((R((R ttest_invalidate¥s:(R4R5R@(((R R?žscCsdS(sS The invalidateCache method invalidates a connection's cache. It also prevents reads until the end of a transaction. >>> from ZODB.tests.util import DB >>> import transaction >>> db = DB() >>> tm = transaction.TransactionManager() >>> connection = db.open(transaction_manager=tm) >>> connection.root()['a'] = StubObject() >>> connection.root()['a'].x = 1 >>> connection.root()['b'] = StubObject() >>> connection.root()['b'].x = 1 >>> connection.root()['c'] = StubObject() >>> connection.root()['c'].x = 1 >>> tm.commit() >>> connection.root()['b']._p_deactivate() >>> connection.root()['c'].x = 2 So we have a connection and an active transaction with some modifications. Lets call invalidateCache: >>> connection.invalidateCache() Now, if we try to load an object, we'll get a read conflict: >>> connection.root()['b'].x Traceback (most recent call last): ... ReadConflictError: database read conflict error If we try to commit the transaction, we'll get a conflict error: >>> tm.commit() Traceback (most recent call last): ... ConflictError: database conflict error and the cache will have been cleared: >>> print connection.root()['a']._p_changed None >>> print connection.root()['b']._p_changed None >>> print connection.root()['c']._p_changed None But we'll be able to access data again: >>> connection.root()['b'].x 1 Aborting a transaction after a read conflict also lets us read data and go on about our business: >>> connection.invalidateCache() >>> connection.root()['c'].x Traceback (most recent call last): ... ReadConflictError: database read conflict error >>> tm.abort() >>> connection.root()['c'].x 1 >>> connection.root()['c'].x = 2 >>> tm.commit() >>> db.close() N((((R ttest_invalidateCacheásHRcBstZRS(N(R4R5(((R R-sR cBstZRS(N(R4R5(((R R 0stErrorOnGetstateExceptioncBstZRS(N(R4R5(((R RB3stErrorOnGetstateObjectcBstZd„ZRS(NcCs t‚dS(N(RB(R((R t __getstate__8s(R4R5RD(((R RC6sR/cBstZd„Zd„ZRS(NcCs ||_dS(N(tpRt_v_p(RRE((R t__init__=scCs,|ii|iƒ|i|_ti|ƒS(N(RRRRFREt PersistentRD(R((R RD@s (R4R5RGRD(((R R/;s t StubStoragecBsbtZdZdZeZd„Zd„Zd„Zd„Z d„Z d„Z d„Z d „Z RS( sVery simple in-memory storage that does *just* enough to support tests. Only one concurrent transaction is supported. Voting is not supported. Versions are not supported. Inspect self._stored and self._finished to see how the storage has been used during a unit test. Whenever an object is stored in the store() method, its oid is appended to self._stored. When a transaction is finished, the oids that have been stored during the transaction are appended to self._finished. icCs1g|_g|_h|_h|_g|_dS(N(RR(R+t_datat _transdatat _transstored(R((R RGXs     cCs"t|iƒ}|id7_|S(Ni(tstrRt_oidR(RR((R tnew_oid`scCsdS(NsStubStorage sortKey((R((R tsortKeyescCs^|djotdƒ‚n>|idjo ||_n!|i|jotdƒ‚ndS(Nstransaction may not be Nones/StubStorage uses only one transaction at a time(R RRRt _transactiont RuntimeError(RR ((R R"hs   cCsT|djotdƒ‚n!|i|jotdƒ‚n|`|iiƒdS(Nstransaction may not be Nones/StubStorage uses only one transaction at a time(R RRRRQRRRKtclear(RR ((R R#qs  cCs|djotdƒ‚n!|i|jotdƒ‚n|ii|iƒ|i i |i ƒ||ƒ|`|i i ƒg|_dS(Nstransaction may not be Nones/StubStorage uses only one transaction at a time(R RRRRQRRR+textendRLRJtupdateRKtcallbackRS(RR RV((R R*zs   cCs(|djotdƒ‚n|i|S(Nts&StubStorage does not support versions.(tversionRRRJR(RRRX((R tload‡s cCs‘|djotdƒ‚n|djotdƒ‚n!|i|jotdƒ‚n|ii|ƒ|i i|ƒ||f|i | (tZODB.interfacest IConnectiontdatabaseFromStringRRtcnt verifyObject(RR`RbR((R ttest_connection_interfacežs   (R4R5Rd(((R R^œsRcBs5tZd„ZdZdZhde