Skip to content

Commit

Permalink
Add support for Jython. This required only relatively minor test chan…
Browse files Browse the repository at this point in the history
…ges.
  • Loading branch information
jamadden committed Apr 13, 2015
1 parent c35f252 commit cfec72a
Show file tree
Hide file tree
Showing 13 changed files with 100 additions and 22 deletions.
10 changes: 6 additions & 4 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
==================
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
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
Expand Down
12 changes: 11 additions & 1 deletion src/ZODB/DemoStorage.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:

Expand Down
5 changes: 4 additions & 1 deletion src/ZODB/FileStorage/iterator.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
13 changes: 11 additions & 2 deletions src/ZODB/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
##############################################################################
import sys

IS_JYTHON = sys.platform.startswith('java')

try:
# Python 2.x
import cPickle
Expand Down Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions src/ZODB/tests/dbopen.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/ZODB/tests/testConnectionSavepoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def testSavepointDoesCacheGC():
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
Expand Down Expand Up @@ -129,6 +130,13 @@ def testSavepointDoesCacheGC():
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
Expand Down
5 changes: 4 additions & 1 deletion src/ZODB/tests/testDB.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions src/ZODB/tests/testRecover.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,28 @@ def populate(self):
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
Expand Down
14 changes: 12 additions & 2 deletions src/ZODB/tests/testSerialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

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):
Expand Down Expand Up @@ -139,7 +139,17 @@ def test_funky_datetime_serialization(self):
# 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; '
Expand Down
5 changes: 4 additions & 1 deletion src/ZODB/tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,7 @@ def faux_time():
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
11 changes: 8 additions & 3 deletions src/ZODB/utils.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
7 changes: 7 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[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]
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit cfec72a

Please sign in to comment.