Home | History | Annotate | Download | only in test
      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