[Zodb-checkins] CVS: ZODB4/ZEO/tests - ConnectionTests.py:1.1 TestThread.py:1.1 testClientCache.py:1.1 testConnection.py:1.1 Cache.py:1.10 CommitLockTests.py:1.6 ThreadTests.py:1.5 __init__.py:1.4 forker.py:1.17 multi.py:1.10 speed.py:1.9 stress.py:1.8 testStart.py:1.5 testTransactionBuffer.py:1.5 testZEO.py:1.27 winserver.py:1.5

Jeremy Hylton jeremy@zope.com
Fri, 22 Nov 2002 16:24:54 -0500


Update of /cvs-repository/ZODB4/ZEO/tests
In directory cvs.zope.org:/tmp/cvs-serv7160/ZEO/tests

Modified Files:
	Cache.py CommitLockTests.py ThreadTests.py __init__.py 
	forker.py multi.py speed.py stress.py testStart.py 
	testTransactionBuffer.py testZEO.py winserver.py 
Added Files:
	ConnectionTests.py TestThread.py testClientCache.py 
	testConnection.py 
Log Message:
Merge ZEO2 into ZODB4.


=== Added File ZODB4/ZEO/tests/ConnectionTests.py === (527/627 lines abridged)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation 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
#
##############################################################################
import asyncore
import os
import random
import select
import socket
import sys
import tempfile
import threading
import time

import zLOG

from ZEO.ClientStorage import ClientStorage
from ZEO.Exceptions import Disconnected
from ZEO.zrpc.marshal import Marshaller

from Transaction import get_transaction
from ZODB.POSException import ReadOnlyError
from ZODB.ZTransaction import Transaction
from ZODB.tests.StorageTestBase import StorageTestBase
from ZODB.tests.MinPO import MinPO
from ZODB.tests.StorageTestBase import zodb_pickle, zodb_unpickle
from ZODB.tests.StorageTestBase import handle_all_serials, ZERO

class DummyDB:
    def invalidate(self, *args):
        pass

class ConnectionTests(StorageTestBase):
    """Tests that explicitly manage the server process.

    To test the cache or re-connection, these test cases explicit
    start and stop a ZEO storage server.

    This must be subclassed; the subclass must provide implementations
    of startServer() and shutdownServer().
    """

[-=- -=- -=- 527 lines omitted -=- -=- -=-]

            # expanded in-line (mostly).

            # Create oid->serial mappings
            for c in clients:
                c.__oids = []
                c.__serials = {}

            # Begin a transaction
            t = Transaction()
            for c in clients:
                #print "%s.%s.%s begin\n" % (tname, c.__name, i),
                c.tpc_begin(t)

            for j in range(testcase.nobj):
                for c in clients:
                    # Create and store a new object on each server
                    oid = c.new_oid()
                    c.__oids.append(oid)
                    data = MinPO("%s.%s.t%d.o%d" % (tname, c.__name, i, j))
                    #print data.value
                    data = zodb_pickle(data)
                    s = c.store(oid, ZERO, data, '', t)
                    c.__serials.update(handle_all_serials(oid, s))

            # Vote on all servers and handle serials
            for c in clients:
                #print "%s.%s.%s vote\n" % (tname, c.__name, i),
                s = c.tpc_vote(t)
                c.__serials.update(handle_all_serials(None, s))

            # Finish on all servers
            for c in clients:
                #print "%s.%s.%s finish\n" % (tname, c.__name, i),
                c.tpc_finish(t)

            for c in clients:
                # Check that we got serials for all oids
                for oid in c.__oids:
                    testcase.failUnless(c.__serials.has_key(oid))
                # Check that we got serials for no other oids
                for oid in c.__serials.keys():
                    testcase.failUnless(oid in c.__oids)

    def closeclients(self):
        # Close clients opened by run()
        for c in self.clients:
            try:
                c.close()
            except:
                pass


=== Added File ZODB4/ZEO/tests/TestThread.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation 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
#
##############################################################################
"""A Thread base class for use with unittest."""

from cStringIO import StringIO
import threading
import traceback

class TestThread(threading.Thread):
    __super_init = threading.Thread.__init__
    __super_run = threading.Thread.run

    def __init__(self, testcase, group=None, target=None, name=None,
                 args=(), kwargs={}, verbose=None):
        self.__super_init(group, target, name, args, kwargs, verbose)
        self.setDaemon(1)
        self._testcase = testcase

    def run(self):
        try:
            self.testrun()
        except Exception, err:
            s = StringIO()
            traceback.print_exc(file=s)
            self._testcase.fail("Exception in thread %s:\n%s\n" %
                                (self, s.getvalue()))

    def cleanup(self, timeout=15):
        self.join(timeout)
        if self.isAlive():
            self._testcase.fail("Thread did not finish: %s" % self)


=== Added File ZODB4/ZEO/tests/testClientCache.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation 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
#
##############################################################################
"""Test suite for the ZEO.ClientCache module.

At times, we do 'white box' testing, i.e. we know about the internals
of the ClientCache object.
"""
from __future__ import nested_scopes

