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 @@
- Fix command-line parsing of --verbose and --verify arguments.
(The short versions -v and -V were parsed correctly.)
- Add support for PyPy, and fix the methods in ``ZODB.serialize`` that
find object references under Python 2.7 (used in scripts like
``referrers``, ``netspace``, and ``fsrecover`` among others). This
requires the addition of the ``zodbpickle`` dependency.
- Add support for PyPy and Jython 2.7.
- Fix the methods in ``ZODB.serialize`` that find object references
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)
==================
......
......@@ -60,6 +60,7 @@ Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Programming Language :: Python :: Implementation :: Jython
Topic :: Database
Topic :: Software Development :: Libraries :: Python Modules
Operating System :: Microsoft :: Windows
......
......@@ -14,7 +14,10 @@ existing, base, storage without updating the storage.
... return now
>>> import 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
puting an object (in addition to the root object) in it:
......@@ -45,6 +48,13 @@ and combine the 2 in a demofilestorage:
>>> from ZODB.DemoStorage import DemoStorage
>>> 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
of the base database:
......
......@@ -13,7 +13,10 @@ We'll make some assertions about time, so we'll take it over:
... return now
>>> import 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:
......
......@@ -13,6 +13,8 @@
##############################################################################
import sys
IS_JYTHON = sys.platform.startswith('java')
try:
# Python 2.x
import cPickle
......@@ -113,8 +115,15 @@ def PersistentUnpickler(find_global, load_persistent, *args, **kwargs):
try:
# Python 2.x
# XXX: why not just import BytesIO from io?
from cStringIO import StringIO as BytesIO
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?
from cStringIO import StringIO as BytesIO
except ImportError:
# Python 3.x
from io import BytesIO
......
......@@ -228,17 +228,18 @@ that are still alive.
3
If a connection object is abandoned (it becomes unreachable), then it
will vanish from pool.all automatically. However, connections are
involved in cycles, so exactly when a connection vanishes from pool.all
isn't predictable. It can be forced by running gc.collect():
will vanish from pool.all automatically. However, connections are
involved in cycles, so exactly when a connection vanishes from
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()
>>> len(pool.all)
3
>>> c3 = None
>>> dummy = gc.collect() # removes c3 from pool.all
>>> len(pool.all)
>>> len(pool.all) if not sys.platform.startswith("java") else 2
2
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
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.
>>> import gc
>>> import ZODB
>>> from ZODB.tests.MinPO import MinPO
>>> from ZODB.MappingStorage import MappingStorage
......@@ -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.
>>> 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
True
......
......@@ -125,7 +125,10 @@ def connectionDebugInfo():
... now += .1
... return now
>>> 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
>>> import transaction
......
......@@ -69,12 +69,28 @@ class RecoverTest(ZODB.tests.util.TestCase):
def damage(self, num, size):
self.storage.close()
# 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)
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)
v = f.read(size)
self.assertEqual(b"\0" * size, v)
with open(self.path, 'a+b') as f:
f.write(b"\0" * size)
ITERATIONS = 5
# Run recovery, from self.path to self.dest. Return whatever
......
......@@ -17,7 +17,7 @@ import unittest
import ZODB.tests.util
from ZODB import serialize
from ZODB._compat import Pickler, BytesIO, _protocol
from ZODB._compat import Pickler, BytesIO, _protocol, IS_JYTHON
class ClassWithNewargs(int):
......@@ -139,7 +139,17 @@ class SerializerFunctestCase(unittest.TestCase):
# buildout doesn't arrange for the sys.path to be exported,
# so force it ourselves
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)
load_args = [sys.executable, '-c',
'from ZODB.tests.testSerialize import _functest_load; '
......
......@@ -182,4 +182,7 @@ def mess_with_time(test=None, globs=None, now=1278864701.5):
import time
zope.testing.setupstack.register(test, setattr, time, 'time', time.time)
time.time = faux_time
if isinstance(time,type):
time.time = staticmethod(faux_time) # jython
else:
time.time = faux_time
......@@ -41,7 +41,12 @@ To see this work (in a predictable way), we'll first hack time.time:
>>> import 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
time:
......@@ -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
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)
>>> print(ZODB.TimeStamp.TimeStamp(tid))
2008-10-24 05:11:09.120000
......@@ -194,4 +199,4 @@ supports optional method preconditions [1]_.
locked. Combining preconditions with locking provides both
efficiency and concise expressions. A more general-purpose
facility would almost certainly provide separate descriptors for
preconditions.
preconditions.
[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
[testenv]
......@@ -29,6 +32,10 @@ commands =
python setup.py test -q
deps = {[testenv]deps}
[testenv:jython]
commands =
jython setup.py test -q
[testenv:coverage]
basepython = python2.7
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