From a4945b5bf330d3c8c8d4aedc2ee56cada2ae263c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 13 Sep 2026 14:42:18 +0200 Subject: [PATCH 1/2] gh-155907: Complete PyMarshal C API tests Add tests on PyMarshal_ReadObjectFromString() and PyMarshal_WriteObjectToString(). Add test on PyMarshal_WriteObjectToFile(NULL). --- Lib/test/test_capi/test_marshal.py | 247 ++++++++++++++++++++--------- Modules/_testcapi/marshal.c | 92 ++++++++--- 2 files changed, 250 insertions(+), 89 deletions(-) diff --git a/Lib/test/test_capi/test_marshal.py b/Lib/test/test_capi/test_marshal.py index 82a20c44fac424..3c30eb14fe7c13 100644 --- a/Lib/test/test_capi/test_marshal.py +++ b/Lib/test/test_capi/test_marshal.py @@ -1,19 +1,54 @@ +# Test PyMarshal C API + import marshal import os.path +import struct import unittest from test import support from test.support import import_helper from test.support import os_helper -from test.test_marshal import HelperMixin, omit_last_byte # Skip this test if _testcapi is are not available. _testcapi = import_helper.import_module('_testcapi') +def noop_func(): + pass + +NULL = None +SIMPLE_OBJECT = 123 +# Only test a few objects: see test_marshal for more exhaustive tests +TEST_OBJECTS = ( + '\u20ac', + b'abc', + True, + 123, + 45.6, + 7+8j, + 'long line '*1000, + # Check that serializing code object is allowed (allow_code = 1) + noop_func.__code__, +) +UNMARSHALLABLE = object() + +# Invalid marshal data +JUNK_BYTES = b'\xff' * 32 + + +def read_file(filename): + with open(filename, 'rb') as fp: + return fp.read() + + +def write_file(filename, data): + with open(filename, 'wb') as fp: + fp.write(data) + + @support.cpython_only -class CAPI_TestCase(unittest.TestCase, HelperMixin): +class CAPI_TestCase(unittest.TestCase): def test_read_from_file_error(self): # A read error is reported as OSError, not EOFError. @@ -38,89 +73,159 @@ def test_write_to_file_error(self): _testcapi.pymarshal_write_object_to_file(obj, '/dev/full', marshal.version) - def test_write_unmarshallable_to_file(self): - self.addCleanup(os_helper.unlink, os_helper.TESTFN) - with self.assertRaisesRegex(ValueError, 'unmarshallable object'): - _testcapi.pymarshal_write_object_to_file(object(), os_helper.TESTFN, - marshal.version) + def check_object(self, obj2, obj): + self.assertEqual(obj2, obj) + self.assertEqual(type(obj2), type(obj)) def test_write_long_to_file(self): - for v in range(marshal.version + 1): - _testcapi.pymarshal_write_long_to_file(0x12345678, os_helper.TESTFN, v) - with open(os_helper.TESTFN, 'rb') as f: - data = f.read() - os_helper.unlink(os_helper.TESTFN) - self.assertEqual(data, b'\x78\x56\x34\x12') + # Test PyMarshal_WriteLongToFile() + write_long_to_file = _testcapi.pymarshal_write_long_to_file + filename = os_helper.TESTFN + self.addCleanup(os_helper.unlink, filename) + + def mask32(value): + res = value & (2 ** 32 - 1) + if res >= 2147483648: + return res - 4294967296 + else: + return res + + limit = 2 ** 31 + for version in range(marshal.version + 1): + for value in ( + _testcapi.LONG_MIN, _testcapi.LONG_MAX, + -limit - 2, -limit, -limit + 2, + limit - 2, limit, limit + 2, + 0, 123, -123, + ): + with self.subTest(value=value, version=version): + write_long_to_file(value, filename, version) + data = read_file(filename) + self.assertEqual(len(data), 4) + value2 = struct.unpack(' Date: Mon, 14 Sep 2026 14:09:52 +0200 Subject: [PATCH 2/2] Fix PyMarshal_WriteLongToFile() for 32-bit long (Windows) --- Lib/test/test_capi/test_marshal.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_capi/test_marshal.py b/Lib/test/test_capi/test_marshal.py index 3c30eb14fe7c13..972ff4ed53d687 100644 --- a/Lib/test/test_capi/test_marshal.py +++ b/Lib/test/test_capi/test_marshal.py @@ -91,13 +91,17 @@ def mask32(value): return res limit = 2 ** 31 + values = [ + _testcapi.LONG_MIN, _testcapi.LONG_MAX, + -limit, -limit + 2, limit - 2, limit - 1, + 0, 123, -123, + ] + # Test values larger than 32-bit on platforms with 64-bit C long + if _testcapi.LONG_MAX > (2**31-1): + values.extend((-limit - 2, limit, limit + 2)) + for version in range(marshal.version + 1): - for value in ( - _testcapi.LONG_MIN, _testcapi.LONG_MAX, - -limit - 2, -limit, -limit + 2, - limit - 2, limit, limit + 2, - 0, 123, -123, - ): + for value in values: with self.subTest(value=value, version=version): write_long_to_file(value, filename, version) data = read_file(filename)