import os
import time
import tempfile
import unittest

from ZEO.ClientCache import ClientCache

class ClientCacheTests(unittest.TestCase):

    def setUp(self):
        unittest.TestCase.setUp(self)
        self.cachesize = 10*1000*1000
        self.cache = ClientCache(size=self.cachesize)
        self.cache.open()

    def tearDown(self):
        self.cache.close()
        unittest.TestCase.tearDown(self)

    def testOpenClose(self):
        pass # All the work is done by setUp() / tearDown()

    def testStoreLoad(self):
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))

    def testMissingLoad(self):
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        loaded = cache.load('garbage1', '')
        self.assertEqual(loaded, None)

    def testInvalidate(self):
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))
        cache.invalidate(oid, '')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, None)

    def testVersion(self):
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        vname = 'myversion'
        vdata = '5678'*200
        vserial = 'IJKLMNOP'
        cache.store(oid, data, serial, vname, vdata, vserial)
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))
        vloaded = cache.load(oid, vname)
        self.assertEqual(vloaded, (vdata, vserial))

    def testVersionOnly(self):
        cache = self.cache
        oid = 'abcdefgh'
        data = ''
        serial = ''
        vname = 'myversion'
        vdata = '5678'*200
        vserial = 'IJKLMNOP'
        cache.store(oid, data, serial, vname, vdata, vserial)
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, None)
        vloaded = cache.load(oid, vname)
        self.assertEqual(vloaded, (vdata, vserial))

    def testInvalidateNonVersion(self):
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        vname = 'myversion'
        vdata = '5678'*200
        vserial = 'IJKLMNOP'
        cache.store(oid, data, serial, vname, vdata, vserial)
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))
        vloaded = cache.load(oid, vname)
        self.assertEqual(vloaded, (vdata, vserial))
        cache.invalidate(oid, '')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, None)
        # The version data is also invalidated at this point
        vloaded = cache.load(oid, vname)
        self.assertEqual(vloaded, None)

    def testInvalidateVersion(self):
        # Invalidating a version should not invalidate the non-version data.
        # (This tests for the same bug as testInvalidatePersists below.)
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))
        cache.invalidate(oid, 'bogus')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))

    def testVerify(self):
        cache = self.cache
        results = []
        def verifier(oid, serial, vserial):
            results.append((oid, serial, vserial))
        cache.verify(verifier)
        self.assertEqual(results, [])
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        results = []
        cache.verify(verifier)
        self.assertEqual(results, [(oid, serial, None)])

    def testCheckSize(self):
        # Make sure that cache._index[oid] is erased for oids that are
        # stored in the cache file that's rewritten after a flip.
        cache = self.cache
        oid = 'abcdefgh'
        data = '1234'*100
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        cache.checkSize(10*self.cachesize) # Force a file flip
        oid2 = 'abcdefgz'
        data2 = '1234'*10
        serial2 = 'ABCDEFGZ'
        cache.store(oid2, data2, serial2, '', '', '')
        cache.checkSize(10*self.cachesize) # Force another file flip
        self.assertNotEqual(cache._index.get(oid2), None)
        self.assertEqual(cache._index.get(oid), None)

    def testCopyToCurrent(self):
        # - write some objects to cache file 0
        # - force a flip
        # - write some objects to cache file 1
        # - load some objects that are in cache file 0
        # - load the same objects, making sure they are now in file 1
        # - write some more objects
        # - force another flip
        # - load the same objects again
        # - make sure they are now in file 0 again

        cache = self.cache

        # Create some objects
        oid1 = 'abcdefgh'
        data1 = '1234' * 100
        serial1 = 'ABCDEFGH'
        oid2 = 'bcdefghi'
        data2 = '2345' * 200
        serial2 = 'BCDEFGHI'
        version2 = 'myversion'
        nonversion = 'nada'
        vdata2 = '5432' * 250
        vserial2 = 'IHGFEDCB'
        oid3 = 'cdefghij'
        data3 = '3456' * 300
        serial3 = 'CDEFGHIJ'

        # Store them in the cache
        cache.store(oid1, data1, serial1, '', '', '')
        cache.store(oid2, data2, serial2, version2, vdata2, vserial2)
        cache.store(oid3, data3, serial3, '', '', '')

        # Verify that they are in file 0
        self.assert_(None is not cache._index.get(oid1) > 0)
        self.assert_(None is not cache._index.get(oid2) > 0)
        self.assert_(None is not cache._index.get(oid3) > 0)

        # Load them and verify that the loads return correct data
        self.assertEqual(cache.load(oid1, ''), (data1, serial1))
        self.assertEqual(cache.load(oid2, ''), (data2, serial2))
        self.assertEqual(cache.load(oid2, nonversion), (data2, serial2))
        self.assertEqual(cache.load(oid2, version2), (vdata2, vserial2))
        self.assertEqual(cache.load(oid3, ''), (data3, serial3))

        # Verify that they are still in file 0
        self.assert_(None is not cache._index.get(oid1) > 0)
        self.assert_(None is not cache._index.get(oid2) > 0)
        self.assert_(None is not cache._index.get(oid3) > 0)

        # Cause a cache flip
        cache.checkSize(10*self.cachesize)

        # Load o1, o2, o4 again and verify that the loads return correct data
        self.assertEqual(cache.load(oid1, ''), (data1, serial1))
        self.assertEqual(cache.load(oid2, version2), (vdata2, vserial2))
        self.assertEqual(cache.load(oid2, nonversion), (data2, serial2))
        self.assertEqual(cache.load(oid2, ''), (data2, serial2))

        # Verify that o1, o2, 04 are now in file 1, o3 still in file 0
        self.assert_(None is not cache._index.get(oid1) < 0)
        self.assert_(None is not cache._index.get(oid2) < 0)
        self.assert_(None is not cache._index.get(oid3) > 0)

        # Cause another cache flip
        cache.checkSize(10*self.cachesize)

        # Load o1 and o2 again and verify that the loads return correct data
        self.assertEqual(cache.load(oid1, ''), (data1, serial1))
        self.assertEqual(cache.load(oid2, nonversion), (data2, serial2))
        self.assertEqual(cache.load(oid2, version2), (vdata2, vserial2))
        self.assertEqual(cache.load(oid2, ''), (data2, serial2))

        # Verify that o1 and o2 are now back in file 0, o3 is lost
        self.assert_(None is not cache._index.get(oid1) > 0)
        self.assert_(None is not cache._index.get(oid2) > 0)
        self.assert_(None is cache._index.get(oid3))

        # Invalidate version data for o2
        cache.invalidate(oid2, nonversion)
        self.assertEqual(cache.load(oid2, ''), (data2, serial2))
        self.assertEqual(cache.load(oid2, nonversion), None)
        self.assertEqual(cache.load(oid2, version2), None)

        # Cause another cache flip
        cache.checkSize(10*self.cachesize)

        # Load o1 and o2 again and verify that the loads return correct data
        self.assertEqual(cache.load(oid1, ''), (data1, serial1))
        self.assertEqual(cache.load(oid2, version2), None)
        self.assertEqual(cache.load(oid2, nonversion), None)
        self.assertEqual(cache.load(oid2, ''), (data2, serial2))

        # Verify that o1 and o2 are now in file 1
        self.assert_(None is not cache._index.get(oid1) < 0)
        self.assert_(None is not cache._index.get(oid2) < 0)

