Commit 71b173c0 authored by Barry Warsaw's avatar Barry Warsaw

Back porting fixes from zodb4 to specify a `keep' flag on the

storage's artifacts.  Some tests expect to reconnect in read-only mode
to a previously created storage.
parent 2177ef77
...@@ -42,15 +42,12 @@ class DummyDB: ...@@ -42,15 +42,12 @@ class DummyDB:
pass pass
class ConnectionTests(StorageTestBase): class CommonSetupTearDown(StorageTestBase):
"""Tests that explicitly manage the server process. """Common boilerplate"""
To test the cache or re-connection, these test cases explicit
start and stop a ZEO storage server.
"""
__super_setUp = StorageTestBase.setUp __super_setUp = StorageTestBase.setUp
__super_tearDown = StorageTestBase.tearDown __super_tearDown = StorageTestBase.tearDown
keep = 0
def setUp(self): def setUp(self):
"""Test setup for connection tests. """Test setup for connection tests.
...@@ -119,7 +116,8 @@ class ConnectionTests(StorageTestBase): ...@@ -119,7 +116,8 @@ class ConnectionTests(StorageTestBase):
(create, index, read_only, addr)) (create, index, read_only, addr))
path = "%s.%d" % (self.file, index) path = "%s.%d" % (self.file, index)
conf = self.getConfig(path, create, read_only) conf = self.getConfig(path, create, read_only)
zeoport, adminaddr, pid = forker.start_zeo_server(conf, addr, ro_svr) zeoport, adminaddr, pid = forker.start_zeo_server(
conf, addr, ro_svr, self.keep)
self._pids.append(pid) self._pids.append(pid)
self._servers.append(adminaddr) self._servers.append(adminaddr)
...@@ -151,6 +149,14 @@ class ConnectionTests(StorageTestBase): ...@@ -151,6 +149,14 @@ class ConnectionTests(StorageTestBase):
if now > giveup: if now > giveup:
self.fail("timed out waiting for storage to disconnect") self.fail("timed out waiting for storage to disconnect")
class ConnectionTests(CommonSetupTearDown):
"""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.
"""
def checkMultipleAddresses(self): def checkMultipleAddresses(self):
for i in range(4): for i in range(4):
self._newAddr() self._newAddr()
...@@ -189,21 +195,6 @@ class ConnectionTests(StorageTestBase): ...@@ -189,21 +195,6 @@ class ConnectionTests(StorageTestBase):
# Stores should fail here # Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore) self.assertRaises(ReadOnlyError, self._dostore)
def checkReadOnlyStorage(self):
# Open a read-only client to a read-only *storage*; stores fail
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Start a read-only server
self.startServer(create=0, index=0, read_only=1)
# Start a read-only client
self._storage = self.openClientStorage(read_only=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
def checkReadOnlyServer(self): def checkReadOnlyServer(self):
# Open a read-only client to a read-only *server*; stores fail # Open a read-only client to a read-only *server*; stores fail
...@@ -227,21 +218,6 @@ class ConnectionTests(StorageTestBase): ...@@ -227,21 +218,6 @@ class ConnectionTests(StorageTestBase):
# Stores should succeed here # Stores should succeed here
self._dostore() self._dostore()
def checkReadOnlyFallbackReadOnlyStorage(self):
# Open a fallback client to a read-only *storage*; stores fail
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Start a read-only server
self.startServer(create=0, index=0, read_only=1)
# Start a read-only-fallback client
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
def checkReadOnlyFallbackReadOnlyServer(self): def checkReadOnlyFallbackReadOnlyServer(self):
# Open a fallback client to a read-only *server*; stores fail # Open a fallback client to a read-only *server*; stores fail
...@@ -285,120 +261,6 @@ class ConnectionTests(StorageTestBase): ...@@ -285,120 +261,6 @@ class ConnectionTests(StorageTestBase):
# Stores should succeed here # Stores should succeed here
self._dostore() self._dostore()
def checkReconnectReadOnly(self):
# A read-only client reconnects from a read-write to a
# read-only server
# Start a client
self._storage = self.openClientStorage(read_only=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
# Shut down the server
self.shutdownServer()
self._servers = []
self._pids = []
# Poll until the client disconnects
self.pollDown()
# Stores should still fail
self.assertRaises(ReadOnlyError, self._dostore)
# Restart the server
self.startServer(create=0, read_only=1)
# Poll until the client connects
self.pollUp()
# Stores should still fail
self.assertRaises(ReadOnlyError, self._dostore)
def checkReconnectFallback(self):
# A fallback client reconnects from a read-write to a
# read-only server
# Start a client in fallback mode
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should succeed here
self._dostore()
# Shut down the server
self.shutdownServer()
self._servers = []
self._pids = []
# Poll until the client disconnects
self.pollDown()
# Stores should fail now
self.assertRaises(Disconnected, self._dostore)
# Restart the server
self.startServer(create=0, read_only=1)
# Poll until the client connects
self.pollUp()
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
def checkReconnectUpgrade(self):
# A fallback client reconnects from a read-only to a
# read-write server
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Start a read-only server
self.startServer(create=0, read_only=1)
# Start a client in fallback mode
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
# Shut down the server
self.shutdownServer()
self._servers = []
self._pids = []
# Poll until the client disconnects
self.pollDown()
# Stores should fail now
self.assertRaises(Disconnected, self._dostore)
# Restart the server, this time read-write
self.startServer(create=0)
# Poll until the client sconnects
self.pollUp()
# Stores should now succeed
self._dostore()
def checkReconnectSwitch(self):
# A fallback client initially connects to a read-only server,
# then discovers a read-write server and switches to that
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Allocate a second address (for the second server)
self._newAddr()
# Start a read-only server
self.startServer(create=0, index=0, read_only=1)
# Start a client in fallback mode
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
# Start a read-write server
self.startServer(index=1, read_only=0)
# After a while, stores should work
for i in range(300): # Try for 30 seconds
try:
self._dostore()
break
except (Disconnected, ReadOnlyError,
select.error, threading.ThreadError, socket.error):
time.sleep(0.1)
else:
self.fail("Couldn't store after starting a read-write server")
def checkDisconnectionError(self): def checkDisconnectionError(self):
# Make sure we get a Disconnected when we try to read an # Make sure we get a Disconnected when we try to read an
# object when we're not connected to a storage server and the # object when we're not connected to a storage server and the
...@@ -559,6 +421,155 @@ class ConnectionTests(StorageTestBase): ...@@ -559,6 +421,155 @@ class ConnectionTests(StorageTestBase):
for t in threads: for t in threads:
t.closeclients() t.closeclients()
class ReconnectionTests(CommonSetupTearDown):
keep = 1
def checkReadOnlyStorage(self):
# Open a read-only client to a read-only *storage*; stores fail
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Start a read-only server
self.startServer(create=0, index=0, read_only=1)
# Start a read-only client
self._storage = self.openClientStorage(read_only=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
def checkReadOnlyFallbackReadOnlyStorage(self):
# Open a fallback client to a read-only *storage*; stores fail
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Start a read-only server
self.startServer(create=0, index=0, read_only=1)
# Start a read-only-fallback client
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
def checkReconnectReadOnly(self):
# A read-only client reconnects from a read-write to a
# read-only server
# Start a client
self._storage = self.openClientStorage(read_only=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
# Shut down the server
self.shutdownServer()
self._servers = []
self._pids = []
# Poll until the client disconnects
self.pollDown()
# Stores should still fail
self.assertRaises(ReadOnlyError, self._dostore)
# Restart the server
self.startServer(create=0, read_only=1)
# Poll until the client connects
self.pollUp()
# Stores should still fail
self.assertRaises(ReadOnlyError, self._dostore)
def checkReconnectFallback(self):
# A fallback client reconnects from a read-write to a
# read-only server
# Start a client in fallback mode
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should succeed here
self._dostore()
# Shut down the server
self.shutdownServer()
self._servers = []
self._pids = []
# Poll until the client disconnects
self.pollDown()
# Stores should fail now
self.assertRaises(Disconnected, self._dostore)
# Restart the server
self.startServer(create=0, read_only=1)
# Poll until the client connects
self.pollUp()
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
def checkReconnectUpgrade(self):
# A fallback client reconnects from a read-only to a
# read-write server
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Start a read-only server
self.startServer(create=0, read_only=1)
# Start a client in fallback mode
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
# Shut down the server
self.shutdownServer()
self._servers = []
self._pids = []
# Poll until the client disconnects
self.pollDown()
# Stores should fail now
self.assertRaises(Disconnected, self._dostore)
# Restart the server, this time read-write
self.startServer(create=0)
# Poll until the client sconnects
self.pollUp()
# Stores should now succeed
self._dostore()
def checkReconnectSwitch(self):
# A fallback client initially connects to a read-only server,
# then discovers a read-write server and switches to that
# We don't want the read-write server created by setUp()
self.shutdownServer()
self._servers = []
self._pids = []
# Allocate a second address (for the second server)
self._newAddr()
# Start a read-only server
self.startServer(create=0, index=0, read_only=1)
# Start a client in fallback mode
self._storage = self.openClientStorage(read_only_fallback=1)
# Stores should fail here
self.assertRaises(ReadOnlyError, self._dostore)
# Start a read-write server
self.startServer(index=1, read_only=0)
# After a while, stores should work
for i in range(300): # Try for 30 seconds
try:
self._dostore()
break
except (Disconnected, ReadOnlyError,
select.error, threading.ThreadError, socket.error):
time.sleep(0.1)
else:
self.fail("Couldn't store after starting a read-write server")
class MSTThread(threading.Thread): class MSTThread(threading.Thread):
__super_init = threading.Thread.__init__ __super_init = threading.Thread.__init__
......
...@@ -17,10 +17,8 @@ import os ...@@ -17,10 +17,8 @@ import os
import sys import sys
import time import time
import errno import errno
import types
import random import random
import socket import socket
import asyncore
import tempfile import tempfile
import traceback import traceback
...@@ -31,6 +29,7 @@ PROFILE = 0 ...@@ -31,6 +29,7 @@ PROFILE = 0
if PROFILE: if PROFILE:
import hotshot import hotshot
def get_port(): def get_port():
"""Return a port that is not in use. """Return a port that is not in use.
...@@ -53,7 +52,7 @@ def get_port(): ...@@ -53,7 +52,7 @@ def get_port():
raise RuntimeError, "Can't find port" raise RuntimeError, "Can't find port"
def start_zeo_server(conf, addr=None, ro_svr=0): def start_zeo_server(conf, addr=None, ro_svr=0, keep=0):
"""Start a ZEO server in a separate process. """Start a ZEO server in a separate process.
Returns the ZEO port, the test server port, and the pid. Returns the ZEO port, the test server port, and the pid.
...@@ -76,6 +75,8 @@ def start_zeo_server(conf, addr=None, ro_svr=0): ...@@ -76,6 +75,8 @@ def start_zeo_server(conf, addr=None, ro_svr=0):
args = [sys.executable, script, '-C', tmpfile] args = [sys.executable, script, '-C', tmpfile]
if ro_svr: if ro_svr:
args.append('-r') args.append('-r')
if keep:
args.append('-k')
args.append(str(port)) args.append(str(port))
d = os.environ.copy() d = os.environ.copy()
d['PYTHONPATH'] = os.pathsep.join(sys.path) d['PYTHONPATH'] = os.pathsep.join(sys.path)
......
...@@ -20,12 +20,10 @@ platform-dependent scaffolding. ...@@ -20,12 +20,10 @@ platform-dependent scaffolding.
# System imports # System imports
import unittest import unittest
# Import the actual test class # Import the actual test class
from ZEO.tests.ConnectionTests import ConnectionTests from ZEO.tests import ConnectionTests
class FileStorageConnectionTests(ConnectionTests): class FileStorageConfig:
"""Add FileStorage-specific test."""
def getConfig(self, path, create, read_only): def getConfig(self, path, create, read_only):
return """\ return """\
<Storage> <Storage>
...@@ -38,9 +36,7 @@ class FileStorageConnectionTests(ConnectionTests): ...@@ -38,9 +36,7 @@ class FileStorageConnectionTests(ConnectionTests):
read_only and 'yes' or 'no') read_only and 'yes' or 'no')
class BDBConnectionTests(FileStorageConnectionTests): class BerkeleyStorageConfig:
"""Berkeley storage tests."""
def getConfig(self, path, create, read_only): def getConfig(self, path, create, read_only):
# Full always creates and doesn't have a read_only flag # Full always creates and doesn't have a read_only flag
return """\ return """\
...@@ -51,13 +47,42 @@ class BDBConnectionTests(FileStorageConnectionTests): ...@@ -51,13 +47,42 @@ class BDBConnectionTests(FileStorageConnectionTests):
</Storage>""" % (path, read_only) </Storage>""" % (path, read_only)
test_classes = [FileStorageConnectionTests] class FileStorageConnectionTests(
FileStorageConfig,
ConnectionTests.ConnectionTests
):
"""FileStorage-specific connection tests."""
class FileStorageReconnectionTests(
FileStorageConfig,
ConnectionTests.ReconnectionTests
):
"""FileStorage-specific re-connection tests."""
class BDBConnectionTests(
BerkeleyStorageConfig,
ConnectionTests.ConnectionTests
):
"""Berkeley storage connection tests."""
class BDBReconnectionTests(
BerkeleyStorageConfig,
ConnectionTests.ReconnectionTests
):
"""Berkeley storage re-connection tests."""
test_classes = [FileStorageConnectionTests, FileStorageReconnectionTests]
try: try:
from bsddb3Storage.Full import Full from bsddb3Storage.Full import Full
except ImportError: except ImportError:
pass pass
else: else:
test_classes.append(BDBConnectionTests) test_classes.append(BDBConnectionTests)
test_classes.append(BDBReconnectionTests)
def test_suite(): def test_suite():
......
...@@ -65,13 +65,14 @@ class ZEOTestServer(asyncore.dispatcher): ...@@ -65,13 +65,14 @@ class ZEOTestServer(asyncore.dispatcher):
""" """
__super_init = asyncore.dispatcher.__init__ __super_init = asyncore.dispatcher.__init__
def __init__(self, addr, storage): def __init__(self, addr, storage, keep):
self.__super_init() self.__super_init()
self.storage = storage self._storage = storage
self._keep = keep
# Count down to zero, the number of connects # Count down to zero, the number of connects
self.count = 1 self._count = 1
# For zLOG # For zLOG
self.label ='zeoserver:%d @ %s' % (os.getpid(), addr) self._label ='zeoserver:%d @ %s' % (os.getpid(), addr)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
# Some ZEO tests attempt a quick start of the server using the same # Some ZEO tests attempt a quick start of the server using the same
# port so we have to set the reuse flag. # port so we have to set the reuse flag.
...@@ -87,22 +88,23 @@ class ZEOTestServer(asyncore.dispatcher): ...@@ -87,22 +88,23 @@ class ZEOTestServer(asyncore.dispatcher):
self.log('bound and listening') self.log('bound and listening')
def log(self, msg, *args): def log(self, msg, *args):
log(self.label, msg, *args) log(self._label, msg, *args)
def handle_accept(self): def handle_accept(self):
sock, addr = self.accept() sock, addr = self.accept()
self.log('in handle_accept()') self.log('in handle_accept()')
# When we're done with everything, close the storage. Do not write # When we're done with everything, close the storage. Do not write
# the ack character until the storage is finished closing. # the ack character until the storage is finished closing.
if self.count <= 0: if self._count <= 0:
self.log('closing the storage') self.log('closing the storage')
self.storage.close() self._storage.close()
cleanup(self.storage) if not self._keep:
cleanup(self._storage)
self.log('exiting') self.log('exiting')
os._exit(0) os._exit(0)
self.log('continuing') self.log('continuing')
sock.send('X') sock.send('X')
self.count -= 1 self._count -= 1
def main(): def main():
...@@ -111,12 +113,15 @@ def main(): ...@@ -111,12 +113,15 @@ def main():
# We don't do much sanity checking of the arguments, since if we get it # We don't do much sanity checking of the arguments, since if we get it
# wrong, it's a bug in the test suite. # wrong, it's a bug in the test suite.
ro_svr = 0 ro_svr = 0
keep = 0
configfile = None configfile = None
# Parse the arguments and let getopt.error percolate # Parse the arguments and let getopt.error percolate
opts, args = getopt.getopt(sys.argv[1:], 'rC:') opts, args = getopt.getopt(sys.argv[1:], 'rkC:')
for opt, arg in opts: for opt, arg in opts:
if opt == '-r': if opt == '-r':
ro_svr = 1 ro_svr = 1
elif opt == '-k':
keep = 1
elif opt == '-C': elif opt == '-C':
configfile = arg configfile = arg
# Open the config file and let ZConfig parse the data there. Then remove # Open the config file and let ZConfig parse the data there. Then remove
...@@ -129,8 +134,8 @@ def main(): ...@@ -129,8 +134,8 @@ def main():
zeo_port = int(args[0]) zeo_port = int(args[0])
test_port = zeo_port + 1 test_port = zeo_port + 1
try: try:
log(label, 'creating the test server') log(label, 'creating the test server, ro: %s, keep: %s', ro_svr, keep)
t = ZEOTestServer(('', test_port), storage) t = ZEOTestServer(('', test_port), storage, keep)
except socket.error, e: except socket.error, e:
if e[0] <> errno.EADDRINUSE: raise if e[0] <> errno.EADDRINUSE: raise
log(label, 'addr in use, closing and exiting') log(label, 'addr in use, closing and exiting')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment