Skip to content

Commit 7adb4cc

Browse files
authored
gh-155907: Complete PyMarshal C API tests (#157452)
Add tests on PyMarshal_ReadObjectFromString() and PyMarshal_WriteObjectToString(). Add test on PyMarshal_WriteObjectToFile(NULL).
1 parent e5fbabb commit 7adb4cc

2 files changed

Lines changed: 254 additions & 89 deletions

File tree

‎Lib/test/test_capi/test_marshal.py‎

Lines changed: 180 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,54 @@
1+
# Test PyMarshal C API
2+
13
import marshal
24
import os.path
5+
import struct
36
import unittest
47

58
from test import support
69
from test.support import import_helper
710
from test.support import os_helper
8-
from test.test_marshal import HelperMixin, omit_last_byte
911

1012

1113
# Skip this test if _testcapi is are not available.
1214
_testcapi = import_helper.import_module('_testcapi')
1315

1416

17+
def noop_func():
18+
pass
19+
20+
NULL = None
21+
SIMPLE_OBJECT = 123
22+
# Only test a few objects: see test_marshal for more exhaustive tests
23+
TEST_OBJECTS = (
24+
'\u20ac',
25+
b'abc',
26+
True,
27+
123,
28+
45.6,
29+
7+8j,
30+
'long line '*1000,
31+
# Check that serializing code object is allowed (allow_code = 1)
32+
noop_func.__code__,
33+
)
34+
UNMARSHALLABLE = object()
35+
36+
# Invalid marshal data
37+
JUNK_BYTES = b'\xff' * 32
38+
39+
40+
def read_file(filename):
41+
with open(filename, 'rb') as fp:
42+
return fp.read()
43+
44+
45+
def write_file(filename, data):
46+
with open(filename, 'wb') as fp:
47+
fp.write(data)
48+
49+
1550
@support.cpython_only
16-
class CAPI_TestCase(unittest.TestCase, HelperMixin):
51+
class CAPI_TestCase(unittest.TestCase):
1752

1853
def test_read_from_file_error(self):
1954
# A read error is reported as OSError, not EOFError.
@@ -38,89 +73,163 @@ def test_write_to_file_error(self):
3873
_testcapi.pymarshal_write_object_to_file(obj, '/dev/full',
3974
marshal.version)
4075

41-
def test_write_unmarshallable_to_file(self):
42-
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
43-
with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
44-
_testcapi.pymarshal_write_object_to_file(object(), os_helper.TESTFN,
45-
marshal.version)
76+
def check_object(self, obj2, obj):
77+
self.assertEqual(obj2, obj)
78+
self.assertEqual(type(obj2), type(obj))
4679

4780
def test_write_long_to_file(self):
48-
for v in range(marshal.version + 1):
49-
_testcapi.pymarshal_write_long_to_file(0x12345678, os_helper.TESTFN, v)
50-
with open(os_helper.TESTFN, 'rb') as f:
51-
data = f.read()
52-
os_helper.unlink(os_helper.TESTFN)
53-
self.assertEqual(data, b'\x78\x56\x34\x12')
81+
# Test PyMarshal_WriteLongToFile()
82+
write_long_to_file = _testcapi.pymarshal_write_long_to_file
83+
filename = os_helper.TESTFN
84+
self.addCleanup(os_helper.unlink, filename)
85+
86+
def mask32(value):
87+
res = value & (2 ** 32 - 1)
88+
if res >= 2147483648:
89+
return res - 4294967296
90+
else:
91+
return res
92+
93+
limit = 2 ** 31
94+
values = [
95+
_testcapi.LONG_MIN, _testcapi.LONG_MAX,
96+
-limit, -limit + 2, limit - 2, limit - 1,
97+
0, 123, -123,
98+
]
99+
# Test values larger than 32-bit on platforms with 64-bit C long
100+
if _testcapi.LONG_MAX > (2**31-1):
101+
values.extend((-limit - 2, limit, limit + 2))
102+
103+
for version in range(marshal.version + 1):
104+
for value in values:
105+
with self.subTest(value=value, version=version):
106+
write_long_to_file(value, filename, version)
107+
data = read_file(filename)
108+
self.assertEqual(len(data), 4)
109+
value2 = struct.unpack('<i', data)[0]
110+
self.assertEqual(value2, mask32(value))
54111

55112
def test_write_object_to_file(self):
56-
obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 'long line '*1000)
57-
for v in range(marshal.version + 1):
58-
_testcapi.pymarshal_write_object_to_file(obj, os_helper.TESTFN, v)
59-
with open(os_helper.TESTFN, 'rb') as f:
60-
data = f.read()
61-
os_helper.unlink(os_helper.TESTFN)
62-
self.assertEqual(marshal.loads(data), obj)
113+
# Test PyMarshal_WriteObjectToFile()
114+
write_object_to_file = _testcapi.pymarshal_write_object_to_file
115+
filename = os_helper.TESTFN
116+
self.addCleanup(os_helper.unlink, filename)
117+
118+
for version in range(marshal.version + 1):
119+
for obj in TEST_OBJECTS:
120+
with self.subTest(obj=obj, version=version):
121+
write_object_to_file(obj, filename, version)
122+
data = read_file(filename)
123+
self.assertEqual(marshal.loads(data), obj)
124+
125+
with self.assertRaises(SystemError):
126+
write_object_to_file(NULL, filename, version)
127+
128+
with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
129+
write_object_to_file(UNMARSHALLABLE, filename, version)
63130

