diff --git a/Lib/test/test_capi/test_marshal.py b/Lib/test/test_capi/test_marshal.py new file mode 100644 index 00000000000000..ab9010efe7c313 --- /dev/null +++ b/Lib/test/test_capi/test_marshal.py @@ -0,0 +1,217 @@ +# Test PyMarshal C API + +import marshal +import struct +import unittest +from test.support import import_helper +from test.support import os_helper + +_testcapi = import_helper.import_module('_testcapi') + +NULL = None +Py_MARSHAL_VERSION = _testcapi.Py_MARSHAL_VERSION + +def noop_func(): + pass + +SIMPLE_OBJECT = 123 +# Only test a few objects: see test_marshal for more exhaustive tests +TEST_OBJECTS = ( + '\u20ac', + b'abc', + True, + 45.6, + 7+8j, + SIMPLE_OBJECT, + # Check that serializing code object is allowed (allow_code = 1) + noop_func.__code__, +) + +# 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) + + +class CAPIUnicodeTest(unittest.TestCase): + def check_object(self, obj2, obj): + self.assertEqual(obj2, obj) + self.assertEqual(type(obj2), type(obj)) + + def test_pymarshal_readobjectfromstring(self): + # Test PyMarshal_ReadObjectFromString() + readobjectfromstring = _testcapi.pymarshal_readobjectfromstring + for obj in TEST_OBJECTS: + for version in range(Py_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, Py_MARSHAL_VERSION) + data = data[:-1] # truncate + 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(Py_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.assertRaises(SystemError): + writeobjecttostring(NULL, version) + + def test_pymarshal_writeobjecttofile(self): + # Test PyMarshal_WriteObjectToFile() + writeobjecttofile = _testcapi.pymarshal_writeobjecttofile + + filename = os_helper.TESTFN + self.addCleanup(os_helper.unlink, filename) + + for version in range(Py_MARSHAL_VERSION + 1): + for obj in TEST_OBJECTS: + with self.subTest(obj=obj, version=version): + writeobjecttofile(obj, filename, version) + data = read_file(filename) + obj2 = marshal.loads(data) + self.check_object(obj2, obj) + + with self.assertRaises(SystemError): + writeobjecttofile(NULL, filename, version) + + def test_pymarshal_writelongtofile(self): + # Test PyMarshal_WriteLongToFile() + writelongtofile = _testcapi.pymarshal_writelongtofile + + def mask32(value): + res = value & (2 ** 32 - 1) + if res >= 2147483648: + return res - 4294967296 + else: + return res + + filename = os_helper.TESTFN + self.addCleanup(os_helper.unlink, filename) + + limit = 2 ** 31 + for version in range(Py_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): + writelongtofile(value, filename, version) + data = read_file(filename) + self.assertEqual(len(data), 4) + value2 = struct.unpack(', so include it explicitly +#include "marshal.h" + + +// 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); +} + + +// Test PyMarshal_WriteLongToFile() +static PyObject* +pymarshal_writelongtofile(PyObject* self, PyObject *args) +{ + long value; + PyObject *filename; + int version; + if (!PyArg_ParseTuple(args, "lOi", &value, &filename, &version)) { + return NULL; + } + + FILE *fp = Py_fopen(filename, "w"); + if (fp == NULL) { + return NULL; + } + + assert(!PyErr_Occurred()); + PyMarshal_WriteLongToFile(value, fp, version); + fclose(fp); + if (PyErr_Occurred()) { + return NULL; + } + + Py_RETURN_NONE; +} + + +// Test PyMarshal_WriteObjectToFile() +static PyObject* +pymarshal_writeobjecttofile(PyObject* self, PyObject *args) +{ + PyObject *obj; + PyObject *filename; + int version; + if (!PyArg_ParseTuple(args, "OOi", &obj, &filename, &version)) { + return NULL; + } + NULLABLE(obj); + + FILE *fp = Py_fopen(filename, "w"); + 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_readshortfromfile(PyObject* self, PyObject *args) +{ + PyObject *filename; + if (!PyArg_ParseTuple(args, "O", &filename)) { + return NULL; + } + + FILE *fp = Py_fopen(filename, "r"); + if (fp == NULL) { + return NULL; + } + + assert(!PyErr_Occurred()); + int value = PyMarshal_ReadShortFromFile(fp); + fclose(fp); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + assert(!PyErr_Occurred()); + + return PyLong_FromLong(value); +} + + +// Test PyMarshal_ReadLongFromFile() +static PyObject* +pymarshal_readlongfromfile(PyObject* self, PyObject *args) +{ + PyObject *filename; + if (!PyArg_ParseTuple(args, "O", &filename)) { + return NULL; + } + + FILE *fp = Py_fopen(filename, "r"); + if (fp == NULL) { + return NULL; + } + + assert(!PyErr_Occurred()); + long value = PyMarshal_ReadLongFromFile(fp); + fclose(fp); + if (value == -1 && PyErr_Occurred()) { + return NULL; + } + assert(!PyErr_Occurred()); + + return PyLong_FromLong(value); +} + + +// Test PyMarshal_ReadObjectFromFile() +static PyObject* +pymarshal_readobjectfromfile(PyObject* self, PyObject *args) +{ + PyObject *filename; + if (!PyArg_ParseTuple(args, "O", &filename)) { + return NULL; + } + + FILE *fp = Py_fopen(filename, "r"); + if (fp == NULL) { + return NULL; + } + + assert(!PyErr_Occurred()); + PyObject *obj = PyMarshal_ReadObjectFromFile(fp); + fclose(fp); + if (obj == NULL) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + + return obj; +} + + +// Test PyMarshal_ReadLastObjectFromFile() +static PyObject* +pymarshal_readlastobjectfromfile(PyObject* self, PyObject *args) +{ + PyObject *filename; + if (!PyArg_ParseTuple(args, "O", &filename)) { + return NULL; + } + + FILE *fp = Py_fopen(filename, "r"); + if (fp == NULL) { + return NULL; + } + + assert(!PyErr_Occurred()); + PyObject *obj = PyMarshal_ReadLastObjectFromFile(fp); + fclose(fp); + if (obj == NULL) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + + return obj; +} + + +static PyMethodDef TestMethods[] = { + {"pymarshal_readobjectfromstring", pymarshal_readobjectfromstring, METH_VARARGS}, + {"pymarshal_writeobjecttostring", pymarshal_writeobjecttostring, METH_VARARGS}, + {"pymarshal_writelongtofile", pymarshal_writelongtofile, METH_VARARGS}, + {"pymarshal_writeobjecttofile", pymarshal_writeobjecttofile, METH_VARARGS}, + {"pymarshal_readshortfromfile", pymarshal_readshortfromfile, METH_VARARGS}, + {"pymarshal_readlongfromfile", pymarshal_readlongfromfile, METH_VARARGS}, + {"pymarshal_readobjectfromfile", pymarshal_readobjectfromfile, METH_VARARGS}, + {"pymarshal_readlastobjectfromfile", pymarshal_readlastobjectfromfile, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestCapi_Init_Marshal(PyObject *mod) +{ + if (PyModule_AddFunctions(mod, TestMethods) < 0) { + return -1; + } + if (PyModule_AddIntMacro(mod, Py_MARSHAL_VERSION) < 0) { + return -1; + } + return 0; +} diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 98b5dd47accde3..1ae3f0773e42f8 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -68,5 +68,6 @@ int _PyTestCapi_Init_Type(PyObject *mod); int _PyTestCapi_Init_Function(PyObject *mod); int _PyTestCapi_Init_Module(PyObject *mod); int _PyTestCapi_Init_Weakref(PyObject *mod); +int _PyTestCapi_Init_Marshal(PyObject *mod); #endif // Py_TESTCAPI_PARTS_H diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c01197d15bad5f..55ea7d953dfd77 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3937,6 +3937,9 @@ _testcapi_exec(PyObject *m) if (_PyTestCapi_Init_Weakref(m) < 0) { return -1; } + if (_PyTestCapi_Init_Marshal(m) < 0) { + return -1; + } return 0; } diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 64e50b67be4656..d856b70bbdd579 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -134,6 +134,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index a3b62e1df663e0..554e5f3075f7eb 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -135,6 +135,9 @@ Source Files + + Source Files +