Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 1 | # test_pickle dumps and loads pickles via pickle.py. |
| 2 | # test_cpickle does the same, but via the cPickle module. |
| 3 | # This test covers the other two cases, making pickles with one module and |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 4 | # loading them via the other. It also tests backwards compatibility with |
| 5 | # previous version of Python by bouncing pickled objects through Python 2.4 |
| 6 | # and Python 2.5 running this file. |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 7 | |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 8 | import cPickle |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 9 | import os |
| 10 | import os.path |
| 11 | import pickle |
| 12 | import subprocess |
| 13 | import sys |
| 14 | import types |
| 15 | import unittest |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 16 | |
| 17 | from test import test_support |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 18 | |
| 19 | # Most distro-supplied Pythons don't include the tests |
| 20 | # or test support files, and some don't include a way to get these back even if |
| 21 | # you're will to install extra packages (like Ubuntu). Doing things like this |
| 22 | # "provides" a pickletester module for older versions of Python that may be |
| 23 | # installed without it. Note that one other design for this involves messing |
| 24 | # with sys.path, which is less precise. |
| 25 | mod_path = os.path.abspath(os.path.join(os.path.dirname(__file__), |
| 26 | "pickletester.py")) |
| 27 | pickletester = types.ModuleType("test.pickletester") |
Ezio Melotti | dde5b94 | 2010-02-03 05:37:26 +0000 | [diff] [blame^] | 28 | exec compile(open(mod_path).read(), mod_path, 'exec') in pickletester.__dict__ |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 29 | AbstractPickleTests = pickletester.AbstractPickleTests |
| 30 | if pickletester.__name__ in sys.modules: |
| 31 | raise RuntimeError("Did not expect to find test.pickletester loaded") |
| 32 | sys.modules[pickletester.__name__] = pickletester |
| 33 | |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 34 | |
| 35 | class DumpCPickle_LoadPickle(AbstractPickleTests): |
| 36 | |
| 37 | error = KeyError |
| 38 | |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 39 | def dumps(self, arg, proto=0, fast=False): |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 40 | # Ignore fast |
| 41 | return cPickle.dumps(arg, proto) |
| 42 | |
| 43 | def loads(self, buf): |
| 44 | # Ignore fast |
| 45 | return pickle.loads(buf) |
| 46 | |
| 47 | class DumpPickle_LoadCPickle(AbstractPickleTests): |
| 48 | |
| 49 | error = cPickle.BadPickleGet |
| 50 | |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 51 | def dumps(self, arg, proto=0, fast=False): |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 52 | # Ignore fast |
| 53 | return pickle.dumps(arg, proto) |
| 54 | |
| 55 | def loads(self, buf): |
| 56 | # Ignore fast |
| 57 | return cPickle.loads(buf) |
| 58 | |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 59 | def have_python_version(name): |
| 60 | """Check whether the given name is a valid Python binary. |
| 61 | |
| 62 | This respects your PATH. |
| 63 | |
| 64 | Args: |
| 65 | name: short string name of a Python binary such as "python2.4". |
| 66 | |
| 67 | Returns: |
| 68 | True if the name is valid, False otherwise. |
| 69 | """ |
| 70 | return os.system(name + " -c 'import sys; sys.exit()'") == 0 |
| 71 | |
| 72 | |
| 73 | class AbstractCompatTests(AbstractPickleTests): |
| 74 | |
| 75 | module = None |
| 76 | python = None |
| 77 | error = None |
| 78 | |
| 79 | def setUp(self): |
| 80 | self.assertTrue(self.python) |
| 81 | self.assertTrue(self.module) |
| 82 | self.assertTrue(self.error) |
| 83 | |
| 84 | def send_to_worker(self, python, obj, proto): |
| 85 | """Bounce a pickled object through another version of Python. |
| 86 | |
| 87 | This will pickle the object, send it to a child process where it will be |
| 88 | unpickled, then repickled and sent back to the parent process. |
| 89 | |
| 90 | Args: |
| 91 | python: the name of the Python binary to start. |
| 92 | obj: object to pickle. |
| 93 | proto: pickle protocol number to use. |
| 94 | |
| 95 | Returns: |
| 96 | The pickled data received from the child process. |
| 97 | """ |
| 98 | # Prevent the subprocess from picking up invalid .pyc files. |
| 99 | target = __file__ |
| 100 | if target[-1] in ("c", "o"): |
| 101 | target = target[:-1] |
| 102 | |
| 103 | data = self.module.dumps((proto, obj), proto) |
| 104 | worker = subprocess.Popen([python, target, "worker"], |
| 105 | stdin=subprocess.PIPE, |
| 106 | stdout=subprocess.PIPE, |
| 107 | stderr=subprocess.PIPE) |
| 108 | stdout, stderr = worker.communicate(data) |
| 109 | if worker.returncode != 0: |
| 110 | raise RuntimeError(stderr) |
| 111 | return stdout |
| 112 | |
| 113 | def dumps(self, arg, proto=0, fast=False): |
| 114 | return self.send_to_worker(self.python, arg, proto) |
| 115 | |
| 116 | def loads(self, input): |
| 117 | return self.module.loads(input) |
| 118 | |
| 119 | # These tests are disabled because they require some special setup |
| 120 | # on the worker that's hard to keep in sync. |
| 121 | def test_global_ext1(self): |
| 122 | pass |
| 123 | |
| 124 | def test_global_ext2(self): |
| 125 | pass |
| 126 | |
| 127 | def test_global_ext4(self): |
| 128 | pass |
| 129 | |
| 130 | # This is a cut-down version of pickletester's test_float. Backwards |
| 131 | # compatibility for the values in for_bin_protos was explicitly broken in |
| 132 | # r68903 to fix a bug. |
| 133 | def test_float(self): |
| 134 | for_bin_protos = [4.94e-324, 1e-310] |
| 135 | neg_for_bin_protos = [-x for x in for_bin_protos] |
| 136 | test_values = [0.0, 7e-308, 6.626e-34, 0.1, 0.5, |
| 137 | 3.14, 263.44582062374053, 6.022e23, 1e30] |
| 138 | test_proto0_values = test_values + [-x for x in test_values] |
| 139 | test_values = test_proto0_values + for_bin_protos + neg_for_bin_protos |
| 140 | |
| 141 | for value in test_proto0_values: |
| 142 | pickle = self.dumps(value, 0) |
| 143 | got = self.loads(pickle) |
| 144 | self.assertEqual(value, got) |
| 145 | |
| 146 | for proto in pickletester.protocols[1:]: |
| 147 | for value in test_values: |
| 148 | pickle = self.dumps(value, proto) |
| 149 | got = self.loads(pickle) |
| 150 | self.assertEqual(value, got) |
| 151 | |
| 152 | # Backwards compatibility was explicitly broken in r67934 to fix a bug. |
| 153 | def test_unicode_high_plane(self): |
| 154 | pass |
| 155 | |
| 156 | if test_support.have_unicode: |
| 157 | # This is a cut-down version of pickletester's test_unicode. Backwards |
| 158 | # compatibility was explicitly broken in r67934 to fix a bug. |
| 159 | def test_unicode(self): |
| 160 | endcases = [u'', u'<\\u>', u'<\\\u1234>', u'<\n>', u'<\\>'] |
| 161 | for proto in pickletester.protocols: |
| 162 | for u in endcases: |
| 163 | p = self.dumps(u, proto) |
| 164 | u2 = self.loads(p) |
| 165 | self.assertEqual(u2, u) |
| 166 | |
| 167 | |
| 168 | def run_compat_test(python_name): |
| 169 | return (test_support.is_resource_enabled("xpickle") and |
| 170 | have_python_version(python_name)) |
| 171 | |
| 172 | |
| 173 | # Test backwards compatibility with Python 2.4. |
| 174 | if not run_compat_test("python2.4"): |
| 175 | class CPicklePython24Compat(unittest.TestCase): |
| 176 | pass |
| 177 | else: |
| 178 | class CPicklePython24Compat(AbstractCompatTests): |
| 179 | |
| 180 | module = cPickle |
| 181 | python = "python2.4" |
| 182 | error = cPickle.BadPickleGet |
| 183 | |
| 184 | # Disable these tests for Python 2.4. Making them pass would require |
| 185 | # nontrivially monkeypatching the pickletester module in the worker. |
| 186 | def test_reduce_calls_base(self): |
| 187 | pass |
| 188 | |
| 189 | def test_reduce_ex_calls_base(self): |
| 190 | pass |
| 191 | |
| 192 | class PicklePython24Compat(CPicklePython24Compat): |
| 193 | |
| 194 | module = pickle |
| 195 | error = KeyError |
| 196 | |
| 197 | |
| 198 | # Test backwards compatibility with Python 2.5. |
| 199 | if not run_compat_test("python2.5"): |
| 200 | class CPicklePython25Compat(unittest.TestCase): |
| 201 | pass |
| 202 | else: |
| 203 | class CPicklePython25Compat(AbstractCompatTests): |
| 204 | |
| 205 | module = cPickle |
| 206 | python = "python2.5" |
| 207 | error = cPickle.BadPickleGet |
| 208 | |
| 209 | class PicklePython25Compat(CPicklePython25Compat): |
| 210 | |
| 211 | module = pickle |
| 212 | error = KeyError |
| 213 | |
| 214 | |
| 215 | # Test backwards compatibility with Python 2.6. |
| 216 | if not run_compat_test("python2.6"): |
| 217 | class CPicklePython26Compat(unittest.TestCase): |
| 218 | pass |
| 219 | else: |
| 220 | class CPicklePython26Compat(AbstractCompatTests): |
| 221 | |
| 222 | module = cPickle |
| 223 | python = "python2.6" |
| 224 | error = cPickle.BadPickleGet |
| 225 | |
| 226 | class PicklePython26Compat(CPicklePython26Compat): |
| 227 | |
| 228 | module = pickle |
| 229 | error = KeyError |
| 230 | |
| 231 | |
| 232 | def worker_main(in_stream, out_stream): |
| 233 | message = cPickle.load(in_stream) |
| 234 | protocol, obj = message |
| 235 | cPickle.dump(obj, out_stream, protocol) |
| 236 | |
| 237 | |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 238 | def test_main(): |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 239 | if not test_support.is_resource_enabled("xpickle"): |
| 240 | print >>sys.stderr, "test_xpickle -- skipping backwards compat tests." |
| 241 | print >>sys.stderr, "Use 'regrtest.py -u xpickle' to run them." |
| 242 | sys.stderr.flush() |
| 243 | |
Walter Dörwald | 21d3a32 | 2003-05-01 17:45:56 +0000 | [diff] [blame] | 244 | test_support.run_unittest( |
| 245 | DumpCPickle_LoadPickle, |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 246 | DumpPickle_LoadCPickle, |
| 247 | CPicklePython24Compat, |
| 248 | CPicklePython25Compat, |
| 249 | CPicklePython26Compat, |
| 250 | PicklePython24Compat, |
| 251 | PicklePython25Compat, |
| 252 | PicklePython26Compat, |
Walter Dörwald | 21d3a32 | 2003-05-01 17:45:56 +0000 | [diff] [blame] | 253 | ) |
Tim Peters | c0c9370 | 2003-02-13 19:30:57 +0000 | [diff] [blame] | 254 | |
| 255 | if __name__ == "__main__": |
Collin Winter | f8089c7 | 2009-04-09 16:46:46 +0000 | [diff] [blame] | 256 | if "worker" in sys.argv: |
| 257 | worker_main(sys.stdin, sys.stdout) |
| 258 | else: |
| 259 | test_main() |