Commit 18601a1a authored by Jim Fulton's avatar Jim Fulton

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.
parent 3ea253ad
...@@ -185,8 +185,8 @@ setup(name="ZODB3", ...@@ -185,8 +185,8 @@ setup(name="ZODB3",
read_file("README.txt") + "\n\n" + read_file("README.txt") + "\n\n" +
read_file("src", "CHANGES.txt")), read_file("src", "CHANGES.txt")),
test_suite="__main__.alltests", # to support "setup.py test" test_suite="__main__.alltests", # to support "setup.py test"
tests_require = ['zope.testing'], tests_require = ['zope.testing', 'manuel'],
extras_require = dict(test=['zope.testing']), extras_require = dict(test=['zope.testing', 'manuel']),
install_requires = [ install_requires = [
'transaction', 'transaction',
'zc.lockfile', 'zc.lockfile',
......
...@@ -29,8 +29,6 @@ use: ...@@ -29,8 +29,6 @@ use:
on the object. If the method succeeds, then the object change can be on the object. If the method succeeds, then the object change can be
committed, otherwise a ConflictError is raised as usual. committed, otherwise a ConflictError is raised as usual.
::
def _p_resolveConflict(oldState, savedState, newState): def _p_resolveConflict(oldState, savedState, newState):
Return the state of the object after resolving different changes. Return the state of the object after resolving different changes.
...@@ -41,6 +39,7 @@ use: ...@@ -41,6 +39,7 @@ use:
transaction were based on. transaction were based on.
The method is permitted to modify this value. The method is permitted to modify this value.
savedState savedState
The state of the object that is currently stored in the The state of the object that is currently stored in the
database. This state was written after oldState and reflects database. This state was written after oldState and reflects
...@@ -48,6 +47,7 @@ use: ...@@ -48,6 +47,7 @@ use:
current transaction. current transaction.
The method is permitted to modify this value. The method is permitted to modify this value.
newState newState
The state after changes made by the current transaction. The state after changes made by the current transaction.
...@@ -62,22 +62,29 @@ use: ...@@ -62,22 +62,29 @@ use:
Consider an extremely simple example, a counter:: Consider an extremely simple example, a counter::
>>> from persistent import Persistent from persistent import Persistent
>>> class PCounter(Persistent): class PCounter(Persistent):
... '`value` is readonly; increment it with `inc`.' '`value` is readonly; increment it with `inc`.'
... _val = 0 _val = 0
... def inc(self): def inc(self):
... self._val += 1 self._val += 1
... @property @property
... def value(self): def value(self):
... return self._val return self._val
... def _p_resolveConflict(self, oldState, savedState, newState): def _p_resolveConflict(self, oldState, savedState, newState):
... oldState['_val'] = ( oldState['_val'] = (
... savedState.get('_val', 0) + savedState.get('_val', 0) +
... newState.get('_val', 0) - newState.get('_val', 0) -
... oldState.get('_val', 0)) oldState.get('_val', 0))
... return oldState 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 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, __setstate__: a dictionary, in most cases. We'll look at more details below,
...@@ -174,16 +181,21 @@ DANGERS: The changes you make to the instance will be discarded. The ...@@ -174,16 +181,21 @@ DANGERS: The changes you make to the instance will be discarded. The
instance is not initialized, so other methods that depend on instance instance is not initialized, so other methods that depend on instance
attributes will not work. 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): class PCounter2(PCounter):
... def __init__(self): def __init__(self):
... self.data = [] self.data = []
... def _p_resolveConflict(self, oldState, savedState, newState): def _p_resolveConflict(self, oldState, savedState, newState):
... self.data.append('bad idea') self.data.append('bad idea')
... return super(PCounter2, self)._p_resolveConflict( return super(PCounter2, self)._p_resolveConflict(
... oldState, savedState, newState) 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. Now we'll prepare for the conflict again.
...@@ -387,6 +399,12 @@ BTree Buckets and Sets, each with conflict resolution, needs to think ...@@ -387,6 +399,12 @@ BTree Buckets and Sets, each with conflict resolution, needs to think
through these kinds of problems or be faced with potential data through these kinds of problems or be faced with potential data
integrity issues. integrity issues.
.. cleanup
>>> db.close()
>>> db1.close()
>>> db2.close()
.. ......... .. .. ......... ..
.. FOOTNOTES .. .. FOOTNOTES ..
.. ......... .. .. ......... ..
...@@ -394,16 +412,24 @@ integrity issues. ...@@ -394,16 +412,24 @@ integrity issues.
.. [#get_persistent_reference] We'll catch persistent references with a class .. [#get_persistent_reference] We'll catch persistent references with a class
mutable. mutable.
>>> class PCounter3(PCounter): ::
... data = []
... def _p_resolveConflict(self, oldState, savedState, newState): class PCounter3(PCounter):
... PCounter3.data.append( data = []
... (oldState.get('other'), def _p_resolveConflict(self, oldState, savedState, newState):
... savedState.get('other'), PCounter3.data.append(
... newState.get('other'))) (oldState.get('other'),
... return super(PCounter3, self)._p_resolveConflict( savedState.get('other'),
... oldState, savedState, newState) 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 = conn_A.root()['p3'] = PCounter3()
>>> p3_A.other = conn_A.root()['p'] >>> p3_A.other = conn_A.root()['p']
>>> tm_A.commit() >>> tm_A.commit()
......
...@@ -287,6 +287,12 @@ possible. If historical connections are used for conflict resolution, these ...@@ -287,6 +287,12 @@ possible. If historical connections are used for conflict resolution, these
connections will probably be temporary--not saved in a pool--so that the extra connections will probably be temporary--not saved in a pool--so that the extra
memory usage would also be brief and unlikely to overlap. memory usage would also be brief and unlikely to overlap.
.. cleanup
>>> db.close()
>>> db2.close()
.. ......... .. .. ......... ..
.. Footnotes .. .. Footnotes ..
.. ......... .. .. ......... ..
......
...@@ -11,33 +11,30 @@ ...@@ -11,33 +11,30 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
""" import manuel.doctest
$Id$ import manuel.footnote
""" import manuel.capture
import manuel.testing
import unittest import unittest
from zope.testing import doctest, module import ZODB.ConflictResolution
import ZODB.tests.util import ZODB.tests.util
import zope.testing.module
def setUp(test): def setUp(test):
ZODB.tests.util.setUp(test) ZODB.tests.util.setUp(test)
module.setUp(test, 'ConflictResolution_txt') zope.testing.module.setUp(test, 'ConflictResolution_txt')
def tearDown(test): def tearDown(test):
test.globs['db'].close() zope.testing.module.tearDown(test)
test.globs['db1'].close()
test.globs['db2'].close()
module.tearDown(test)
ZODB.tests.util.tearDown(test) ZODB.tests.util.tearDown(test)
ZODB.ConflictResolution._class_cache.clear()
def test_suite(): def test_suite():
return unittest.TestSuite(( return manuel.testing.TestSuite(
doctest.DocFileSuite('../ConflictResolution.txt', manuel.doctest.Manuel()
setUp=setUp, + manuel.footnote.Manuel()
tearDown=tearDown, + manuel.capture.Manuel(),
optionflags=doctest.INTERPRET_FOOTNOTES, '../ConflictResolution.txt',
), setUp=setUp, tearDown=tearDown,
)) )
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
...@@ -11,33 +11,14 @@ ...@@ -11,33 +11,14 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
""" import manuel.doctest
$Id$ import manuel.footnote
""" import manuel.testing
import unittest
from zope.testing import doctest, module
import ZODB.tests.util 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(): def test_suite():
return unittest.TestSuite(( return manuel.testing.TestSuite(
doctest.DocFileSuite('../historical_connections.txt', manuel.doctest.Manuel() + manuel.footnote.Manuel(),
setUp=setUp, '../historical_connections.txt',
tearDown=tearDown, setUp=ZODB.tests.util.setUp, tearDown=ZODB.tests.util.tearDown,
optionflags=doctest.INTERPRET_FOOTNOTES, )
),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment