Skip to content

Commit 7ba3de8

Browse files
Fix compatibility.
1 parent dd2cd6b commit 7ba3de8

File tree

3 files changed

+111
-96
lines changed

3 files changed

+111
-96
lines changed

Lib/test/pickletester.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,6 +2630,7 @@ def check(code, exc):
26302630
class AbstractPickleTests:
26312631
# Subclass must define self.dumps, self.loads.
26322632

2633+
py_version = sys.version_info # for test_xpickle
26332634
optimized = False
26342635

26352636
_testdata = AbstractUnpickleTests._testdata
@@ -3218,8 +3219,15 @@ def test_builtin_types(self):
32183219
self.assertIs(self.loads(s), t)
32193220

32203221
def test_builtin_exceptions(self):
3222+
new_names = {'EncodingWarning': (3, 10),
3223+
'BaseExceptionGroup': (3, 11),
3224+
'ExceptionGroup': (3, 11),
3225+
'_IncompleteInputError': (3, 13),
3226+
'PythonFinalizationError': (3, 13)}
32213227
for t in builtins.__dict__.values():
32223228
if isinstance(t, type) and issubclass(t, BaseException):
3229+
if t.__name__ in new_names and self.py_version < new_names[t.__name__]:
3230+
continue
32233231
for proto in protocols:
32243232
s = self.dumps(t, proto)
32253233
u = self.loads(s)
@@ -3231,8 +3239,11 @@ def test_builtin_exceptions(self):
32313239
self.assertIs(u, t)
32323240

32333241
def test_builtin_functions(self):
3242+
new_names = {'aiter': (3, 10), 'anext': (3, 10)}
32343243
for t in builtins.__dict__.values():
32353244
if isinstance(t, types.BuiltinFunctionType):
3245+
if t.__name__ in new_names and self.py_version < new_names[t.__name__]:
3246+
continue
32363247
for proto in protocols:
32373248
s = self.dumps(t, proto)
32383249
self.assertIs(self.loads(s), t)

Lib/test/support/__init__.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Supporting definitions for the Python regression tests."""
22

3+
from __future__ import annotations # for test_xpickle
4+
35
if __name__ != 'test.support':
46
raise ImportError('support must be imported from the test package')
57

6-
import annotationlib
78
import contextlib
89
import functools
910
import inspect
@@ -645,7 +646,7 @@ def requires_working_socket(*, module=False):
645646
return unittest.skipUnless(has_socket_support, msg)
646647

647648

648-
@functools.cache
649+
@functools.lru_cache()
649650
def has_remote_subprocess_debugging():
650651
"""Check if we have permissions to debug subprocesses remotely.
651652
@@ -2545,7 +2546,7 @@ def requires_venv_with_pip():
25452546
return unittest.skipUnless(ctypes, 'venv: pip requires ctypes')
25462547

25472548

2548-
@functools.cache
2549+
@functools.lru_cache()
25492550
def _findwheel(pkgname):
25502551
"""Try to find a wheel with the package specified as pkgname.
25512552
@@ -2789,7 +2790,10 @@ def exceeds_recursion_limit():
27892790

27902791
Py_TRACE_REFS = hasattr(sys, 'getobjects')
27912792

2792-
_JIT_ENABLED = sys._jit.is_enabled()
2793+
try:
2794+
_JIT_ENABLED = sys._jit.is_enabled()
2795+
except AttributeError:
2796+
_JIT_ENABLED = False
27932797
requires_jit_enabled = unittest.skipUnless(_JIT_ENABLED, "requires JIT enabled")
27942798
requires_jit_disabled = unittest.skipIf(_JIT_ENABLED, "requires JIT disabled")
27952799

@@ -2996,10 +3000,8 @@ def force_color(color: bool):
29963000
import _colorize
29973001
from .os_helper import EnvironmentVarGuard
29983002

2999-
with (
3000-
swap_attr(_colorize, "can_colorize", lambda *, file=None: color),
3001-
EnvironmentVarGuard() as env,
3002-
):
3003+
with swap_attr(_colorize, "can_colorize", lambda *, file=None: color), \
3004+
EnvironmentVarGuard() as env:
30033005
env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS")
30043006
env.set("FORCE_COLOR" if color else "NO_COLOR", "1")
30053007
yield
@@ -3218,9 +3220,11 @@ def __init__(
32183220
self.__forward_is_class__ = is_class
32193221
self.__forward_module__ = module
32203222
self.__owner__ = owner
3223+
import annotationlib
3224+
self._ForwardRef = annotationlib.ForwardRef
32213225

32223226
def __eq__(self, other):
3223-
if not isinstance(other, (EqualToForwardRef, annotationlib.ForwardRef)):
3227+
if not isinstance(other, (EqualToForwardRef, self._ForwardRef)):
32243228
return NotImplemented
32253229
return (
32263230
self.__forward_arg__ == other.__forward_arg__

Lib/test/test_xpickle.py

Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
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
45
import os
56
import pathlib
67
import pickle
78
import subprocess
89
import sys
10+
import unittest
911

1012

1113
from test import support
1214
from test import pickletester
13-
from test.test_pickle import PyPicklerTests
1415

1516
try:
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

230215
if 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

259259
if __name__ == '__main__':
260-
test_main()
260+
unittest.main()

0 commit comments

Comments
 (0)