Skip to content

Commit 52edfad

Browse files
committed
gh-155742: Add tests on the PyMarshal C API
Add Lib/test/test_capi/test_marshal.py and Modules/_testcapi/marshal.c files.
1 parent 1620e0f commit 52edfad

7 files changed

Lines changed: 446 additions & 1 deletion

File tree

Lib/test/test_capi/test_marshal.py

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Test PyMarshal C API
2+
3+
import marshal
4+
import struct
5+
import unittest
6+
from test.support import import_helper
7+
from test.support import os_helper
8+
9+
_testcapi = import_helper.import_module('_testcapi')
10+
11+
NULL = None
12+
Py_MARSHAL_VERSION = _testcapi.Py_MARSHAL_VERSION
13+
14+
def noop_func():
15+
pass
16+
17+
SIMPLE_OBJECT = 123
18+
# Only test a few objects: see test_marshal for more exhaustive tests
19+
TEST_OBJECTS = (
20+
'\u20ac',
21+
b'abc',
22+
True,
23+
45.6,
24+
7+8j,
25+
SIMPLE_OBJECT,
26+
# Check that serializing code object is allowed (allow_code = 1)
27+
noop_func.__code__,
28+
)
29+
30+
# Invalid marshal data
31+
JUNK_BYTES = b'\xff' * 32
32+
33+
34+
def read_file(filename):
35+
with open(filename, 'rb') as fp:
36+
return fp.read()
37+
38+
39+
def write_file(filename, data):
40+
with open(filename, 'wb') as fp:
41+
fp.write(data)
42+
43+
44+
class CAPIUnicodeTest(unittest.TestCase):
45+
def check_object(self, obj2, obj):
46+
self.assertEqual(obj2, obj)
47+
self.assertEqual(type(obj2), type(obj))
48+
49+
def test_pymarshal_readobjectfromstring(self):
50+
# Test PyMarshal_ReadObjectFromString()
51+
readobjectfromstring = _testcapi.pymarshal_readobjectfromstring
52+
for obj in TEST_OBJECTS:
53+
for version in range(Py_MARSHAL_VERSION + 1):
54+
with self.subTest(obj=obj, version=version):
55+
data = marshal.dumps(obj, version)
56+
obj2 = readobjectfromstring(data)
57+
self.check_object(obj2, obj)
58+
59+
data = marshal.dumps(SIMPLE_OBJECT, Py_MARSHAL_VERSION)
60+
data = data[:-1] # truncate
61+
with self.assertRaises(EOFError):
62+
readobjectfromstring(data)
63+
64+
with self.assertRaisesRegex(ValueError, 'bad marshal data'):
65+
readobjectfromstring(JUNK_BYTES)
66+
67+
def test_pymarshal_writeobjecttostring(self):
68+
# Test PyMarshal_WriteObjectToString()
69+
writeobjecttostring = _testcapi.pymarshal_writeobjecttostring
70+
for version in range(Py_MARSHAL_VERSION + 1):
71+
for obj in TEST_OBJECTS:
72+
with self.subTest(obj=obj, version=version):
73+
data = writeobjecttostring(obj, version)
74+
obj2 = marshal.loads(data)
75+
self.check_object(obj2, obj)
76+
77+
with self.assertRaises(SystemError):
78+
writeobjecttostring(NULL, version)
79+
80+
def test_pymarshal_writeobjecttofile(self):
81+
# Test PyMarshal_WriteObjectToFile()
82+
writeobjecttofile = _testcapi.pymarshal_writeobjecttofile
83+
84+
filename = os_helper.TESTFN
85+
self.addCleanup(os_helper.unlink, filename)
86+
87+
for version in range(Py_MARSHAL_VERSION + 1):
88+
for obj in TEST_OBJECTS:
89+
with self.subTest(obj=obj, version=version):
90+
writeobjecttofile(obj, filename, version)
91+
data = read_file(filename)
92+
obj2 = marshal.loads(data)
93+
self.check_object(obj2, obj)
94+
95+
with self.assertRaises(SystemError):
96+
writeobjecttofile(NULL, filename, version)
97+
98+
def test_pymarshal_writelongtofile(self):
99+
# Test PyMarshal_WriteLongToFile()
100+
writelongtofile = _testcapi.pymarshal_writelongtofile
101+
102+
def mask32(value):
103+
res = value & (2 ** 32 - 1)
104+
if res >= 2147483648:
105+
return res - 4294967296
106+
else:
107+
return res
108+
109+
filename = os_helper.TESTFN
110+
self.addCleanup(os_helper.unlink, filename)
111+
112+
limit = 2 ** 31
113+
for version in range(Py_MARSHAL_VERSION + 1):
114+
for value in (
115+
_testcapi.LONG_MIN,
116+
_testcapi.LONG_MAX,
117+
-limit - 2,
118+
-limit,
119+
-limit + 2,
120+
limit - 2,
121+
limit,
122+
limit + 2,
123+
0,
124+
123,
125+
-123,
126+
):
127+
with self.subTest(value=value, version=version):
128+
writelongtofile(value, filename, version)
129+
data = read_file(filename)
130+
self.assertEqual(len(data), 4)
131+
value2 = struct.unpack('<i', data)[0]
132+
self.assertEqual(value2, mask32(value))
133+
134+
def test_pymarshal_readshortfromfile(self):
135+
# Test PyMarshal_ReadShortFromFile()
136+
readshortfromfile = _testcapi.pymarshal_readshortfromfile
137+
138+
filename = os_helper.TESTFN
139+
self.addCleanup(os_helper.unlink, filename)
140+
141+
for value in (
142+
-2**15,
143+
2**15-1,
144+
0,
145+
123,
146+
-123,
147+
):
148+
with self.subTest(value=value):
149+
data = struct.pack('<h', value)
150+
write_file(filename, data)
151+
value2 = readshortfromfile(filename)
152+
self.assertEqual(value2, value)
153+
154+
write_file(filename, b'\x00') # less than 2 bytes
155+
with self.assertRaises(EOFError):
156+
readshortfromfile(filename)
157+
158+
def test_pymarshal_readlongfromfile(self):
159+
# Test PyMarshal_ReadLongFromFile()
160+
readlongfromfile = _testcapi.pymarshal_readlongfromfile
161+
162+
filename = os_helper.TESTFN
163+
self.addCleanup(os_helper.unlink, filename)
164+
165+
for value in (
166+
-2**31,
167+
2**31-1,
168+
0,
169+
123,
170+
-123,
171+
):
172+
with self.subTest(value=value):
173+
data = struct.pack('<i', value)
174+
write_file(filename, data)
175+
value2 = readlongfromfile(filename)
176+
self.assertEqual(value2, value)
177+
178+
write_file(filename, b'\x00\x01\x02') # less than 4 bytes
179+
with self.assertRaises(EOFError):
180+
readlongfromfile(filename)
181+
182+
def check_read_object(self, read_object_func):
183+
filename = os_helper.TESTFN
184+
self.addCleanup(os_helper.unlink, filename)
185+
186+
version = Py_MARSHAL_VERSION
187+
for obj in TEST_OBJECTS:
188+
with self.subTest(obj=obj):
189+
data = marshal.dumps(obj, version)
190+
data += b'abc' # following data is ignored
191+
write_file(filename, data)
192+
obj2 = read_object_func(filename)
193+
self.check_object(obj2, obj)
194+
195+
data = marshal.dumps(SIMPLE_OBJECT, version)
196+
data = data[:-1] # truncate
197+
write_file(filename, data)
198+
with self.assertRaises(EOFError):
199+
read_object_func(filename)
200+
201+
write_file(filename, JUNK_BYTES)
202+
with self.assertRaisesRegex(ValueError, 'bad marshal data'):
203+
read_object_func(filename)
204+
205+
def test_pymarshal_readobjetfromfile(self):
206+
# Test PyMarshal_ReadObjectFromFile()
207+
readobjectfromfile = _testcapi.pymarshal_readobjectfromfile
208+
self.check_read_object(readobjectfromfile)
209+
210+
def test_pymarshal_readlastobjetfromfile(self):
211+
# Test PyMarshal_ReadLastObjectFromFile()
212+
readlastobjectfromfile = _testcapi.pymarshal_readlastobjectfromfile
213+
self.check_read_object(readlastobjectfromfile)
214+
215+
216+
if __name__ == "__main__":
217+
unittest.main()

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@
173173
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
174174
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
175175
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c
176-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
176+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c
177177
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c
178178
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
179179
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c

0 commit comments

Comments
 (0)