class PersistentClientCacheTests(unittest.TestCase):

    def setUp(self):
        unittest.TestCase.setUp(self)
        self.vardir = os.getcwd() # Don't use /tmp, it's a security risk
        self.cachesize = 10*1000*1000
        self.storagename = 'foo'
        self.clientname = 'test'
        # Predict file names
        fn0 = 'c%s-%s-0.zec' % (self.storagename, self.clientname)
        fn1 = 'c%s-%s-1.zec' % (self.storagename, self.clientname)
        for fn in fn0, fn1:
            fn = os.path.join(self.vardir, fn)
            try:
                os.unlink(fn)
            except os.error:
                pass
        self.openCache()

    def openCache(self):
        self.cache = ClientCache(storage=self.storagename,
                                 size=self.cachesize,
                                 client=self.clientname,
                                 var=self.vardir)
        self.cache.open()

    def reopenCache(self):
        self.cache.close()
        self.openCache()
        return self.cache

    def tearDown(self):
        self.cache.close()
        for filename in self.cache._p:
            if filename is not None:
                try:
                    os.unlink(filename)
                except os.error:
                    pass
        unittest.TestCase.tearDown(self)

    def testCacheFileSelection(self):
        # A bug in __init__ read the wrong slice of the file to determine
        # the serial number of the first record, reading the
        # last byte of the data size plus the first seven bytes of the
        # serial number.  This caused random selection of the proper
        # 'current' file when a persistent cache was opened.
        cache = self.cache
        self.assertEqual(cache._current, 0) # Check that file 0 is current
        oid = 'abcdefgh'
        data = '1234'
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        cache.checkSize(10*self.cachesize) # Force a file flip
        self.assertEqual(cache._current, 1) # Check that the flip worked
        oid = 'abcdefgh'
        data = '123'
        serial = 'ABCDEFGZ'
        cache.store(oid, data, serial, '', '', '')
        cache = self.reopenCache()
        loaded = cache.load(oid, '')
        # Check that we got the most recent data:
        self.assertEqual(loaded, (data, serial))
        self.assertEqual(cache._current, 1) # Double check that 1 is current

    def testInvalidationPersists(self):
        # A bug in invalidate() caused invalidation to overwrite the
        # 2nd byte of the data size on disk, rather rather than
        # overwriting the status byte.  For certain data sizes this
        # can be observed by reopening a persistent cache: the
        # invalidated data will appear valid (but with altered size).
        cache = self.cache
        magicsize = (ord('i') + 1) << 16
        cache = self.cache
        oid = 'abcdefgh'
        data = '!'*magicsize
        serial = 'ABCDEFGH'
        cache.store(oid, data, serial, '', '', '')
        loaded = cache.load(oid, '')
        self.assertEqual(loaded, (data, serial))
        cache.invalidate(oid, '')
        cache = self.reopenCache()
        loaded = cache.load(oid, '')
        if loaded != None:
            self.fail("invalidated data resurrected, size %d, was %d" %
                      (len(loaded[0]), len(data)))

