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, cache={}):
     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     if name not in cache:
     72         cache[name] = os.system(name + ' -c "import test.test_support"') == 0
     73     return cache[name]
     74 
     75 
     76 class AbstractCompatTests(AbstractPickleTests):
     77 
     78     module = None
     79     python = None
     80     error = None
     81 
     82     def setUp(self):
     83         self.assertTrue(self.python)
     84         self.assertTrue(self.module)
     85         self.assertTrue(self.error)
     86         test_support.requires("xpickle")
     87         if not have_python_version(self.python):
     88             self.skipTest('%s not available' % self.python)
     89 
     90     def send_to_worker(self, python, obj, proto):
     91         """Bounce a pickled object through another version of Python.
     92 
     93         This will pickle the object, send it to a child process where it will be
     94         unpickled, then repickled and sent back to the parent process.
     95 
     96         Args:
     97             python: the name of the Python binary to start.
     98             obj: object to pickle.
     99             proto: pickle protocol number to use.
    100 
    101         Returns:
    102             The pickled data received from the child process.
    103         """
    104         # Prevent the subprocess from picking up invalid .pyc files.
    105         target = __file__
    106         if target[-1] in ("c", "o"):
    107             target = target[:-1]
    108 
    109         data = self.module.dumps((proto, obj), proto)
    110         worker = subprocess.Popen([python, target, "worker"],
    111                                   stdin=subprocess.PIPE,
    112                                   stdout=subprocess.PIPE,
    113                                   stderr=subprocess.PIPE)
    114         stdout, stderr = worker.communicate(data)
    115         if worker.returncode != 0:
    116             raise RuntimeError(stderr)
    117         return stdout
    118 
    119     def dumps(self, arg, proto=0, fast=False):
    120         return self.send_to_worker(self.python, arg, proto)
    121 
    122     def loads(self, input):
    123         return self.module.loads(input)
    124 
    125     # These tests are disabled because they require some special setup
    126     # on the worker that's hard to keep in sync.
    127     test_global_ext1 = None
    128     test_global_ext2 = None
    129     test_global_ext4 = None
    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     test_unicode_high_plane = None
    155 
    156     # This tests a fix that's in 2.7 only
    157     test_dynamic_class = None
    158 
    159     # This is a cut-down version of pickletester's test_unicode. Backwards
    160     # compatibility was explicitly broken in r67934 to fix a bug.
    161     def test_unicode(self):
    162         if not test_support.have_unicode:
    163             # Python 2.5 has no unittest.skipUnless
    164             self.skipTest('no unicode support')
    165         endcases = [u'', u'<\\u>', u'<\\%c>' % 0x1234, 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     # The ability to pickle recursive objects was added in 2.7.11 to fix
    173     # a crash in CPickle (issue #892902).
    174     test_recursive_list_subclass_and_inst = None
    175     test_recursive_tuple_subclass_and_inst = None
    176     test_recursive_dict_subclass_and_inst = None
    177     test_recursive_set_and_inst = None
    178     test_recursive_frozenset_and_inst = None
    179 
    180 
    181 # Test backwards compatibility with Python 2.4.
    182 class CPicklePython24Compat(AbstractCompatTests):
    183 
    184     module = cPickle
    185     python = "python2.4"
    186     error = cPickle.BadPickleGet
    187 
    188     # Disable these tests for Python 2.4. Making them pass would require
    189     # nontrivially monkeypatching the pickletester module in the worker.
    190     test_reduce_calls_base = None
    191     test_reduce_ex_calls_base = None
    192 
    193 class PicklePython24Compat(CPicklePython24Compat):
    194 
    195     module = pickle
    196     error = KeyError
    197 
    198 
    199 # Test backwards compatibility with Python 2.5.
    200 class CPicklePython25Compat(AbstractCompatTests):
    201 
    202     module = cPickle
    203     python = "python2.5"
    204     error = cPickle.BadPickleGet
    205 
    206 class PicklePython25Compat(CPicklePython25Compat):
    207 
    208     module = pickle
    209     error = KeyError
    210 
    211 
    212 # Test backwards compatibility with Python 2.6.
    213 class CPicklePython26Compat(AbstractCompatTests):
    214 
    215     module = cPickle
    216     python = "python2.6"
    217     error = cPickle.BadPickleGet
    218 
    219 class PicklePython26Compat(CPicklePython26Compat):
    220 
    221     module = pickle
    222     error = KeyError
    223 
    224 
    225 class CPicklePython27Compat(AbstractCompatTests):
    226 
    227     module = cPickle
    228     python = "python2.7"
    229     error = cPickle.BadPickleGet
    230 
    231 class PicklePython27Compat(CPicklePython27Compat):
    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     test_support.run_unittest(
    245         DumpCPickle_LoadPickle,
    246         DumpPickle_LoadCPickle,
    247         CPicklePython24Compat,
    248         CPicklePython25Compat,
    249         CPicklePython26Compat,
    250         CPicklePython27Compat,
    251         PicklePython24Compat,
    252         PicklePython25Compat,
    253         PicklePython26Compat,
    254         PicklePython27Compat,
    255     )
    256 
    257 if __name__ == "__main__":
    258     if "worker" in sys.argv:
    259         worker_main(sys.stdin, sys.stdout)
    260     else:
    261         test_main()
    262