8000 gh-132775: Add _PyMarshal_GetXIData() (gh-133108) · python/cpython@bdd23c0 · GitHub
[go: up one dir, main page]

Skip to content

Commit bdd23c0

Browse files
gh-132775: Add _PyMarshal_GetXIData() (gh-133108)
Note that the bulk of this change is tests.
1 parent 68a7376 commit bdd23c0

File tree

5 files changed

+337
-10
lines changed

5 files changed

+337
-10
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,13 @@ PyAPI_FUNC(_PyBytes_data_t *) _PyBytes_GetXIDataWrapped(
171171
xid_newobjfunc,
172172
_PyXIData_t *);
173173

174+
// _PyObject_GetXIData() for marshal
175+
PyAPI_FUNC(PyObject *) _PyMarshal_ReadObjectFromXIData(_PyXIData_t *);
176+
PyAPI_FUNC(int) _PyMarshal_GetXIData(
177+
PyThreadState *,
178+
PyObject *,
179+
_PyXIData_t *);
180+
174181

175182
/* using cross-interpreter data */
176183

Lib/test/_crossinterp_definitions.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def ham_C_closure(z):
100100
ham_C_closure, *_ = eggs_closure_C(2)
101101

102102

103-
FUNCTIONS = [
103+
TOP_FUNCTIONS = [
104104
# shallow
105105
spam_minimal,
106106
spam_full,
@@ -112,6 +112,8 @@ def ham_C_closure(z):
112112
spam_NC,
113113
spam_CN,
114114
spam_CC,
115+
]
116+
NESTED_FUNCTIONS = [
115117
# inner func
116118
eggs_nested,
117119
eggs_closure,
@@ -125,6 +127,10 @@ def ham_C_closure(z):
125127
ham_C_nested,
126128
ham_C_closure,
127129
]
130+
FUNCTIONS = [
131+
*TOP_FUNCTIONS,
132+
*NESTED_FUNCTIONS,
133+
]
128134

129135

130136
#######################################
@@ -157,8 +163,10 @@ async def asyncgen_spam(*args):
157163
gen_spam_1,
158164
gen_spam_2,
159165
async_spam,
160-
coro_spam, # actually FunctionType?
161166
asyncgen_spam,
167+
]
168+
FUNCTION_LIKE_APPLIED = [
169+
coro_spam, # actually FunctionType?
162170
asynccoro_spam, # actually FunctionType?
163171
]
164172

@@ -202,6 +210,13 @@ def __init__(self, a, b, c):
202210
# __str__
203211
# ...
204212

213+
def __eq__(self, other):
214+
if not isinstance(other, SpamFull):
215+
return NotImplemented
216+
return (self.a == other.a and
217+
self.b == other.b and
218+
self.c == other.c)
219+
205220
@property
206221
def prop(self):
207222
return True
@@ -222,9 +237,47 @@ class EggsNested:
222237
EggsNested = class_eggs_inner()
223238

224239

240+
TOP_CLASSES = {
241+
Spam: (),
242+
SpamOkay: (),
243+
SpamFull: (1, 2, 3),
244+
SubSpamFull: (1, 2, 3),
245+
SubTuple: ([1, 2, 3],),
246+
}
247+
CLASSES_WITHOUT_EQUALITY = [
248+
Spam,
249+
SpamOkay,
250+
]
251+
BUILTIN_SUBCLASSES = [
252+
SubTuple,
253+
]
254+
NESTED_CLASSES = {
255+
EggsNested: (),
256+
}
257+
CLASSES = {
258+
**TOP_CLASSES,
259+
**NESTED_CLASSES,
260+
}
261+
225262

226263
#######################################
227264
# exceptions
228265

229266
class MimimalError(Exception):
230267
pass
268+
269+
270+
class RichError(Exception):
271+
def __init__(self, msg, value=None):
272+
super().__init__(msg, value)
273+
self.msg = msg
274+
self.value = value
275+
276+
def __eq__(self, other):
277+
if not isinstance(other, RichError):
278+
return NotImplemented
279+
if self.msg != other.msg:
280+
return False
281+
if self.value != other.value:
282+
return False
283+
return True

Lib/test/test_crossinterp.py

Lines changed: 227 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
if isinstance(o, type)]
1818
EXCEPTION_TYPES = [cls for cls in BUILTIN_TYPES
1919
if issubclass(cls, BaseException)]
20+
OTHER_TYPES = [o for n, o in vars(types).items()
21+
if (isinstance(o, type) and
22+
n not in ('DynamicClassAttribute', '_GeneratorWrapper'))]
2023

2124