def test_suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(ClientCacheTests))
    suite.addTest(unittest.makeSuite(PersistentClientCacheTests))
    return suite

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')


=== Added File ZODB4/ZEO/tests/testConnection.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation 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
#
##############################################################################
"""Test setup for ZEO connection logic.

The actual tests are in ConnectionTests.py; this file provides the
platform-dependent scaffolding.
"""

# System imports
import os
import time
import socket
import unittest

# Zope/ZODB3 imports
import zLOG

# ZEO test support
from ZEO.tests import forker

# Import the actual test class
from ZEO.tests.ConnectionTests import ConnectionTests

class UnixConnectionTests(ConnectionTests):

    """Add Unix-specific scaffolding to the generic test suite."""

    def startServer(self, create=1, index=0, read_only=0, ro_svr=0):
        zLOG.LOG("testZEO", zLOG.INFO,
                 "startServer(create=%d, index=%d, read_only=%d)" %
                 (create, index, read_only))
        path = "%s.%d" % (self.file, index)
        addr = self.addr[index]
        pid, server = forker.start_zeo_server(
            'FileStorage', (path, create, read_only), addr, ro_svr)
        self._pids.append(pid)
        self._servers.append(server)

    def shutdownServer(self, index=0):
        zLOG.LOG("testZEO", zLOG.INFO, "shutdownServer(index=%d)" % index)
        self._servers[index].close()
        if self._pids[index] is not None:
            try:
                os.waitpid(self._pids[index], 0)
                self._pids[index] = None
            except os.error, err:
                print err

class WindowsConnectionTests(ConnectionTests):

    """Add Windows-specific scaffolding to the generic test suite."""

    def startServer(self, create=1, index=0, read_only=0, ro_svr=0):
        zLOG.LOG("testZEO", zLOG.INFO,
                 "startServer(create=%d, index=%d, read_only=%d)" %
                 (create, index, read_only))
        path = "%s.%d" % (self.file, index)
        addr = self.addr[index]
        args = (path, '='+str(create), '='+str(read_only))
        _addr, test_addr, test_pid = forker.start_zeo_server(
            'FileStorage', args, addr, ro_svr)
        self._pids.append(test_pid)
        self._servers.append(test_addr)

    def shutdownServer(self, index=0):
        zLOG.LOG("testZEO", zLOG.INFO, "shutdownServer(index=%d)" % index)
        if self._servers[index] is not None:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect(self._servers[index])
            s.close()
            self._servers[index] = None
            # XXX waitpid() isn't available until Python 2.3
            time.sleep(0.5)

if os.name == "posix":
    test_classes = [UnixConnectionTests]
elif os.name == "nt":
    test_classes = [WindowsConnectionTests]
else:
    raise RuntimeError, "unsupported os: %s" % os.name

def test_suite():

    # shutup warnings about mktemp
    import warnings
    warnings.filterwarnings("ignore", "mktemp")

    suite = unittest.TestSuite()
    for klass in test_classes:
        sub = unittest.makeSuite(klass, 'check')
        suite.addTest(sub)
    return suite

if __name__ == "__main__":
    unittest.main(defaultTest='test_suite')


=== ZODB4/ZEO/tests/Cache.py 1.9 => 1.10 ===
--- ZODB4/ZEO/tests/Cache.py:1.9	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/Cache.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 """Tests of the ZEO cache"""
 


=== ZODB4/ZEO/tests/CommitLockTests.py 1.5 => 1.6 ===
--- ZODB4/ZEO/tests/CommitLockTests.py:1.5	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/CommitLockTests.py	Fri Nov 22 16:24:53 2002
@@ -13,15 +13,16 @@
 ##############################################################################
 """Tests of the distributed commit lock."""
 
-import time
 import threading
+import time
 
-from ZODB.TimeStamp import TimeStamp
 from ZODB.ZTransaction import Transaction
+from ZODB.TimeStamp import TimeStamp
 from ZODB.tests.StorageTestBase import zodb_pickle, MinPO
 
 import ZEO.ClientStorage
 from ZEO.Exceptions import Disconnected
+from ZEO.tests.TestThread import TestThread
 
 ZERO = '\0'*8
 
@@ -29,19 +30,19 @@
     def invalidate(self, *args):
         pass
 
-class WorkerThread(threading.Thread):
+class WorkerThread(TestThread):
 
     # run the entire test in a thread so that the blocking call for
     # tpc_vote() doesn't hang the test suite.
 
