[Zodb-checkins] SVN: ZODB/trunk/s Use doctest rather than zope.testing.doctest.
Jim Fulton
jim at zope.com
Tue May 18 15:44:14 EDT 2010
Log message for revision 112485:
Use doctest rather than zope.testing.doctest.
This required using manuel to get the footnote
feature. Unfortunately, manuel imports zope.testing.doctest. :)
Hpefully, this will be fixed soon.
Updated the testhistoricalconnections.py tearDown to clear conflict
resolution class cache. The cache was causing tests to fail when tests
were run multiple times.
Changed:
U ZODB/trunk/setup.py
U ZODB/trunk/src/ZODB/ConflictResolution.txt
U ZODB/trunk/src/ZODB/historical_connections.txt
U ZODB/trunk/src/ZODB/tests/testconflictresolution.py
U ZODB/trunk/src/ZODB/tests/testhistoricalconnections.py
-=-
Modified: ZODB/trunk/setup.py
===================================================================
--- ZODB/trunk/setup.py 2010-05-18 19:44:11 UTC (rev 112484)
+++ ZODB/trunk/setup.py 2010-05-18 19:44:13 UTC (rev 112485)
@@ -185,8 +185,8 @@
read_file("README.txt") + "\n\n" +
read_file("src", "CHANGES.txt")),
test_suite="__main__.alltests", # to support "setup.py test"
- tests_require = ['zope.testing'],
- extras_require = dict(test=['zope.testing']),
+ tests_require = ['zope.testing', 'manuel'],
+ extras_require = dict(test=['zope.testing', 'manuel']),
install_requires = [
'transaction',
'zc.lockfile',
Modified: ZODB/trunk/src/ZODB/ConflictResolution.txt
===================================================================
--- ZODB/trunk/src/ZODB/ConflictResolution.txt 2010-05-18 19:44:11 UTC (rev 112484)
+++ ZODB/trunk/src/ZODB/ConflictResolution.txt 2010-05-18 19:44:13 UTC (rev 112485)
@@ -29,56 +29,63 @@
on the object. If the method succeeds, then the object change can be
committed, otherwise a ConflictError is raised as usual.
- ::
+ def _p_resolveConflict(oldState, savedState, newState):
+ Return the state of the object after resolving different changes.
- def _p_resolveConflict(oldState, savedState, newState):
- Return the state of the object after resolving different changes.
+ Arguments:
- Arguments:
+ oldState
+ The state of the object that the changes made by the current
+ transaction were based on.
- oldState
- The state of the object that the changes made by the current
- transaction were based on.
+ The method is permitted to modify this value.
- The method is permitted to modify this value.
- savedState
- The state of the object that is currently stored in the
- database. This state was written after oldState and reflects
- changes made by a transaction that committed before the
- current transaction.
+ savedState
+ The state of the object that is currently stored in the
+ database. This state was written after oldState and reflects
+ changes made by a transaction that committed before the
+ current transaction.
- The method is permitted to modify this value.
- newState
- The state after changes made by the current transaction.
+ The method is permitted to modify this value.
- The method is not permitted to modify this value.
+ newState
+ The state after changes made by the current transaction.
- This method should compute a new state by merging changes
- reflected in savedState and newState, relative to oldState.
+ The method is not permitted to modify this value.
- If the method cannot resolve the changes, then it should raise
- ZODB.POSException.ConflictError.
-
+ This method should compute a new state by merging changes
+ reflected in savedState and newState, relative to oldState.
- Consider an extremely simple example, a counter::
+ If the method cannot resolve the changes, then it should raise
+ ZODB.POSException.ConflictError.
- >>> from persistent import Persistent
- >>> class PCounter(Persistent):
- ... '`value` is readonly; increment it with `inc`.'
- ... _val = 0
- ... def inc(self):
- ... self._val += 1
- ... @property
- ... def value(self):
- ... return self._val
- ... def _p_resolveConflict(self, oldState, savedState, newState):
- ... oldState['_val'] = (
- ... savedState.get('_val', 0) +
- ... newState.get('_val', 0) -
- ... oldState.get('_val', 0))
- ... return oldState
- ...
+ Consider an extremely simple example, a counter::
+
+ from persistent import Persistent
+ class PCounter(Persistent):
+ '`value` is readonly; increment it with `inc`.'
+ _val = 0
+ def inc(self):
+ self._val += 1
+ @property
+ def value(self):
+ return self._val
+ def _p_resolveConflict(self, oldState, savedState, newState):
+ oldState['_val'] = (
+ savedState.get('_val', 0) +
+ newState.get('_val', 0) -
+ oldState.get('_val', 0))
+ return oldState
+
+ .. -> src
+
+ >>> import ConflictResolution_txt
+ >>> exec src in ConflictResolution_txt.__dict__
+ >>> PCounter = ConflictResolution_txt.PCounter
+ >>> PCounter.__module__ = 'ConflictResolution_txt'
+
+
By "state", the excerpt above means the value used by __getstate__ and
__setstate__: a dictionary, in most cases. We'll look at more details below,
but let's continue the example above with a simple successful resolution
@@ -149,7 +156,7 @@
Caveats and Dangers
===================
-Here are caveats for working with this conflict resolution approach.
+Here are caveats for working with this conflict resolution approach.
Each sub-section has a "DANGERS" section that outlines what might happen
if you ignore the warning. We work from the least danger to the most.
@@ -174,17 +181,22 @@
instance is not initialized, so other methods that depend on instance
attributes will not work.
-Here's an example of a broken _p_resolveConflict method.
+Here's an example of a broken _p_resolveConflict method::
- >>> class PCounter2(PCounter):
- ... def __init__(self):
- ... self.data = []
- ... def _p_resolveConflict(self, oldState, savedState, newState):
- ... self.data.append('bad idea')
- ... return super(PCounter2, self)._p_resolveConflict(
- ... oldState, savedState, newState)
- ...
+ class PCounter2(PCounter):
+ def __init__(self):
+ self.data = []
+ def _p_resolveConflict(self, oldState, savedState, newState):
+ self.data.append('bad idea')
+ return super(PCounter2, self)._p_resolveConflict(
+ oldState, savedState, newState)
+.. -> src
+
+ >>> exec src in ConflictResolution_txt.__dict__
+ >>> PCounter2 = ConflictResolution_txt.PCounter2
+ >>> PCounter2.__module__ = 'ConflictResolution_txt'
+
Now we'll prepare for the conflict again.
>>> p2_A = conn_A.root()['p2'] = PCounter2()
@@ -235,35 +247,35 @@
class IPersistentReference(zope.interface.Interface):
'''public contract for references to persistent objects from an object
with conflicts.'''
-
+
oid = zope.interface.Attribute(
'The oid of the persistent object that this reference represents')
-
+
database_name = zope.interface.Attribute(
'''The name of the database of the reference, *if* different.
-
+
If not different, None.''')
klass = zope.interface.Attribute(
'''class meta data. Presence is not reliable.''')
-
+
weak = zope.interface.Attribute(
'''bool: whether this reference is weak''')
def __cmp__(other):
'''if other is equivalent reference, return 0; else raise ValueError.
-
+
Equivalent in this case means that oid and database_name are the same.
-
+
If either is a weak reference, we only support `is` equivalence, and
otherwise raise a ValueError even if the datbase_names and oids are
the same, rather than guess at the correct semantics.
-
+
It is impossible to sort reliably, since the actual persistent
class may have its own comparison, and we have no idea what it is.
We assert that it is reasonably safe to assume that an object is
equivalent to itself, but that's as much as we can say.
-
+
We don't compare on 'is other', despite the
PersistentReferenceFactory.data cache, because it is possible to
have two references to the same object that are spelled with different
@@ -380,13 +392,19 @@
>>> tm_A.abort()
However, the story highlights the kinds of subtle problems that units
-made up of multiple composite Persistent objects need to contemplate.
+made up of multiple composite Persistent objects need to contemplate.
Any structure made up of objects that contain persistent objects with
conflict resolution code, as a catalog index is made up of multiple
BTree Buckets and Sets, each with conflict resolution, needs to think
through these kinds of problems or be faced with potential data
integrity issues.
+.. cleanup
+
+ >>> db.close()
+ >>> db1.close()
+ >>> db2.close()
+
.. ......... ..
.. FOOTNOTES ..
.. ......... ..
@@ -394,16 +412,24 @@
.. [#get_persistent_reference] We'll catch persistent references with a class
mutable.
- >>> class PCounter3(PCounter):
- ... data = []
- ... def _p_resolveConflict(self, oldState, savedState, newState):
- ... PCounter3.data.append(
- ... (oldState.get('other'),
- ... savedState.get('other'),
- ... newState.get('other')))
- ... return super(PCounter3, self)._p_resolveConflict(
- ... oldState, savedState, newState)
- ...
+ ::
+
+ class PCounter3(PCounter):
+ data = []
+ def _p_resolveConflict(self, oldState, savedState, newState):
+ PCounter3.data.append(
+ (oldState.get('other'),
+ savedState.get('other'),
+ newState.get('other')))
+ return super(PCounter3, self)._p_resolveConflict(
+ oldState, savedState, newState)
+
+ .. -> src
+
+ >>> exec src in ConflictResolution_txt.__dict__
+ >>> PCounter3 = ConflictResolution_txt.PCounter3
+ >>> PCounter3.__module__ = 'ConflictResolution_txt'
+
>>> p3_A = conn_A.root()['p3'] = PCounter3()
>>> p3_A.other = conn_A.root()['p']
>>> tm_A.commit()
@@ -441,7 +467,7 @@
.. [#instantiation_test] We'll simply instantiate PersistentReferences
with examples of types described in ZODB/serialize.py.
-
+
>>> from ZODB.ConflictResolution import PersistentReference
>>> ref1 = PersistentReference('my_oid')
@@ -522,13 +548,13 @@
True
Non-weak references with the same oid and database_name are equal.
-
+
>>> ref1 == ref2 and ref4 == ref5
True
Everything else raises a ValueError: weak references with the same oid and
database, and references with a different database_name or oid.
-
+
>>> ref3 == ref6
Traceback (most recent call last):
...
Modified: ZODB/trunk/src/ZODB/historical_connections.txt
===================================================================
--- ZODB/trunk/src/ZODB/historical_connections.txt 2010-05-18 19:44:11 UTC (rev 112484)
+++ ZODB/trunk/src/ZODB/historical_connections.txt 2010-05-18 19:44:13 UTC (rev 112485)
@@ -13,7 +13,7 @@
A database can be opened historically ``at`` or ``before`` a given transaction
serial or datetime. Here's a simple example. It should work with any storage
-that supports ``loadBefore``.
+that supports ``loadBefore``.
We'll begin our example with a fairly standard set up. We
@@ -29,25 +29,25 @@
>>> conn = db.open()
>>> import persistent.mapping
-
+
>>> conn.root()['first'] = persistent.mapping.PersistentMapping(count=0)
-
+
>>> import transaction
>>> transaction.commit()
We wait for some time to pass, record he time, and then make some other changes.
-
+
>>> import time
>>> time.sleep(.01)
>>> import datetime
>>> now = datetime.datetime.utcnow()
>>> time.sleep(.01)
-
+
>>> root = conn.root()
>>> root['second'] = persistent.mapping.PersistentMapping()
>>> root['first']['count'] += 1
-
+
>>> transaction.commit()
Now we will show a historical connection. We'll open one using the ``now``
@@ -56,14 +56,14 @@
historical state.
>>> transaction1 = transaction.TransactionManager()
-
+
>>> historical_conn = db.open(transaction_manager=transaction1, at=now)
-
+
>>> sorted(conn.root().keys())
['first', 'second']
>>> conn.root()['first']['count']
1
-
+
>>> historical_conn.root().keys()
['first']
>>> historical_conn.root()['first']['count']
@@ -93,7 +93,7 @@
>>> historical_serial = historical_conn.root()._p_serial
>>> historical_conn.close()
-
+
>>> historical_conn = db.open(transaction_manager=transaction1,
... at=historical_serial)
>>> historical_conn.root().keys()
@@ -155,7 +155,7 @@
>>> db.getHistoricalTimeout()
400
-All three of these values can be specified in a ZConfig file.
+All three of these values can be specified in a ZConfig file.
>>> import ZODB.config
>>> db2 = ZODB.config.databaseFromString('''
@@ -287,6 +287,12 @@
connections will probably be temporary--not saved in a pool--so that the extra
memory usage would also be brief and unlikely to overlap.
+
+.. cleanup
+
+ >>> db.close()
+ >>> db2.close()
+
.. ......... ..
.. Footnotes ..
.. ......... ..
Modified: ZODB/trunk/src/ZODB/tests/testconflictresolution.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testconflictresolution.py 2010-05-18 19:44:11 UTC (rev 112484)
+++ ZODB/trunk/src/ZODB/tests/testconflictresolution.py 2010-05-18 19:44:13 UTC (rev 112485)
@@ -11,33 +11,30 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""
-$Id$
-"""
+import manuel.doctest
+import manuel.footnote
+import manuel.capture
+import manuel.testing
import unittest
-from zope.testing import doctest, module
+import ZODB.ConflictResolution
import ZODB.tests.util
+import zope.testing.module
def setUp(test):
ZODB.tests.util.setUp(test)
- module.setUp(test, 'ConflictResolution_txt')
+ zope.testing.module.setUp(test, 'ConflictResolution_txt')
def tearDown(test):
- test.globs['db'].close()
- test.globs['db1'].close()
- test.globs['db2'].close()
- module.tearDown(test)
+ zope.testing.module.tearDown(test)
ZODB.tests.util.tearDown(test)
+ ZODB.ConflictResolution._class_cache.clear()
def test_suite():
- return unittest.TestSuite((
- doctest.DocFileSuite('../ConflictResolution.txt',
- setUp=setUp,
- tearDown=tearDown,
- optionflags=doctest.INTERPRET_FOOTNOTES,
- ),
- ))
+ return manuel.testing.TestSuite(
+ manuel.doctest.Manuel()
+ + manuel.footnote.Manuel()
+ + manuel.capture.Manuel(),
+ '../ConflictResolution.txt',
+ setUp=setUp, tearDown=tearDown,
+ )
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
-
Modified: ZODB/trunk/src/ZODB/tests/testhistoricalconnections.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testhistoricalconnections.py 2010-05-18 19:44:11 UTC (rev 112484)
+++ ZODB/trunk/src/ZODB/tests/testhistoricalconnections.py 2010-05-18 19:44:13 UTC (rev 112485)
@@ -11,33 +11,14 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""
-$Id$
-"""
-import unittest
-from zope.testing import doctest, module
+import manuel.doctest
+import manuel.footnote
+import manuel.testing
import ZODB.tests.util
-def setUp(test):
- ZODB.tests.util.setUp(test)
- module.setUp(test, 'historical_connections_txt')
-
-def tearDown(test):
- test.globs['db'].close()
- test.globs['db2'].close()
- # the DB class masks the module because of __init__ shenanigans
- module.tearDown(test)
- ZODB.tests.util.tearDown(test)
-
def test_suite():
- return unittest.TestSuite((
- doctest.DocFileSuite('../historical_connections.txt',
- setUp=setUp,
- tearDown=tearDown,
- optionflags=doctest.INTERPRET_FOOTNOTES,
- ),
- ))
-
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
-
+ return manuel.testing.TestSuite(
+ manuel.doctest.Manuel() + manuel.footnote.Manuel(),
+ '../historical_connections.txt',
+ setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown,
+ )
More information about the Zodb-checkins
mailing list