64131
def test_read_short_from_file(self):
65-
with open(os_helper.TESTFN, 'wb') as f:
66-
f.write(b'\x34\x12xxxx')
67-
r, p = _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
68-
os_helper.unlink(os_helper.TESTFN)
69-
self.assertEqual(r, 0x1234)
70-
self.assertEqual(p, 2)
71-
72-
with open(os_helper.TESTFN, 'wb') as f:
73-
f.write(b'\x12')
132+
# Test PyMarshal_ReadShortFromFile()
133+
read_short_from_file = _testcapi.pymarshal_read_short_from_file
134+
filename = os_helper.TESTFN
135+
self.addCleanup(os_helper.unlink, filename)
136+
137+
for value in (-2**15, 2**15-1, 0, 123, -123):
138+
with self.subTest(value=value):
139+
data = struct.pack('<h', value) + b'xxxx'
140+
write_file(filename, data)
141+
value2 = read_short_from_file(filename)
142+
self.assertEqual(value2, value)
143+
144+
write_file(filename, b'\x12') # less than 2 bytes
74145
with self.assertRaises(EOFError):
75-
_testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
76-
os_helper.unlink(os_helper.TESTFN)
146+
read_short_from_file(filename)
77147

78148
def test_read_long_from_file(self):
79-
with open(os_helper.TESTFN, 'wb') as f:
80-
f.write(b'\x78\x56\x34\x12xxxx')
81-
r, p = _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
82-
os_helper.unlink(os_helper.TESTFN)
83-
self.assertEqual(r, 0x12345678)
84-
self.assertEqual(p, 4)
85-
86-
with open(os_helper.TESTFN, 'wb') as f:
87-
f.write(b'\x56\x34\x12')
149+
# Test PyMarshal_ReadLongFromFile()
150+
read_long_from_file = _testcapi.pymarshal_read_long_from_file
151+
filename = os_helper.TESTFN
152+
self.addCleanup(os_helper.unlink, filename)
153+
154+
for value in (_testcapi.INT_MIN, _testcapi.INT_MAX, 0, 123, -123):
155+
with self.subTest(value=value):
156+
data = struct.pack('<i', value)
157+
write_file(filename, data)
158+
value2 = read_long_from_file(filename)
159+
self.assertEqual(value2, value)
160+
161+
write_file(filename, b'\x56\x34\x12') # less than 4 bytes
162+
with self.assertRaises(EOFError):
163+
read_long_from_file(filename)
164+
165+
def check_read_object(self, read_object_func, check_pos=True):
166+
filename = os_helper.TESTFN
167+
self.addCleanup(os_helper.unlink, filename)
168+
169+
version = marshal.version
170+
for obj in TEST_OBJECTS:
171+
with self.subTest(obj=obj):
172+
data = marshal.dumps(obj, version)
173+
data += b'abc' # following data is ignored
174+
write_file(filename, data)
175+
obj2, pos = read_object_func(filename)
176+
self.check_object(obj2, obj)
177+
if check_pos:
178+
self.assertEqual(pos, len(data))
179+
180+
data = marshal.dumps(SIMPLE_OBJECT, version)
181+
data = data[:-1] # truncate last byte
182+
write_file(filename, data)
88183
with self.assertRaises(EOFError):
89-
_testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
90-
os_helper.unlink(os_helper.TESTFN)
184+
read_object_func(filename)
185+
186+
write_file(filename, JUNK_BYTES)
187+
with self.assertRaisesRegex(ValueError, 'bad marshal data'):
188+
read_object_func(filename)
91189