2225
class _GetXIDataTests(unittest.TestCase):
@@ -40,16 +43,42 @@ def iter_roundtrip_values(self, values, *, mode=None):
4043
got = _testinternalcapi.restore_crossinterp_data(xid)
4144
yield obj, got
4245

43-
def assert_roundtrip_equal(self, values, *, mode=None):
44-
for obj, got in self.iter_roundtrip_values(values, mode=mode):
45-
self.assertEqual(got, obj)
46-
self.assertIs(type(got), type(obj))
47-
4846
def assert_roundtrip_identical(self, values, *, mode=None):
4947
for obj, got in self.iter_roundtrip_values(values, mode=mode):
5048
# XXX What about between interpreters?
5149
self.assertIs(got, obj)
5250

51+
def assert_roundtrip_equal(self, values, *, mode=None, expecttype=None):
52+
for obj, got in self.iter_roundtrip_values(values, mode=mode):
53+
self.assertEqual(got, obj)
54+
self.assertIs(type(got),
55+
type(obj) if expecttype is None else expecttype)
56+
57+
# def assert_roundtrip_equal_not_identical(self, values, *,
58+
# mode=None, expecttype=None):
59+
# mode = self._resolve_mode(mode)
60+
# for obj in values:
61+
# cls = type(obj)
62+
# with self.subTest(obj):
63+
# got = self._get_roundtrip(obj, mode)
64+
# self.assertIsNot(got, obj)
65+
# self.assertIs(type(got), type(obj))
66+
# self.assertEqual(got, obj)
67+
# self.assertIs(type(got),
68+
# cls if expecttype is None else expecttype)
69+
#
70+
# def assert_roundtrip_not_equal(self, values, *, mode=None, expecttype=None):
71+
# mode = self._resolve_mode(mode)
72+
# for obj in values:
73+
# cls = type(obj)
74+
# with self.subTest(obj):
75+
# got = self._get_roundtrip(obj, mode)
76+
# self.assertIsNot(got, obj)
77+
# self.assertIs(type(got), type(obj))
78+
# self.assertNotEqual(got, obj)
79+
# self.assertIs(type(got),
80+
# cls if expecttype is None else expecttype)
81+
5382
def assert_not_shareable(self, values, exctype=None, *, mode=None):
5483
mode = self._resolve_mode(mode)
5584
for obj in values:
@@ -66,6 +95,197 @@ def _resolve_mode(self, mode):
6695
return mode
6796

6897

98+
class MarshalTests(_GetXIDataTests):
99+
100+
MODE = 'marshal'
101+
< 10000 /td>
102+
def test_simple_builtin_singletons(self):
103+
self.assert_roundtrip_identical([
104+
True,
105+
False,
106+
None,
107+
Ellipsis,
108+
])
109+
self.assert_not_shareable([
110+
NotImplemented,
111+
])
112+
113+
def test_simple_builtin_objects(self):
114+
self.assert_roundtrip_equal([
115+
# int
116+
*range(-1, 258),
117+
sys.maxsize + 1,
118+
sys.maxsize,
119+
-sys.maxsize - 1,
120+
-sys.maxsize - 2,
121+
2**1000,
122+
# complex
123+
1+2j,
124+
# float
125+
0.0,
126+
1.1,
127+
-1.0,
128+
0.12345678,
129+
-0.12345678,
130+
# bytes
131+
*(i.to_bytes(2, 'little', signed=True)
132+
for i in range(-1, 258)),
133+
b'hello world',
134+
# str
135+
'hello world',
136+
'你好世界',
137+
'',
138+
])
139+
self.assert_not_shareable([
140+
object(),
141+
types.SimpleNamespace(),
142+
])
143+
144+
def test_bytearray(self):
145+
# bytearray is special because it unmarshals to bytes, not bytearray.
146+
self.assert_roundtrip_equal([
147+
bytearray(),
148+
bytearray(b'hello world'),
149+
], expecttype=bytes)
150+
151+
def test_compound_immutable_builtin_objects(self):
152+
self.assert_roundtrip_equal([
153+
# tuple
154+
(),
155+
(1,),
156+
("hello", "world"),
157+
(1, True, "hello"),
158+
# frozenset
159+
frozenset([1, 2, 3]),
160+
])
161+
# nested
162+
self.assert_roundtrip_equal([
163+
# tuple
164+
((1,),),
165+
((1, 2), (3, 4)),
166+
((1, 2), (3, 4), (5, 6)),
167+
# frozenset
168+
frozenset([frozenset([1]), frozenset([2]), frozenset([3])]),
169+
])
170+
171+
def test_compound_mutable_builtin_objects(self):
172+
self.assert_roundtrip_equal([
173+
# list
174+
[],
175+
[1, 2, 3],
176+
# dict
177+
{},
178+
{1: 7, 2: 8, 3: 9},
179+
# set
180+
set(),
181+
{1, 2, 3},
182+
])
183+
# nested
184+
self.assert_roundtrip_equal([
185+
[[1], [2], [3]],
186+
{1: {'a': True}, 2: {'b': False}},
187+
{(1, 2, 3,)},
188+
])
189+
190+
def test_compound_builtin_objects_with_bad_items(self):
191+
bogus = object()
192+
self.assert_not_shareable([
193+
(bogus,),
194+
frozenset([bogus]),
195+
[bogus],
196+
{bogus: True},
197+
{True: bogus},
198+
{bogus},
199+
])
200+
201+
def test_builtin_code(self):
202+
self.assert_roundtrip_equal([
203+
*(f.__code__ for f in defs.FUNCTIONS),
204+
*(f.__code__ for f in defs.FUNCTION_LIKE),
205+
])
206+
207+
def test_builtin_type(self):
208+
shareable = [
209+
StopIteration,
210+
]
211+
types = [
212+
*BUILTIN_TYPES,
213+
*OTHER_TYPES,
214+
]
215+
self.assert_not_shareable(cls for cls in types
216+
if cls not in shareable)
217+
self.assert_roundtrip_identical(cls for cls in types
218+
if cls in shareable)
219+
220+
def test_builtin_function(self):
221+
functions = [
222+
len,
223+
sys.is_finalizing,
224+
sys.exit,
225+
_testinternalcapi.get_crossinterp_data,
226+
]
227+
for func in functions:
228+
assert type(func) is types.BuiltinFunctionType, func
229+
230+
self.assert_not_shareable(functions)
231+
232+
def test_builtin_exception(self):
233+
msg = 'error!'
234+
try:
235+
raise Exception
236+
except Exception as exc:
237+
caught = exc
238+
special = {
239+
BaseExceptionGroup: (msg, [caught]),
240+
ExceptionGroup: (msg, [caught]),
241+
# UnicodeError: (None, msg, None, None, None),
242+
UnicodeEncodeError: ('utf-8', '', 1, 3, msg),
243+
UnicodeDecodeError: ('utf-8', b'', 1, 3, msg),
244+
UnicodeTranslateError: ('', 1, 3, msg),
245+
}
246+
exceptions = []
247+
for cls in EXCEPTION_TYPES:
248+
args = special.get(cls) or (msg,)
249+
exceptions.append(cls(*args))
250+
251+
self.assert_not_shareable(exceptions)
252+
# Note that StopIteration (the type) can be marshalled,
253+
# but its instances cannot.
254+
255+
def test_module(self):
256+
assert type(sys) is types.ModuleType, type(sys)
257+
assert type(defs) is types.ModuleType, type(defs)
258+
assert type(unittest) is types.ModuleType, type(defs)
259+
260+
assert 'emptymod' not in sys.modules
261+
with import_helper.ready_to_import('emptymod', ''):
262+
import emptymod
263+
264+
self.assert_not_shareable([
265+
sys,
266+
defs,
267+
unittest,
268+
emptymod,
269+
])
270+
271+
def test_user_class(self):
272+
self.assert_not_shareable(defs.TOP_CLASSES)
273+
274+
instances = []
275+
for cls, args in defs.TOP_CLASSES.items():
276+
instances.append(cls(*args))
277+
self.assert_not_shareable(instances)
278+
279+
def test_user_function(self):
280+
self.assert_not_shareable(defs.TOP_FUNCTIONS)
281+
282+
def test_user_exception(self):
283+
self.assert_not_shareable([
284+
defs.MimimalError('error!'),
285+
defs.RichError('error!', 42),
286+
])
287+
288+
69289
class ShareableTypeTests(_GetXIDataTests):
70290

71291
MODE = 'xidata'
@@ -184,6 +404,7 @@ def test_builtin_function(self):
184404

185405
def test_function_like(self):
186406
self.assert_not_shareable(defs.FUNCTION_LIKE)
407+
self.assert_not_shareable(defs.FUNCTION_LIKE_APPLIED)
187408

188409
def test_builtin_wrapper(self):
189410
_wrappers = {
@@ -243,9 +464,7 @@ def test_class(self):
243464
def test_builtin_type(self):
244465
self.assert_not_shareable([
245466
*BUILTIN_TYPES,
246-
*(o for n, o in vars(types).items()
247-
if (isinstance(o, type) and
248-
n not in ('DynamicClassAttribute', '_GeneratorWrapper'))),
467+
*OTHER_TYPES,
249468
])
250469

251470
def test_exception(self):

0 commit comments

Comments
 (0)
0