Commit cfec72a1 authored by Jason Madden's avatar Jason Madden

Add support for Jython. This required only relatively minor test changes.

parent c35f2528
...@@ -8,10 +8,12 @@ ...@@ -8,10 +8,12 @@
- Fix command-line parsing of --verbose and --verify arguments. - Fix command-line parsing of --verbose and --verify arguments.
(The short versions -v and -V were parsed correctly.) (The short versions -v and -V were parsed correctly.)
- Add support for PyPy, and fix the methods in ``ZODB.serialize`` that - Add support for PyPy and Jython 2.7.
find object references under Python 2.7 (used in scripts like
``referrers``, ``netspace``, and ``fsrecover`` among others). This - Fix the methods in ``ZODB.serialize`` that find object references
requires the addition of the ``zodbpickle`` dependency. under Python 2.7 (used in scripts like ``referrers``, ``netspace``,
and ``fsrecover`` among others). This requires the addition of the
``zodbpickle`` dependency.
4.1.0 (2015-01-11) 4.1.0 (2015-01-11)
================== ==================
......
...@@ -60,6 +60,7 @@ Programming Language :: Python :: 3.3 ...@@ -60,6 +60,7 @@ Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.4
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy
Programming Language :: Python :: Implementation :: Jython
Topic :: Database Topic :: Database
Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Libraries :: Python Modules
Operating System :: Microsoft :: Windows Operating System :: Microsoft :: Windows
......
...@@ -14,7 +14,10 @@ existing, base, storage without updating the storage. ...@@ -14,7 +14,10 @@ existing, base, storage without updating the storage.
... return now ... return now
>>> import time >>> import time
>>> real_time_time = time.time >>> real_time_time = time.time
>>> time.time = faux_time_time >>> if isinstance(time,type):
... time.time = staticmethod(faux_time_time) # Jython
... else:
... time.time = faux_time_time
To see how this works, we'll start by creating a base storage and To see how this works, we'll start by creating a base storage and
puting an object (in addition to the root object) in it: puting an object (in addition to the root object) in it:
...@@ -45,6 +48,13 @@ and combine the 2 in a demofilestorage: ...@@ -45,6 +48,13 @@ and combine the 2 in a demofilestorage:
>>> from ZODB.DemoStorage import DemoStorage >>> from ZODB.DemoStorage import DemoStorage
>>> storage = DemoStorage(base=base, changes=changes) >>> storage = DemoStorage(base=base, changes=changes)
The storage will assign OIDs in a pseudo-random fashion, but for test
purposes we need to control where they start (since the random seeds
can be different on different platforms):
>>> storage._next_oid = 3553260803050964942
If there are no transactions, the storage reports the lastTransaction If there are no transactions, the storage reports the lastTransaction
of the base database: of the base database:
......
...@@ -13,7 +13,10 @@ We'll make some assertions about time, so we'll take it over: ...@@ -13,7 +13,10 @@ We'll make some assertions about time, so we'll take it over:
... return now ... return now
>>> import time >>> import time
>>> time_time = time.time >>> time_time = time.time
>>> time.time = faux_time >>> if isinstance(time,type):
... time.time = staticmethod(faux_time) # Jython
... else:
... time.time = faux_time
Commit a bunch of transactions: Commit a bunch of transactions:
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
############################################################################## ##############################################################################
import sys import sys
IS_JYTHON = sys.platform.startswith('java')
try: try:
# Python 2.x # Python 2.x
import cPickle import cPickle
...@@ -113,6 +115,13 @@ def PersistentUnpickler(find_global, load_persistent, *args, **kwargs): ...@@ -113,6 +115,13 @@ def PersistentUnpickler(find_global, load_persistent, *args, **kwargs):
try: try:
# Python 2.x # Python 2.x
if IS_JYTHON:
# Jython 2.7rc2 cStringIO.StringIO class has a bug
# resulting in StringIndexOutOfBoundExceptions
# when repeatedly writing and then seeking back to 0
# http://bugs.jython.org/issue2324
from io import BytesIO
else:
# XXX: why not just import BytesIO from io? # XXX: why not just import BytesIO from io?
from cStringIO import StringIO as BytesIO from cStringIO import StringIO as BytesIO
except ImportError: except ImportError:
......
...@@ -229,16 +229,17 @@ that are still alive. ...@@ -229,16 +229,17 @@ that are still alive.
If a connection object is abandoned (it becomes unreachable), then it If a connection object is abandoned (it becomes unreachable), then it
will vanish from pool.all automatically. However, connections are will vanish from pool.all automatically. However, connections are
involved in cycles, so exactly when a connection vanishes from pool.all involved in cycles, so exactly when a connection vanishes from
isn't predictable. It can be forced by running gc.collect(): pool.all isn't predictable. It can be forced (on most platforms but
not Jython) by running gc.collect():
>>> import gc >>> import gc, sys
>>> dummy = gc.collect() >>> dummy = gc.collect()
>>> len(pool.all) >>> len(pool.all)
3 3
>>> c3 = None >>> c3 = None
>>> dummy = gc.collect() # removes c3 from pool.all >>> dummy = gc.collect() # removes c3 from pool.all
>>> len(pool.all) >>> len(pool.all) if not sys.platform.startswith("java") else 2
2 2
Note that c3 is really gone; in particular it didn't get added back to Note that c3 is really gone; in particular it didn't get added back to
......
...@@ -99,6 +99,7 @@ one traditional use for savepoints is simply to free memory space midstream ...@@ -99,6 +99,7 @@ one traditional use for savepoints is simply to free memory space midstream
during a long transaction. Before ZODB 3.4.2, making a savepoint failed during a long transaction. Before ZODB 3.4.2, making a savepoint failed
to trigger cache gc, and this test verifies that it now does. to trigger cache gc, and this test verifies that it now does.
>>> import gc
>>> import ZODB >>> import ZODB
>>> from ZODB.tests.MinPO import MinPO >>> from ZODB.tests.MinPO import MinPO
>>> from ZODB.MappingStorage import MappingStorage >>> from ZODB.MappingStorage import MappingStorage
...@@ -129,6 +130,13 @@ Making a savepoint at this time used to leave the cache holding the same ...@@ -129,6 +130,13 @@ Making a savepoint at this time used to leave the cache holding the same
number of objects. Make sure the cache shrinks now instead. number of objects. Make sure the cache shrinks now instead.
>>> dummy = transaction.savepoint() >>> dummy = transaction.savepoint()
Jython needs a GC, and needs to actually access the map to be sure the size
is updated:
>>> _ = gc.collect()
>>> _ = getattr(cn._cache, 'data', {}).values()
>>> _ = getattr(cn._cache, 'data', {}).keys()
>>> len(cn._cache) <= CACHESIZE + 1 >>> len(cn._cache) <= CACHESIZE + 1
True True
......
...@@ -125,7 +125,10 @@ def connectionDebugInfo(): ...@@ -125,7 +125,10 @@ def connectionDebugInfo():
... now += .1 ... now += .1
... return now ... return now
>>> real_time = time.time >>> real_time = time.time
>>> time.time = faux_time >>> if isinstance(time,type):
... time.time = staticmethod(faux_time) # Jython
... else:
... time.time = faux_time
>>> from ZODB.tests.util import DB >>> from ZODB.tests.util import DB
>>> import transaction >>> import transaction
......
...@@ -69,10 +69,26 @@ class RecoverTest(ZODB.tests.util.TestCase): ...@@ -69,10 +69,26 @@ class RecoverTest(ZODB.tests.util.TestCase):
def damage(self, num, size): def damage(self, num, size):
self.storage.close() self.storage.close()
# Drop size null bytes into num random spots. # Drop size null bytes into num random spots.
for i in range(num): for i in range(num - 1):
offset = random.randint(0, self.storage._pos - size) offset = random.randint(0, self.storage._pos - size)
with open(self.path, "a+b") as f: # Note that we open the file as r+, not a+. Seeking a file
# open in append mode is effectively a no-op *depending on
# platform*, as the write may simply append to the file. An
# earlier version of this code opened the file is a+ mode,
# meaning on some platforms it was only writing to the end of the
# file, and so the test cases were always finding that bad data.
# For compatibility with that, we do one write outside the loop
# at the end.
with open(self.path, "r+b") as f:
f.seek(offset)
f.write(b"\0" * size)
with open(self.path, 'rb') as f:
f.seek(offset) f.seek(offset)
v = f.read(size)
self.assertEqual(b"\0" * size, v)
with open(self.path, 'a+b') as f:
f.write(b"\0" * size) f.write(b"\0" * size)
ITERATIONS = 5 ITERATIONS = 5
......
...@@ -17,7 +17,7 @@ import unittest ...@@ -17,7 +17,7 @@ import unittest
import ZODB.tests.util import ZODB.tests.util
from ZODB import serialize from ZODB import serialize
from ZODB._compat import Pickler, BytesIO, _protocol from ZODB._compat import Pickler, BytesIO, _protocol, IS_JYTHON
class ClassWithNewargs(int): class ClassWithNewargs(int):
...@@ -139,7 +139,17 @@ class SerializerFunctestCase(unittest.TestCase): ...@@ -139,7 +139,17 @@ class SerializerFunctestCase(unittest.TestCase):
# buildout doesn't arrange for the sys.path to be exported, # buildout doesn't arrange for the sys.path to be exported,
# so force it ourselves # so force it ourselves
environ = os.environ.copy() environ = os.environ.copy()
environ['PYTHONPATH'] = os.pathsep.join(sys.path) if IS_JYTHON:
# Jython 2.7rc2 has a bug; if it's Lib directory is
# specifically put on the PYTHONPATH, then it doesn't add
# it itself, which means it fails to 'import site' because
# it can't import '_jythonlib' and the whole process fails
# We would use multiprocessing here, but it doesn't exist on jython
sys_path = [x for x in sys.path
if not x.endswith('Lib') and x != '__classpath__' and x!= '__pyclasspath__/']
else:
sys_path = sys.path
environ['PYTHONPATH'] = os.pathsep.join(sys_path)
subprocess.check_call(prep_args, env=environ) subprocess.check_call(prep_args, env=environ)
load_args = [sys.executable, '-c', load_args = [sys.executable, '-c',
'from ZODB.tests.testSerialize import _functest_load; ' 'from ZODB.tests.testSerialize import _functest_load; '
......
...@@ -182,4 +182,7 @@ def mess_with_time(test=None, globs=None, now=1278864701.5): ...@@ -182,4 +182,7 @@ def mess_with_time(test=None, globs=None, now=1278864701.5):
import time import time
zope.testing.setupstack.register(test, setattr, time, 'time', time.time) zope.testing.setupstack.register(test, setattr, time, 'time', time.time)
if isinstance(time,type):
time.time = staticmethod(faux_time) # jython
else:
time.time = faux_time time.time = faux_time
...@@ -41,7 +41,12 @@ To see this work (in a predictable way), we'll first hack time.time: ...@@ -41,7 +41,12 @@ To see this work (in a predictable way), we'll first hack time.time:
>>> import time >>> import time
>>> old_time = time.time >>> old_time = time.time
>>> time.time = lambda : 1224825068.12 >>> time_value = 1224825068.12
>>> faux_time = lambda: time_value
>>> if isinstance(time,type):
... time.time = staticmethod(faux_time) # Jython
... else:
... time.time = faux_time
Now, if we ask for a new time stamp, we'll get one based on our faux Now, if we ask for a new time stamp, we'll get one based on our faux
time: time:
...@@ -71,7 +76,7 @@ Here, since we called it at the same time, we got a time stamp that ...@@ -71,7 +76,7 @@ Here, since we called it at the same time, we got a time stamp that
was only slightly larger than the previos one. Of course, at a later was only slightly larger than the previos one. Of course, at a later
time, the time stamp we get will be based on the time: time, the time stamp we get will be based on the time:
>>> time.time = lambda : 1224825069.12 >>> time_value = 1224825069.12
>>> tid = ZODB.utils.newTid(tid2) >>> tid = ZODB.utils.newTid(tid2)
>>> print(ZODB.TimeStamp.TimeStamp(tid)) >>> print(ZODB.TimeStamp.TimeStamp(tid))
2008-10-24 05:11:09.120000 2008-10-24 05:11:09.120000
......
[tox] [tox]
# Jython 2.7rc2 does work, but unfortunately has an issue running
# with Tox 1.9.2 (http://bugs.jython.org/issue2325)
#envlist = py26,py27,py32,py33,py34,pypy,simple,jython,pypy3
envlist = py26,py27,py32,py33,py34,pypy,simple,pypy3 envlist = py26,py27,py32,py33,py34,pypy,simple,pypy3
[testenv] [testenv]
...@@ -29,6 +32,10 @@ commands = ...@@ -29,6 +32,10 @@ commands =
python setup.py test -q python setup.py test -q
deps = {[testenv]deps} deps = {[testenv]deps}
[testenv:jython]
commands =
jython setup.py test -q
[testenv:coverage] [testenv:coverage]
basepython = python2.7 basepython = python2.7
usedevelop = true usedevelop = true
......
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