92190
def test_read_last_object_from_file(self):
93-
obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
94-
for v in range(marshal.version + 1):
95-
data = marshal.dumps(obj, v)
96-
with open(os_helper.TESTFN, 'wb') as f:
97-
f.write(data + b'xxxx')
98-
r, p = _testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
99-
os_helper.unlink(os_helper.TESTFN)
100-
self.assertEqual(r, obj)
101-
102-
with open(os_helper.TESTFN, 'wb') as f:
103-
f.write(omit_last_byte(data))
104-
with self.assertRaises(EOFError):
105-
_testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
106-
os_helper.unlink(os_helper.TESTFN)
191+
# Test PyMarshal_ReadLastObjectFromFile()
192+
read_last_object_from_file = _testcapi.pymarshal_read_last_object_from_file
193+
self.check_read_object(read_last_object_from_file)
107194

108195
def test_read_object_from_file(self):
109-
obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
110-
for v in range(marshal.version + 1):
111-
data = marshal.dumps(obj, v)
112-
with open(os_helper.TESTFN, 'wb') as f:
113-
f.write(data + b'xxxx')
114-
r, p = _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
115-
os_helper.unlink(os_helper.TESTFN)
116-
self.assertEqual(r, obj)
117-
self.assertEqual(p, len(data))
118-
119-
with open(os_helper.TESTFN, 'wb') as f:
120-
f.write(omit_last_byte(data))
121-
with self.assertRaises(EOFError):
122-
_testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
123-
os_helper.unlink(os_helper.TESTFN)
196+
# Test PyMarshal_ReadObjectFromFile()
197+
read_object_from_file = _testcapi.pymarshal_read_object_from_file
198+
self.check_read_object(read_object_from_file, check_pos=False)
199+
200+
def test_pymarshal_readobjectfromstring(self):
201+
# Test PyMarshal_ReadObjectFromString()
202+
readobjectfromstring = _testcapi.pymarshal_readobjectfromstring
203+
for obj in TEST_OBJECTS:
204+
for version in range(marshal.version + 1):
205+
with self.subTest(obj=obj, version=version):
206+
data = marshal.dumps(obj, version)
207+
obj2 = readobjectfromstring(data)
208+
self.check_object(obj2, obj)
209+
210+
data = marshal.dumps(SIMPLE_OBJECT, marshal.version)
211+
data = data[:-1] # truncate last byte
212+
with self.assertRaises(EOFError):
213+
readobjectfromstring(data)
214+
215+
with self.assertRaisesRegex(ValueError, 'bad marshal data'):
216+
readobjectfromstring(JUNK_BYTES)
217+
218+
def test_pymarshal_writeobjecttostring(self):
219+
# Test PyMarshal_WriteObjectToString()
220+
writeobjecttostring = _testcapi.pymarshal_writeobjecttostring
221+
for version in range(marshal.version + 1):
222+
for obj in TEST_OBJECTS:
223+
with self.subTest(obj=obj, version=version):
224+
data = writeobjecttostring(obj, version)
225+
obj2 = marshal.loads(data)
226+
self.check_object(obj2, obj)
227+
228+
with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
229+
writeobjecttostring(UNMARSHALLABLE, version)
230+
231+
with self.assertRaises(SystemError):
232+
writeobjecttostring(NULL, version)
124233

125234

126235
if __name__ == "__main__":

0 commit comments

Comments
 (0)