[Checkins] SVN: zc.catalog/trunk/ * adjusted extentcatalog tests to
trigger (and discuss and test) the queueing
Gary Poster
gary at zope.com
Fri Jan 5 00:31:04 EST 2007
Log message for revision 71713:
* adjusted extentcatalog tests to trigger (and discuss and test) the queueing
behavior.
* fixed problem with excessive conflict errors due to queueing code.
Changed:
U zc.catalog/trunk/CHANGES.txt
U zc.catalog/trunk/src/zc/catalog/extentcatalog.py
U zc.catalog/trunk/src/zc/catalog/extentcatalog.txt
U zc.catalog/trunk/src/zc/catalog/tests.py
-=-
Modified: zc.catalog/trunk/CHANGES.txt
===================================================================
--- zc.catalog/trunk/CHANGES.txt 2007-01-04 18:47:18 UTC (rev 71712)
+++ zc.catalog/trunk/CHANGES.txt 2007-01-05 05:31:01 UTC (rev 71713)
@@ -2,6 +2,25 @@
zc.catalog changes
==================
+1.0 (2007-1-5)
+==============
+
+Bugs fixed
+----------
+
+* adjusted extentcatalog tests to trigger (and discuss and test) the queueing
+ behavior.
+
+* fixed problem with excessive conflict errors due to queueing code.
+
+* updated stemming to work with newest version of TextIndexNG's extensions.
+
+* omitted stemming test when TextIndexNG's extensions are unavailable, so
+ tests pass without it. Since TextIndexNG's extensions are optional, this
+ seems reasonable.
+
+* removed use of zapi in extentcatalog.
+
0.2 (2006-11-22)
================
Modified: zc.catalog/trunk/src/zc/catalog/extentcatalog.py
===================================================================
--- zc.catalog/trunk/src/zc/catalog/extentcatalog.py 2007-01-04 18:47:18 UTC (rev 71712)
+++ zc.catalog/trunk/src/zc/catalog/extentcatalog.py 2007-01-05 05:31:01 UTC (rev 71713)
@@ -22,7 +22,7 @@
from zope import interface, component
from zope.app.catalog import catalog
from zope.app.intid.interfaces import IIntIds
-from zope.app import zapi
+import zope.component
import zope.app.component.hooks
from zc.catalog import interfaces
@@ -145,7 +145,8 @@
elif docid in self.extent:
super(Catalog, self).unindex_doc(docid)
self.extent.remove(docid)
- self.queue.clear()
+ self.queue._p_invalidate() # avoid conflict errors
+ assert not self.queue
finally:
zope.app.component.hooks.setSite(old_site)
@@ -187,7 +188,7 @@
# not an index in us. Let the superclass handle it.
super(Catalog, self).updateIndex(index)
else:
- uidutil = zapi.getUtility(IIntIds)
+ uidutil = zope.component.getUtility(IIntIds)
if interfaces.ISelfPopulatingExtent.providedBy(self.extent):
if not self.extent.populated:
@@ -209,7 +210,7 @@
index.index_doc(uid, obj)
def updateIndexes(self):
- uidutil = zapi.getUtility(IIntIds)
+ uidutil = zope.component.getUtility(IIntIds)
if interfaces.ISelfPopulatingExtent.providedBy(self.extent):
if not self.extent.populated:
Modified: zc.catalog/trunk/src/zc/catalog/extentcatalog.txt
===================================================================
--- zc.catalog/trunk/src/zc/catalog/extentcatalog.txt 2007-01-04 18:47:18 UTC (rev 71712)
+++ zc.catalog/trunk/src/zc/catalog/extentcatalog.txt 2007-01-05 05:31:01 UTC (rev 71713)
@@ -2,167 +2,229 @@
indexes items addable to its extent. The extent is both a filter and a
set that may be merged with other result sets.
+In addition, the catalog has a queueing feature that postpones catalog work
+until the end of a transaction. This can save a lot of work under certain
+circumstances, such as when an object is added (one event) and modified (one
+event) in the same transaction; or when an object is modified and deleted in
+the same transaction; or any time when multiple events that cause reindexing
+are fired for the same object within a single transaction.
+
To show the extent catalog at work, we need an intid utility, an index,
some items to index, and a filter that determines what the extent accepts.
+Because we want to show the behavior of the queueing as well, we'll do this
+within a real ZODB, with transactions, and a real intid utility [#setup]_.
>>> from zc.catalog import interfaces, extentcatalog
>>> from zope import interface, component
>>> from zope.interface import verify
- >>> import zope.app.intid.interfaces
- >>> class DummyIntId(object):
- ... interface.implements(zope.app.intid.interfaces.IIntIds)
- ... MARKER = '__dummy_int_id__'
- ... def __init__(self):
- ... self.counter = 0
- ... self.data = {}
- ... def register(self, obj):
- ... intid = getattr(obj, self.MARKER, None)
- ... if intid is None:
- ... setattr(obj, self.MARKER, self.counter)
- ... self.data[self.counter] = obj
- ... intid = self.counter
- ... self.counter += 1
- ... return intid
- ... def getObject(self, intid):
- ... return self.data[intid]
- ... def __iter__(self):
- ... return iter(self.data)
- ...
- >>> intid = DummyIntId()
- >>> component.provideUtility(
- ... intid, zope.app.intid.interfaces.IIntIds)
- >>> import sets
+ >>> import persistent
+ >>> import BTrees.IFBTree
+
+ >>> root = makeRoot()
+ >>> intid = zope.component.getUtility(
+ ... zope.app.intid.interfaces.IIntIds, context=root)
+
>>> from zope.app.container.interfaces import IContained
- >>> class DummyIndex(object):
+ >>> class DummyIndex(persistent.Persistent):
... interface.implements(IContained)
... __parent__ = __name__ = None
... def __init__(self):
- ... self.uids = sets.Set()
+ ... self.uids = BTrees.IFBTree.IFTreeSet()
... def unindex_doc(self, uid):
- ... self.uids.discard(uid)
+ ... self.uids.remove(uid)
... def index_doc(self, uid, obj):
- ... self.uids.add(uid)
+ ... self.uids.insert(uid)
... def clear(self):
... self.uids.clear()
...
- >>> class DummyContent(object):
- ... pass
+ >>> class DummyContent(persistent.Persistent):
+ ... def __init__(self, name, parent):
+ ... self.id = name
+ ... self.__parent__ = parent
...
- >>> content = {}
- >>> for i in range(100):
- ... c = DummyContent()
- ... content[intid.register(c)] = c
- ...
>>> def filter(extent, uid, ob):
... assert interfaces.IExtent.providedBy(extent)
- ... assert getattr(ob, DummyIntId.MARKER) == uid
... # This is an extent of objects with odd-numbered uids without a
... # True ignore attribute
... return uid % 2 and not getattr(ob, 'ignore', False)
+ ...
>>> extent = extentcatalog.FilterExtent(filter)
>>> verify.verifyObject(interfaces.IFilterExtent, extent)
True
- >>> catalog = extentcatalog.Catalog(extent)
+ >>> root['catalog'] = catalog = extentcatalog.Catalog(extent)
>>> verify.verifyObject(interfaces.IExtentCatalog, catalog)
True
>>> index = DummyIndex()
>>> catalog['index'] = index
+ >>> transaction.commit()
-Now we have a catalog set up with an index and an extent, and some content to
-index. If we ask the catalog to index all of the content, only the ones that
-match the filter will be in the extent and in the index.
+Now we have a catalog set up with an index and an extent. If we create
+some content and ask the catalog to index it, only the ones that match
+the filter will be in the extent and in the index.
- >>> for c in content.values():
- ... catalog.index_doc(intid.register(c), c)
+ >>> matches = []
+ >>> fails = []
+ >>> i = 0
+ >>> while True:
+ ... c = DummyContent(i, root)
+ ... root[i] = c
+ ... doc_id = intid.register(c)
+ ... catalog.index_doc(doc_id, c)
+ ... if filter(extent, doc_id, c):
+ ... matches.append(doc_id)
+ ... else:
+ ... fails.append(doc_id)
+ ... i += 1
+ ... if i > 99 and len(matches) > 4:
+ ... break
...
- >>> matches = sorted(
- ... [id for id, ob in content.items() if filter(extent, id, ob)])
+ >>> matches.sort()
>>> sorted(extent) == sorted(index.uids) == matches
+ False
+
+Wait a second! That was supposed to be True, to show that the extent was
+constrained by the filter! Why did that not work?
+
+ >>> list(index.uids)
+ []
+ >>> sorted(extent) == sorted(matches)
True
+Oh...this is a result of the queued behavior discussed at the start of this
+document--we need to commit the transaction for the index to be affected.
+
+ >>> transaction.commit()
+ >>> sorted(extent) == sorted(index.uids) == matches
+ True
+
+Ah, there we go! As we were trying to demonstrate, if we create some
+content and ask the catalog to index it, only the ones that match the
+filter will be in the extent and in the index.
+
+Also, this shows that we will need to commit the transaction every time we
+want to see the effect of the index requests during the course of these
+examples.
+
If a content object is indexed that used to match the filter but no longer
does, it should be removed from the extent and indexes.
- >>> 5 in catalog.extent
+ >>> matches[0] in catalog.extent
True
- >>> content[5].ignore = True
- >>> catalog.index_doc(5, content[5])
- >>> 5 in catalog.extent
+ >>> obj = intid.getObject(matches[0])
+ >>> obj.ignore = True
+ >>> filter(extent, matches[0], obj)
False
- >>> matches.remove(5)
+ >>> catalog.index_doc(matches[0], obj)
+ >>> doc_id = matches.pop(0)
+ >>> doc_id in catalog.extent # postponed till transaction
+ True
+ >>> sorted(extent) == sorted(index.uids) == matches # postponed
+ False
+ >>> transaction.commit()
+ >>> doc_id in catalog.extent
+ False
>>> sorted(extent) == sorted(index.uids) == matches
True
Unindexing an object that is in the catalog should simply remove it from the
catalog and index as usual.
- >>> 99 in catalog.extent
+ >>> matches[0] in catalog.extent
True
- >>> 99 in catalog['index'].uids
+ >>> matches[0] in catalog['index'].uids
True
- >>> catalog.unindex_doc(99)
- >>> 99 in catalog.extent
+ >>> catalog.unindex_doc(matches[0])
+ >>> matches[0] in catalog.extent # postponed till transaction
+ True
+ >>> matches[0] in catalog['index'].uids # postponed till transaction
+ True
+ >>> transaction.commit()
+ >>> matches[0] in catalog.extent
False
- >>> 99 in catalog['index'].uids
+ >>> matches[0] in catalog['index'].uids
False
- >>> matches.remove(99)
+ >>> doc_id = matches.pop(0)
>>> sorted(extent) == sorted(index.uids) == matches
True
And similarly, unindexing an object that is not in the catalog should be a
no-op.
- >>> 0 in catalog.extent
+ >>> fails[0] in catalog.extent
False
- >>> catalog.unindex_doc(0)
- >>> 0 in catalog.extent
+ >>> catalog.unindex_doc(fails[0])
+ >>> fails[0] in catalog.extent
False
>>> sorted(extent) == sorted(index.uids) == matches
True
+ >>> transaction.commit()
+ >>> sorted(extent) == sorted(index.uids) == matches
+ True
-Clearing the catalog clears both the extent and the contained indexes.
+Clearing the catalog clears both the extent and the contained indexes. Note
+that this does /not/ wait for transaction boundaries to take effect.
>>> catalog.clear()
>>> list(catalog.extent) == list(catalog['index'].uids) == []
True
Updating all indexes and an individual index both also update the extent.
+updateIndexes waits for transaction boundaries for much of its work.
>>> catalog.updateIndexes()
- >>> matches.append(99)
- >>> sorted(extent) == sorted(index.uids) == matches
+ >>> matches.insert(0, doc_id)
+ >>> sorted(extent) == sorted(index.uids) == matches # postponed
+ False
+ >>> transaction.commit()
+ >>> sorted(extent) == sorted(index.uids) == matches # postponed
True
+
+updateIndex does its work immediately.
+
>>> index2 = DummyIndex()
>>> catalog['index2'] = index2
- >>> index.uids.remove(1) # to confirm that only index 2 is touched
+ >>> index2.__parent__ == catalog
+ True
+ >>> index.uids.remove(matches[0]) # to confirm that only index 2 is touched
>>> catalog.updateIndex(index2)
>>> sorted(extent) == sorted(index2.uids) == matches
True
- >>> 1 in index.uids
+
+ >>> transaction.commit()
+ >>> matches[0] in index.uids
False
- >>> 1 in index2.uids
+ >>> matches[0] in index2.uids
True
- >>> index.uids.add(1) # normalize things again.
+ >>> res = index.uids.insert(matches[0]) # normalize things again.
If you update a single index and an object is no longer a member of the extent,
it is removed from all indexes.
- >>> 1 in catalog.extent
+ >>> matches[0] in catalog.extent
True
- >>> 1 in index.uids
+ >>> matches[0] in index.uids
True
- >>> 1 in index2.uids
+ >>> matches[0] in index2.uids
True
- >>> content[1].ignore = True
+ >>> obj = intid.getObject(matches[0])
+ >>> obj.ignore = True
>>> catalog.updateIndex(index2)
- >>> 1 in catalog.extent
+ >>> matches[0] in catalog.extent # postponed
+ True
+ >>> matches[0] in index.uids # postponed
+ True
+ >>> matches[0] in index2.uids # postponed
+ True
+ >>> transaction.commit()
+ >>> matches[0] in catalog.extent
False
- >>> 1 in index.uids
+ >>> matches[0] in index.uids
False
- >>> 1 in index2.uids
+ >>> matches[0] in index2.uids
False
- >>> matches.remove(1)
- >>> matches == sorted(catalog.extent)
+ >>> doc_id = matches.pop(0)
+ >>> (matches == sorted(catalog.extent) == sorted(index.uids)
+ ... == sorted(index2.uids))
True
The extent itself provides a number of merging features to allow its values to
@@ -174,52 +236,56 @@
and reverse differences can be spelled "data - extent". Unions and
intersections are weighted.
+ >>> extent = extentcatalog.FilterExtent(filter)
+ >>> for i in range(1, 100, 2):
+ ... extent.add(i, None)
+ ...
>>> from BTrees import IFBTree
>>> alt_set = IFBTree.IFTreeSet()
>>> alt_set.update(range(0, 166, 33)) # return value is unimportant here
6
>>> sorted(alt_set)
[0, 33, 66, 99, 132, 165]
- >>> sorted(catalog.extent & alt_set)
+ >>> sorted(extent & alt_set)
[33, 99]
- >>> sorted(alt_set & catalog.extent)
+ >>> sorted(alt_set & extent)
[33, 99]
- >>> sorted(catalog.extent.intersection(alt_set))
+ >>> sorted(extent.intersection(alt_set))
[33, 99]
- >>> union_matches = sets.Set(matches)
- >>> union_matches.union_update(alt_set)
+ >>> original = set(extent)
+ >>> union_matches = original.copy()
+ >>> union_matches.update(alt_set)
>>> union_matches = sorted(union_matches)
- >>> sorted(alt_set | catalog.extent) == union_matches
+ >>> sorted(alt_set | extent) == union_matches
True
- >>> sorted(catalog.extent | alt_set) == union_matches
+ >>> sorted(extent | alt_set) == union_matches
True
- >>> sorted(catalog.extent.union(alt_set)) == union_matches
+ >>> sorted(extent.union(alt_set)) == union_matches
True
- >>> sorted(alt_set - catalog.extent)
+ >>> sorted(alt_set - extent)
[0, 66, 132, 165]
- >>> sorted(catalog.extent.rdifference(alt_set))
+ >>> sorted(extent.rdifference(alt_set))
[0, 66, 132, 165]
- >>> matches.remove(33)
- >>> matches.remove(99)
- >>> sorted(catalog.extent - alt_set) == matches
+ >>> original.remove(33)
+ >>> original.remove(99)
+ >>> set(extent - alt_set) == original
True
- >>> sorted(catalog.extent.difference(alt_set)) == matches
+ >>> set(extent.difference(alt_set)) == original
True
Self-populating extents
-----------------------
-An extent use the initialize an extent catalog may know how to
-populate itself; this is especially useful if the catalog can be
-initialized with fewer items than those available in the IIntIds
-utility that are also within the nearest Zope 3 site (the policy coded
-in the basic Zope 3 catalog).
+An extent may know how to populate itself; this is especially useful if
+the catalog can be initialized with fewer items than those available in
+the IIntIds utility that are also within the nearest Zope 3 site (the
+policy coded in the basic Zope 3 catalog).
-The such an extent must implement the `ISelfPopulatingExtent`
-interface, which requires two attributes. Let's use the
-`FilterExtent` class as a base for implementing such an extent, with a
-method that selects object 42 (created and registered above)::
+Such an extent must implement the `ISelfPopulatingExtent` interface,
+which requires two attributes. Let's use the `FilterExtent` class as a
+base for implementing such an extent, with a method that selects content item
+0 (created and registered above)::
>>> class PopulatingExtent(extentcatalog.FilterExtent):
...
@@ -230,7 +296,7 @@
... def populate(self):
... if self.populated:
... return
- ... self.add(42, content[42])
+ ... self.add(intid.getId(root[0]), root[0])
... self.populated = True
Creating a catalog based on this extent ignores objects in the
@@ -243,6 +309,8 @@
>>> catalog = extentcatalog.Catalog(extent)
>>> index = DummyIndex()
>>> catalog['index'] = index
+ >>> root['catalog2'] = catalog
+ >>> transaction.commit()
At this point, our extent remains unpopulated::
@@ -262,50 +330,54 @@
>>> extent.populated
True
- >>> list(extent)
- [42]
+ >>> list(extent) == [intid.getId(root[0])]
+ True
The index has been updated with the documents identified by the
extent::
- >>> index.uids
- Set([42])
+ >>> list(index.uids) == [intid.getId(root[0])]
+ True
Updating the same index repeatedly will continue to use the extent as
the source of documents to include::
>>> catalog.updateIndex(index)
- >>> list(extent)
- [42]
- >>> index.uids
- Set([42])
+ >>> list(extent) == [intid.getId(root[0])]
+ True
+ >>> list(index.uids) == [intid.getId(root[0])]
+ True
The `updateIndexes()` method has a similar behavior. If we add an
additional index to the catalog, we see that it indexes only those
-objects from the extent::
+objects from the extent (after the transaction.commit())::
>>> index2 = DummyIndex()
>>> catalog['index2'] = index2
>>> catalog.updateIndexes()
- >>> list(extent)
- [42]
- >>> index.uids
- Set([42])
- >>> index2.uids
- Set([42])
+ >>> list(extent) == [intid.getId(root[0])]
+ True
+ >>> list(index.uids) == [intid.getId(root[0])]
+ True
+ >>> list(index2.uids) == [intid.getId(root[0])]
+ False
+ >>> transaction.commit()
+ >>> list(index2.uids) == [intid.getId(root[0])]
+ True
When we have fresh catalog and extent (not yet populated), we see that
`updateIndexes()` will cause the extent to be populated::
>>> extent = PopulatingExtent(accept_any)
- >>> catalog = extentcatalog.Catalog(extent)
+ >>> root['catalog3'] = catalog = extentcatalog.Catalog(extent)
>>> index1 = DummyIndex()
>>> index2 = DummyIndex()
>>> catalog['index1'] = index1
>>> catalog['index2'] = index2
+ >>> transaction.commit()
>>> extent.populated
False
@@ -315,15 +387,140 @@
>>> extent.populated
True
- >>> list(extent)
- [42]
- >>> index.uids
- Set([42])
- >>> index2.uids
- Set([42])
+ >>> list(extent) == [intid.getId(root[0])]
+ True
+ >>> list(index1.uids) == [intid.getId(root[0])]
+ False
+ >>> list(index2.uids) == [intid.getId(root[0])]
+ False
+ >>> transaction.commit()
+ >>> list(index1.uids) == [intid.getId(root[0])]
+ True
+ >>> list(index2.uids) == [intid.getId(root[0])]
+ True
+Regression Tests
+----------------
-Let's clean up behind ourselves::
+The following section is for maintainers. This should always be the last
+section in the document (or moved out to another document).
- >>> from zope.app.testing import ztapi
- >>> ztapi.unprovideUtility(zope.app.intid.interfaces.IIntIds)
+When concurrent transactions are possible that both cause /any/ changes to
+a given extent catalog, the queued behavior described above had a serious
+flaw: it would /always/ provoke a conflict error! This is because, even if
+an object starts and begins in the same state, if it is marked "dirty"
+(changed), then it will be stored as a new object in the ZODB, and will
+be the source of a ConflictError with any concurrent transactions that also
+cause the object to be marked dirty.
+
+The queue in the extentcatalog is a persistent object. This makes it possible
+for it to behave correctly in the face of rolled back subtransactions. However,
+it also makes it possible to generate the kind of needless conflict errors that
+are described above.
+
+There are at least three possible solutions. One is to associate the queue
+with a transaction manager and not make it actually persistent in the database.
+That is probably the "purest" solution but is not the most practical. Another
+solution is to write a conflict resolution method (_p_resolveConflict): this
+is potentially reasonable solution. However, for our case, the third solution
+is the easiest and the quickest: invalidate the changes on the queue at the
+end of the transaction, causing the dirty state to be tossed away and never
+saved to the database. This is the approach that was implemented.
+
+What follows would provoke the conflict error without the change.
+
+ >>> transaction.commit() # clear the thread transactions
+
+ >>> tm1 = transaction.TransactionManager()
+ >>> conn1 = root._p_jar.db().open(transaction_manager=tm1)
+ >>> root1 = conn1.root()
+ >>> setSiteManager(root1['components'])
+
+ >>> len(root1['catalog'].queue)
+ 0
+ >>> root1['catalog'].index_doc(
+ ... matches[0],
+ ... zope.component.getUtility(zope.app.intid.IIntIds).getObject(
+ ... matches[0]))
+ >>> len(root1['catalog'].queue)
+ 1
+
+ >>> tm2 = transaction.TransactionManager()
+ >>> conn2 = root._p_jar.db().open(transaction_manager=tm2)
+ >>> root2 = conn2.root()
+ >>> setSiteManager(root2['components'])
+
+ >>> len(root2['catalog'].queue)
+ 0
+ >>> root2['catalog'].index_doc(
+ ... matches[-1],
+ ... zope.component.getUtility(zope.app.intid.IIntIds).getObject(
+ ... matches[-1]))
+ >>> len(root2['catalog'].queue)
+ 1
+
+ >>> tm2.commit()
+ >>> setSiteManager(root1['components'])
+ >>> tm1.commit()
+
+
+.. [#setup] We create the state that the text needs here.
+
+ >>> import zope.app.keyreference.persistent
+ >>> import zope.component
+ >>> import zope.app.intid
+ >>> import zope.component
+ >>> import zope.component.interfaces
+ >>> import zope.component.persistentregistry
+ >>> from ZODB.tests.util import DB
+ >>> import transaction
+
+ >>> zope.component.provideAdapter(
+ ... zope.app.keyreference.persistent.KeyReferenceToPersistent,
+ ... adapts=(zope.interface.Interface,))
+ >>> zope.component.provideAdapter(
+ ... zope.app.keyreference.persistent.connectionOfPersistent,
+ ... adapts=(zope.interface.Interface,))
+
+ >>> site_manager = None
+ >>> def getSiteManager(context=None):
+ ... if context is None:
+ ... if site_manager is None:
+ ... return zope.component.getGlobalSiteManager()
+ ... else:
+ ... return site_manager
+ ... else:
+ ... try:
+ ... return zope.component.interfaces.IComponentLookup(context)
+ ... except TypeError, error:
+ ... raise zope.component.ComponentLookupError(*error.args)
+ ...
+ >>> def setSiteManager(sm):
+ ... global site_manager
+ ... site_manager = sm
+ ... if sm is None:
+ ... zope.component.getSiteManager.reset()
+ ... else:
+ ... zope.component.getSiteManager.sethook(getSiteManager)
+ ...
+ >>> def makeRoot():
+ ... db = DB()
+ ... conn = db.open()
+ ... root = conn.root()
+ ... site_manager = root['components'] = (
+ ... zope.component.persistentregistry.PersistentComponents())
+ ... site_manager.__bases__ = (zope.component.getGlobalSiteManager(),)
+ ... site_manager.registerUtility(
+ ... zope.app.intid.IntIds(),
+ ... provided=zope.app.intid.interfaces.IIntIds)
+ ... setSiteManager(site_manager)
+ ... transaction.commit()
+ ... return root
+ ...
+
+ >>> @zope.component.adapter(zope.interface.Interface)
+ ... @zope.interface.implementer(zope.component.interfaces.IComponentLookup)
+ ... def getComponentLookup(obj):
+ ... return obj._p_jar.root()['components']
+ ...
+ >>> zope.component.provideAdapter(getComponentLookup)
Modified: zc.catalog/trunk/src/zc/catalog/tests.py
===================================================================
--- zc.catalog/trunk/src/zc/catalog/tests.py 2007-01-04 18:47:18 UTC (rev 71712)
+++ zc.catalog/trunk/src/zc/catalog/tests.py 2007-01-05 05:31:01 UTC (rev 71713)
@@ -17,11 +17,22 @@
"""
import unittest
-from zope.testing import doctest
+from zope.testing import doctest, module
+import zope.component.testing
+def modSetUp(test):
+ zope.component.testing.setUp(test)
+ module.setUp(test, 'zc.catalog.doctest_test')
+
+def modTearDown(test):
+ module.tearDown(test)
+ zope.component.testing.tearDown(test)
+
def test_suite():
tests = unittest.TestSuite((
- doctest.DocFileSuite('extentcatalog.txt'),
+ doctest.DocFileSuite(
+ 'extentcatalog.txt', setUp=modSetUp, tearDown=modTearDown,
+ optionflags=doctest.INTERPRET_FOOTNOTES),
doctest.DocFileSuite('setindex.txt'),
doctest.DocFileSuite('valueindex.txt'),
doctest.DocFileSuite('normalizedindex.txt'),
More information about the Checkins
mailing list