[Checkins] SVN: zc.zodbdgc/branches/dev/ Initial version. Tests seem to be sufficient and pass.
Jim Fulton
jim at zope.com
Mon May 11 17:38:04 EDT 2009
Log message for revision 99857:
Initial version. Tests seem to be sufficient and pass.
Requires non-released file-storage packing fixes.
Changed:
_U zc.zodbdgc/branches/dev/
U zc.zodbdgc/branches/dev/buildout.cfg
U zc.zodbdgc/branches/dev/setup.py
A zc.zodbdgc/branches/dev/src/zc/zodbdgc/
A zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.test
A zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.txt
A zc.zodbdgc/branches/dev/src/zc/zodbdgc/__init__.py
A zc.zodbdgc/branches/dev/src/zc/zodbdgc/oidset.test
A zc.zodbdgc/branches/dev/src/zc/zodbdgc/tests.py
-=-
Property changes on: zc.zodbdgc/branches/dev
___________________________________________________________________
Added: svn:externals
+ zodb svn+ssh://svn.zope.org/repos/main/ZODB/trunk
Modified: zc.zodbdgc/branches/dev/buildout.cfg
===================================================================
--- zc.zodbdgc/branches/dev/buildout.cfg 2009-05-11 21:08:05 UTC (rev 99856)
+++ zc.zodbdgc/branches/dev/buildout.cfg 2009-05-11 21:38:04 UTC (rev 99857)
@@ -1,10 +1,10 @@
[buildout]
-develop = .
+develop = . zodb
parts = test py
[test]
recipe = zc.recipe.testrunner
-eggs =
+eggs = zc.zodbdgc
[py]
recipe = zc.recipe.egg
Modified: zc.zodbdgc/branches/dev/setup.py
===================================================================
--- zc.zodbdgc/branches/dev/setup.py 2009-05-11 21:08:05 UTC (rev 99856)
+++ zc.zodbdgc/branches/dev/setup.py 2009-05-11 21:38:04 UTC (rev 99857)
@@ -12,7 +12,7 @@
#
##############################################################################
-name, version = '', '0'
+name, version = 'zc.zodbdgc', '0'
import os
from setuptools import setup, find_packages
@@ -36,14 +36,14 @@
version = version,
author = 'Jim Fulton',
author_email = 'jim at zope.com',
- description = '',
+ description = 'ZODB Distributed Garbage Collection',
long_description=long_description,
license = 'ZPL 2.1',
-
+
packages = find_packages('src'),
namespace_packages = ['zc'],
package_dir = {'': 'src'},
- install_requires = ['setuptools'],
+ install_requires = ['setuptools', 'ZODB3'],
zip_safe = False,
entry_points=entry_points,
include_package_data = True,
Added: zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.test
===================================================================
--- zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.test (rev 0)
+++ zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.test 2009-05-11 21:38:04 UTC (rev 99857)
@@ -0,0 +1,203 @@
+ZODB Distributed GC
+===================
+
+This package provides a script for performing distributed garbage
+collection for a collection of ZODB storages, which will typically be
+ZEO clients.
+
+Here, we'll test the underlying script.
+
+We'll need to control time.
+
+ >>> now = 1241458549.614022
+ >>> def faux_time():
+ ... global now
+ ... now += 1
+ ... return now
+ >>> import time
+ >>> time_time = time.time
+ >>> time.time = faux_time
+
+Let's define some storages::
+
+ >>> open('config', 'w').write("""
+ ... <zodb db1>
+ ... <filestorage>
+ ... pack-gc false
+ ... path 1.fs
+ ... </filestorage>
+ ... </zodb>
+ ... <zodb db2>
+ ... <filestorage>
+ ... pack-gc false
+ ... path 2.fs
+ ... </filestorage>
+ ... </zodb>
+ ... <zodb db3>
+ ... <filestorage>
+ ... pack-gc false
+ ... path 3.fs
+ ... </filestorage>
+ ... </zodb>
+ ... """)
+
+ >>> import ZODB.config, transaction
+ >>> db = ZODB.config.databaseFromFile(open('config'))
+
+And perform some updates:
+
+ >>> conn1 = db.open()
+ >>> conn2 = conn1.get_connection('db2')
+ >>> conn3 = conn1.get_connection('db3')
+
+ >>> import persistent.mapping
+ >>> C = persistent.mapping.PersistentMapping
+
+ >>> conn1.root.x = C()
+ >>> conn2.root.x = C()
+ >>> conn3.root.x = C()
+ >>> transaction.commit()
+ >>> conn2.root.y = C()
+ >>> conn3.root.x = C()
+ >>> transaction.commit()
+ >>> conn3.root.z = C()
+ >>> transaction.commit()
+
+ >>> conn1.root.x.y = conn2.root.y
+ >>> del conn2.root.y
+ >>> conn1.root.x.y.z = conn3.root.z
+ >>> del conn3.root.z
+
+In db loops:
+
+ >>> conn1.root.a = C()
+ >>> transaction.commit()
+ >>> conn1.root.b = C()
+ >>> transaction.commit()
+ >>> conn1.root.a.b = conn1.root.b
+ >>> conn1.root.b.a = conn1.root.a
+
+cross db loops
+
+ >>> conn2.root.x.x = conn3.root.x
+ >>> conn3.root.x.x = conn2.root.x
+
+ >>> transaction.commit()
+
+
+No garbage yet, because everything's reachable.
+
+ >>> from ZODB.utils import u64, p64
+ >>> print u64(conn1.root.a._p_oid), u64(conn1.root.b._p_oid)
+ 2 3
+ >>> print u64(conn2.root.x._p_oid), u64(conn3.root.x._p_oid)
+ 1 2
+ >>> del conn1.root.a
+ >>> del conn1.root.b
+ >>> del conn2.root.x
+ >>> del conn3.root.x
+
+ >>> transaction.commit()
+
+The objects we just deleted are now garbage.
+
+Time passes. :)
+
+ >>> now += 7 * 86400 # 7 days
+
+We'll create some more garbage:
+
+ >>> conn2.root.a = C()
+ >>> transaction.commit()
+ >>> conn2.root.b = C()
+ >>> transaction.commit()
+ >>> conn2.root.a.b = conn2.root.b
+ >>> conn2.root.b.a = conn2.root.a
+
+ >>> transaction.commit()
+
+ >>> print u64(conn2.root.a._p_oid), u64(conn2.root.b._p_oid)
+ 3 4
+ >>> del conn2.root.a
+ >>> del conn2.root.b
+
+More time passes.
+
+ >>> now += 1
+
+The number of objecs in the databases now:
+
+ >>> len(conn1._storage), len(conn2._storage), len(conn3._storage)
+ (4, 5, 4)
+
+ >>> for d in db.databases.values():
+ ... d.pack()
+
+Packing doesn't change it:
+
+ >>> len(conn1._storage), len(conn2._storage), len(conn3._storage)
+ (4, 5, 4)
+
+ >>> _ = conn1._storage.load(p64(2))
+ >>> _ = conn1._storage.load(p64(3))
+ >>> _ = conn2._storage.load(p64(1))
+ >>> _ = conn3._storage.load(p64(1))
+ >>> _ = conn3._storage.load(p64(2))
+
+ >>> _ = [d.close() for d in db.databases.values()]
+
+Now let's perform gc.
+
+ >>> import zc.zodbdgc
+ >>> bad = zc.zodbdgc.gc('config', days=2)
+
+ >>> for name, oid in sorted(bad.iterator()):
+ ... print name, u64(oid)
+ db1 2
+ db1 3
+ db2 1
+ db3 1
+ db3 2
+
+ >>> db = ZODB.config.databaseFromFile(open('config'))
+ >>> conn1 = db.open()
+ >>> conn2 = conn1.get_connection('db2')
+ >>> conn3 = conn1.get_connection('db3')
+
+Note that we still have the same number of objects, because we
+haven't packed yet.
+
+ >>> len(conn1._storage), len(conn2._storage), len(conn3._storage)
+ (4, 5, 4)
+
+ >>> now += 1
+
+ >>> for d in db.databases.values():
+ ... d.pack()
+
+ >>> _ = [d.close() for d in db.databases.values()]
+ >>> db = ZODB.config.databaseFromFile(open('config'))
+ >>> conn1 = db.open()
+ >>> conn2 = conn1.get_connection('db2')
+ >>> conn3 = conn1.get_connection('db3')
+
+ >>> len(conn1._storage), len(conn2._storage), len(conn3._storage)
+ (2, 4, 2)
+
+ >>> import ZODB.POSException
+ >>> for name, oid in bad.iterator():
+ ... try:
+ ... conn1.get_connection(name)._storage.load(oid)
+ ... except ZODB.POSException.POSKeyError:
+ ... pass
+ ... else:
+ ... print 'waaa', name, u64(oid)
+
+Make sure we have no broken refs:
+
+ >>> _ = [d.close() for d in db.databases.values()]
+ >>> zc.zodbdgc.check('config')
+
+.. cleanup
+
+ >>> time.time = time_time
Property changes on: zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.test
___________________________________________________________________
Added: svn:eol-style
+ native
Added: zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.txt
===================================================================
--- zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.txt (rev 0)
+++ zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.txt 2009-05-11 21:38:04 UTC (rev 99857)
@@ -0,0 +1,31 @@
+ZODB Distributed GC
+===================
+
+This package provides a script for performing distributed garbage
+collection for a collection of ZODB storages, which will typically be
+ZEO clients.
+
+Note that this script will likely be included in future ZODB
+releases. It's being developed independently now because it is new and
+we don't want to be limited by or to affect the ZODB release cycle.
+
+The script takes the fillowing options:
+
+-d n, --days n
+
+ Provide the number of days in the past to garbage collect to. And
+ objects written after than number of days will be considered to be
+ non garbage. This defaults to 3.
+
+-s config, --storage config
+
+ The name of a configuration file defining storages to be garbage
+ collected.
+
+-a config, --analyze config
+
+ The name of a configuration file defining storage servers to use
+ for analysis. This is useful with replicated storages, as it
+ allows analysis to take place using stprage servers that are under
+ lighter load. If not provided, then the storages specified using
+ the --storage option are used for analysis.
Property changes on: zc.zodbdgc/branches/dev/src/zc/zodbdgc/README.txt
___________________________________________________________________
Added: svn:eol-style
+ native
Added: zc.zodbdgc/branches/dev/src/zc/zodbdgc/__init__.py
===================================================================
--- zc.zodbdgc/branches/dev/src/zc/zodbdgc/__init__.py (rev 0)
+++ zc.zodbdgc/branches/dev/src/zc/zodbdgc/__init__.py 2009-05-11 21:38:04 UTC (rev 99857)
@@ -0,0 +1,227 @@
+##############################################################################
+#
+# Copyright (c) Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+from ZODB.utils import u64, z64, p64
+import BTrees.IIBTree
+import cPickle
+import cStringIO
+import logging
+import marshal
+import os
+import shutil
+import tempfile
+import time
+import transaction
+import ZODB.config
+import ZODB.TimeStamp
+
+logger = logging.getLogger(__name__)
+
+def gc(conf, days=1, conf2=None):
+ db = ZODB.config.databaseFromFile(open(conf))
+ if conf2 is None:
+ db2 = db
+ else:
+ db2 = ZODB.config.databaseFromFile(open(conf2))
+ if set(db.databases) != set(db2.databases):
+ raise ValueError("primary and secondary databases don't match.")
+
+ databases = db2.databases
+ storages = dict((name, d.storage) for (name, d) in databases.items())
+
+ ptid = repr(
+ ZODB.TimeStamp.TimeStamp(*time.gmtime(time.time() - 86400*days)[:6])
+ )
+
+ # Pre-populate good with roots and recently-written objects
+ good = oidset(databases)
+ bad = oidset(databases)
+ baddir = tempfile.mkdtemp()
+ for name in storages:
+ os.mkdir(os.path.join(baddir, name))
+
+ for name, storage in storages.iteritems():
+ # Make sure we can get the roots
+ _ = storage.load(z64, '')
+ good.insert(name, z64)
+
+ # All new records are good
+ for trans in storage.iterator(ptid):
+ for record in trans:
+ good.insert(name, record.oid)
+ # and anything they reference
+ for ref in getrefs(record.data, name):
+ good.insert(*ref)
+
+ # Now iterate over older records
+ for trans in storage.iterator(None, ptid):
+ for record in trans:
+ oid = record.oid
+ data = record.data
+ if good.has(name, oid):
+ if not data:
+ continue
+ for ref in getrefs(data, name):
+ if good.insert(*ref) and bad.has(*ref):
+ bad_to_good(baddir, bad, good, *ref)
+ else:
+ bad.insert(name, oid)
+ if not data:
+ continue
+ refs = tuple(ref for ref in getrefs(data, name)
+ if (not good.has(*ref)) and not bad.has(*ref))
+ if not refs:
+ continue # leaves are common
+ f = open(os.path.join(baddir, name,
+ oid.encode('base64').strip()),
+ 'ab')
+ marshal.dump(refs, f)
+ f.close()
+
+ # Now, we have the garbage in bad. Remove it.
+ for name, db in db.databases.iteritems():
+ storage = db.storage
+ t = transaction.begin()
+ storage.tpc_begin(t)
+ nd = 0
+ for oid in bad.iterator(name):
+ p, s = storage.load(oid, '')
+ storage.deleteObject(oid, s, t)
+ nd += 1
+ logger.info("Removed %s objects from %s", nd, name)
+ if nd:
+ storage.tpc_vote(t)
+ storage.tpc_finish(t)
+ transaction.commit()
+ else:
+ storage.tpc_abort(t)
+ transaction.abort()
+ db.close()
+
+ shutil.rmtree(baddir)
+
+ return bad
+
+def bad_to_good(baddir, bad, good, name, oid):
+ bad.remove(name, oid)
+
+ path = os.path.join(baddir, name, oid.encode('base64').strip())
+ if not os.path.exists(path):
+ return
+
+ f = open(path , 'rb')
+ while 1:
+ try:
+ refs = marshal.load(f)
+ except EOFError:
+ break
+
+ for ref in refs:
+ if good.insert(*ref) and bad.has(*ref):
+ bad_to_good(baddir, bad, good, *ref)
+
+ f.close()
+ os.remove(path)
+
+def getrefs(p, rname):
+ refs = []
+ u = cPickle.Unpickler(cStringIO.StringIO(p))
+ u.persistent_load = refs
+ u.noload()
+ u.noload()
+ for ref in refs:
+ name = rname
+ if isinstance(ref, tuple):
+ yield rname, ref[0]
+ elif isinstance(ref, str):
+ yield rname, ref
+ else:
+ assert isinstance(ref, list)
+ yield ref[1][:2]
+
+class oidset(dict):
+
+ def __init__(self, names):
+ for name in names:
+ self[name] = {}
+
+ def insert(self, name, oid):
+ ioid1, ioid2 = divmod(u64(oid), 2147483648L)
+ ioid2 = int(ioid2)
+ data = self[name].get(ioid1)
+ if data is None:
+ data = self[name][ioid1] = BTrees.IIBTree.TreeSet()
+ elif ioid2 in data:
+ return False
+ data.insert(ioid2)
+ return True
+
+ def remove(self, name, oid):
+ ioid1, ioid2 = divmod(u64(oid), 2147483648L)
+ ioid2 = int(ioid2)
+ data = self[name].get(ioid1)
+ if data and ioid2 in data:
+ data.remove(ioid2)
+ if not data:
+ del self[name][ioid1]
+
+ def has(self, name, oid):
+ ioid1, ioid2 = divmod(u64(oid), 2147483648L)
+ data = self[name].get(ioid1)
+ return bool(data and (int(ioid2) in data))
+
+ def iterator(self, name=None):
+ if name is None:
+ for name in self:
+ for oid in self.iterator(name):
+ yield name, oid
+ else:
+ for ioid1, data in self[name].iteritems():
+ ioid1 *= 2147483648L
+ for ioid2 in data:
+ yield p64(ioid1+ioid2)
+
+def check(config):
+ db = ZODB.config.databaseFromFile(open(config))
+ databases = db.databases
+ storages = dict((name, db.storage) for (name, db) in databases.iteritems())
+ roots = set((name, z64) for name in databases)
+ referers = {}
+ seen = oidset(databases)
+ while roots:
+ name, oid = roots.pop()
+ if not seen.insert(name, oid):
+ continue
+
+ try:
+ p, tid = storages[name].load(oid, '')
+ except:
+ print '!!!', name, u64(oid),
+ referer = referers.pop((name, oid), None)
+ if referer:
+ rname, roid = referer
+ print rname, u64(roid)
+ else:
+ print '?'
+ continue
+
+ referers.pop((name, oid), None)
+
+ for ref in getrefs(p, name):
+ if seen.has(*ref):
+ continue
+ if ref in roots:
+ continue
+ roots.add(ref)
+ referers[ref] = name, oid
Property changes on: zc.zodbdgc/branches/dev/src/zc/zodbdgc/__init__.py
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added: zc.zodbdgc/branches/dev/src/zc/zodbdgc/oidset.test
===================================================================
--- zc.zodbdgc/branches/dev/src/zc/zodbdgc/oidset.test (rev 0)
+++ zc.zodbdgc/branches/dev/src/zc/zodbdgc/oidset.test 2009-05-11 21:38:04 UTC (rev 99857)
@@ -0,0 +1,208 @@
+The zc.zodbdgc module uses an oidset class to keep track of sets of
+name/oid pairs efficiently.
+
+ >>> import zc.zodbdgc
+ >>> oids = zc.zodbdgc.oidset(('foo', 'bar', 'baz'))
+
+ >>> from ZODB.utils import p64, u64
+
+ >>> oids.has('foo', p64(0))
+ False
+
+ >>> sorted(oids.iterator())
+ []
+
+ >>> oids.insert('foo', p64(0))
+ True
+ >>> oids.has('foo', p64(0))
+ True
+ >>> oids.has('bar', p64(0))
+ False
+ >>> oids.has('foo', p64(1))
+ False
+
+ >>> oids.has('foo', p64(1<<31))
+ False
+ >>> oids.has('foo', p64((1<<31)+1))
+ False
+ >>> oids.has('foo', p64((1<<31)-1))
+ False
+
+ >>> oids.insert('foo', p64(1<<31))
+ True
+ >>> oids.has('foo', p64(1<<31))
+ True
+ >>> oids.has('foo', p64((1<<31)+1))
+ False
+ >>> oids.has('foo', p64((1<<31)-1))
+ False
+
+ >>> oids.insert('foo', p64((1<<31)+1))
+ True
+ >>> oids.has('foo', p64(1<<31))
+ True
+ >>> oids.has('foo', p64((1<<31)+1))
+ True
+ >>> oids.has('foo', p64((1<<31)-1))
+ False
+
+ >>> oids.insert('foo', p64((1<<31)-1))
+ True
+ >>> oids.has('foo', p64(1<<31))
+ True
+ >>> oids.has('foo', p64((1<<31)+1))
+ True
+ >>> oids.has('foo', p64((1<<31)-1))
+ True
+
+ >>> oids.has('foo', p64((1<<32)))
+ False
+ >>> oids.has('foo', p64((1<<34)))
+ False
+ >>> oids.has('foo', p64((1<<35)))
+ False
+
+ >>> oids.insert('foo', p64((1<<32)))
+ True
+ >>> oids.insert('foo', p64((1<<34)))
+ True
+ >>> oids.insert('foo', p64((1<<35)))
+ True
+
+ >>> oids.has('foo', p64((1<<32)))
+ True
+ >>> oids.has('foo', p64((1<<34)))
+ True
+ >>> oids.has('foo', p64((1<<35)))
+ True
+
+ >>> oids.insert('foo', p64((1<<32)))
+ False
+ >>> oids.insert('foo', p64((1<<34)))
+ False
+ >>> oids.insert('foo', p64((1<<35)))
+ False
+ >>> oids.insert('foo', p64(1<<31))
+ False
+ >>> oids.insert('foo', p64((1<<31)+1))
+ False
+ >>> oids.insert('foo', p64((1<<31)-1))
+ False
+
+ >>> import pprint
+
+ >>> pprint.pprint(
+ ... sorted((name, u64(oid)) for (name, oid) in oids.iterator()),
+ ... width=1)
+ [('foo',
+ 0L),
+ ('foo',
+ 2147483647L),
+ ('foo',
+ 2147483648L),
+ ('foo',
+ 2147483649L),
+ ('foo',
+ 4294967296L),
+ ('foo',
+ 17179869184L),
+ ('foo',
+ 34359738368L)]
+
+ >>> pprint.pprint(
+ ... sorted(u64(oid) for oid in oids.iterator('foo')),
+ ... width=1)
+ [0L,
+ 2147483647L,
+ 2147483648L,
+ 2147483649L,
+ 4294967296L,
+ 17179869184L,
+ 34359738368L]
+
+ >>> for oid in oids.iterator('foo'):
+ ... if not oids.insert('bar', oid):
+ ... print `oid`
+
+ >>> sorted(oids.iterator('foo')) == sorted(oids.iterator('bar'))
+ True
+
+ >>> pprint.pprint(
+ ... sorted((name, u64(oid)) for (name, oid) in oids.iterator()),
+ ... width=1)
+ [('bar',
+ 0L),
+ ('bar',
+ 2147483647L),
+ ('bar',
+ 2147483648L),
+ ('bar',
+ 2147483649L),
+ ('bar',
+ 4294967296L),
+ ('bar',
+ 17179869184L),
+ ('bar',
+ 34359738368L),
+ ('foo',
+ 0L),
+ ('foo',
+ 2147483647L),
+ ('foo',
+ 2147483648L),
+ ('foo',
+ 2147483649L),
+ ('foo',
+ 4294967296L),
+ ('foo',
+ 17179869184L),
+ ('foo',
+ 34359738368L)]
+
+ >>> oids.remove('foo', p64(1<<31))
+ >>> oids.remove('foo', p64((1<<31)+1))
+ >>> oids.remove('foo', p64((1<<31)-1))
+
+ >>> pprint.pprint(
+ ... sorted(u64(oid) for oid in oids.iterator('foo')),
+ ... width=1)
+ [0L,
+ 4294967296L,
+ 17179869184L,
+ 34359738368L]
+
+ >>> import random
+ >>> r = random.Random()
+ >>> r.seed(0)
+
+ >>> generated_oids = list(oids.iterator())
+ >>> sorted(generated_oids) == sorted(oids.iterator())
+ True
+
+ >>> for i in range(1000):
+ ... name = r.choice(('foo', 'bar'))
+ ... oid = p64(r.randint(0, 1<<32))
+ ... if (name, oid) in generated_oids:
+ ... print 'dup', (name, oid)
+ ... if oids.insert(name, oid):
+ ... print 'wth dup', name, `oid`
+ ... else:
+ ... if not oids.insert(name, oid):
+ ... print 'wth', name, `oid`
+ ... generated_oids.append((name, oid))
+
+ >>> sorted(generated_oids) == sorted(oids.iterator())
+ True
+
+ >>> for i in range(1500):
+ ... action = r.choice('ri')
+ ... choice = r.choice(generated_oids)
+ ... if action == 'i':
+ ... if oids.insert(*choice):
+ ... print 'wth', choice
+ ... else:
+ ... generated_oids.remove(choice)
+ ... oids.remove(*choice)
+
+ >>> sorted(generated_oids) == sorted(oids.iterator())
+ True
Property changes on: zc.zodbdgc/branches/dev/src/zc/zodbdgc/oidset.test
___________________________________________________________________
Added: svn:eol-style
+ native
Added: zc.zodbdgc/branches/dev/src/zc/zodbdgc/tests.py
===================================================================
--- zc.zodbdgc/branches/dev/src/zc/zodbdgc/tests.py (rev 0)
+++ zc.zodbdgc/branches/dev/src/zc/zodbdgc/tests.py 2009-05-11 21:38:04 UTC (rev 99857)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""XXX short summary goes here.
+
+$Id$
+"""
+import unittest
+from zope.testing import doctest, setupstack
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite(
+ 'README.test', 'oidset.test',
+ setUp=setupstack.setUpDirectory, tearDown = setupstack.tearDown,
+ ),
+ ))
+
Property changes on: zc.zodbdgc/branches/dev/src/zc/zodbdgc/tests.py
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
More information about the Checkins
mailing list