-    def __init__(self, storage, trans, method="tpc_finish"):
+    def __init__(self, testcase, storage, trans, method="tpc_finish"):
         self.storage = storage
         self.trans = trans
         self.method = method
-        threading.Thread.__init__(self)
-        self.setDaemon(1)
+        self.ready = threading.Event()
+        TestThread.__init__(self, testcase)
 
-    def run(self):
+    def testrun(self):
         try:
             self.storage.tpc_begin(self.trans)
             oid = self.storage.new_oid()
@@ -50,6 +51,7 @@
             oid = self.storage.new_oid()
             p = zodb_pickle(MinPO("c"))
             self.storage.store(oid, ZERO, p, '', self.trans)
+            self.ready.set()
             self.storage.tpc_vote(self.trans)
             if self.method == "tpc_finish":
                 self.storage.tpc_finish(self.trans)
@@ -107,9 +109,27 @@
         # check the commit lock when a client attemps a transaction,
         # but fails/exits before finishing the commit.
 
-        # Start on transaction normally.
+        # The general flow of these tests is to start a transaction by
+        # calling tpc_begin().  Then begin one or more other
+        # connections that also want to commit.  This causes the
+        # commit lock code to be exercised.  Once the other
+        # connections are started, the first transaction completes.
+        # Either by commit or abort, depending on whether method_name
+        # is "tpc_finish."
+
+        # The tests are parameterized by method_name, dosetup(), and
+        # dowork().  The dosetup() function is called with a
+        # connectioned client storage, transaction, and timestamp.
+        # Any work it does occurs after the first transaction has
+        # started, but before it finishes.  The dowork() function
+        # executes after the first transaction has completed.
+
+        # Start on transaction normally and get the lock.
         t = Transaction()
         self._storage.tpc_begin(t)
+        oid = self._storage.new_oid()
+        self._storage.store(oid, ZERO, zodb_pickle(MinPO(1)), '', t)
+        self._storage.tpc_vote(t)
 
         # Start a second transaction on a different connection without
         # blocking the test thread.
@@ -124,9 +144,6 @@
             else:
                 self._storages.append((storage2, t2))
 
-        oid = self._storage.new_oid()
-        self._storage.store(oid, ZERO, zodb_pickle(MinPO(1)), '', t)
-        self._storage.tpc_vote(t)
         if method_name == "tpc_finish":
             self._storage.tpc_finish(t)
             self._storage.load(oid, '')
@@ -153,15 +170,14 @@
 
     def _dosetup2(self, storage, trans, tid):
         self._threads = []
-        t = WorkerThread(storage, trans)
+        t = WorkerThread(self, storage, trans)
         self._threads.append(t)
         t.start()
+        t.ready.wait()
 
     def _dowork2(self, method_name):
         for t in self._threads:
-            t.join(10)
-        for t in self._threads:
-            self.failIf(t.isAlive())
+            t.cleanup()
 
     def _duplicate_client(self):
         "Open another ClientStorage to the same server."
@@ -169,13 +185,12 @@
         # The rpc mgr addr attribute is a list.  Each element in the
         # list is a socket domain (AF_INET, AF_UNIX, etc.) and an
         # address.
-        addr = self._storage._rpc_mgr.addr[0][1]
+        addr = self._storage._addr
         new = ZEO.ClientStorage.ClientStorage(addr, wait=1)
         new.registerDB(DummyDB())
         return new
 
     def _get_timestamp(self):
         t = time.time()
-        t = apply(TimeStamp,(time.gmtime(t)[:5]+(t%60,)))
-        return t.raw()
-
+        ts = TimeStamp(*(time.gmtime(t)[:5] + (t % 60,)))
+        return ts.raw()


=== ZODB4/ZEO/tests/ThreadTests.py 1.4 => 1.5 ===
--- ZODB4/ZEO/tests/ThreadTests.py:1.4	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/ThreadTests.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 """Compromising positions involving threads."""
 
@@ -145,3 +145,25 @@
         self.assertEqual(thread1.gotValueError, 1)
         self.assertEqual(thread2.gotValueError, 1)
         self.assertEqual(thread2.gotDisconnected, 1)
+
+    # Run a bunch of threads doing small and large stores in parallel
+    def checkMTStores(self):
+        threads = []
+        for i in range(5):
+            t = threading.Thread(target=self.mtstorehelper)
+            threads.append(t)
+            t.start()
+        for t in threads:
+            t.join(30)
+        for i in threads:
+            self.failUnless(not t.isAlive())
+
+    # Helper for checkMTStores
+    def mtstorehelper(self):
+        name = threading.currentThread().getName()
+        objs = []
+        for i in range(10):
+            objs.append(MinPO("X" * 200000))
+            objs.append(MinPO("X"))
+        for obj in objs:
+            self._dostore(data=obj)


