test cross version serialization by launching subprocesses; much cleaner!
--HG-- branch : trunk
This commit is contained in:
		
							parent
							
								
									8f69d23f18
								
							
						
					
					
						commit
						4d598370b4
					
				|  | @ -35,8 +35,6 @@ if _INPY3: | ||||||
|         pass |         pass | ||||||
|     bytes = bytes |     bytes = bytes | ||||||
| else: | else: | ||||||
|     class bytes(str): |  | ||||||
|         pass |  | ||||||
|     b = str |     b = str | ||||||
|     _b = bytes |     _b = bytes | ||||||
|     _unicode = unicode |     _unicode = unicode | ||||||
|  | @ -80,25 +78,28 @@ class Serializer(object): | ||||||
|             raise UnserializableType("can't serialize %s" % (tp,)) |             raise UnserializableType("can't serialize %s" % (tp,)) | ||||||
|         dispatch(self, obj) |         dispatch(self, obj) | ||||||
| 
 | 
 | ||||||
|  |     dispatch = {} | ||||||
|  | 
 | ||||||
|     def save_bytes(self, bytes_): |     def save_bytes(self, bytes_): | ||||||
|         self.stream.write(BYTES) |         self.stream.write(BYTES) | ||||||
|         self._write_byte_sequence(bytes_) |         self._write_byte_sequence(bytes_) | ||||||
|  |     dispatch[bytes] = save_bytes | ||||||
| 
 | 
 | ||||||
|     def save_unicode(self, s): |     if _INPY3: | ||||||
|         self.stream.write(UNICODE) |         def save_string(self, s): | ||||||
|         self._write_unicode_string(s) |  | ||||||
| 
 |  | ||||||
|     def save_string(self, s): |  | ||||||
|         if _INPY3: |  | ||||||
|             self.stream.write(PY3STRING) |             self.stream.write(PY3STRING) | ||||||
|             self._write_unicode_string(s) |             self._write_unicode_string(s) | ||||||
|         else: |     else: | ||||||
|             # Case for tests |         def save_string(self, s): | ||||||
|             if _REALLY_PY3 and isinstance(s, str): |  | ||||||
|                 s = s.encode("latin-1") |  | ||||||
|             self.stream.write(PY2STRING) |             self.stream.write(PY2STRING) | ||||||
|             self._write_byte_sequence(s) |             self._write_byte_sequence(s) | ||||||
| 
 | 
 | ||||||
|  |         def save_unicode(self, s): | ||||||
|  |             self.stream.write(UNICODE) | ||||||
|  |             self._write_unicode_string(s) | ||||||
|  |         dispatch[unicode] = save_unicode | ||||||
|  |     dispatch[str] = save_string | ||||||
|  | 
 | ||||||
|     def _write_unicode_string(self, s): |     def _write_unicode_string(self, s): | ||||||
|         try: |         try: | ||||||
|             as_bytes = s.encode("utf-8") |             as_bytes = s.encode("utf-8") | ||||||
|  | @ -113,6 +114,7 @@ class Serializer(object): | ||||||
|     def save_int(self, i): |     def save_int(self, i): | ||||||
|         self.stream.write(INT) |         self.stream.write(INT) | ||||||
|         self._write_int4(i) |         self._write_int4(i) | ||||||
|  |     dispatch[int] = save_int | ||||||
| 
 | 
 | ||||||
|     def _write_int4(self, i, error="int must be less than %i" % |     def _write_int4(self, i, error="int must be less than %i" % | ||||||
|                     (FOUR_BYTE_INT_MAX,)): |                     (FOUR_BYTE_INT_MAX,)): | ||||||
|  | @ -125,6 +127,7 @@ class Serializer(object): | ||||||
|         self._write_int4(len(L), "list is too long") |         self._write_int4(len(L), "list is too long") | ||||||
|         for i, item in enumerate(L): |         for i, item in enumerate(L): | ||||||
|             self._write_setitem(i, item) |             self._write_setitem(i, item) | ||||||
|  |     dispatch[list] = save_list | ||||||
| 
 | 
 | ||||||
|     def _write_setitem(self, key, value): |     def _write_setitem(self, key, value): | ||||||
|         self._save(key) |         self._save(key) | ||||||
|  | @ -135,12 +138,14 @@ class Serializer(object): | ||||||
|         self.stream.write(NEWDICT) |         self.stream.write(NEWDICT) | ||||||
|         for key, value in d.items(): |         for key, value in d.items(): | ||||||
|             self._write_setitem(key, value) |             self._write_setitem(key, value) | ||||||
|  |     dispatch[dict] = save_dict | ||||||
| 
 | 
 | ||||||
