https://github.com/python/cpython/commit/7adb4cca1e07d1a14537f36c81f5387342f27951
commit: 7adb4cca1e07d1a14537f36c81f5387342f27951
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-14T23:49:39+02:00
summary:
gh-155907: Complete PyMarshal C API tests (#157452)
Add tests on PyMarshal_ReadObjectFromString() and
PyMarshal_WriteObjectToString().
Add test on PyMarshal_WriteObjectToFile(NULL).
files:
M Lib/test/test_capi/test_marshal.py
M Modules/_testcapi/marshal.c
diff --git a/Lib/test/test_capi/test_marshal.py
b/Lib/test/test_capi/test_marshal.py
index 82a20c44fac424..972ff4ed53d687 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,163 @@ 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
+ 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 values:
+ 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('<i', data)[0]
+ self.assertEqual(value2, mask32(value))
def test_write_object_to_file(self):
- obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 'long line '*1000)
- for v in range(marshal.version + 1):
- _testcapi.pymarshal_write_object_to_file(obj, os_helper.TESTFN, v)
- with open(os_helper.TESTFN, 'rb') as f:
- data = f.read()
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(marshal.loads(data), obj)
+ # Test PyMarshal_WriteObjectToFile()
+ write_object_to_file = _testcapi.pymarshal_write_object_to_file
+ filename = os_helper.TESTFN
+ self.addCleanup(os_helper.unlink, filename)
+
+ for version in range(marshal.version + 1):
+ for obj in TEST_OBJECTS:
+ with self.subTest(obj=obj, version=version):
+ write_object_to_file(obj, filename, version)
+ data = read_file(filename)
+ self.assertEqual(marshal.loads(data), obj)
+
+ with self.assertRaises(SystemError):
+ write_object_to_file(NULL, filename, version)
+
+ with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
+ write_object_to_file(UNMARSHALLABLE, filename, version)
def test_read_short_from_file(self):
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x34\x12xxxx')
- r, p = _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, 0x1234)
- self.assertEqual(p, 2)
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x12')
+ # Test PyMarshal_ReadShortFromFile()
+ read_short_from_file = _testcapi.pymarshal_read_short_from_file
+ filename = os_helper.TESTFN
+ self.addCleanup(os_helper.unlink, filename)
+
+ for value in (-2**15, 2**15-1, 0, 123, -123):
+ with self.subTest(value=value):
+ data = struct.pack('<h', value) + b'xxxx'
+ write_file(filename, data)
+ value2 = read_short_from_file(filename)
+ self.assertEqual(value2, value)
+
+ write_file(filename, b'\x12') # less than 2 bytes
with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
+ read_short_from_file(filename)
def test_read_long_from_file(self):
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x78\x56\x34\x12xxxx')
- r, p = _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, 0x12345678)
- self.assertEqual(p, 4)
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x56\x34\x12')
+ # Test PyMarshal_ReadLongFromFile()
+ read_long_from_file = _testcapi.pymarshal_read_long_from_file
+ filename = os_helper.TESTFN
+ self.addCleanup(os_helper.unlink, filename)
+
+ for value in (_testcapi.INT_MIN, _testcapi.INT_MAX, 0, 123, -123):
+ with self.subTest(value=value):
+ data = struct.pack('<i', value)
+ write_file(filename, data)
+ value2 = read_long_from_file(filename)
+ self.assertEqual(value2, value)
+
+ write_file(filename, b'\x56\x34\x12') # less than 4 bytes
+ with self.assertRaises(EOFError):
+ read_long_from_file(filename)
+
+ def check_read_object(self, read_object_func, check_pos=True):
+ filename = os_helper.TESTFN
+ self.addCleanup(os_helper.unlink, filename)
+
+ version = marshal.version
+ for obj in TEST_OBJECTS:
+ with self.subTest(obj=obj):
+ data = marshal.dumps(obj, version)
+ data += b'abc' # following data is ignored
+ write_file(filename, data)
+ obj2, pos = read_object_func(filename)
+ self.check_object(obj2, obj)
+ if check_pos:
+ self.assertEqual(pos, len(data))
+
+ data = marshal.dumps(SIMPLE_OBJECT, version)
+ data = data[:-1] # truncate last byte
+ write_file(filename, data)
with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
+ read_object_func(filename)
+
+ write_file(filename, JUNK_BYTES)
+ with self.assertRaisesRegex(ValueError, 'bad marshal data'):
+ read_object_func(filename)
def test_read_last_object_from_file(self):
- obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
- for v in range(marshal.version + 1):
- data = marshal.dumps(obj, v)
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(data + b'xxxx')
- r, p =
_testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, obj)
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(omit_last_byte(data))
- with self.assertRaises(EOFError):
-
_testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
+ # Test PyMarshal_ReadLastObjectFromFile()
+ read_last_object_from_file =
_testcapi.pymarshal_read_last_object_from_file
+ self.check_read_object(read_last_object_from_file)
def test_read_object_from_file(self):
- obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
- for v in range(marshal.version + 1):
- data = marshal.dumps(obj, v)
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(data + b'xxxx')
- r, p = _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, obj)
- self.assertEqual(p, len(data))
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(omit_last_byte(data))
- with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
+ # Test PyMarshal_ReadObjectFromFile()
+ read_object_from_file = _testcapi.pymarshal_read_object_from_file
+ self.check_read_object(read_object_from_file, check_pos=False)
+
+ def test_pymarshal_readobjectfromstring(self):
+ # Test PyMarshal_ReadObjectFromString()
+ readobjectfromstring = _testcapi.pymarshal_readobjectfromstring
+ for obj in TEST_OBJECTS:
+ for version in range(marshal.version + 1):
+ with self.subTest(obj=obj, version=version):
+ data = marshal.dumps(obj, version)
+ obj2 = readobjectfromstring(data)
+ self.check_object(obj2, obj)
+
+ data = marshal.dumps(SIMPLE_OBJECT, marshal.version)
+ data = data[:-1] # truncate last byte
+ with self.assertRaises(EOFError):
+ readobjectfromstring(data)
+
+ with self.assertRaisesRegex(ValueError, 'bad marshal data'):
+ readobjectfromstring(JUNK_BYTES)
+
+ def test_pymarshal_writeobjecttostring(self):
+ # Test PyMarshal_WriteObjectToString()
+ writeobjecttostring = _testcapi.pymarshal_writeobjecttostring
+ for version in range(marshal.version + 1):
+ for obj in TEST_OBJECTS:
+ with self.subTest(obj=obj, version=version):
+ data = writeobjecttostring(obj, version)
+ obj2 = marshal.loads(data)
+ self.check_object(obj2, obj)
+
+ with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
+ writeobjecttostring(UNMARSHALLABLE, version)
+
+ with self.assertRaises(SystemError):
+ writeobjecttostring(NULL, version)
if __name__ == "__main__":
diff --git a/Modules/_testcapi/marshal.c b/Modules/_testcapi/marshal.c
index fe5b8259b88578..cf5c2250dfe609 100644
--- a/Modules/_testcapi/marshal.c
+++ b/Modules/_testcapi/marshal.c
@@ -1,25 +1,28 @@
// Test PyMarshal C API
#include "parts.h"
+#include "util.h"
#include "marshal.h" // PyMarshal_WriteLongToFile()
+
+// Test PyMarshal_WriteLongToFile()
static PyObject*
pymarshal_write_long_to_file(PyObject* self, PyObject *args)
{
long value;
PyObject *filename;
int version;
- FILE *fp;
if (!PyArg_ParseTuple(args, "lOi:pymarshal_write_long_to_file",
&value, &filename, &version))
return NULL;
- fp = Py_fopen(filename, "wb");
+ FILE *fp = Py_fopen(filename, "wb");
if (fp == NULL) {
return NULL;
}
+ assert(!PyErr_Occurred());
PyMarshal_WriteLongToFile(value, fp, version);
fclose(fp);
@@ -29,81 +32,97 @@ pymarshal_write_long_to_file(PyObject* self, PyObject *args)
Py_RETURN_NONE;
}
+
+// Test PyMarshal_WriteObjectToFile()
static PyObject*
pymarshal_write_object_to_file(PyObject* self, PyObject *args)
{
PyObject *obj;
PyObject *filename;
int version;
- FILE *fp;
if (!PyArg_ParseTuple(args, "OOi:pymarshal_write_object_to_file",
- &obj, &filename, &version))
+ &obj, &filename, &version)) {
return NULL;
+ }
+ NULLABLE(obj);
- fp = Py_fopen(filename, "wb");
+ FILE *fp = Py_fopen(filename, "wb");
if (fp == NULL) {
return NULL;
}
+ assert(!PyErr_Occurred());
PyMarshal_WriteObjectToFile(obj, fp, version);
-
fclose(fp);
if (PyErr_Occurred()) {
return NULL;
}
+
Py_RETURN_NONE;
}
+
+// Test PyMarshal_ReadShortFromFile()
static PyObject*
pymarshal_read_short_from_file(PyObject* self, PyObject *args)
{
int value;
long pos;
PyObject *filename;
- FILE *fp;
-
if (!PyArg_ParseTuple(args, "O:pymarshal_read_short_from_file", &filename))
return NULL;
- fp = Py_fopen(filename, "rb");
+ FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
return NULL;
}
+ assert(!PyErr_Occurred());
value = PyMarshal_ReadShortFromFile(fp);
pos = ftell(fp);
fclose(fp);
- if (PyErr_Occurred())
+ if (PyErr_Occurred()) {
+ assert(value == -1);
return NULL;
- return Py_BuildValue("il", value, pos);
+ }
+
+ assert(pos == 2);
+ return PyLong_FromLong(value);
}
+
+// Test PyMarshal_ReadLongFromFile()
static PyObject*
pymarshal_read_long_from_file(PyObject* self, PyObject *args)
{
- long value, pos;
+ long pos;
PyObject *filename;
- FILE *fp;
-
if (!PyArg_ParseTuple(args, "O:pymarshal_read_long_from_file", &filename))
return NULL;
- fp = Py_fopen(filename, "rb");
+ FILE *fp = Py_fopen(filename, "rb");
if (fp == NULL) {
return NULL;
}
- value = PyMarshal_ReadLongFromFile(fp);
+ assert(!PyErr_Occurred());
+ long value = PyMarshal_ReadLongFromFile(fp);
pos = ftell(fp);
fclose(fp);
- if (PyErr_Occurred())
+ if (PyErr_Occurred()) {
+ assert(value == -1);
return NULL;
- return Py_BuildValue("ll", value, pos);
+ }
+
+ assert(pos == 4);
+ return PyLong_FromLong(value);
}
+
+// Test PyMarshal_ReadLastObjectFromFile()
static PyObject*
pymarshal_read_last_object_from_file(PyObject* self, PyObject *args)
{
@@ -116,6 +135,7 @@ pymarshal_read_last_object_from_file(PyObject* self,
PyObject *args)
return NULL;
}
+ assert(!PyErr_Occurred());
PyObject *obj = PyMarshal_ReadLastObjectFromFile(fp);
long pos = ftell(fp);
@@ -126,6 +146,8 @@ pymarshal_read_last_object_from_file(PyObject* self,
PyObject *args)
return Py_BuildValue("Nl", obj, pos);
}
+
+// Test PyMarshal_ReadObjectFromFile()
static PyObject*
pymarshal_read_object_from_file(PyObject* self, PyObject *args)
{
@@ -138,6 +160,7 @@ pymarshal_read_object_from_file(PyObject* self, PyObject
*args)
return NULL;
}
+ assert(!PyErr_Occurred());
PyObject *obj = PyMarshal_ReadObjectFromFile(fp);
long pos = ftell(fp);
@@ -149,6 +172,35 @@ pymarshal_read_object_from_file(PyObject* self, PyObject
*args)
}
+// Test PyMarshal_ReadObjectFromString()
+static PyObject*
+pymarshal_readobjectfromstring(PyObject* self, PyObject *args)
+{
+ const char *str;
+ Py_ssize_t size;
+ if (!PyArg_ParseTuple(args, "s#", &str, &size)) {
+ return NULL;
+ }
+
+ return PyMarshal_ReadObjectFromString(str, size);
+}
+
+
+// Test PyMarshal_WriteObjectToString()
+static PyObject*
+pymarshal_writeobjecttostring(PyObject* self, PyObject *args)
+{
+ PyObject *obj;
+ int version;
+ if (!PyArg_ParseTuple(args, "Oi", &obj, &version)) {
+ return NULL;
+ }
+ NULLABLE(obj);
+
+ return PyMarshal_WriteObjectToString(obj, version);
+}
+
+
static PyMethodDef test_methods[] = {
{"pymarshal_write_long_to_file",
pymarshal_write_long_to_file, METH_VARARGS},
@@ -162,6 +214,10 @@ static PyMethodDef test_methods[] = {
pymarshal_read_last_object_from_file, METH_VARARGS},
{"pymarshal_read_object_from_file",
pymarshal_read_object_from_file, METH_VARARGS},
+ {"pymarshal_readobjectfromstring",
+ pymarshal_readobjectfromstring, METH_VARARGS},
+ {"pymarshal_writeobjecttostring",
+ pymarshal_writeobjecttostring, METH_VARARGS},
{NULL},
};
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]