=== ZODB4/ZEO/tests/__init__.py 1.3 => 1.4 ===
--- ZODB4/ZEO/tests/__init__.py:1.3	Fri Mar 15 00:11:54 2002
+++ ZODB4/ZEO/tests/__init__.py	Fri Nov 22 16:24:53 2002
@@ -2,12 +2,12 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################


=== ZODB4/ZEO/tests/forker.py 1.16 => 1.17 ===
--- ZODB4/ZEO/tests/forker.py:1.16	Tue Jun 11 09:43:06 2002
+++ ZODB4/ZEO/tests/forker.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 """Library for forking storage server and connecting client storage"""
 
@@ -50,7 +50,7 @@
 
 if os.name == "nt":
 
-    def start_zeo_server(storage_name, args, addr=None):
+    def start_zeo_server(storage_name, args, addr=None, ro_svr=0):
         """Start a ZEO server in a separate process.
 
         Returns the ZEO port, the test server port, and the pid.
@@ -63,7 +63,11 @@
         script = ZEO.tests.winserver.__file__
         if script.endswith('.pyc'):
             script = script[:-1]
-        args = (sys.executable, script, str(port), storage_name) + args
+        if ro_svr:
+            prefix = (sys.executable, script, "-r")
+        else:
+            prefix = (sys.executable, script)
+        args = prefix + (str(port), storage_name) + args
         d = os.environ.copy()
         d['PYTHONPATH'] = os.pathsep.join(sys.path)
         pid = os.spawnve(os.P_NOWAIT, sys.executable, args, d)
@@ -103,21 +107,23 @@
             except os.error:
                 pass
 
-    def start_zeo_server(storage_name, args, addr):
+    def start_zeo_server(storage_name, args, addr, ro_svr=0):
         assert isinstance(args, types.TupleType)
         rd, wr = os.pipe()
         pid = os.fork()
         if pid == 0:
+            asyncore.socket_map.clear() # Don't service the parent's sockets
             import ZEO.zrpc.log
-            reload(ZEO.zrpc.log)
+            reload(ZEO.zrpc.log) # Don't share the logging file object
             try:
                 if PROFILE:
                     p = hotshot.Profile("stats.s.%d" % os.getpid())
-                    p.runctx("run_server(storage, addr, rd, wr)",
-                             globals(), locals())
+                    p.runctx(
+                        "run_server(addr, rd, wr, storage_name, args, ro_svr)",
+                        globals(), locals())
                     p.close()
                 else:
-                    run_server(addr, rd, wr, storage_name, args)
+                    run_server(addr, rd, wr, storage_name, args, ro_svr)
             except:
                 print "Exception in ZEO server process"
                 traceback.print_exc()
@@ -132,14 +138,14 @@
         klass = getattr(mod, name)
         return klass(*args)
 
-    def run_server(addr, rd, wr, storage_name, args):
+    def run_server(addr, rd, wr, storage_name, args, ro_svr):
         # in the child, run the storage server
         global server
         os.close(wr)
         ZEOServerExit(rd)
         import ZEO.StorageServer, ZEO.zrpc.server
         storage = load_storage(storage_name, args)
-        server = ZEO.StorageServer.StorageServer(addr, {'1':storage})
+        server = ZEO.StorageServer.StorageServer(addr, {'1':storage}, ro_svr)
         ZEO.zrpc.server.loop()
         storage.close()
         if isinstance(addr, types.StringType):


=== ZODB4/ZEO/tests/multi.py 1.9 => 1.10 ===
--- ZODB4/ZEO/tests/multi.py:1.9	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/multi.py	Fri Nov 22 16:24:53 2002
@@ -2,22 +2,22 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 """A multi-client test of the ZEO storage server"""
+# XXX This code is currently broken.
 
 import ZODB, ZODB.DB, ZODB.FileStorage, ZODB.POSException
 import Persistence
 import PersistentMapping
 from ZEO.tests import forker
-from Transaction import get_transaction
 
 import asyncore
 import os


=== ZODB4/ZEO/tests/speed.py 1.8 => 1.9 ===
--- ZODB4/ZEO/tests/speed.py:1.8	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/speed.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 usage="""Test speed of a ZODB storage
 
@@ -47,7 +47,6 @@
 
 import ZODB, ZODB.FileStorage
 import Persistence
-from Transaction import get_transaction
 import ZEO.ClientStorage, ZEO.StorageServer
 from ZEO.tests import forker
 from ZODB.POSException import ConflictError
@@ -160,8 +159,8 @@
         s = s.Storage
         server = None
     else:
-        fs = ZODB.FileStorage.FileStorage(fs_name, create=1)
-        s, server, pid = forker.start_zeo(fs, domain=domain)
+        s, server, pid = forker.start_zeo("FileStorage",
+                                          (fs_name, 1), domain=domain)
 
     data=open(data).read()
     db=ZODB.DB(s,


=== ZODB4/ZEO/tests/stress.py 1.7 => 1.8 ===
--- ZODB4/ZEO/tests/stress.py:1.7	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/stress.py	Fri Nov 22 16:24:53 2002
@@ -2,20 +2,22 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 """A ZEO client-server stress test to look for leaks.
 
 The stress test should run in an infinite loop and should involve
 multiple connections.
 """
+# XXX This code is currently broken.
+
 from __future__ import nested_scopes
 
 import ZODB
@@ -24,7 +26,6 @@
 from ZEO.tests import forker
 from ZODB.tests import MinPO
 import zLOG
-from Transaction import get_transaction
 
 import os
 import random


=== ZODB4/ZEO/tests/testStart.py 1.4 => 1.5 ===
--- ZODB4/ZEO/tests/testStart.py:1.4	Tue Aug  6 10:59:23 2002
+++ ZODB4/ZEO/tests/testStart.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 
 import os
@@ -18,32 +18,45 @@
 import tempfile
 import time
 import unittest
+import errno
 
 import ZEO.start
 from ZEO.ClientStorage import ClientStorage
 from ZEO.util import Environment
 
-class StartTests(unittest.TestCase):
+try:
+    from ZODB.tests.StorageTestBase import removefs
+except ImportError:
+    # for compatibility with Zope 2.5 &c.
+    import errno
+
+    def removefs(base):
+        """Remove all files created by FileStorage with path base."""
+        for ext in '', '.old', '.tmp', '.lock', '.index', '.pack':
+            path = base + ext
+            try:
+                os.remove(path)
+            except os.error, err:
+                if err[0] != errno.ENOENT:
+                    raise
 
-    cmd = "%s %s" % (sys.executable, ZEO.start.__file__)
-    if cmd[-1] == "c":
-        cmd = cmd[:-1]
+
+class StartTests(unittest.TestCase):
 
     def setUp(self):
+        startfile = ZEO.start.__file__
+        if startfile[-1] == 'c':
+            startfile = startfile[:-1]
+        self.env = Environment(startfile)
+        self.cmd = '%s %s' % (sys.executable, startfile)
         self.pids = {}
-        self.env = Environment(self.cmd)
 
     def tearDown(self):
         try:
             self.stop_server()
             self.shutdown()
         finally:
-            for ext in "", ".index", ".tmp", ".lock", ".old":
-                f = "Data.fs" + ext
-                try:
-                    os.remove(f)
-                except os.error:
-                    pass
+            removefs("Data.fs")
             try:
                 os.remove(self.env.zeo_pid)
             except os.error:
@@ -52,14 +65,11 @@
     def getpids(self):
         if not os.path.exists(self.env.zeo_pid):
             # If there's no pid file, assume the server isn't running
-            return None, None
+            return []
         return map(int, open(self.env.zeo_pid).read().split())
 
     def stop_server(self):
-        ppid, pid = self.getpids()
-        if ppid is None:
-            return
-        self.kill(pids=[pid])
+        self.kill(pids=self.getpids())
 
     def kill(self, sig=signal.SIGTERM, pids=None):
         if pids is None:
@@ -128,10 +138,10 @@
         cs = ClientStorage(('', port), wait=wait)
         cs.close()
 
-    def testNoPort(self):
+    def testErrNoPort(self):
         outp = self.system("-s")
         self.assert_(outp.find("No port specified") != -1)
-        
+
     def testStart(self):
         port = 9090
         outp = self.fork("-s", "-p", str(port))
@@ -141,17 +151,25 @@
         port = 9090
         logfile1 = tempfile.mktemp(suffix="log")
         logfile2 = tempfile.mktemp(suffix="log")
+        os.environ["STUPID_LOG_FILE"] = logfile1
         os.environ["EVENT_LOG_FILE"] = logfile1
 
         try:
             outp = self.fork("-s", "-p", str(port))
             self.connect(port=port)
-            buf1 = open(logfile1).read()
+            buf1 = None
+            for i in range(10):
+                try:
+                    buf1 = open(logfile1).read()
+                except IOError, e:
+                    if e.errno != errno.ENOENT:
+                        raise
+                    time.sleep(1)
+                else:
+                    break
             self.assert_(buf1)
             os.rename(logfile1, logfile2)
-            ppid, pid = self.getpids()
-    ##        os.kill(ppid, signal.SIGHUP)
-            os.kill(pid, signal.SIGHUP)
+            self.kill(signal.SIGUSR2, pids=self.getpids())
             self.connect(port=port)
             buf2 = open(logfile1).read()
             self.assert_(buf2)
@@ -165,11 +183,15 @@
                 os.unlink(logfile2)
             except os.error:
                 pass
-        
+
 def test_suite():
+
+    # shutup warnings about mktemp
+    import warnings
+    warnings.filterwarnings("ignore", "mktemp")
+
     if os.name == "posix":
         return unittest.makeSuite(StartTests)
     else:
         # Don't even bother with these tests on Windows
         return None
-


=== ZODB4/ZEO/tests/testTransactionBuffer.py 1.4 => 1.5 ===
--- ZODB4/ZEO/tests/testTransactionBuffer.py:1.4	Tue Jun 11 09:43:06 2002
+++ ZODB4/ZEO/tests/testTransactionBuffer.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 import random
 import unittest


=== ZODB4/ZEO/tests/testZEO.py 1.26 => 1.27 === (441/541 lines abridged)
--- ZODB4/ZEO/tests/testZEO.py:1.26	Mon Aug 12 18:59:05 2002
+++ ZODB4/ZEO/tests/testZEO.py	Fri Nov 22 16:24:53 2002
@@ -2,122 +2,82 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
-"""Test suite for ZEO based on ZODB.tests"""
+"""Test suite for ZEO based on ZODB.tests."""
 
-import asyncore
+# System imports
 import os
-import random
-import select
-import socket
 import sys
-import tempfile
-import thread
 import time
-import types
+import socket
+import asyncore
+import tempfile
 import unittest
 
-import ZEO.ClientStorage, ZEO.StorageServer
-import ThreadedAsync, ZEO.trigger
-from ZODB.FileStorage import FileStorage
-from ZODB.ZTransaction import Transaction
-from ZODB.tests.StorageTestBase import zodb_pickle, MinPO
+# Zope/ZODB3 imports
 import zLOG
-from Transaction import get_transaction
 
-from ZEO.tests import forker, Cache, CommitLockTests, ThreadTests
-from ZEO.smac import Disconnected
+# ZODB test support
+import ZODB
+from ZODB.tests.MinPO import MinPO

[-=- -=- -=- 441 lines omitted -=- -=- -=-]

-        self._pids.append(pid)
-        self._servers.append(server)
-
-    def shutdownServer(self, index=0):
-        if self.running:
-            self.running = 0
-            self._servers[index].close()
-            try:
-                os.waitpid(self._pids[index], 0)
-            except os.error:
-                pass
-
-class WindowsConnectionTests(ConnectionTests):
-    
-    def _startServer(self, create=1, index=0):
-        path = "%s.%d" % (self.file, index)
-        addr = self.addr[index]
-        _addr, test_addr, test_pid = forker.start_zeo_server('FileStorage',
-                                                 (path, str(create)), addr)
-        self._pids.append(test_pid)
-        self._servers.append(test_addr)
-
-    def shutdownServer(self, index=0):
-        if self.running:
-            self.running = 0
-            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            s.connect(self._servers[index])
-            s.close()
-            # XXX waitpid() isn't available until Python 2.3
-            time.sleep(0.5)
+        removefs(self.__fs_base)
 
 if os.name == "posix":
-    test_classes = ZEOFileStorageTests, UnixConnectionTests
+    test_classes = [UnixTests]
 elif os.name == "nt":
-    test_classes = WindowsZEOFileStorageTests, WindowsConnectionTests
+    test_classes = [WindowsTests]
 else:
     raise RuntimeError, "unsupported os: %s" % os.name
 
 def test_suite():
+
+    # shutup warnings about mktemp
+    import warnings
+    warnings.filterwarnings("ignore", "mktemp")
+
     suite = unittest.TestSuite()
     for klass in test_classes:
         sub = unittest.makeSuite(klass, 'check')


=== ZODB4/ZEO/tests/winserver.py 1.4 => 1.5 ===
--- ZODB4/ZEO/tests/winserver.py:1.4	Fri Mar 15 00:11:54 2002
+++ ZODB4/ZEO/tests/winserver.py	Fri Nov 22 16:24:53 2002
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2001, 2002 Zope Corporation 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
-# 
+#
 ##############################################################################
 """Helper file used to launch ZEO server for Windows tests"""
 
@@ -50,16 +50,33 @@
     mod = getattr(package, name)
     return getattr(mod, name)
 
-def main(port, storage_name, args):
+def main(args):
+    ro_svr = 0
+    if args[0] == "-r":
+        ro_svr = 1
+        del args[0]
+    port, storage_name, rawargs = args[0], args[1], args[2:]
     klass = load_storage_class(storage_name)
+    args = []
+    for arg in rawargs:
+        if arg.startswith('='):
+            arg = eval(arg[1:], {'__builtins__': {}})
+        args.append(arg)
     storage = klass(*args)
     zeo_port = int(port)
     test_port = zeo_port + 1
     t = ZEOTestServer(('', test_port), storage)
-    serv = ZEO.StorageServer.StorageServer(('', zeo_port), {'1': storage})
+    addr = ('', zeo_port)
+    serv = ZEO.StorageServer.StorageServer(addr, {'1': storage}, ro_svr)
     asyncore.loop()
+    # XXX The code below is evil because it can cause deadlocks in zrpc.
+    # (To fix it, calling ThreadedAsync._start_loop() might help.)
+##    import zLOG
+##    label = "winserver:%d" % os.getpid()
+##    while asyncore.socket_map:
+##        zLOG.LOG(label, zLOG.DEBUG, "map: %r" % asyncore.socket_map)
+##        asyncore.poll(30.0)
 
 if __name__ == "__main__":
     import sys
-
-    main(sys.argv[1], sys.argv[2], sys.argv[3:])
+    main(sys.argv[1:])