|     def save_tuple(self, tup): |     def save_tuple(self, tup): | ||||||
|         for item in tup: |         for item in tup: | ||||||
|             self._save(item) |             self._save(item) | ||||||
|         self.stream.write(BUILDTUPLE) |         self.stream.write(BUILDTUPLE) | ||||||
|         self._write_int4(len(tup), "tuple is too long") |         self._write_int4(len(tup), "tuple is too long") | ||||||
|  |     dispatch[tuple] = save_tuple | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class _UnserializationOptions(object): | class _UnserializationOptions(object): | ||||||
|  | @ -156,35 +161,10 @@ class _Py3UnserializationOptions(_UnserializationOptions): | ||||||
|     def __init__(self, py2_strings_as_str=False): |     def __init__(self, py2_strings_as_str=False): | ||||||
|         self.py2_strings_as_str = py2_strings_as_str |         self.py2_strings_as_str = py2_strings_as_str | ||||||
| 
 | 
 | ||||||
| 
 | if _INPY3: | ||||||
| _unchanging_dispatch = {} |     UnserializationOptions = _Py3UnserializationOptions | ||||||
| for tp in (dict, list, tuple, int): | else: | ||||||
|     name = "save_%s" % (tp.__name__,) |     UnserializationOptions = _Py2UnserializationOptions | ||||||
|     _unchanging_dispatch[tp] = getattr(Serializer, name) |  | ||||||
| del tp, name |  | ||||||
| 
 |  | ||||||
| def _setup_dispatch(): |  | ||||||
|     dispatch = _unchanging_dispatch.copy() |  | ||||||
|     # This is sutble.  bytes is aliased to str in 2.6, so |  | ||||||
|     # dispatch[bytes] is overwritten.  Additionally, we alias unicode |  | ||||||
|     # to str in 3.x, so dispatch[unicode] is overwritten with |  | ||||||
|     # save_string. |  | ||||||
|     dispatch[bytes] = Serializer.save_bytes |  | ||||||
|     dispatch[unicode] = Serializer.save_unicode |  | ||||||
|     dispatch[str] = Serializer.save_string |  | ||||||
|     Serializer.dispatch = dispatch |  | ||||||
| 
 |  | ||||||
| def _setup_version_dependent_constants(leave_unicode_alone=False): |  | ||||||
|     global unicode, UnserializationOptions |  | ||||||
|     if _INPY3: |  | ||||||
|         unicode = str |  | ||||||
|         UnserializationOptions = _Py3UnserializationOptions |  | ||||||
|     else: |  | ||||||
|         UnserializationOptions = _Py2UnserializationOptions |  | ||||||
|         unicode = _unicode |  | ||||||
|     _setup_dispatch() |  | ||||||
| _setup_version_dependent_constants() |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class _Stop(Exception): | class _Stop(Exception): | ||||||
|     pass |     pass | ||||||
|  | @ -236,7 +216,7 @@ class Unserializer(object): | ||||||
| 
 | 
 | ||||||
|     def load_py3string(self): |     def load_py3string(self): | ||||||
|         as_bytes = self._read_byte_string() |         as_bytes = self._read_byte_string() | ||||||
|         if (not _INPY3 and self.options.py3_strings_as_str) and not _REALLY_PY3: |         if not _INPY3 and self.options.py3_strings_as_str: | ||||||
|             # XXX Should we try to decode into latin-1? |             # XXX Should we try to decode into latin-1? | ||||||
|             self.stack.append(as_bytes) |             self.stack.append(as_bytes) | ||||||
|         else: |         else: | ||||||
|  | @ -245,8 +225,7 @@ class Unserializer(object): | ||||||
| 
 | 
 | ||||||
|     def load_py2string(self): |     def load_py2string(self): | ||||||
|         as_bytes = self._read_byte_string() |         as_bytes = self._read_byte_string() | ||||||
|         if (_INPY3 and self.options.py2_strings_as_str) or \ |         if _INPY3 and self.options.py2_strings_as_str: | ||||||
|                (_REALLY_PY3 and not _INPY3): |  | ||||||
|             s = as_bytes.decode("latin-1") |             s = as_bytes.decode("latin-1") | ||||||
|         else: |         else: | ||||||
|             s = as_bytes |             s = as_bytes | ||||||
|  | @ -254,7 +233,7 @@ class Unserializer(object): | ||||||
|     opcodes[PY2STRING] = load_py2string |     opcodes[PY2STRING] = load_py2string | ||||||
| 
 | 
 | ||||||
|     def load_bytes(self): |     def load_bytes(self): | ||||||
|         s = bytes(self._read_byte_string()) |         s = self._read_byte_string() | ||||||
|         self.stack.append(s) |         self.stack.append(s) | ||||||
|     opcodes[BYTES] = load_bytes |     opcodes[BYTES] = load_bytes | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,50 +1,78 @@ | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| import shutil | import sys | ||||||
|  | import os | ||||||
|  | import tempfile | ||||||
|  | import subprocess | ||||||
| import py | import py | ||||||
| from py.__.execnet import serializer | from py.__.execnet import serializer | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | def _find_version(suffix=""): | ||||||
|  |     name = "python" + suffix | ||||||
|  |     executable = py.path.local.sysfind(name) | ||||||
|  |     if executable is None: | ||||||
|  |         py.test.skip("can't find a %r executable" % (name,)) | ||||||
|  |     return executable | ||||||
|  | 
 | ||||||
| def setup_module(mod): | def setup_module(mod): | ||||||
|     mod._save_python3 = serializer._INPY3 |     mod.TEMPDIR = py.path.local(tempfile.mkdtemp()) | ||||||
|  |     if sys.version_info > (3, 0): | ||||||
|  |         mod._py3_wrapper = PythonWrapper(py.path.local(sys.executable)) | ||||||
|  |         mod._py2_wrapper = PythonWrapper(_find_version()) | ||||||
|  |     else: | ||||||
|  |         mod._py3_wrapper = PythonWrapper(_find_version("3")) | ||||||
|  |         mod._py2_wrapper = PythonWrapper(py.path.local(sys.executable)) | ||||||
|  |     mod._old_pypath = os.environ.get("PYTHONPATH") | ||||||
|  |     pylib = str(py.path.local(py.__file__).dirpath().join("..")) | ||||||
|  |     os.environ["PYTHONPATH"] = pylib | ||||||
| 
 | 
 | ||||||
| def teardown_module(mod): | def teardown_module(mod): | ||||||
|     serializer._setup_version_dependent_constants() |     TEMPDIR.remove(True) | ||||||
|  |     if _old_pypath is not None: | ||||||
|  |         os.environ["PYTHONPATH"] = _old_pypath | ||||||
| 
 | 
 | ||||||
| def _dump(obj): |  | ||||||
|     stream = py.io.BytesIO() |  | ||||||
|     saver = serializer.Serializer(stream) |  | ||||||
|     saver.save(obj) |  | ||||||
|     return stream.getvalue() |  | ||||||
| 
 | 
 | ||||||
| def _load(serialized, str_coerion): | class PythonWrapper(object): | ||||||
|     stream = py.io.BytesIO(serialized) |  | ||||||
|     opts = serializer.UnserializationOptions(str_coerion) |  | ||||||
|     unserializer = serializer.Unserializer(stream, opts) |  | ||||||
|     return unserializer.load() |  | ||||||
| 
 | 
 | ||||||
| def _run_in_version(is_py3, func, *args): |     def __init__(self, executable): | ||||||
|     serializer._INPY3 = is_py3 |         self.executable = executable | ||||||
|     serializer._setup_version_dependent_constants() |  | ||||||
|     try: |  | ||||||
|         return func(*args) |  | ||||||
|     finally: |  | ||||||
|         serializer._INPY3 = _save_python3 |  | ||||||
| 
 | 
 | ||||||
| def dump_py2(obj): |     def dump(self, obj_rep): | ||||||
|     return _run_in_version(False, _dump, obj) |         script_file = TEMPDIR.join("dump.py") | ||||||
|  |         script_file.write(""" | ||||||
|  | from py.__.execnet import serializer | ||||||
|  | import sys | ||||||
|  | if sys.version_info > (3, 0): # Need binary output | ||||||
|  |     sys.stdout = sys.stdout.detach() | ||||||
|  | saver = serializer.Serializer(sys.stdout) | ||||||
|  | saver.save(%s)""" % (obj_rep,)) | ||||||
|  |         return self.executable.sysexec(script_file) | ||||||
| 
 | 
 | ||||||
| def dump_py3(obj): |     def load(self, data, option_args=""): | ||||||
|     return _run_in_version(True, _dump, obj) |         script_file = TEMPDIR.join("load.py") | ||||||
|  |         script_file.write(r""" | ||||||
|  | from py.__.execnet import serializer | ||||||
|  | import sys | ||||||
|  | if sys.version_info > (3, 0): | ||||||
|  |     sys.stdin = sys.stdin.detach() | ||||||
|  | options = serializer.UnserializationOptions(%s) | ||||||
|  | loader = serializer.Unserializer(sys.stdin, options) | ||||||
|  | obj = loader.load() | ||||||
|  | sys.stdout.write(type(obj).__name__ + "\n") | ||||||
|  | sys.stdout.write(repr(obj))""" % (option_args,)) | ||||||
|  |         popen = subprocess.Popen([str(self.executable), str(script_file)], | ||||||
|  |                                  stdin=subprocess.PIPE, | ||||||
|  |                                  stderr=subprocess.PIPE, | ||||||
|  |                                  stdout=subprocess.PIPE) | ||||||
|  |         stdout, stderr = popen.communicate(data.encode("latin-1")) | ||||||
|  |         ret = popen.returncode | ||||||
|  |         if ret: | ||||||
|  |             raise py.process.cmdexec.Error(ret, ret, str(self.executable), | ||||||
|  |                                            stdout, stderr) | ||||||
|  |         return [s.decode("ascii") for s in stdout.splitlines()] | ||||||
| 
 | 
 | ||||||
| def load_py2(serialized, str_coercion=False): |     def __repr__(self): | ||||||
|     return _run_in_version(False, _load, serialized, str_coercion) |         return "<PythonWrapper for %s>" % (self.executable,) | ||||||
| 
 |  | ||||||
| def load_py3(serialized, str_coercion=False): |  | ||||||
|     return _run_in_version(True, _load, serialized, str_coercion) |  | ||||||
| 
 |  | ||||||
| try: |  | ||||||
|     bytes |  | ||||||
| except NameError: |  | ||||||
|     bytes = str |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_funcarg__py2(request): | def pytest_funcarg__py2(request): | ||||||
|  | @ -55,73 +83,86 @@ def pytest_funcarg__py3(request): | ||||||
| 
 | 
 | ||||||
| class TestSerializer: | class TestSerializer: | ||||||
| 
 | 
 | ||||||
|     def test_int(self): |     def test_int(self, py2, py3): | ||||||
|         for dump in dump_py2, dump_py3: |         for dump in py2.dump, py3.dump: | ||||||
|             p = dump_py2(4) |             p = dump(4) | ||||||
|             for load in load_py2, load_py3: |             for load in py2.load, py3.load: | ||||||
|                 i = load(p) |                 tp, v = load(p) | ||||||
|                 assert isinstance(i, int) |                 assert tp == "int" | ||||||
|                 assert i == 4 |                 assert int(v) == 4 | ||||||
|             py.test.raises(serializer.SerializationError, dump, 123456678900) |         py.test.raises(serializer.SerializationError, | ||||||
|  |                        serializer.Serializer(py.io.BytesIO()).save, | ||||||
|  |                        123456678900) | ||||||
| 
 | 
 | ||||||
|     def test_bytes(self): |     def test_bytes(self, py2, py3): | ||||||
|         for dump in dump_py2, dump_py3: |         p = py3.dump("b'hi'") | ||||||
|             p = dump(serializer._b('hi')) |         tp, v = py2.load(p) | ||||||
|             for load in load_py2, load_py3: |         assert tp == "str" | ||||||
|                 s = load(p) |         assert v == "'hi'" | ||||||
|                 assert isinstance(s, serializer.bytes) |         tp, v = py3.load(p) | ||||||
|                 assert s == serializer._b('hi') |         assert tp == "bytes" | ||||||
|  |         assert v == "b'hi'" | ||||||
| 
 | 
 | ||||||
|     def check_sequence(self, seq): |     def check_sequence(self, seq, tp_name, rep, py2, py3): | ||||||
|         for dump in dump_py2, dump_py3: |         for dump in py2.dump, py3.dump: | ||||||
|             p = dump(seq) |             p = dump(seq) | ||||||
|             for load in load_py2, load_py3: |             for load in py2.load, py3.load: | ||||||
|                 l = load(p) |                 tp, v = load(p) | ||||||
|                 assert l == seq |                 assert tp == tp_name | ||||||
|  |                 assert v == rep | ||||||
| 
 | 
 | ||||||
|     def test_list(self): |     def test_list(self, py2, py3): | ||||||
|         self.check_sequence([1, 2, 3]) |         self.check_sequence([1, 2, 3], "list", "[1, 2, 3]", py2, py3) | ||||||
| 
 | 
 | ||||||
|     @py.test.mark.xfail |     @py.test.mark.xfail | ||||||
|     # I'm not sure if we need the complexity. |     # I'm not sure if we need the complexity. | ||||||
|     def test_recursive_list(self): |     def test_recursive_list(self, py2, py3): | ||||||
|         l = [1, 2, 3] |         l = [1, 2, 3] | ||||||
|         l.append(l) |         l.append(l) | ||||||
|         self.check_sequence(l) |         p = py2.dump(l) | ||||||
|  |         tp, rep = py2.load(l) | ||||||
|  |         assert tp == "list" | ||||||
| 
 | 
 | ||||||
|     def test_tuple(self): |     def test_tuple(self, py2, py3): | ||||||
|         self.check_sequence((1, 2, 3)) |         self.check_sequence((1, 2, 3), "tuple", "(1, 2, 3)", py2, py3) | ||||||
| 
 | 
 | ||||||
|     def test_dict(self): |     def test_dict(self, py2, py3): | ||||||
|         for dump in dump_py2, dump_py3: |         for dump in py2.dump, py3.dump: | ||||||
|             p = dump({"hi" : 2, (1, 2, 3) : 32}) |             p = dump({6 : 2, (1, 2, 3) : 32}) | ||||||
|             for load in load_py2, load_py3: |             for load in py2.load, py3.load: | ||||||
|                 d = load(p, True) |                 tp, v = load(p) | ||||||
|                 assert d == {"hi" : 2, (1, 2, 3) : 32} |                 assert tp == "dict" | ||||||
|  |                 # XXX comparing dict reprs | ||||||
|  |                 assert v == "{6: 2, (1, 2, 3): 32}" | ||||||
| 
 | 
 | ||||||
|     def test_string(self): |     def test_string(self, py2, py3): | ||||||
|         py.test.skip("will rewrite") |         p = py2.dump("'xyz'") | ||||||
|         p = dump_py2("xyz") |         tp, s = py2.load(p) | ||||||
|         s = load_py2(p) |         assert tp == "str" | ||||||
|         assert isinstance(s, str) |         assert s == "'xyz'" | ||||||
|         assert s == "xyz" |         tp, s = py3.load(p) | ||||||
|         s = load_py3(p) |         assert tp == "bytes" | ||||||
|         assert isinstance(s, bytes) |         assert s == "b'xyz'" | ||||||
|         assert s == serializer.b("xyz") |         tp, s = py3.load(p, "True") | ||||||
|         p = dump_py2("xyz") |         assert tp == "str" | ||||||
|         s = load_py3(p, True) |         assert s == "'xyz'" | ||||||
|         assert isinstance(s, serializer._unicode) |         p = py3.dump("'xyz'") | ||||||
|         assert s == serializer.unicode("xyz") |         tp, s = py2.load(p, True) | ||||||
|         p = dump_py3("xyz") |         assert tp == "str" | ||||||
|         s = load_py2(p, True) |         assert s == "'xyz'" | ||||||
|         assert isinstance(s, str) |  | ||||||
|         assert s == "xyz" |  | ||||||
| 
 | 
 | ||||||
|     def test_unicode(self): |     def test_unicode(self, py2, py3): | ||||||
|         py.test.skip("will rewrite") |         p = py2.dump("u'hi'") | ||||||
|         for dump, uni in (dump_py2, serializer._unicode), (dump_py3, str): |         tp, s = py2.load(p) | ||||||
|             p = dump(uni("xyz")) |         assert tp == "unicode" | ||||||
|             for load in load_py2, load_py3: |         assert s == "u'hi'" | ||||||
|                 s = load(p) |         tp, s = py3.load(p) | ||||||
|                 assert isinstance(s, serializer._unicode) |         assert tp == "str" | ||||||
|                 assert s == serializer._unicode("xyz") |         assert s == "'hi'" | ||||||
|  |         p = py3.dump("'hi'") | ||||||
|  |         tp, s = py3.load(p) | ||||||
|  |         assert tp == "str" | ||||||
|  |         assert s == "'hi'" | ||||||
|  |         tp, s = py2.load(p) | ||||||
|  |         assert tp == "unicode" | ||||||
|  |         assert s == "u'hi'" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue