11# This test covers backwards compatibility with
22# previous version of Python by bouncing pickled objects through Python 3.6
33# and Python 3.9 by running xpickle_worker.py.
4+ import io
45import os
56import pathlib
67import pickle
78import subprocess
89import sys
10+ import unittest
911
1012
1113from test import support
1214from test import pickletester
13- from test .test_pickle import PyPicklerTests
1415
1516try :
1617 import _pickle
@@ -70,38 +71,41 @@ def have_python_version(py_version):
7071 if py_version not in py_executable_map :
7172 with open (os .devnull , 'w' ) as devnull :
7273 for target in targets [0 if is_windows else 1 :]:
73- worker = subprocess .Popen ([* target , '-c' ,'import test.support' ],
74- stdout = devnull ,
75- stderr = devnull ,
76- shell = is_windows )
77- worker .communicate ()
78- if worker .returncode == 0 :
79- py_executable_map [py_version ] = target
74+ try :
75+ worker = subprocess .Popen ([* target , '-c' ,'import test.support' ],
76+ stdout = devnull ,
77+ stderr = devnull ,
78+ shell = is_windows )
79+ worker .communicate ()
80+ if worker .returncode == 0 :
81+ py_executable_map [py_version ] = target
82+ break
83+ except FileNotFoundError :
84+ pass
8085
8186 return py_executable_map .get (py_version , None )
8287
8388
84-
85- class AbstractCompatTests (PyPicklerTests ):
89+ class AbstractCompatTests (pickletester .AbstractPickleTests ):
8690 py_version = None
8791 _OLD_HIGHEST_PROTOCOL = pickle .HIGHEST_PROTOCOL
8892
89- def setUp (self ):
90- self .assertIsNotNone (self .py_version ,
91- msg = 'Needs a python version tuple' )
92- if not have_python_version (self .py_version ):
93- py_version_str = "." .join (map (str , self .py_version ))
94- self .skipTest (f'Python { py_version_str } not available' )
95-
93+ @classmethod
94+ def setUpClass (cls ):
95+ assert cls .py_version is not None , 'Needs a python version tuple'
96+ if not have_python_version (cls .py_version ):
97+ py_version_str = "." .join (map (str , cls .py_version ))
98+ raise unittest .SkipTest (f'Python { py_version_str } not available' )
9699 # Override the default pickle protocol to match what xpickle worker
97100 # will be running.
98- highest_protocol = highest_proto_for_py_version (self .py_version )
101+ highest_protocol = highest_proto_for_py_version (cls .py_version )
99102 pickletester .protocols = range (highest_protocol + 1 )
100103 pickle .HIGHEST_PROTOCOL = highest_protocol
101104
102- def tearDown (self ):
105+ @classmethod
106+ def tearDownClass (cls ):
103107 # Set the highest protocol back to the default.
104- pickle .HIGHEST_PROTOCOL = self ._OLD_HIGHEST_PROTOCOL
108+ pickle .HIGHEST_PROTOCOL = cls ._OLD_HIGHEST_PROTOCOL
105109 pickletester .protocols = range (pickle .HIGHEST_PROTOCOL + 1 )
106110
107111 @staticmethod
@@ -131,6 +135,11 @@ def send_to_worker(python, data):
131135 except (pickle .UnpicklingError , EOFError ):
132136 raise RuntimeError (stderr )
133137 else :
138+ if support .verbose > 1 :
139+ print ()
140+ print (f'{ data = } ' )
141+ print (f'{ stdout = } ' )
142+ print (f'{ stderr = } ' )
134143 if isinstance (exception , Exception ):
135144 # To allow for tests which test for errors.
136145 raise exception
@@ -144,12 +153,18 @@ def dumps(self, arg, proto=0, **kwargs):
144153 # it works in a different Python version.
145154 if 'buffer_callback' in kwargs :
146155 self .skipTest ('Test does not support "buffer_callback" argument.' )
147- data = super ().dumps ((proto , arg ), proto , ** kwargs )
156+ f = io .BytesIO ()
157+ p = self .pickler (f , proto , ** kwargs )
158+ p .dump ((proto , arg ))
159+ f .seek (0 )
160+ data = bytes (f .read ())
148161 python = py_executable_map [self .py_version ]
149162 return self .send_to_worker (python , data )
150163
151- def loads (self , * args , ** kwargs ):
152- return super ().loads (* args , ** kwargs )
164+ def loads (self , buf , ** kwds ):
165+ f = io .BytesIO (buf )
166+ u = self .unpickler (f , ** kwds )
167+ return u .load ()
153168
154169 # A scaled-down version of test_bytes from pickletester, to reduce
155170 # the number of calls to self.dumps() and hence reduce the number of
@@ -174,9 +189,6 @@ def test_bytes(self):
174189 test_global_ext2 = None
175190 test_global_ext4 = None
176191
177- # Backwards compatibility was explicitly broken in r67934 to fix a bug.
178- test_unicode_high_plane = None
179-
180192 # These tests fail because they require classes from pickletester
181193 # which cannot be properly imported by the xpickle worker.
182194 test_c_methods = None
@@ -185,76 +197,64 @@ def test_bytes(self):
185197
186198 test_recursive_dict_key = None
187199 test_recursive_nested_names = None
200+ test_recursive_nested_names2 = None
188201 test_recursive_set = None
189202
190203 # Attribute lookup problems are expected, disable the test
191204 test_dynamic_class = None
205+ test_evil_class_mutating_dict = None
192206
193- # Base class for tests using Python 3.7 and earlier
194- class CompatLowerPython37 (AbstractCompatTests ):
195- # Python versions 3.7 and earlier are incompatible with these tests:
196-
197- # This version does not support buffers
198- test_in_band_buffers = None
199-
200-
201- # Base class for tests using Python 3.6 and earlier
202- class CompatLowerPython36 (CompatLowerPython37 ):
203- # Python versions 3.6 and earlier are incompatible with these tests:
204- # This version has changes in framing using protocol 4
205- test_framing_large_objects = None
207+ # Expected exception is raised during unpickling in a subprocess.
208+ test_pickle_setstate_None = None
206209
207- # These fail for protocol 0
208- test_simple_newobj = None
209- test_complex_newobj = None
210- test_complex_newobj_ex = None
211-
212-
213- # Test backwards compatibility with Python 3.6.
214- class PicklePython36Compat (CompatLowerPython36 ):
215- py_version = (3 , 6 )
216-
217- # Test backwards compatibility with Python 3.7.
218- class PicklePython37Compat (CompatLowerPython37 ):
219- py_version = (3 , 7 )
220-
221- # Test backwards compatibility with Python 3.8.
222- class PicklePython38Compat (AbstractCompatTests ):
223- py_version = (3 , 8 )
224-
225- # Test backwards compatibility with Python 3.9.
226- class PicklePython39Compat (AbstractCompatTests ):
227- py_version = (3 , 9 )
228210
211+ class PyPicklePythonCompat (AbstractCompatTests ):
212+ pickler = pickle ._Pickler
213+ unpickler = pickle ._Unpickler
229214
230215if has_c_implementation :
231- class CPicklePython36Compat (PicklePython36Compat ):
232- pickler = pickle ._Pickler
233- unpickler = pickle ._Unpickler
234-
235- class CPicklePython37Compat (PicklePython37Compat ):
236- pickler = pickle ._Pickler
237- unpickler = pickle ._Unpickler
238-
239- class CPicklePython38Compat (PicklePython38Compat ):
240- pickler = pickle ._Pickler
241- unpickler = pickle ._Unpickler
242-
243- class CPicklePython39Compat (PicklePython39Compat ):
244- pickler = pickle ._Pickler
245- unpickler = pickle ._Unpickler
246-
247- def test_main ():
248- support .requires ('xpickle' )
249- tests = [PicklePython36Compat ,
250- PicklePython37Compat , PicklePython38Compat ,
251- PicklePython39Compat ]
252- if has_c_implementation :
253- tests .extend ([CPicklePython36Compat ,
254- CPicklePython37Compat , CPicklePython38Compat ,
255- CPicklePython39Compat ])
256- support .run_unittest (* tests )
216+ class CPicklePythonCompat (AbstractCompatTests ):
217+ pickler = _pickle .Pickler
218+ unpickler = _pickle .Unpickler
219+
220+
221+ skip_tests = {
222+ (3 , 6 ): [
223+ # This version has changes in framing using protocol 4
224+ 'test_framing_large_objects' ,
225+
226+ # These fail for protocol 0
227+ 'test_simple_newobj' ,
228+ 'test_complex_newobj' ,
229+ 'test_complex_newobj_ex' ,
230+ ],
231+ (3 , 7 ): [
232+ # This version does not support buffers
233+ 'test_in_band_buffers' ,
234+ ],
235+ }
236+
237+
238+ def make_test (py_version , base ):
239+ class_dict = {'py_version' : py_version }
240+ for key , value in skip_tests .items ():
241+ if py_version <= key :
242+ for test_name in value :
243+ class_dict [test_name ] = None
244+ name = base .__name__ .replace ('Python' , 'Python%d%d' % py_version )
245+ return type (name , (base , unittest .TestCase ), class_dict )
246+
247+ def load_tests (loader , tests , pattern ):
248+ major = sys .version_info .major
249+ assert major == 3
250+ for minor in range (sys .version_info .minor ):
251+ test_class = make_test ((major , minor ), PyPicklePythonCompat )
252+ tests .addTest (loader .loadTestsFromTestCase (test_class ))
253+ if has_c_implementation :
254+ test_class = make_test ((major , minor ), CPicklePythonCompat )
255+ tests .addTest (loader .loadTestsFromTestCase (test_class ))
256+ return tests
257257
258258
259259if __name__ == '__main__' :
260- test_main ()
260+ unittest . main ()
0 commit comments