Skip to content

Commit a4945b5

Browse files
committed
gh-155907: Complete PyMarshal C API tests
Add tests on PyMarshal_ReadObjectFromString() and PyMarshal_WriteObjectToString(). Add test on PyMarshal_WriteObjectToFile(NULL).
1 parent fb46c67 commit a4945b5

2 files changed

Lines changed: 250 additions & 89 deletions

File tree

Lib/test/test_capi/test_marshal.py

Lines changed: 176 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,159 @@ 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+
for version in range(marshal.version + 1):
95+
for value in (
96+
_testcapi.LONG_MIN, _testcapi.LONG_MAX,
97+
-limit - 2, -limit, -limit + 2,
98+
limit - 2, limit, limit + 2,
99+
0, 123, -123,
100+
):
101+
with self.subTest(value=value, version=version):
102+
write_long_to_file(value, filename, version)
103+
data = read_file(filename)
104+
self.assertEqual(len(data), 4)
105+
value2 = struct.unpack('<i', data)[0]
106+
self.assertEqual(value2, mask32(value))
54107

55108
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)
109+
# Test PyMarshal_WriteObjectToFile()
110+
write_object_to_file = _testcapi.pymarshal_write_object_to_file
111+
filename = os_helper.TESTFN
112+
self.addCleanup(os_helper.unlink, filename)
113+
114+
for version in range(marshal.version + 1):
115+
for obj in TEST_OBJECTS:
116+
with self.subTest(obj=obj, version=version):
117+
write_object_to_file(obj, filename, version)
118+
data = read_file(filename)
119+
self.assertEqual(marshal.loads(data), obj)
120+
121+
with self.assertRaises(SystemError):
122+
write_object_to_file(NULL, filename, version)
123+
124+
with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
125+
write_object_to_file(UNMARSHALLABLE, filename, version)
63126

64127
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')
128+
# Test PyMarshal_ReadShortFromFile()
129+
read_short_from_file = _testcapi.pymarshal_read_short_from_file
130+
filename = os_helper.TESTFN
131+
self.addCleanup(os_helper.unlink, filename)
132+
133+
for value in (-2**15, 2**15-1, 0, 123, -123):
134+
with self.subTest(value=value):
135+
data = struct.pack('<h', value) + b'xxxx'
136+
write_file(filename, data)
137+
value2 = read_short_from_file(filename)
138+
self.assertEqual(value2, value)
139+
140+
write_file(filename, b'\x12') # less than 2 bytes
74141
with self.assertRaises(EOFError):
75-
_testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
76-
os_helper.unlink(os_helper.TESTFN)
142+
read_short_from_file(filename)
77143

78144
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')
145+
# Test PyMarshal_ReadLongFromFile()
146+
read_long_from_file = _testcapi.pymarshal_read_long_from_file
147+
filename = os_helper.TESTFN
148+
self.addCleanup(os_helper.unlink, filename)
149+
150+
for value in (_testcapi.INT_MIN, _testcapi.INT_MAX, 0, 123, -123):
151+
with self.subTest(value=value):
152+
data = struct.pack('<i', value)
153+
write_file(filename, data)
154+
value2 = read_long_from_file(filename)
155+
self.assertEqual(value2, value)
156+
157+
write_file(filename, b'\x56\x34\x12') # less than 4 bytes
158+
with self.assertRaises(EOFError):
159+
read_long_from_file(filename)
160+
161+
def check_read_object(self, read_object_func, check_pos=True):
162+
filename = os_helper.TESTFN
163+
self.addCleanup(os_helper.unlink, filename)
164+
165+
version = marshal.version
166+
for obj in TEST_OBJECTS:
167+
with self.subTest(obj=obj):
168+
data = marshal.dumps(obj, version)
169+
data += b'abc' # following data is ignored
170+
write_file(filename, data)
171+
obj2, pos = read_object_func(filename)
172+
self.check_object(obj2, obj)
173+
if check_pos:
174+
self.assertEqual(pos, len(data))
175+
176+
data = marshal.dumps(SIMPLE_OBJECT, version)
177+
data = data[:-1] # truncate last byte
178+
write_file(filename, data)
88179
with self.assertRaises(EOFError):
89-
_testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
90-
os_helper.unlink(os_helper.TESTFN)
180+
read_object_func(filename)
181+
182+
write_file(filename, JUNK_BYTES)
183+
with self.assertRaisesRegex(ValueError, 'bad marshal data'):
184+
read_object_func(filename)
91185

92186
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)
187+
# Test PyMarshal_ReadLastObjectFromFile()
188+
read_last_object_from_file = _testcapi.pymarshal_read_last_object_from_file
189+
self.check_read_object(read_last_object_from_file)
107190

108191
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)
192+
# Test PyMarshal_ReadObjectFromFile()
193+
read_object_from_file = _testcapi.pymarshal_read_object_from_file
194+
self.check_read_object(read_object_from_file, check_pos=False)
195+
196+
def test_pymarshal_readobjectfromstring(self):
197+
# Test PyMarshal_ReadObjectFromString()
198+
readobjectfromstring = _testcapi.pymarshal_readobjectfromstring
199+
for obj in TEST_OBJECTS:
200+
for version in range(marshal.version + 1):
201+
with self.subTest(obj=obj, version=version):
202+
data = marshal.dumps(obj, version)
203+
obj2 = readobjectfromstring(data)
204+
self.check_object(obj2, obj)
205+
206+
data = marshal.dumps(SIMPLE_OBJECT, marshal.version)
207+
data = data[:-1] # truncate last byte
208+
with self.assertRaises(EOFError):
209+
readobjectfromstring(data)
210+
211+
with self.assertRaisesRegex(ValueError, 'bad marshal data'):
212+
readobjectfromstring(JUNK_BYTES)
213+
214+
def test_pymarshal_writeobjecttostring(self):
215+
# Test PyMarshal_WriteObjectToString()
216+
writeobjecttostring = _testcapi.pymarshal_writeobjecttostring
217+
for version in range(marshal.version + 1):
218+
for obj in TEST_OBJECTS:
219+
with self.subTest(obj=obj, version=version):
220+
data = writeobjecttostring(obj, version)
221+
obj2 = marshal.loads(data)
222+
self.check_object(obj2, obj)
223+
224+
with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
225+
writeobjecttostring(UNMARSHALLABLE, version)
226+
227+
with self.assertRaises(SystemError):
228+
writeobjecttostring(NULL, version)
124229

125230

126231
if __name__ == "__main__":

0 commit comments

Comments
 (0)