Commit 82c6df06 authored by Jérome Perrin's avatar Jérome Perrin

fixup! fixup! fixup! fixup! XMLExportImport: more support pickle protocol 3 🚧 ( repair python2 )

parent 51cb4518
############################################################################## ##############################################################################
# # coding: utf-8
# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved. # Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
# TAHARA Yusei <yusei@nexedi.com> # TAHARA Yusei <yusei@nexedi.com>
# #
...@@ -26,16 +26,17 @@ ...@@ -26,16 +26,17 @@
# #
############################################################################## ##############################################################################
import base64
import unittest import unittest
import zodbpickle import zodbpickle
import zodbpickle.fastpickle as pickle import zodbpickle.fastpickle as pickle
import re import re
import xml.sax from io import BytesIO
from io import BytesIO, StringIO from six import StringIO
from Products.ERP5Type.XMLExportImport import importXML, ppml
from Products.ERP5Type.XMLExportImport import ppml
import six import six
class DummyClass: class DummyClass:
""" """
A dummy data class A dummy data class
...@@ -50,26 +51,37 @@ class XMLPickleTestCase(unittest.TestCase): ...@@ -50,26 +51,37 @@ class XMLPickleTestCase(unittest.TestCase):
def dump_to_xml(self, obj): def dump_to_xml(self, obj):
pickled_string = pickle.dumps(obj, protocol=self._pickle_protocol) pickled_string = pickle.dumps(obj, protocol=self._pickle_protocol)
f = BytesIO(pickled_string) f = BytesIO(pickled_string)
return str(ppml.ToXMLUnpickler(f).load()) xml = ppml.ToXMLUnpickler(f).load().__str__()
if six.PY2 and isinstance(xml, six.text_type):
def load_from_xml(self, xml_string): xml = xml.encode('utf-8')
output = StringIO() return xml
F=ppml.xmlPickler()
F.file = output def load_from_xml(self, xml_string, persistent_load=None):
F.binary = 1 # XML pickle actually only supports the case of binary = 1 assertEqual = self.assertEqual
class DummyJar:
content_handler = xml.sax.handler.ContentHandler() loaded = None
content_handler.startElement = F.unknown_starttag """follow interface expected by importXML"""
content_handler.endElement = F.unknown_endtag def importFile(self, file, clue):
content_handler.characters = F.handle_data assertEqual(clue, 'ignored')
xml.sax.parseString(xml_string, content_handler) assertEqual(file.read(4), b'ZEXP')
unpickler = pickle.Unpickler(file)
reconstructed_pickled_data = F._stack[0][0] if persistent_load:
return pickle.loads(reconstructed_pickled_data) unpickler.persistent_load = persistent_load
self.loaded = unpickler.load()
jar = DummyJar()
xml_string = '<?xml version="1.0"?>\n<ZopeData>%s</ZopeData>' % xml_string
importXML(jar, StringIO(xml_string), clue='ignored')
return jar.loaded
def dump_and_load(self, obj): def dump_and_load(self, obj):
return self.load_from_xml(self.dump_to_xml(obj)) return self.load_from_xml(self.dump_to_xml(obj))
def check_and_load(self, v):
reconstructed = self.dump_and_load(v)
self.assertEqual(reconstructed, v)
self.assertIs(type(reconstructed), type(v))
class TestXMLPickle(XMLPickleTestCase): class TestXMLPickle(XMLPickleTestCase):
...@@ -101,76 +113,95 @@ class TestXMLPickle(XMLPickleTestCase): ...@@ -101,76 +113,95 @@ class TestXMLPickle(XMLPickleTestCase):
self.assertIs(self.dump_and_load(False), False) self.assertIs(self.dump_and_load(False), False)
def test_int(self): def test_int(self):
def check_int(v): self.check_and_load(-0)
reconstructed = self.dump_and_load(v) self.check_and_load(1)
self.assertEqual(reconstructed, v) self.check_and_load(-1)
self.assertIs(type(reconstructed), int) self.check_and_load(0xff)
check_int(-0) self.check_and_load(0xff1)
check_int(1) self.check_and_load(0xffff)
check_int(-1) self.check_and_load(2**128)
check_int(0xff) # long4
check_int(0xff1) # https://github.com/python/cpython/blob/4d4a6f1b/Lib/test/pickletester.py#L2049-L2050
check_int(0xffff) self.check_and_load(12345678910111213141516178920 << (256*8))
check_int(0xffff1)
if six.PY2:
def test_long(self):
self.check_and_load(long(-0))
self.check_and_load(long(1))
self.check_and_load(long(-1))
self.check_and_load(long(0xff))
self.check_and_load(long(0xff1))
self.check_and_load(long(0xffff))
self.check_and_load(long(2**128))
self.check_and_load(12345678910111213141516178920 << (256*8))
def test_float(self): def test_float(self):
def check_float(v): self.check_and_load(-0.0)
reconstructed = self.dump_and_load(v) self.check_and_load(1.0)
self.assertEqual(reconstructed, v) self.check_and_load(-1.0)
self.assertIs(type(reconstructed), float) self.check_and_load(.33)
check_float(-0.0)
check_float(1.0)
check_float(-1.0)
check_float(.1 + .2)
def test_None(self): def test_None(self):
self.assertIs( self.assertIs(
self.dump_and_load(None), None) self.dump_and_load(None), None)
def test_bytes(self): def test_bytes(self):
self.assertEqual(self.dump_and_load(b"bytes"), b"bytes") self.check_and_load(b"bytes")
self.assertEqual(self.dump_and_load(b"long bytes" * 100), b"long bytes" * 100) self.check_and_load(b"long bytes" * 100)
self.assertEqual( self.check_and_load(zodbpickle.binary(b"bytes"))
self.dump_and_load(zodbpickle.binary(b"bytes")), self.check_and_load(zodbpickle.binary(b""))
zodbpickle.binary(b"bytes"))
self.assertIs(type(self.dump_and_load(zodbpickle.binary(b"bytes"))), zodbpickle.binary)
def test_unicode(self): def test_unicode(self): # BBB PY2
self.assertIs(type(self.dump_and_load(u"OK")), six.text_type) self.assertIs(type(self.dump_and_load(u"OK")), six.text_type)
self.assertEqual(self.dump_and_load(u"short"), u"short") self.check_and_load(u"short")
self.assertEqual(self.dump_and_load(u"unicode 👍"), u"unicode 👍") self.check_and_load(u"unicode 👍")
self.assertEqual(self.dump_and_load(u"long" * 100), u"long" * 100) self.check_and_load(u"long" * 100)
self.assertEqual(self.dump_and_load(u"long…" * 100), u"long…" * 100) self.check_and_load(u"long…" * 100)
self.assertEqual(self.dump_and_load(u">"), u">") self.check_and_load(u">")
self.assertEqual(self.dump_and_load(u"a\nb"), u"a\nb") self.check_and_load(u"a\nb")
self.check_and_load(u" with spaces ")
self.check_and_load(u"\twith\ttabs\t")
self.check_and_load(u"")
def test_str(self):
self.assertIs(type(self.dump_and_load("OK")), str)
self.check_and_load("short")
self.check_and_load("unicode 👍")
self.check_and_load("long" * 100)
self.check_and_load("long…" * 100)
self.check_and_load(">")
self.check_and_load("a\nb")
self.check_and_load(" with spaces ")
self.check_and_load("\twith\ttabs\t")
self.check_and_load("")
def test_dict(self): def test_dict(self):
self.assertEqual( self.check_and_load({'a': 1, 'b': 2})
self.dump_and_load({'a': 1, 'b': 2}), {'a': 1, 'b': 2}) self.check_and_load({'hé': 'ho'})
self.check_and_load(dict.fromkeys(range(3000)))
def test_tuple(self): def test_tuple(self):
self.assertEqual( self.check_and_load((1, ))
self.dump_and_load((1, )), (1, )) self.check_and_load((1, 'two'))
self.assertEqual( self.check_and_load((1, 'two', 3.0))
self.dump_and_load((1, 'two')), (1, 'two')) self.check_and_load(tuple([1] * 1000))
self.assertEqual( self.check_and_load(())
self.dump_and_load((1, 'two', 3.0)), (1, 'two', 3.0)) self.check_and_load(('hé',))
self.assertEqual( self.check_and_load(('hé', 'hé'))
self.dump_and_load(tuple([1] * 1000)), tuple([1] * 1000)) self.check_and_load(('hé', 'hé', 'hé'))
self.assertEqual( self.check_and_load(('hé', 'hé', 'hé', 'hé'))
self.dump_and_load(()), ())
def test_list(self): def test_list(self):
self.assertEqual( self.check_and_load([1])
self.dump_and_load([1]), [1]) self.check_and_load([])
self.assertEqual( self.check_and_load([1] * 1000)
self.dump_and_load([]), []) self.check_and_load(['hé'])
self.assertEqual(
self.dump_and_load([1] * 1000), [1] * 1000)
def test_set(self): def test_set(self):
self.assertEqual( self.check_and_load(set('abc'))
self.dump_and_load(set('abc')), set('abc')) self.check_and_load(set('hé'))
self.check_and_load(set([]))
def test_reference(self): def test_reference(self):
ref = [] ref = []
...@@ -178,6 +209,14 @@ class TestXMLPickle(XMLPickleTestCase): ...@@ -178,6 +209,14 @@ class TestXMLPickle(XMLPickleTestCase):
self.assertEqual(reconstructed, [ref, ref, ref]) self.assertEqual(reconstructed, [ref, ref, ref])
self.assertIs(reconstructed[0], reconstructed[1]) self.assertIs(reconstructed[0], reconstructed[1])
def test_reference_long(self):
# same as reference (which is using BINPUT/BINGET but with large data
# to use LONG_BINPUT/LONG_BINGET)
ref = [list() for _ in range(256)]
reconstructed = self.dump_and_load([ref, ref, ref])
self.assertEqual(reconstructed, [ref, ref, ref])
self.assertIs(reconstructed[0], reconstructed[1])
class TestXMLPickleStringEncoding(XMLPickleTestCase): class TestXMLPickleStringEncoding(XMLPickleTestCase):
def test_string_base64(self): def test_string_base64(self):
...@@ -214,19 +253,39 @@ class TestXMLPickleStringEncoding(XMLPickleTestCase): ...@@ -214,19 +253,39 @@ class TestXMLPickleStringEncoding(XMLPickleTestCase):
class TestXMLPickleStringHeuristics(XMLPickleTestCase): class TestXMLPickleStringHeuristics(XMLPickleTestCase):
"""Heuristics to map python2 str to unicode or bytes in business templates. """Heuristics to map python2 str to unicode or bytes in business templates.
""" """
def test_oid_base64(self):
# if it looks like an oid, it's bytes
self.assertEqual(
self.load_from_xml("""
<pickle><string encoding="base64">AAAAAAAAAAE=</string></pickle>
"""),
b"\x00\x00\x00\x00\x00\x00\x00\x01")
def test_bytes_base64(self): def test_bytes_base64(self):
# if it does not decode as utf-8 it's bytes # if it does not decode as utf-8, it's bytes
self.assertEqual( self.assertEqual(
self.load_from_xml(""" self.load_from_xml("""
<pickle><string encoding="base64">/wA=</string></pickle> <pickle><string encoding="base64">/wA=</string></pickle>
"""), """),
b"\xFF\x00") b"\xFF\x00")
def test_long_bytes_base64(self):
# if it does not decode as utf-8, it's bytes
long_bytes = b"\xFF\x00" * 256
self.assertEqual(
self.load_from_xml("""
<pickle><string encoding="base64">%s</string></pickle>
""" % base64.b64encode(long_bytes).decode()),
long_bytes)
def test_string_persistent_id_base64(self):
# persistent ids are loaded as bytes
persistent_ids = []
def persistent_load(oid):
persistent_ids.append(oid)
self.assertEqual(
self.load_from_xml("""
<pickle>
<persistent>
<string encoding="base64">AAAAAAAAAAE=</string>
</persistent>
</pickle>
""",
persistent_load=persistent_load),
None)
self.assertEqual(
persistent_ids,
[b'\x00\x00\x00\x00\x00\x00\x00\x01'])
...@@ -363,12 +363,6 @@ def exportXML(jar, oid, file=None): ...@@ -363,12 +363,6 @@ def exportXML(jar, oid, file=None):
p = getReorderedPickle(oid) p = getReorderedPickle(oid)
write(XMLrecord(oid, len(p), p, id_mapping)) write(XMLrecord(oid, len(p), p, id_mapping))
write('</ZopeData>\n') write('</ZopeData>\n')
if 0:
try:
print(file.getvalue())
except AttributeError:
pass
import pdb; pdb.set_trace()
return file return file
class zopedata: class zopedata:
...@@ -421,7 +415,6 @@ def importXML(jar, file, clue=''): ...@@ -421,7 +415,6 @@ def importXML(jar, file, clue=''):
F.end_handlers['record'] = save_record F.end_handlers['record'] = save_record
F.end_handlers['ZopeData'] = save_zopedata F.end_handlers['ZopeData'] = save_zopedata
F.start_handlers['ZopeData'] = start_zopedata F.start_handlers['ZopeData'] = start_zopedata
F.binary=1
F.file=outfile F.file=outfile
# <patch> # <patch>
# Our BTs XML files don't declare encoding but have accented chars in them # Our BTs XML files don't declare encoding but have accented chars in them
......
This diff is collapsed.
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