[Checkins] SVN: z3c.sharding/ Added a ZODB container type that stores its contents in multiple
Shane Hathaway
shane at hathawaymix.org
Wed Dec 10 15:26:47 EST 2008
Log message for revision 93853:
Added a ZODB container type that stores its contents in multiple
databases automatically.
Changed:
A z3c.sharding/
A z3c.sharding/.installed.cfg
A z3c.sharding/CHANGES.txt
A z3c.sharding/README.txt
A z3c.sharding/bootstrap.py
A z3c.sharding/buildout.cfg
A z3c.sharding/setup.py
A z3c.sharding/src/
A z3c.sharding/src/z3c/
A z3c.sharding/src/z3c/__init__.py
A z3c.sharding/src/z3c/sharding/
A z3c.sharding/src/z3c/sharding/README.txt
A z3c.sharding/src/z3c/sharding/__init__.py
A z3c.sharding/src/z3c/sharding/apidoc.zcml
A z3c.sharding/src/z3c/sharding/container.py
A z3c.sharding/src/z3c/sharding/interfaces.py
A z3c.sharding/src/z3c/sharding/tests.py
-=-
Added: z3c.sharding/.installed.cfg
===================================================================
--- z3c.sharding/.installed.cfg (rev 0)
+++ z3c.sharding/.installed.cfg 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,33 @@
+[buildout]
+installed_develop_eggs = /home/shane/src/z3c.sharding/develop-eggs/z3c.sharding.egg-link
+parts = test coverage
+
+[test]
+__buildout_installed__ = /home/shane/src/z3c.sharding/parts/test
+ /home/shane/src/z3c.sharding/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.1.0-py2.5.egg zc.recipe.egg-1.1.0-py2.5.egg setuptools-5UNm9gjHTOWl6Jm0S8SffQ== zope.testing-3.7.1-py2.5.egg zc.buildout-1.1.1-py2.5.egg zc.buildout-1.1.1-py2.5.egg zope.interface-3.5.0-py2.5-linux-x86_64.egg
+_b = /home/shane/src/z3c.sharding/bin
+_d = /home/shane/src/z3c.sharding/develop-eggs
+_e = /home/shane/.buildout/eggs
+bin-directory = /home/shane/src/z3c.sharding/bin
+develop-eggs-directory = /home/shane/src/z3c.sharding/develop-eggs
+eggs = z3c.sharding [test]
+eggs-directory = /home/shane/.buildout/eggs
+executable = /usr/bin/python
+location = /home/shane/src/z3c.sharding/parts/test
+recipe = zc.recipe.testrunner
+script = /home/shane/src/z3c.sharding/bin/test
+
+[coverage]
+__buildout_installed__ = /home/shane/src/z3c.sharding/bin/coverage
+ /home/shane/src/z3c.sharding/bin/coveragediff
+__buildout_signature__ = zc.recipe.egg-1.1.0-py2.5.egg setuptools-5UNm9gjHTOWl6Jm0S8SffQ== zc.buildout-1.1.1-py2.5.egg
+_b = /home/shane/src/z3c.sharding/bin
+_d = /home/shane/src/z3c.sharding/develop-eggs
+_e = /home/shane/.buildout/eggs
+bin-directory = /home/shane/src/z3c.sharding/bin
+develop-eggs-directory = /home/shane/src/z3c.sharding/develop-eggs
+eggs = z3c.coverage
+eggs-directory = /home/shane/.buildout/eggs
+executable = /usr/bin/python
+recipe = zc.recipe.egg
Added: z3c.sharding/CHANGES.txt
===================================================================
--- z3c.sharding/CHANGES.txt (rev 0)
+++ z3c.sharding/CHANGES.txt 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,8 @@
+=======
+CHANGES
+=======
+
+Version 0.1.0 (2008-??-??)
+--------------------------
+
+- Initial Release
Added: z3c.sharding/README.txt
===================================================================
--- z3c.sharding/README.txt (rev 0)
+++ z3c.sharding/README.txt 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,3 @@
+This package provides a database sharding API based on persistent
+multi-database references. A more complete description is provided
+in src/z3c/sharding/README.txt.
Added: z3c.sharding/bootstrap.py
===================================================================
--- z3c.sharding/bootstrap.py (rev 0)
+++ z3c.sharding/bootstrap.py 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 17 2008-02-27 09:29:05Z srichter $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+ ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+ cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+ os.P_WAIT, sys.executable, sys.executable,
+ '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+ dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse('setuptools')).location
+ ),
+ ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
Added: z3c.sharding/buildout.cfg
===================================================================
--- z3c.sharding/buildout.cfg (rev 0)
+++ z3c.sharding/buildout.cfg 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,11 @@
+[buildout]
+develop = .
+parts = test coverage
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.sharding [test]
+
+[coverage]
+recipe = zc.recipe.egg
+eggs = z3c.coverage
Added: z3c.sharding/setup.py
===================================================================
--- z3c.sharding/setup.py (rev 0)
+++ z3c.sharding/setup.py 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+"""Package setup.
+
+$Id: setup.py 17 2008-02-27 09:29:05Z srichter $
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+ return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup(
+ name='z3c.sharding',
+ version = '0.1.0-dev',
+ author='Keas, Inc.',
+ description='Database Sharding for ZODB',
+ long_description=(
+ read('README.txt')
+ + '\n\n' +
+ read('CHANGES.txt')
+ ),
+ license = "ZPL-2.1",
+ keywords = "ZODB sharding",
+ packages=find_packages('src'),
+ package_dir = {'': 'src'},
+ namespace_packages = ['z3c'],
+ extras_require=dict(
+ test=['zope.app.testing',
+ 'zope.testing',],
+ ),
+ install_requires=[
+ 'setuptools',
+ ],
+ include_package_data = True,
+ zip_safe = False,
+ )
Added: z3c.sharding/src/z3c/__init__.py
===================================================================
--- z3c.sharding/src/z3c/__init__.py (rev 0)
+++ z3c.sharding/src/z3c/__init__.py 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+ import pkg_resources
+ pkg_resources.declare_namespace(__name__)
+except ImportError:
+ import pkgutil
+ __path__ = pkgutil.extend_path(__path__, __name__)
Added: z3c.sharding/src/z3c/sharding/README.txt
===================================================================
--- z3c.sharding/src/z3c/sharding/README.txt (rev 0)
+++ z3c.sharding/src/z3c/sharding/README.txt 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,159 @@
+===========================
+Container Database Sharding
+===========================
+
+This package provides a container that automatically partitions its contents
+across multiple databases. Objects are spread between participating databases
+in order to reduce database load and increase scalability.
+
+This code puts a master index (a BTree) in the database holding the
+`ShardContainer` object. The values in the master index are ZODB
+cross-database references to objects in partitions. Partitions are
+`BTreeContainer` components.
+
+The `ShardContainer` chooses the partition for a new object based on a weighted
+random selection of partition names. The partition names are configured in
+the `ShardContainer` instance. `ShardContainer` expects the multi-database
+configuration to be set up ahead of time.
+
+See the tests below for further details.
+
+ >>> from zope.interface.verify import verifyClass
+ >>> from transaction import commit, abort
+ >>> from z3c.sharding.interfaces import IShardPartition, IShardContainer
+ >>> from z3c.sharding.container import Partition, ShardContainer
+ >>> verifyClass(IShardPartition, Partition)
+ True
+ >>> verifyClass(IShardContainer, ShardContainer)
+ True
+
+Manually create a partition and verify its representation.
+
+ >>> p1 = Partition('p1', 1.0)
+ >>> print p1
+ Partition('p1', 1.0)
+
+Create a multi-database out of DemoStorages. A multi-database is a set of
+ZODB DB objects linked by a common 'databases' map. Objects stored
+in multi-databases can freely cross database boundaries.
+
+ >>> from ZODB.DemoStorage import DemoStorage
+ >>> from ZODB.DB import DB
+ >>> databases = {}
+ >>> for name in ('p1', 'p2', 'p3'):
+ ... db = DB(DemoStorage(), database_name=name, databases=databases)
+ >>> main_db = DB(DemoStorage(), database_name='main', databases=databases)
+ >>> conn = main_db.open()
+
+Create a ShardContainer named "things" with a shard name of "things000".
+The shard name must be globally unique, but the shard name
+can be distinct from the container's __name__ attribute.
+
+ >>> things = ShardContainer('things000')
+ >>> conn.root()['things'] = things
+ >>> commit()
+
+A partition is required before storing anything.
+
+ >>> from zope.app.container.btree import BTreeContainer
+ >>> things['x'] = BTreeContainer()
+ Traceback (most recent call last):
+ ...
+ AssertionError: No partitions defined
+
+Set up a partition for the "things" contianer.
+
+ >>> parts = []
+ >>> for name in ['p1']:
+ ... parts.append(Partition(name, 0))
+ >>> things.partitions = parts
+
+Add 1 item to the container. The item should be a `Persistent` obejct and
+should implement the `IContained` interface.
+
+ >>> thing = BTreeContainer()
+ >>> thing.attr = 'value'
+ >>> things['0'] = thing
+ >>> thing._p_jar.db().database_name
+ 'p1'
+ >>> things._p_jar.db().database_name
+ 'main'
+
+Commit.
+
+ >>> things._p_changed
+ True
+ >>> commit()
+ >>> thing._p_changed
+ False
+ >>> things._p_changed
+ False
+
+Verify it's possible to load the thing from a new connection.
+
+ >>> conn2 = main_db.open()
+ >>> things2 = conn2.root()['things']
+ >>> thing2 = things2['0']
+ >>> things2._p_changed
+ False
+ >>> thing2.attr
+ 'value'
+ >>> thing2._p_changed
+ False
+ >>> thing2._p_jar.db().database_name
+ 'p1'
+ >>> things2._p_jar.db().database_name
+ 'main'
+ >>> conn2.close()
+
+Enable another partition. Assign p1 a weight of 1 and p2 a weight of 0,
+but note that the manual weights will be ignored until automatic
+weighting is disabled.
+
+ >>> parts = []
+ >>> for name in ['p1', 'p2']:
+ ... parts.append(Partition(name, 2 - int(name[1])))
+ >>> things.partitions = parts
+
+Add another object. Automatic weighting should cause the new object to always
+land in the new partition.
+
+ >>> stuff = BTreeContainer()
+ >>> things['1'] = stuff
+ >>> stuff._p_jar.db().database_name
+ 'p2'
+
+Apply manual weighting. Partition `p1` has weight 1 and `p2` has weight 0,
+so `p1` should get everything.
+
+ >>> things.auto_weight = False
+ >>> stuff = BTreeContainer()
+ >>> things['2'] = stuff
+ >>> stuff._p_jar.db().database_name
+ 'p1'
+
+Verify all of the objects can be found directly in the partitions.
+
+ >>> conn.get_connection('p1').root()['shards'].keys()
+ ['things000']
+ >>> conn.get_connection('p2').root()['shards'].keys()
+ ['things000']
+ >>> sorted(conn.get_connection('p1').root()['shards']['things000'].keys())
+ [u'0', u'2']
+ >>> sorted(conn.get_connection('p2').root()['shards']['things000'].keys())
+ [u'1']
+
+Delete an object from the shard container, which should cause
+the same object to be deleted from its partition.
+
+ >>> del things['0']
+ >>> sorted(conn.get_connection('p1').root()['shards']['things000'].keys())
+ [u'2']
+ >>> commit()
+
+Clean up.
+
+ >>> abort()
+ >>> conn.close()
+ >>> for database in databases.values():
+ ... database.close()
Added: z3c.sharding/src/z3c/sharding/apidoc.zcml
===================================================================
--- z3c.sharding/src/z3c/sharding/apidoc.zcml (rev 0)
+++ z3c.sharding/src/z3c/sharding/apidoc.zcml 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,19 @@
+
+<configure xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="zope">
+
+ <configure
+ xmlns:zcml="http://namespaces.zope.org/zcml"
+ xmlns:apidoc="http://namespaces.zope.org/apidoc"
+ zcml:condition="have apidoc">
+
+ <apidoc:bookchapter
+ id="z3c.sharding"
+ title="Container Database Sharding (z3c.sharding)"
+ doc_path="README.txt"
+ parent="z3c"
+ />
+
+ </configure>
+
+</configure>
Added: z3c.sharding/src/z3c/sharding/container.py
===================================================================
--- z3c.sharding/src/z3c/sharding/container.py (rev 0)
+++ z3c.sharding/src/z3c/sharding/container.py 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,192 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+"""ShardContainer implementation.
+"""
+
+import random
+import time
+
+from persistent import Persistent
+from persistent.list import PersistentList
+from persistent.mapping import PersistentMapping
+from zope.interface import implements
+from zope.app.container.btree import BTreeContainer
+from zope.proxy import removeAllProxies
+
+from z3c.sharding.interfaces import IShardPartition, IShardContainer
+
+
+class Partition(Persistent):
+ """Describes a partition in a shard"""
+ implements(IShardPartition)
+
+ def __init__(self, database_name, weight):
+ self.database_name = database_name
+ self.weight = weight
+
+ def __repr__(self):
+ return '%s(%r, %r)' % (
+ self.__class__.__name__, self.database_name, self.weight)
+
+
+class ShardContainer(BTreeContainer):
+ """A shard container partitions its contents in multiple databases."""
+ implements(IShardContainer)
+
+ _part_table_timeout = 60 * 60 # seconds
+
+ _v_part_table = None # [Partition]
+ _v_part_table_expiration = 0 # time when _v_part_table expires
+
+ def __init__(self, shard_name):
+ super(ShardContainer, self).__init__()
+ self._shard_name = shard_name
+ self._partitions = () # (Partition,)
+ self._auto_weight = True
+
+ def set_partitions(self, partitions):
+ """Replace the partition list.
+
+ The partitions argument holds a list of IShardPartition objects.
+ """
+ for part in partitions:
+ assert IShardPartition.providedBy(part)
+ self._partitions = tuple(partitions)
+ self._v_part_table = None
+ self._get_part_table() # test the new configuration
+
+ def get_partitions(self):
+ return self.partitions
+
+ partitions = property(get_partitions, set_partitions)
+
+ def set_auto_weight(self, auto_weight):
+ """Configure whether the automatic weighting feature is enabled.
+
+ If enabled, the weight of each partition is computed to balance
+ the size of the underlying databases. If disabled, the weight
+ comes from each partition's weight attribute.
+ """
+ self._auto_weight = bool(auto_weight)
+ self._v_part_table = None
+
+ def get_auto_weight(self):
+ return self._auto_weight
+
+ auto_weight = property(get_auto_weight, set_auto_weight)
+
+ def _compute_weights(self):
+ """Returns [(Partition, weight)]."""
+ computed_weights = [] # [(partition, weight)]
+ if self._auto_weight:
+ # gather the current size of all partitions
+ sizes = {}
+ for part in self._partitions:
+ c = self._p_jar.get_connection(part.database_name)
+ size = c.db().getSize() # returns a byte count
+ sizes[part.database_name] = size
+ max_size = 0
+ if sizes:
+ max_size = max(sizes.values())
+ if max_size <= 0:
+ # no sizes are known, so give all partitions the same weight
+ computed_weights = [(part, 0) for part in self._partitions]
+ else:
+ # assign partitions a weight that causes them to auto-balance
+ for part in self._partitions:
+ weight = max_size - sizes[part.database_name]
+ computed_weights.append((part, weight))
+ else:
+ # use the manually chosen weights
+ for part in self._partitions:
+ computed_weights = [(part, part.weight)
+ for part in self._partitions]
+ return computed_weights
+
+ def _get_part_table(self):
+ """Return a list of Partitions to select from.
+
+ Partitions will often be listed more than once in order to give
+ them more weight.
+
+ The result is cached in self._v_part_table.
+ """
+ res = self._v_part_table
+ if res is not None and time.time() < self._v_part_table_expiration:
+ return self._v_part_table
+
+ if self._p_jar is None:
+ # Can't proceed until the container is stored in
+ # some database.
+ raise AssertionError(
+ "ShardContainer is not yet added to a database")
+
+ computed_weights = self._compute_weights()
+ total_weight = 0
+ for part, weight in computed_weights:
+ if weight > 0:
+ total_weight += weight
+ if not total_weight:
+ # no weights given, so give all partitions the same weight
+ part_table = [part for (part, weight) in computed_weights]
+ else:
+ # make a table with approx. 1000 entries.
+ scale = 1000.0 / total_weight
+ part_table = []
+ for part, weight in computed_weights:
+ count = int(scale * weight)
+ for i in xrange(count):
+ part_table.append(part)
+
+ self._v_part_table = part_table
+ self._v_part_table_expiration = time.time() + self._part_table_timeout
+
+ return part_table
+
+ def _choose_container(self):
+ """Returns the container where a new object should be stored."""
+ part_table = self._get_part_table()
+ if not part_table:
+ raise AssertionError("No partitions defined")
+ part = random.choice(part_table)
+ jar = self._p_jar.get_connection(part.database_name)
+ root = jar.root()
+ o1 = root.get('shards')
+ if o1 is None:
+ root['shards'] = o1 = PersistentMapping()
+ jar.add(o1)
+ o2 = o1.get(self._shard_name)
+ if o2 is None:
+ o1[self._shard_name] = o2 = BTreeContainer()
+ jar.add(o2)
+ return o2
+
+ def __setitem__(self, key, value):
+ if value._p_jar is not None:
+ raise ValueError("Contained object is already stored")
+ super(ShardContainer, self).__setitem__(key, value)
+ f = self._choose_container()
+ f[key] = value
+ f._p_jar.add(value)
+
+ def __delitem__(self, key):
+ obj = self._SampleContainer__data[key]
+ obj = removeAllProxies(obj)
+ conn = obj._p_jar
+ partition = conn.root()['shards'][self._shard_name]
+ # Avoid triggering an event involving the partition by deleting
+ # from the partition's underlying BTree.
+ tree = partition._SampleContainer__data
+ super(ShardContainer, self).__delitem__(key)
+ del tree[key]
Added: z3c.sharding/src/z3c/sharding/interfaces.py
===================================================================
--- z3c.sharding/src/z3c/sharding/interfaces.py (rev 0)
+++ z3c.sharding/src/z3c/sharding/interfaces.py 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+"""Shard Database Interfaces
+
+$Id:$
+"""
+import zope.interface
+from zope import schema
+from zope.app.container.interfaces import IContainer
+
+# set up internationalization
+import zope.i18nmessageid
+_ = zope.i18nmessageid.MessageFactory("zope")
+
+class IShardPartition(zope.interface.Interface):
+ """Represents a database back-end"""
+
+ database_name = schema.TextLine(
+ title=u'Database name',
+ description=u'The name of the database to access',
+ required=True)
+
+ weight = schema.Float(
+ title=u'Weight',
+ description=u'How much of the hash space to assign to this database',
+ required=True,
+ default=1.0)
+
+
+class IShardContainer(IContainer):
+ """A container that partitions its contents in multiple databases"""
+
+ partitions = zope.interface.Attribute(
+ "A tuple of IShardPartition objects describing where to store objects")
+
+ auto_weight = schema.Bool(
+ title=u'Automatic Weighting',
+ description=
+ u"""If true, partition weights are chosen automatically to balance
+ the available storage space. If false, weights come from the weight
+ attribute of each IShardPartition.""",
+ required=True,
+ default=True)
Added: z3c.sharding/src/z3c/sharding/tests.py
===================================================================
--- z3c.sharding/src/z3c/sharding/tests.py (rev 0)
+++ z3c.sharding/src/z3c/sharding/tests.py 2008-12-10 20:26:47 UTC (rev 93853)
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+"""Test Setup.
+
+$Id: tests.py 165 2008-03-12 00:09:25Z pcardune $
+"""
+import unittest
+from zope.testing import doctestunit, doctest
+#from zope.app.testing import placelesssetup
+
+# set up internationalization
+#import zope.i18nmessageid
+#_ = zope.i18nmessageid.MessageFactory("zope")
+
+def test_suite():
+ return unittest.TestSuite((
+ doctestunit.DocFileSuite(
+ 'README.txt',
+ #'implementation.txt',
+ #setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
More information about the Checkins
mailing list