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     if test_support.have_unicode:
    158         # This is a cut-down version of pickletester's test_unicode. Backwards

    159         # compatibility was explicitly broken in r67934 to fix a bug.

    160         def test_unicode(self):
    161             endcases = [u'', u'<\\u>', u'<\\\u1234>', u'<\n>', u'<\\>']
    162             for proto in pickletester.protocols:
    163                 for u in endcases:
    164                     p = self.dumps(u, proto)
    165                     u2 = self.loads(p)
    166                     self.assertEqual(u2, u)
    167 
    168 
    169 def run_compat_test(python_name):
    170     return (test_support.is_resource_enabled("xpickle") and
    171             have_python_version(python_name))
    172 
    173 
    174 # Test backwards compatibility with Python 2.4.

    175 if not run_compat_test("python2.4"):
    176     class CPicklePython24Compat(unittest.TestCase):
    177         pass
    178 else:
    179     class CPicklePython24Compat(AbstractCompatTests):
    180 
    181         module = cPickle
    182         python = "python2.4"
    183         error = cPickle.BadPickleGet
    184 
    185         # Disable these tests for Python 2.4. Making them pass would require

    186         # nontrivially monkeypatching the pickletester module in the worker.

    187         def test_reduce_calls_base(self):
    188             pass
    189 
    190         def test_reduce_ex_calls_base(self):
    191             pass
    192 
    193 class PicklePython24Compat(CPicklePython24Compat):
    194 
    195     module = pickle
    196     error = KeyError
    197 
    198 
    199 # Test backwards compatibility with Python 2.5.

    200 if not run_compat_test("python2.5"):
    201     class CPicklePython25Compat(unittest.TestCase):
    202         pass
    203 else:
    204     class CPicklePython25Compat(AbstractCompatTests):
    205 
    206         module = cPickle
    207         python = "python2.5"
    208         error = cPickle.BadPickleGet
    209 
    210 class PicklePython25Compat(CPicklePython25Compat):
    211 
    212     module = pickle
    213     error = KeyError
    214 
    215 
    216 # Test backwards compatibility with Python 2.6.

    217 if not run_compat_test("python2.6"):
    218     class CPicklePython26Compat(unittest.TestCase):
    219         pass
    220 else:
    221     class CPicklePython26Compat(AbstractCompatTests):
    222 
    223         module = cPickle
    224         python = "python2.6"
    225         error = cPickle.BadPickleGet
    226 
    227 class PicklePython26Compat(CPicklePython26Compat):
    228 
    229     module = pickle
    230     error = KeyError
    231 
    232 
    233 def worker_main(in_stream, out_stream):
    234     message = cPickle.load(in_stream)
    235     protocol, obj = message
    236     cPickle.dump(obj, out_stream, protocol)
    237 
    238 
    239 def test_main():
    240     if not test_support.is_resource_enabled("xpickle"):
    241         print >>sys.stderr, "test_xpickle -- skipping backwards compat tests."
    242         print >>sys.stderr, "Use 'regrtest.py -u xpickle' to run them."
    243         sys.stderr.flush()
    244 
    245     test_support.run_unittest(
    246         DumpCPickle_LoadPickle,
    247         DumpPickle_LoadCPickle,
    248         CPicklePython24Compat,
    249         CPicklePython25Compat,
    250         CPicklePython26Compat,
    251         PicklePython24Compat,
    252         PicklePython25Compat,
    253         PicklePython26Compat,
    254     )
    255 
    256 if __name__ == "__main__":
    257     if "worker" in sys.argv:
    258         worker_main(sys.stdin, sys.stdout)
    259     else:
    260         test_main()
    261