https://github.com/python/cpython/commit/90c3c68a658db6951b77a5be50088ec2f6adc8eb
commit: 90c3c68a658db6951b77a5be50088ec2f6adc8eb
branch: main
author: MonadChains <[email protected]>
committer: encukou <[email protected]>
date: 2024-04-01T12:52:25Z
summary:

gh-94808:Improve coverage of PyObject_Print (GH-98749)

files:
A Modules/_testcapi/object.c
M Lib/test/test_capi/test_object.py
M Lib/test/test_class.py
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters

diff --git a/Lib/test/test_capi/test_object.py 
b/Lib/test/test_capi/test_object.py
index c80e9b653789ad..fa23bff4e98918 100644
--- a/Lib/test/test_capi/test_object.py
+++ b/Lib/test/test_capi/test_object.py
@@ -1,8 +1,10 @@
 import enum
 import unittest
 from test.support import import_helper
+from test.support import os_helper
 
 _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+_testcapi = import_helper.import_module('_testcapi')
 
 
 class Constant(enum.IntEnum):
@@ -20,7 +22,7 @@ class Constant(enum.IntEnum):
     INVALID_CONSTANT = Py_CONSTANT_EMPTY_TUPLE + 1
 
 
-class CAPITest(unittest.TestCase):
+class GetConstantTest(unittest.TestCase):
     def check_get_constant(self, get_constant):
         self.assertIs(get_constant(Constant.Py_CONSTANT_NONE), None)
         self.assertIs(get_constant(Constant.Py_CONSTANT_FALSE), False)
@@ -50,5 +52,56 @@ def test_get_constant_borrowed(self):
         self.check_get_constant(_testlimitedcapi.get_constant_borrowed)
 
 
+class PrintTest(unittest.TestCase):
+    def testPyObjectPrintObject(self):
+
+        class PrintableObject:
+
+            def __repr__(self):
+                return "spam spam spam"
+
+            def __str__(self):
+                return "egg egg egg"
+
+        obj = PrintableObject()
+        output_filename = os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, output_filename)
+
+        # Test repr printing
+        _testcapi.call_pyobject_print(obj, output_filename, False)
+        with open(output_filename, 'r') as output_file:
+            self.assertEqual(output_file.read(), repr(obj))
+
+        # Test str printing
+        _testcapi.call_pyobject_print(obj, output_filename, True)
+        with open(output_filename, 'r') as output_file:
+            self.assertEqual(output_file.read(), str(obj))
+
+    def testPyObjectPrintNULL(self):
+        output_filename = os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, output_filename)
+
+        # Test repr printing
+        _testcapi.pyobject_print_null(output_filename)
+        with open(output_filename, 'r') as output_file:
+            self.assertEqual(output_file.read(), '<nil>')
+
+    def testPyObjectPrintNoRefObject(self):
+        output_filename = os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, output_filename)
+
+        # Test repr printing
+        correct_output = _testcapi.pyobject_print_noref_object(output_filename)
+        with open(output_filename, 'r') as output_file:
+            self.assertEqual(output_file.read(), correct_output)
+
+    def testPyObjectPrintOSError(self):
+        output_filename = os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, output_filename)
+
+        open(output_filename, "w+").close()
+        with self.assertRaises(OSError):
+            _testcapi.pyobject_print_os_error(output_filename)
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py
index ad89a22c625dfd..0cf06243dc8da0 100644
--- a/Lib/test/test_class.py
+++ b/Lib/test/test_class.py
@@ -788,5 +788,6 @@ def __init__(self, obj):
             Type(i)
         self.assertEqual(calls, 100)
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index ff5c05f88d0d40..26720ef408fe3c 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -162,7 +162,7 @@
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c 
_xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/file.c _testcapi/codec.c 
_testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c 
_testcapi/bytes.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/file.c _testcapi/codec.c 
_testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c 
_testcapi/bytes.c _testcapi/object.c
 @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c 
_testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c 
_testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c 
_testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c 
_testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c
new file mode 100644
index 00000000000000..ce5b574ec5ce8f
--- /dev/null
+++ b/Modules/_testcapi/object.c
@@ -0,0 +1,133 @@
+#include "parts.h"
+#include "util.h"
+
+static PyObject *
+call_pyobject_print(PyObject *self, PyObject * args)
+{
+    PyObject *object;
+    PyObject *filename;
+    PyObject *print_raw;
+    FILE *fp;
+    int flags = 0;
+
+    if (!PyArg_UnpackTuple(args, "call_pyobject_print", 3, 3,
+                           &object, &filename, &print_raw)) {
+        return NULL;
+    }
+
+    fp = _Py_fopen_obj(filename, "w+");
+
+    if (Py_IsTrue(print_raw)) {
+        flags = Py_PRINT_RAW;
+    }
+
+    if (PyObject_Print(object, fp, flags) < 0) {
+        fclose(fp);
+        return NULL;
+    }
+
+    fclose(fp);
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+pyobject_print_null(PyObject *self, PyObject *args)
+{
+    PyObject *filename;
+    FILE *fp;
+
+    if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
+        return NULL;
+    }
+
+    fp = _Py_fopen_obj(filename, "w+");
+
+    if (PyObject_Print(NULL, fp, 0) < 0) {
+        fclose(fp);
+        return NULL;
+    }
+
+    fclose(fp);
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+pyobject_print_noref_object(PyObject *self, PyObject *args)
+{
+    PyObject *test_string;
+    PyObject *filename;
+    FILE *fp;
+    char correct_string[100];
+
+    test_string = PyUnicode_FromString("Spam spam spam");
+
+    Py_SET_REFCNT(test_string, 0);
+
+    PyOS_snprintf(correct_string, 100, "<refcnt %zd at %p>",
+                  Py_REFCNT(test_string), (void *)test_string);
+
+    if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
+        return NULL;
+    }
+
+    fp = _Py_fopen_obj(filename, "w+");
+
+    if (PyObject_Print(test_string, fp, 0) < 0){
+        fclose(fp);
+        return NULL;
+    }
+
+    fclose(fp);
+
+    Py_SET_REFCNT(test_string, 1);
+    Py_DECREF(test_string);
+
+    return PyUnicode_FromString(correct_string);
+}
+
+static PyObject *
+pyobject_print_os_error(PyObject *self, PyObject *args)
+{
+    PyObject *test_string;
+    PyObject *filename;
+    FILE *fp;
+
+    test_string = PyUnicode_FromString("Spam spam spam");
+
+    if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
+        return NULL;
+    }
+
+    // open file in read mode to induce OSError
+    fp = _Py_fopen_obj(filename, "r");
+
+    if (PyObject_Print(test_string, fp, 0) < 0) {
+        fclose(fp);
+        return NULL;
+    }
+
+    fclose(fp);
+
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef test_methods[] = {
+    {"call_pyobject_print", call_pyobject_print, METH_VARARGS},
+    {"pyobject_print_null", pyobject_print_null, METH_VARARGS},
+    {"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS},
+    {"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
+
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Object(PyObject *m)
+{
+    if (PyModule_AddFunctions(m, test_methods) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index e7c868f6bcff6e..2336cc0bc33a85 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -57,5 +57,6 @@ int _PyTestCapi_Init_Immortal(PyObject *module);
 int _PyTestCapi_Init_GC(PyObject *module);
 int _PyTestCapi_Init_Hash(PyObject *module);
 int _PyTestCapi_Init_Time(PyObject *module);
+int _PyTestCapi_Init_Object(PyObject *module);
 
 #endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 3c30381be6d538..e9db6e5683e344 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4049,6 +4049,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Time(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Object(m) < 0) {
+        return NULL;
+    }
 
     PyState_AddModule(m, &_testcapimodule);
     return m;
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 615d73d5e003b4..afeb934b71b100 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -121,6 +121,7 @@
     <ClCompile Include="..\Modules\_testcapi\codec.c" />
     <ClCompile Include="..\Modules\_testcapi\hash.c" />
     <ClCompile Include="..\Modules\_testcapi\time.c" />
+    <ClCompile Include="..\Modules\_testcapi\object.c" />
     <ClCompile Include="..\Modules\_testcapi\immortal.c" />
     <ClCompile Include="..\Modules\_testcapi\gc.c" />
   </ItemGroup>
diff --git a/PCbuild/_testcapi.vcxproj.filters 
b/PCbuild/_testcapi.vcxproj.filters
index 0c11e918556ff5..b5bc4f36b2ff85 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -99,6 +99,9 @@
     <ClCompile Include="..\Modules\_testcapi\time.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\object.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\Modules\_testcapi\gc.c">
       <Filter>Source Files</Filter>
     </ClCompile>

_______________________________________________
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]

Reply via email to