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 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. 7 8 import cPickle 9 import os 10 import os.path 11 import pickle 12 import subprocess 13 import sys 14 import types 15 import unittest 16 17 from test import test_support 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") 28 exec compile(open(mod_path).read(), mod_path, 'exec') in pickletester.__dict__ 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 34 35 class DumpCPickle_LoadPickle(AbstractPickleTests): 36 37 error = KeyError 38 39 def dumps(self, arg, proto=0, fast=False): 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 51 def dumps(self, arg, proto=0, fast=False): 52 # Ignore fast 53 return pickle.dumps(arg, proto) 54 55 def loads(self, buf): 56 # Ignore fast 57 return cPickle.loads(buf) 58 59 def have_python_version(name): 60 """Check whether the given name is a valid Python binary and has 61 test.test_support. 62 63 This respects your PATH. 64 65 Args: 66 name: short string name of a Python binary such as "python2.4". 67 68 Returns: 69 True if the name is valid, False otherwise. 70 """ 71 return os.system(name + " -c 'import test.test_support'") == 0 72 73 74 class AbstractCompatTests(AbstractPickleTests): 75 76 module = None 77 python = None 78 error = None 79 80 def setUp(self): 81 self.assertTrue(self.python) 82 self.assertTrue(self.module) 83 self.assertTrue(self.error) 84 85 def send_to_worker(self, python, obj, proto): 86 """Bounce a pickled object through another version of Python. 87 88 This will pickle the object, send it to a child process where it will be 89 unpickled, then repickled and sent back to the parent process. 90 91 Args: 92 python: the name of the Python binary to start. 93 obj: object to pickle. 94 proto: pickle protocol number to use. 95 96 Returns: 97 The pickled data received from the child process. 98 """ 99 # Prevent the subprocess from picking up invalid .pyc files. 100 target = __file__ 101 if target[-1] in ("c", "o"): 102 target = target[:-1] 103 104 data = self.module.dumps((proto, obj), proto) 105 worker = subprocess.Popen([python, target, "worker"], 106 stdin=subprocess.PIPE, 107 stdout=subprocess.PIPE, 108 stderr=subprocess.PIPE) 109 stdout, stderr = worker.communicate(data) 110 if worker.returncode != 0: 111 raise RuntimeError(stderr) 112 return stdout 113 114 def dumps(self, arg, proto=0, fast=False): 115 return self.send_to_worker(self.python, arg, proto) 116 117 def loads(self, input): 118 return self.module.loads(input) 119 120 # These tests are disabled because they require some special setup 121 # on the worker that's hard to keep in sync. 122 def test_global_ext1(self): 123 pass 124 125 def test_global_ext2(self): 126 pass 127 128 def test_global_ext4(self): 129 pass 130 131 # This is a cut-down version of pickletester's test_float. Backwards 132 # compatibility for the values in for_bin_protos was explicitly broken in 133 # r68903 to fix a bug. 134 def test_float(self): 135 for_bin_protos = [4.94e-324, 1e-310] 136 neg_for_bin_protos = [-x for x in for_bin_protos] 137 test_values = [0.0, 7e-308, 6.626e-34, 0.1, 0.5, 138 3.14, 263.44582062374053, 6.022e23, 1e30] 139 test_proto0_values = test_values + [-x for x in test_values] 140 test_values = test_proto0_values + for_bin_protos + neg_for_bin_protos 141 142 for value in test_proto0_values: 143 pickle = self.dumps(value, 0) 144 got = self.loads(pickle) 145 self.assertEqual(value, got) 146 147 for proto in pickletester.protocols[1:]: 148 for value in test_values: 149 pickle = self.dumps(value, proto) 150 got = self.loads(pickle) 151 self.assertEqual(value, got) 152 153 # Backwards compatibility was explicitly broken in r67934 to fix a bug. 154 def test_unicode_high_plane(self): 155 pass 156 157 # This tests a fix that's in 2.7 only 158 def test_dynamic_class(self): 159 pass 160 161 if test_support.have_unicode: 162 # This is a cut-down version of pickletester's test_unicode. Backwards 163 # compatibility was explicitly broken in r67934 to fix a bug. 164 def test_unicode(self): 165 endcases = [u'', u'<\\u>', u'<\\\u1234>', u'<\n>', u'<\\>'] 166 for proto in pickletester.protocols: 167 for u in endcases: 168 p = self.dumps(u, proto) 169 u2 = self.loads(p) 170 self.assertEqual(u2, u) 171 172 173 def run_compat_test(python_name): 174 return (test_support.is_resource_enabled("xpickle") and 175 have_python_version(python_name)) 176 177 178 # Test backwards compatibility with Python 2.4. 179 if not run_compat_test("python2.4"): 180 class CPicklePython24Compat(unittest.TestCase): 181 pass 182 else: 183 class CPicklePython24Compat(AbstractCompatTests): 184 185 module = cPickle 186 python = "python2.4" 187 error = cPickle.BadPickleGet 188 189 # Disable these tests for Python 2.4. Making them pass would require 190 # nontrivially monkeypatching the pickletester module in the worker. 191 def test_reduce_calls_base(self): 192 pass 193 194 def test_reduce_ex_calls_base(self): 195 pass 196 197 class PicklePython24Compat(CPicklePython24Compat): 198 199 module = pickle 200 error = KeyError 201 202 203 # Test backwards compatibility with Python 2.5. 204 if not run_compat_test("python2.5"): 205 class CPicklePython25Compat(unittest.TestCase): 206 pass 207 else: 208 class CPicklePython25Compat(AbstractCompatTests): 209 210 module = cPickle 211 python = "python2.5" 212 error = cPickle.BadPickleGet 213 214 class PicklePython25Compat(CPicklePython25Compat): 215 216 module = pickle 217 error = KeyError 218 219 220 # Test backwards compatibility with Python 2.6. 221 if not run_compat_test("python2.6"): 222 class CPicklePython26Compat(unittest.TestCase): 223 pass 224 else: 225 class CPicklePython26Compat(AbstractCompatTests): 226 227 module = cPickle 228 python = "python2.6" 229 error = cPickle.BadPickleGet 230 231 class PicklePython26Compat(CPicklePython26Compat): 232 233 module = pickle 234 error = KeyError 235 236 237 def worker_main(in_stream, out_stream): 238 message = cPickle.load(in_stream) 239 protocol, obj = message 240 cPickle.dump(obj, out_stream, protocol) 241 242 243 def test_main(): 244 if not test_support.is_resource_enabled("xpickle"): 245 print >>sys.stderr, "test_xpickle -- skipping backwards compat tests." 246 print >>sys.stderr, "Use 'regrtest.py -u xpickle' to run them." 247 sys.stderr.flush() 248 249 test_support.run_unittest( 250 DumpCPickle_LoadPickle, 251 DumpPickle_LoadCPickle, 252 CPicklePython24Compat, 253 CPicklePython25Compat, 254 CPicklePython26Compat, 255 PicklePython24Compat, 256 PicklePython25Compat, 257 PicklePython26Compat, 258 ) 259 260 if __name__ == "__main__": 261 if "worker" in sys.argv: 262 worker_main(sys.stdin, sys.stdout) 263 else: 264 test_main() 265