Skip to content

Commit b36b991

Browse files
MonadChainsdiegorusso
authored andcommitted
pythongh-94808:Improve coverage of PyObject_Print (pythonGH-98749)
1 parent e88b7e2 commit b36b991

File tree

8 files changed

+197
-2
lines changed

8 files changed

+197
-2
lines changed

Lib/test/test_capi/test_object.py

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import enum
22
import unittest
33
from test.support import import_helper
4+
from test.support import os_helper
45

56
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
7+
_testcapi = import_helper.import_module('_testcapi')
68

79

810
class Constant(enum.IntEnum):
@@ -20,7 +22,7 @@ class Constant(enum.IntEnum):
2022
INVALID_CONSTANT = Py_CONSTANT_EMPTY_TUPLE + 1
2123

2224

23-
class CAPITest(unittest.TestCase):
25+
class GetConstantTest(unittest.TestCase):
2426
def check_get_constant(self, get_constant):
2527
self.assertIs(get_constant(Constant.Py_CONSTANT_NONE), None)
2628
self.assertIs(get_constant(Constant.Py_CONSTANT_FALSE), False)
@@ -50,5 +52,56 @@ def test_get_constant_borrowed(self):
5052
self.check_get_constant(_testlimitedcapi.get_constant_borrowed)
5153

5254

55+
class PrintTest(unittest.TestCase):
56+
def testPyObjectPrintObject(self):
57+
58+
class PrintableObject:
59+
60+
def __repr__(self):
61+
return "spam spam spam"
62+
63+
def __str__(self):
64+
return "egg egg egg"
65+
66+
obj = PrintableObject()
67+
output_filename = os_helper.TESTFN
68+
self.addCleanup(os_helper.unlink, output_filename)
69+
70+
# Test repr printing
71+
_testcapi.call_pyobject_print(obj, output_filename, False)
72+
with open(output_filename, 'r') as output_file:
73+
self.assertEqual(output_file.read(), repr(obj))
74+
75+
# Test str printing
76+
_testcapi.call_pyobject_print(obj, output_filename, True)
77+
with open(output_filename, 'r') as output_file:
78+
self.assertEqual(output_file.read(), str(obj))
79+
80+
def testPyObjectPrintNULL(self):
81+
output_filename = os_helper.TESTFN
82+
self.addCleanup(os_helper.unlink, output_filename)
83+
84+
# Test repr printing
85+
_testcapi.pyobject_print_null(output_filename)
86+
with open(output_filename, 'r') as output_file:
87+
self.assertEqual(output_file.read(), '<nil>')
88+
89+
def testPyObjectPrintNoRefObject(self):
90+
output_filename = os_helper.TESTFN
91+
self.addCleanup(os_helper.unlink, output_filename)
92+
93+
# Test repr printing
94+
correct_output = _testcapi.pyobject_print_noref_object(output_filename)
95+
with open(output_filename, 'r') as output_file:
96+
self.assertEqual(output_file.read(), correct_output)
97+
98+
def testPyObjectPrintOSError(self):
99+
output_filename = os_helper.TESTFN
100+
self.addCleanup(os_helper.unlink, output_filename)
101+
102+
open(output_filename, "w+").close()
103+
with self.assertRaises(OSError):
104+
_testcapi.pyobject_print_os_error(output_filename)
105+
53106
if __name__ == "__main__":
54107
unittest.main()

Lib/test/test_class.py

+1
Original file line numberDiff line numberDiff line change
@@ -788,5 +788,6 @@ def __init__(self, obj):
788788
Type(i)
789789
self.assertEqual(calls, 100)
790790

791+
791792
if __name__ == '__main__':
792793
unittest.main()

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
163163
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
164164
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
165-
@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
165+
@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
166166
@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
167167
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
168168
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c

Modules/_testcapi/object.c

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#include "parts.h"
2+
#include "util.h"
3+
4+
static PyObject *
5+
call_pyobject_print(PyObject *self, PyObject * args)
6+
{
7+
PyObject *object;
8+
PyObject *filename;
9+
PyObject *print_raw;
10+
FILE *fp;
11+
int flags = 0;
12+
13+
if (!PyArg_UnpackTuple(args, "call_pyobject_print", 3, 3,
14+
&object, &filename, &print_raw)) {
15+
return NULL;
16+
}
17+
18+
fp = _Py_fopen_obj(filename, "w+");
19+
20+
if (Py_IsTrue(print_raw)) {
21+
flags = Py_PRINT_RAW;
22+
}
23+
24+
if (PyObject_Print(object, fp, flags) < 0) {
25+
fclose(fp);
26+
return NULL;
27+
}
28+
29+
fclose(fp);
30+
31+
Py_RETURN_NONE;
32+
}
33+
34+
static PyObject *
35+
pyobject_print_null(PyObject *self, PyObject *args)
36+
{
37+
PyObject *filename;
38+
FILE *fp;
39+
40+
if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
41+
return NULL;
42+
}
43+
44+
fp = _Py_fopen_obj(filename, "w+");
45+
46+
if (PyObject_Print(NULL, fp, 0) < 0) {
47+
fclose(fp);
48+
return NULL;
49+
}
50+
51+
fclose(fp);
52+
53+
Py_RETURN_NONE;
54+
}
55+
56+
static PyObject *
57+
pyobject_print_noref_object(PyObject *self, PyObject *args)
58+
{
59+
PyObject *test_string;
60+
PyObject *filename;
61+
FILE *fp;
62+
char correct_string[100];
63+
64+
test_string = PyUnicode_FromString("Spam spam spam");
65+
66+
Py_SET_REFCNT(test_string, 0);
67+
68+
PyOS_snprintf(correct_string, 100, "<refcnt %zd at %p>",
69+
Py_REFCNT(test_string), (void *)test_string);
70+
71+
if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
72+
return NULL;
73+
}
74+
75+
fp = _Py_fopen_obj(filename, "w+");
76+
77+
if (PyObject_Print(test_string, fp, 0) < 0){
78+
fclose(fp);
79+
return NULL;
80+
}
81+
82+
fclose(fp);
83+
84+
Py_SET_REFCNT(test_string, 1);
85+
Py_DECREF(test_string);
86+
87+
return PyUnicode_FromString(correct_string);
88+
}
89+
90+
static PyObject *
91+
pyobject_print_os_error(PyObject *self, PyObject *args)
92+
{
93+
PyObject *test_string;
94+
PyObject *filename;
95+
FILE *fp;
96+
97+
test_string = PyUnicode_FromString("Spam spam spam");
98+
99+
if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) {
100+
return NULL;
101+
}
102+
103+
// open file in read mode to induce OSError
104+
fp = _Py_fopen_obj(filename, "r");
105+
106+
if (PyObject_Print(test_string, fp, 0) < 0) {
107+
fclose(fp);
108+
return NULL;
109+
}
110+
111+
fclose(fp);
112+
113+
Py_RETURN_NONE;
114+
}
115+
116+
static PyMethodDef test_methods[] = {
117+
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
118+
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
119+
{"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS},
120+
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
121+
122+
{NULL},
123+
};
124+
125+
int
126+
_PyTestCapi_Init_Object(PyObject *m)
127+
{
128+
if (PyModule_AddFunctions(m, test_methods) < 0) {
129+
return -1;
130+
}
131+
132+
return 0;
133+
}

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ int _PyTestCapi_Init_Immortal(PyObject *module);
5757
int _PyTestCapi_Init_GC(PyObject *module);
5858
int _PyTestCapi_Init_Hash(PyObject *module);
5959
int _PyTestCapi_Init_Time(PyObject *module);
60+
int _PyTestCapi_Init_Object(PyObject *module);
6061

6162
#endif // Py_TESTCAPI_PARTS_H

Modules/_testcapimodule.c

+3
Original file line numberDiff line numberDiff line change
@@ -4049,6 +4049,9 @@ PyInit__testcapi(void)
40494049
if (_PyTestCapi_Init_Time(m) < 0) {
40504050
return NULL;
40514051
}
4052+
if (_PyTestCapi_Init_Object(m) < 0) {
4053+
return NULL;
4054+
}
40524055

40534056
PyState_AddModule(m, &_testcapimodule);
40544057
return m;

PCbuild/_testcapi.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
<ClCompile Include="..\Modules\_testcapi\codec.c" />
122122
<ClCompile Include="..\Modules\_testcapi\hash.c" />
123123
<ClCompile Include="..\Modules\_testcapi\time.c" />
124+
<ClCompile Include="..\Modules\_testcapi\object.c" />
124125
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
125126
<ClCompile Include="..\Modules\_testcapi\gc.c" />
126127
</ItemGroup>

PCbuild/_testcapi.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@
9999
<ClCompile Include="..\Modules\_testcapi\time.c">
100100
<Filter>Source Files</Filter>
101101
</ClCompile>
102+
<ClCompile Include="..\Modules\_testcapi\object.c">
103+
<Filter>Source Files</Filter>
104+
</ClCompile>
102105
<ClCompile Include="..\Modules\_testcapi\gc.c">
103106
<Filter>Source Files</Filter>
104107
</ClCompile>

0 commit comments

Comments
 (0)