Commit 8705602c authored by Jim Fulton's avatar Jim Fulton

Python 2.7 support!!!

And holy crap, uncommented some tests that I'd commented early while
working on asyncio and forgot to uncomment before checking in.
parent 48defe9f
...@@ -2,6 +2,8 @@ language: python ...@@ -2,6 +2,8 @@ language: python
sudo: false sudo: false
matrix: matrix:
include: include:
- os: linux
python: 2.7
- os: linux - os: linux
python: 3.4 python: 3.4
- os: linux - os: linux
......
...@@ -21,14 +21,31 @@ if sys.version_info < (2, 7): ...@@ -21,14 +21,31 @@ if sys.version_info < (2, 7):
print("This version of ZEO requires Python 2.7 or higher") print("This version of ZEO requires Python 2.7 or higher")
sys.exit(0) sys.exit(0)
if (3, 0) < sys.version_info < (3, 3): if (3, 0) < sys.version_info < (3, 4):
print("This version of ZEO requires Python 3.3 or higher") print("This version of ZEO requires Python 3.4 or higher")
sys.exit(0) sys.exit(0)
install_requires = [
'ZODB >= 5.0.0a1',
'six',
'transaction >= 1.6.0',
'persistent >= 4.1.0',
'zc.lockfile',
'ZConfig',
'zdaemon',
'zope.interface',
]
tests_require = ['zope.testing', 'manuel', 'random2', 'mock']
if sys.version_info[:2] < (3, ):
install_requires.extend(('futures', 'trollius'))
classifiers = """\ classifiers = """\
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: Zope Public License License :: OSI Approved :: Zope Public License
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.5
...@@ -92,8 +109,6 @@ def alltests(): ...@@ -92,8 +109,6 @@ def alltests():
_unittests_only(suite, mod.test_suite()) _unittests_only(suite, mod.test_suite())
return suite return suite
tests_require = ['zope.testing', 'manuel', 'random2', 'mock']
long_description = ( long_description = (
open('README.rst').read() open('README.rst').read()
+ '\n' + + '\n' +
...@@ -114,16 +129,7 @@ setup(name="ZEO", ...@@ -114,16 +129,7 @@ setup(name="ZEO",
test_suite="__main__.alltests", # to support "setup.py test" test_suite="__main__.alltests", # to support "setup.py test"
tests_require = tests_require, tests_require = tests_require,
extras_require = dict(test=tests_require), extras_require = dict(test=tests_require),
install_requires = [ install_requires = install_requires,
'ZODB >= 5.0.0a1',
'six',
'transaction >= 1.6.0',
'persistent >= 4.1.0',
'zc.lockfile',
'ZConfig',
'zdaemon',
'zope.interface',
],
zip_safe = False, zip_safe = False,
entry_points = """ entry_points = """
[console_scripts] [console_scripts]
......
...@@ -19,7 +19,6 @@ file storage or Berkeley storage. ...@@ -19,7 +19,6 @@ file storage or Berkeley storage.
TODO: Need some basic access control-- a declaration of the methods TODO: Need some basic access control-- a declaration of the methods
exported for invocation by the server. exported for invocation by the server.
""" """
import asyncio
import codecs import codecs
import itertools import itertools
import logging import logging
......
from struct import unpack from .._compat import PY3
import asyncio
if PY3:
import asyncio
else:
import trollius as asyncio
import logging import logging
from struct import unpack
from .marshal import encoder from .marshal import encoder
......
from .._compat import PY3
if PY3:
import asyncio
else:
import trollius as asyncio
from ZEO.Exceptions import ClientDisconnected from ZEO.Exceptions import ClientDisconnected
from ZODB.ConflictResolution import ResolvedSerial from ZODB.ConflictResolution import ResolvedSerial
import asyncio
import concurrent.futures import concurrent.futures
import logging import logging
import random import random
...@@ -251,7 +257,7 @@ class Protocol(base.Protocol): ...@@ -251,7 +257,7 @@ class Protocol(base.Protocol):
self.heartbeat_handle = self.loop.call_later( self.heartbeat_handle = self.loop.call_later(
self.heartbeat_interval, self.heartbeat) self.heartbeat_interval, self.heartbeat)
class Client: class Client(object):
"""asyncio low-level ZEO client interface """asyncio low-level ZEO client interface
""" """
...@@ -589,7 +595,7 @@ class Client: ...@@ -589,7 +595,7 @@ class Client:
else: else:
return protocol.read_only return protocol.read_only
class ClientRunner: class ClientRunner(object):
def set_options(self, addrs, wrapper, cache, storage_key, read_only, def set_options(self, addrs, wrapper, cache, storage_key, read_only,
timeout=30, disconnect_poll=1, timeout=30, disconnect_poll=1,
...@@ -608,7 +614,9 @@ class ClientRunner: ...@@ -608,7 +614,9 @@ class ClientRunner:
from concurrent.futures import Future from concurrent.futures import Future
call_soon_threadsafe = loop.call_soon_threadsafe call_soon_threadsafe = loop.call_soon_threadsafe
def call(meth, *args, timeout=None): def call(meth, *args, **kw):
timeout = kw.pop('timeout', None)
assert not kw
result = Future() result = Future()
call_soon_threadsafe(meth, result, *args) call_soon_threadsafe(meth, result, *args)
return self.wait_for_result(result, timeout) return self.wait_for_result(result, timeout)
...@@ -624,8 +632,8 @@ class ClientRunner: ...@@ -624,8 +632,8 @@ class ClientRunner:
else: else:
raise raise
def call(self, method, *args, timeout=None): def call(self, method, *args, **kw):
return self.__call(self.call_threadsafe, method, args, timeout=timeout) return self.__call(self.call_threadsafe, method, args, **kw)
def call_future(self, method, *args): def call_future(self, method, *args):
# for tests # for tests
...@@ -702,8 +710,8 @@ class ClientThread(ClientRunner): ...@@ -702,8 +710,8 @@ class ClientThread(ClientRunner):
self.thread = threading.Thread( self.thread = threading.Thread(
target=self.run, target=self.run,
name="%s zeo client networking thread" % client.__name__, name="%s zeo client networking thread" % client.__name__,
daemon=True,
) )
self.thread.setDaemon(True)
self.started = threading.Event() self.started = threading.Event()
self.thread.start() self.thread.start()
self.started.wait() self.started.wait()
...@@ -741,13 +749,13 @@ class ClientThread(ClientRunner): ...@@ -741,13 +749,13 @@ class ClientThread(ClientRunner):
def close(self): def close(self):
if not self.closed: if not self.closed:
self.closed = True self.closed = True
super().close() super(ClientThread, self).close()
self.loop.call_soon_threadsafe(self.loop.stop) self.loop.call_soon_threadsafe(self.loop.stop)
self.thread.join(9) self.thread.join(9)
if self.exception: if self.exception:
raise self.exception raise self.exception
class Promise: class Promise(object):
"""Lightweight future with a partial promise API. """Lightweight future with a partial promise API.
These are lighweight because they call callbacks synchronously These are lighweight because they call callbacks synchronously
......
...@@ -41,8 +41,13 @@ with: ...@@ -41,8 +41,13 @@ with:
in ZEO.StorageServer. in ZEO.StorageServer.
""" """
from .._compat import PY3
if PY3:
import asyncio
else:
import trollius as asyncio
import asyncio
import asyncore import asyncore
import socket import socket
import threading import threading
......
import asyncio from .._compat import PY3
if PY3:
import asyncio
else:
import trollius as asyncio
import json import json
import logging import logging
import os import os
...@@ -62,7 +68,8 @@ class ServerProtocol(base.Protocol): ...@@ -62,7 +68,8 @@ class ServerProtocol(base.Protocol):
self.close() self.close()
else: else:
if protocol_version in self.protocols: if protocol_version in self.protocols:
logger.info("received handshake %r" % protocol_version) logger.info("received handshake %r" %
str(protocol_version.decode('ascii')))
self.protocol_version = protocol_version self.protocol_version = protocol_version
self.zeo_storage.notify_connected(self) self.zeo_storage.notify_connected(self)
else: else:
...@@ -142,7 +149,7 @@ def new_connection(loop, addr, socket, zeo_storage): ...@@ -142,7 +149,7 @@ def new_connection(loop, addr, socket, zeo_storage):
cr = loop.create_connection((lambda : protocol), sock=socket) cr = loop.create_connection((lambda : protocol), sock=socket)
asyncio.async(cr, loop=loop) asyncio.async(cr, loop=loop)
class Delay: class Delay(object):
"""Used to delay response to client for synchronous calls. """Used to delay response to client for synchronous calls.
When a synchronous call is made and the original handler returns When a synchronous call is made and the original handler returns
...@@ -203,7 +210,7 @@ class MTDelay(Delay): ...@@ -203,7 +210,7 @@ class MTDelay(Delay):
self.protocol.call_soon_threadsafe(Delay.error, self, exc_info) self.protocol.call_soon_threadsafe(Delay.error, self, exc_info)
class Acceptor: class Acceptor(object):
def __init__(self, storage_server, addr, ssl): def __init__(self, storage_server, addr, ssl):
self.storage_server = storage_server self.storage_server = storage_server
......
import asyncio from .._compat import PY3
if PY3:
import asyncio
else:
import trollius as asyncio
try:
ConnectionRefusedError
except NameError:
class ConnectionRefusedError(OSError):
pass
import pprint import pprint
class Loop: class Loop(object):
protocol = transport = None protocol = transport = None
...@@ -75,14 +87,14 @@ class Loop: ...@@ -75,14 +87,14 @@ class Loop:
def stop(self): def stop(self):
self.stopped = True self.stopped = True
class Handle: class Handle(object):
cancelled = False cancelled = False
def cancel(self): def cancel(self):
self.cancelled = True self.cancelled = True
class Transport: class Transport(object):
capacity = 1 << 64 capacity = 1 << 64
paused = False paused = False
...@@ -127,7 +139,7 @@ class Transport: ...@@ -127,7 +139,7 @@ class Transport:
def get_extra_info(self, name): def get_extra_info(self, name):
return self.extra[name] return self.extra[name]
class AsyncRPC: class AsyncRPC(object):
"""Adapt an asyncio API to an RPC to help hysterical tests """Adapt an asyncio API to an RPC to help hysterical tests
""" """
def __init__(self, api): def __init__(self, api):
...@@ -136,7 +148,7 @@ class AsyncRPC: ...@@ -136,7 +148,7 @@ class AsyncRPC:
def __getattr__(self, name): def __getattr__(self, name):
return lambda *a, **kw: self.api.call(name, *a, **kw) return lambda *a, **kw: self.api.call(name, *a, **kw)
class ClientRunner: class ClientRunner(object):
def __init__(self, addr, client, cache, storage, read_only, timeout, def __init__(self, addr, client, cache, storage, read_only, timeout,
**kw): **kw):
...@@ -152,7 +164,7 @@ class ClientRunner: ...@@ -152,7 +164,7 @@ class ClientRunner:
def start(self, wait=True): def start(self, wait=True):
pass pass
def call(self, method, *args, timeout=None): def call(self, method, *args, **kw):
return getattr(self, method)(*args) return getattr(self, method)(*args)
async = async_iter = call async = async_iter = call
......
from .._compat import PY3
if PY3:
import asyncio
else:
import trollius as asyncio
from zope.testing import setupstack from zope.testing import setupstack
from concurrent.futures import Future from concurrent.futures import Future
from unittest import mock import mock
from ZODB.POSException import ReadOnlyError from ZODB.POSException import ReadOnlyError
import asyncio
import collections import collections
import logging import logging
import pdb import pdb
...@@ -27,7 +33,8 @@ class Base(object): ...@@ -27,7 +33,8 @@ class Base(object):
def unsized(self, data, unpickle=False): def unsized(self, data, unpickle=False):
result = [] result = []
while data: while data:
size, message, *data = data size, message = data[:2]
data = data[2:]
self.assertEqual(struct.unpack(">I", size)[0], len(message)) self.assertEqual(struct.unpack(">I", size)[0], len(message))
if unpickle: if unpickle:
message = decode(message) message = decode(message)
...@@ -622,7 +629,7 @@ class ClientTests(Base, setupstack.TestCase, ClientRunner): ...@@ -622,7 +629,7 @@ class ClientTests(Base, setupstack.TestCase, ClientRunner):
def test_ClientDisconnected_on_call_timeout(self): def test_ClientDisconnected_on_call_timeout(self):
wrapper, cache, loop, client, protocol, transport = self.start() wrapper, cache, loop, client, protocol, transport = self.start()
self.wait_for_result = super().wait_for_result self.wait_for_result = super(ClientTests, self).wait_for_result
self.assertRaises(ClientDisconnected, self.call, 'foo') self.assertRaises(ClientDisconnected, self.call, 'foo')
client.ready = False client.ready = False
self.assertRaises(ClientDisconnected, self.call, 'foo') self.assertRaises(ClientDisconnected, self.call, 'foo')
...@@ -686,7 +693,7 @@ class ClientTests(Base, setupstack.TestCase, ClientRunner): ...@@ -686,7 +693,7 @@ class ClientTests(Base, setupstack.TestCase, ClientRunner):
self.assertTrue(handle.cancelled) self.assertTrue(handle.cancelled)
class MemoryCache: class MemoryCache(object):
def __init__(self): def __init__(self):
# { oid -> [(start, end, data)] } # { oid -> [(start, end, data)] }
...@@ -837,7 +844,7 @@ def response(*data): ...@@ -837,7 +844,7 @@ def response(*data):
def sized(message): def sized(message):
return struct.pack(">I", len(message)) + message return struct.pack(">I", len(message)) + message
class Logging: class Logging(object):
def __init__(self, level=logging.ERROR): def __init__(self, level=logging.ERROR):
self.level = level self.level = level
......
...@@ -24,8 +24,8 @@ A current client should be able to connect to a old server: ...@@ -24,8 +24,8 @@ A current client should be able to connect to a old server:
>>> import ZEO, ZODB.blob, transaction >>> import ZEO, ZODB.blob, transaction
>>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> db = ZEO.DB(addr, client='client', blob_dir='blobs')
>>> wait_connected(db.storage) >>> wait_connected(db.storage)
>>> db.storage.protocol_version >>> str(db.storage.protocol_version.decode('ascii'))
b'Z4' 'Z4'
>>> conn = db.open() >>> conn = db.open()
>>> conn.root().x = 0 >>> conn.root().x = 0
...@@ -105,8 +105,8 @@ Note that we'll have to pull some hijinks: ...@@ -105,8 +105,8 @@ Note that we'll have to pull some hijinks:
>>> ZEO.asyncio.client.Protocol.protocols = [b'Z4'] >>> ZEO.asyncio.client.Protocol.protocols = [b'Z4']
>>> db = ZEO.DB(addr, client='client', blob_dir='blobs') >>> db = ZEO.DB(addr, client='client', blob_dir='blobs')
>>> db.storage.protocol_version >>> str(db.storage.protocol_version.decode('ascii'))
b'Z4' 'Z4'
>>> wait_connected(db.storage) >>> wait_connected(db.storage)
>>> conn = db.open() >>> conn = db.open()
>>> conn.root().x = 0 >>> conn.root().x = 0
......
...@@ -471,7 +471,7 @@ class ZRPCConnectionTests(ZEO.tests.ConnectionTests.CommonSetupTearDown): ...@@ -471,7 +471,7 @@ class ZRPCConnectionTests(ZEO.tests.ConnectionTests.CommonSetupTearDown):
handler = InstalledHandler('ZEO.asyncio.client') handler = InstalledHandler('ZEO.asyncio.client')
import ZODB.POSException import ZODB.POSException
self.assertRaises(TypeError, self._storage.history, z64, None) self.assertRaises(TypeError, self._storage.history, z64, None)
self.assertTrue(" from server: builtins.TypeError" in str(handler)) self.assertTrue(re.search(" from server: .*TypeError", str(handler)))
# POSKeyErrors and ConflictErrors aren't logged: # POSKeyErrors and ConflictErrors aren't logged:
handler.clear() handler.clear()
...@@ -684,15 +684,7 @@ class BlobAdaptedFileStorageTests(FullGenericTests, CommonBlobTests): ...@@ -684,15 +684,7 @@ class BlobAdaptedFileStorageTests(FullGenericTests, CommonBlobTests):
check_data(server_filename) check_data(server_filename)
# If we remove it from the cache and call loadBlob, it should # If we remove it from the cache and call loadBlob, it should
# come back. We can do this in many threads. We'll instrument # come back. We can do this in many threads.
# the method that is used to request data from teh server to
# verify that it is only called once.
sendBlob_org = ZEO.ServerStub.StorageServer.sendBlob
calls = []
def sendBlob(self, oid, serial):
calls.append((oid, serial))
sendBlob_org(self, oid, serial)
ZODB.blob.remove_committed(filename) ZODB.blob.remove_committed(filename)
returns = [] returns = []
...@@ -1510,7 +1502,7 @@ def can_use_empty_string_for_local_host_on_client(): ...@@ -1510,7 +1502,7 @@ def can_use_empty_string_for_local_host_on_client():
""" """
slow_test_classes = [ slow_test_classes = [
#BlobAdaptedFileStorageTests, BlobWritableCacheTests, BlobAdaptedFileStorageTests, BlobWritableCacheTests,
MappingStorageTests, DemoStorageTests, MappingStorageTests, DemoStorageTests,
FileStorageTests, FileStorageSSLTests, FileStorageTests, FileStorageSSLTests,
FileStorageHexTests, FileStorageClientHexTests, FileStorageHexTests, FileStorageClientHexTests,
......
...@@ -218,13 +218,13 @@ We start a transaction and vote, this leads to getting the lock. ...@@ -218,13 +218,13 @@ We start a transaction and vote, this leads to getting the lock.
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
>>> tid1 = start_trans(zs1) >>> tid1 = start_trans(zs1)
>>> zs1.vote(tid1) # doctest: +ELLIPSIS >>> zs1.vote(tid1) # doctest: +ELLIPSIS
ZEO.StorageServer DEBUG ZEO.StorageServer DEBUG
(test-addr-1) ('1') lock: transactions waiting: 0 (test-addr-1) ('1') lock: transactions waiting: 0
ZEO.StorageServer BLATHER ZEO.StorageServer BLATHER
(test-addr-1) Preparing to commit transaction: 1 objects, 108 bytes (test-addr-1) Preparing to commit transaction: 1 objects, ... bytes
If another client tried to vote, it's lock request will be queued and If another client tried to vote, it's lock request will be queued and
a delay will be returned: a delay will be returned:
...@@ -233,7 +233,7 @@ a delay will be returned: ...@@ -233,7 +233,7 @@ a delay will be returned:
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
>>> tid2 = start_trans(zs2) >>> tid2 = start_trans(zs2)
>>> delay = zs2.vote(tid2) >>> delay = zs2.vote(tid2)
ZEO.StorageServer DEBUG ZEO.StorageServer DEBUG
...@@ -296,55 +296,55 @@ increased, so does the logging level: ...@@ -296,55 +296,55 @@ increased, so does the logging level:
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer DEBUG ZEO.StorageServer DEBUG
(test-addr-10) ('1') queue lock: transactions waiting: 2 (test-addr-10) ('1') queue lock: transactions waiting: 2
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer DEBUG ZEO.StorageServer DEBUG
(test-addr-11) ('1') queue lock: transactions waiting: 3 (test-addr-11) ('1') queue lock: transactions waiting: 3
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-12) ('1') queue lock: transactions waiting: 4 (test-addr-12) ('1') queue lock: transactions waiting: 4
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-13) ('1') queue lock: transactions waiting: 5 (test-addr-13) ('1') queue lock: transactions waiting: 5
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-14) ('1') queue lock: transactions waiting: 6 (test-addr-14) ('1') queue lock: transactions waiting: 6
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-15) ('1') queue lock: transactions waiting: 7 (test-addr-15) ('1') queue lock: transactions waiting: 7
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-16) ('1') queue lock: transactions waiting: 8 (test-addr-16) ('1') queue lock: transactions waiting: 8
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-17) ('1') queue lock: transactions waiting: 9 (test-addr-17) ('1') queue lock: transactions waiting: 9
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
ZEO.StorageServer CRITICAL ZEO.StorageServer CRITICAL
(test-addr-18) ('1') queue lock: transactions waiting: 10 (test-addr-18) ('1') queue lock: transactions waiting: 10
...@@ -475,7 +475,7 @@ ZEOStorage as closed and see if trying to get a lock cleans it up: ...@@ -475,7 +475,7 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
>>> tid1 = start_trans(zs1) >>> tid1 = start_trans(zs1)
>>> zs1.vote(tid1) # doctest: +ELLIPSIS >>> zs1.vote(tid1) # doctest: +ELLIPSIS
ZEO.StorageServer DEBUG ZEO.StorageServer DEBUG
...@@ -491,7 +491,7 @@ ZEOStorage as closed and see if trying to get a lock cleans it up: ...@@ -491,7 +491,7 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
Connected server protocol Connected server protocol
ZEO.asyncio.server INFO ZEO.asyncio.server INFO
received handshake b'Z5' received handshake 'Z5'
>>> tid2 = start_trans(zs2) >>> tid2 = start_trans(zs2)
>>> zs2.vote(tid2) # doctest: +ELLIPSIS >>> zs2.vote(tid2) # doctest: +ELLIPSIS
ZEO.StorageServer DEBUG ZEO.StorageServer DEBUG
......
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