Commit 95426303 authored by Tres Seaver's avatar Tres Seaver

Switch to using non-backward-compatible pickles

- Allow protocol 3 under Python 3.

- Do not stori bytes as strings under Python 3.

Fixes issue #4.
parent 58b05544
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
Unreleased Unreleased
========== ==========
- Switch to using non-backward-compatible pickles (protocol 3, without
storing bytes as strings) under Python 3.
- Fixed: A ``UnicodeDecodeError`` could happen for non-ASCII OIDs - Fixed: A ``UnicodeDecodeError`` could happen for non-ASCII OIDs
when using bushy blob layout. when using bushy blob layout.
......
...@@ -25,10 +25,7 @@ except ImportError: ...@@ -25,10 +25,7 @@ except ImportError:
class Pickler(zodbpickle.pickle.Pickler): class Pickler(zodbpickle.pickle.Pickler):
def __init__(self, f, protocol=None): def __init__(self, f, protocol=None):
if protocol: super(Pickler, self).__init__(f, protocol)
# we want to be backwards-compatible with Python 2
assert 0 <= protocol < 3
super(Pickler, self).__init__(f, protocol, bytes_as_strings=True)
class Unpickler(zodbpickle.pickle.Unpickler): class Unpickler(zodbpickle.pickle.Unpickler):
def __init__(self, f): def __init__(self, f):
...@@ -45,16 +42,10 @@ except ImportError: ...@@ -45,16 +42,10 @@ except ImportError:
return self.find_global(modulename, name) return self.find_global(modulename, name)
def dump(o, f, protocol=None): def dump(o, f, protocol=None):
if protocol: return zodbpickle.pickle.dump(o, f, protocol)
# we want to be backwards-compatible with Python 2
assert 0 <= protocol < 3
return zodbpickle.pickle.dump(o, f, protocol, bytes_as_strings=True)
def dumps(o, protocol=None): def dumps(o, protocol=None):
if protocol: return zodbpickle.pickle.dumps(o, protocol)
# we want to be backwards-compatible with Python 2
assert 0 <= protocol < 3
return zodbpickle.pickle.dumps(o, protocol, bytes_as_strings=True)
def loads(s): def loads(s):
return zodbpickle.pickle.loads(s, encoding='ASCII', errors='bytes') return zodbpickle.pickle.loads(s, encoding='ASCII', errors='bytes')
......
...@@ -38,7 +38,7 @@ def make_pickle(ob): ...@@ -38,7 +38,7 @@ def make_pickle(ob):
return sio.getvalue() return sio.getvalue()
def test_factory(conn, module_name, name): def _factory(conn, module_name, name):
return globals()[name] return globals()[name]
class SerializerTestCase(unittest.TestCase): class SerializerTestCase(unittest.TestCase):
...@@ -60,7 +60,7 @@ class SerializerTestCase(unittest.TestCase): ...@@ -60,7 +60,7 @@ class SerializerTestCase(unittest.TestCase):
(ClassWithNewargs, (1,))) (ClassWithNewargs, (1,)))
def test_getClassName(self): def test_getClassName(self):
r = serialize.ObjectReader(factory=test_factory) r = serialize.ObjectReader(factory=_factory)
eq = self.assertEqual eq = self.assertEqual
eq(r.getClassName(self.old_style_with_newargs), eq(r.getClassName(self.old_style_with_newargs),
__name__ + ".ClassWithNewargs") __name__ + ".ClassWithNewargs")
...@@ -82,7 +82,7 @@ class SerializerTestCase(unittest.TestCase): ...@@ -82,7 +82,7 @@ class SerializerTestCase(unittest.TestCase):
__import__(module) __import__(module)
return getattr(sys.modules[module], name) return getattr(sys.modules[module], name)
r = TestObjectReader(factory=test_factory) r = TestObjectReader(factory=_factory)
g = r.getGhost(self.old_style_with_newargs) g = r.getGhost(self.old_style_with_newargs)
self.assertTrue(isinstance(g, ClassWithNewargs)) self.assertTrue(isinstance(g, ClassWithNewargs))
self.assertEqual(g, 1) self.assertEqual(g, 1)
...@@ -119,9 +119,76 @@ class SerializerTestCase(unittest.TestCase): ...@@ -119,9 +119,76 @@ class SerializerTestCase(unittest.TestCase):
self.assertTrue(not serialize.myhasattr(NewStyle(), "rat")) self.assertTrue(not serialize.myhasattr(NewStyle(), "rat"))
class SerializerFunctestCase(unittest.TestCase):
def setUp(self):
import tempfile
self._tempdir = tempfile.mkdtemp(suffix='serializerfunc')
def tearDown(self):
import shutil
shutil.rmtree(self._tempdir)
def test_funky_datetime_serialization(self):
import os
import subprocess
fqn = os.path.join(self._tempdir, 'Data.fs')
prep_args = [sys.executable, '-c',
'from ZODB.tests.testSerialize import _functest_prep; '
'_functest_prep("%s")' % fqn]
subprocess.check_call(prep_args)
load_args = [sys.executable, '-c',
'from ZODB.tests.testSerialize import _functest_load; '
'_functest_load("%s")' % fqn]
subprocess.call(load_args)
def _working_failing_datetimes():
import datetime
WORKING = datetime.datetime(5375, 12, 31, 23, 59, 59)
# Any date after 5375 A.D. appears to trigger this bug.
FAILING = datetime.datetime(5376, 12, 31, 23, 59, 59)
return WORKING, FAILING
def _functest_prep(fqn):
# Prepare the database with a BTree which won't deserialize
# if the bug is present.
# run in separate process)
import transaction
from BTrees.OOBTree import OOBTree
from ZODB import DB
WORKING, FAILING = _working_failing_datetimes()
db = DB(fqn)
conn = db.open()
try:
root = conn.root()
tree = root['tree'] = OOBTree()
tree[WORKING] = 'working'
tree[FAILING] = 'failing'
transaction.commit()
finally: # Windoze
conn.close()
db.close()
def _functest_load(fqn):
# Open the database and attempt to deserialize the tree
# (run in separate process)
from ZODB import DB
WORKING, FAILING = _working_failing_datetimes()
db = DB(fqn)
conn = db.open()
try:
root = conn.root()
tree = root['tree']
assert tree[WORKING] == 'working'
assert tree[FAILING] == 'failing'
finally: # Windoze
conn.close()
db.close()
def test_suite(): def test_suite():
suite = unittest.makeSuite(SerializerTestCase) return unittest.TestSuite((
suite.addTest( unittest.makeSuite(SerializerTestCase),
unittest.makeSuite(SerializerFunctestCase),
doctest.DocTestSuite("ZODB.serialize", doctest.DocTestSuite("ZODB.serialize",
checker=ZODB.tests.util.checker)) checker=ZODB.tests.util.checker),
return suite ))
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