Skip to content

Commit 54e93d3

Browse files
[3.12] gh-111065: Add more tests for the C API with the PySys_ prefix (GH-111067) (GH-111305)
* Move existing tests for PySys_GetObject() and PySys_SetObject() into specialized files. * Add test for PySys_GetXOptions() using _testcapi. * Add tests for PySys_FormatStdout(), PySys_FormatStderr(), PySys_WriteStdout() and PySys_WriteStderr() using ctypes. (cherry picked from commit b2ba298)
1 parent 6f130f2 commit 54e93d3

File tree

8 files changed

+215
-72
lines changed

8 files changed

+215
-72
lines changed

Lib/test/test_capi/test_misc.py

-40
Original file line numberDiff line numberDiff line change
@@ -1090,46 +1090,6 @@ class Data(_testcapi.ObjExtraData):
10901090
del d.extra
10911091
self.assertIsNone(d.extra)
10921092

1093-
def test_sys_getobject(self):
1094-
getobject = _testcapi.sys_getobject
1095-
1096-
self.assertIs(getobject(b'stdout'), sys.stdout)
1097-
with support.swap_attr(sys, '\U0001f40d', 42):
1098-
self.assertEqual(getobject('\U0001f40d'.encode()), 42)
1099-
1100-
self.assertIs(getobject(b'nonexisting'), AttributeError)
1101-
self.assertIs(getobject(b'\xff'), AttributeError)
1102-
# CRASHES getobject(NULL)
1103-
1104-
def test_sys_setobject(self):
1105-
setobject = _testcapi.sys_setobject
1106-
1107-
value = ['value']
1108-
value2 = ['value2']
1109-
try:
1110-
self.assertEqual(setobject(b'newattr', value), 0)
1111-
self.assertIs(sys.newattr, value)
1112-
self.assertEqual(setobject(b'newattr', value2), 0)
1113-
self.assertIs(sys.newattr, value2)
1114-
self.assertEqual(setobject(b'newattr', NULL), 0)
1115-
self.assertFalse(hasattr(sys, 'newattr'))
1116-
self.assertEqual(setobject(b'newattr', NULL), 0)
1117-
finally:
1118-
with contextlib.suppress(AttributeError):
1119-
del sys.newattr
1120-
try:
1121-
self.assertEqual(setobject('\U0001f40d'.encode(), value), 0)
1122-
self.assertIs(getattr(sys, '\U0001f40d'), value)
1123-
self.assertEqual(setobject('\U0001f40d'.encode(), NULL), 0)
1124-
self.assertFalse(hasattr(sys, '\U0001f40d'))
1125-
finally:
1126-
with contextlib.suppress(AttributeError):
1127-
delattr(sys, '\U0001f40d')
1128-
1129-
with self.assertRaises(UnicodeDecodeError):
1130-
setobject(b'\xff', value)
1131-
# CRASHES setobject(NULL, value)
1132-
11331093

11341094
@requires_limited_api
11351095
class TestHeapTypeRelative(unittest.TestCase):

Lib/test/test_capi/test_sys.py

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import unittest
2+
import contextlib
3+
import sys
4+
from test import support
5+
from test.support import import_helper
6+
7+
try:
8+
import _testcapi
9+
except ImportError:
10+
_testcapi = None
11+
12+
NULL = None
13+
14+
class CAPITest(unittest.TestCase):
15+
# TODO: Test the following functions:
16+
#
17+
# PySys_Audit()
18+
19+
maxDiff = None
20+
21+
@support.cpython_only
22+
@unittest.skipIf(_testcapi is None, 'need _testcapi module')
23+
def test_sys_getobject(self):
24+
# Test PySys_GetObject()
25+
getobject = _testcapi.sys_getobject
26+
27+
self.assertIs(getobject(b'stdout'), sys.stdout)
28+
with support.swap_attr(sys, '\U0001f40d', 42):
29+
self.assertEqual(getobject('\U0001f40d'.encode()), 42)
30+
31+
self.assertIs(getobject(b'nonexisting'), AttributeError)
32+
self.assertIs(getobject(b'\xff'), AttributeError)
33+
# CRASHES getobject(NULL)
34+
35+
@support.cpython_only
36+
@unittest.skipIf(_testcapi is None, 'need _testcapi module')
37+
def test_sys_setobject(self):
38+
# Test PySys_SetObject()
39+
setobject = _testcapi.sys_setobject
40+
41+
value = ['value']
42+
value2 = ['value2']
43+
try:
44+
self.assertEqual(setobject(b'newattr', value), 0)
45+
self.assertIs(sys.newattr, value)
46+
self.assertEqual(setobject(b'newattr', value2), 0)
47+
self.assertIs(sys.newattr, value2)
48+
self.assertEqual(setobject(b'newattr', NULL), 0)
49+
self.assertFalse(hasattr(sys, 'newattr'))
50+
self.assertEqual(setobject(b'newattr', NULL), 0)
51+
finally:
52+
with contextlib.suppress(AttributeError):
53+
del sys.newattr
54+
try:
55+
self.assertEqual(setobject('\U0001f40d'.encode(), value), 0)
56+
self.assertIs(getattr(sys, '\U0001f40d'), value)
57+
self.assertEqual(setobject('\U0001f40d'.encode(), NULL), 0)
58+
self.assertFalse(hasattr(sys, '\U0001f40d'))
59+
finally:
60+
with contextlib.suppress(AttributeError):
61+
delattr(sys, '\U0001f40d')
62+
63+
with self.assertRaises(UnicodeDecodeError):
64+
setobject(b'\xff', value)
65+
# CRASHES setobject(NULL, value)
66+
67+
@support.cpython_only
68+
@unittest.skipIf(_testcapi is None, 'need _testcapi module')
69+
def test_sys_getxoptions(self):
70+
# Test PySys_GetXOptions()
71+
getxoptions = _testcapi.sys_getxoptions
72+
73+
self.assertIs(getxoptions(), sys._xoptions)
74+
75+
xoptions = sys._xoptions
76+
try:
77+
sys._xoptions = 'non-dict'
78+
self.assertEqual(getxoptions(), {})
79+
self.assertIs(getxoptions(), sys._xoptions)
80+
81+
del sys._xoptions
82+
self.assertEqual(getxoptions(), {})
83+
self.assertIs(getxoptions(), sys._xoptions)
84+
finally:
85+
sys._xoptions = xoptions
86+
self.assertIs(getxoptions(), sys._xoptions)
87+
88+
def _test_sys_formatstream(self, funname, streamname):
89+
import_helper.import_module('ctypes')
90+
from ctypes import pythonapi, c_char_p, py_object
91+
func = getattr(pythonapi, funname)
92+
func.argtypes = (c_char_p,)
93+
94+
# Supports plain C types.
95+
with support.captured_output(streamname) as stream:
96+
func(b'Hello, %s!', c_char_p(b'world'))
97+
self.assertEqual(stream.getvalue(), 'Hello, world!')
98+
99+
# Supports Python objects.
100+
with support.captured_output(streamname) as stream:
101+
func(b'Hello, %R!', py_object('world'))
102+
self.assertEqual(stream.getvalue(), "Hello, 'world'!")
103+
104+
# The total length is not limited.
105+
with support.captured_output(streamname) as stream:
106+
func(b'Hello, %s!', c_char_p(b'world'*200))
107+
self.assertEqual(stream.getvalue(), 'Hello, ' + 'world'*200 + '!')
108+
109+
def test_sys_formatstdout(self):
110+
# Test PySys_FormatStdout()
111+
self._test_sys_formatstream('PySys_FormatStdout', 'stdout')
112+
113+
def test_sys_formatstderr(self):
114+
# Test PySys_FormatStderr()
115+
self._test_sys_formatstream('PySys_FormatStderr', 'stderr')
116+
117+
def _test_sys_writestream(self, funname, streamname):
118+
import_helper.import_module('ctypes')
119+
from ctypes import pythonapi, c_char_p
120+
func = getattr(pythonapi, funname)
121+
func.argtypes = (c_char_p,)
122+
123+
# Supports plain C types.
124+
with support.captured_output(streamname) as stream:
125+
func(b'Hello, %s!', c_char_p(b'world'))
126+
self.assertEqual(stream.getvalue(), 'Hello, world!')
127+
128+
# There is a limit on the total length.
129+
with support.captured_output(streamname) as stream:
130+
func(b'Hello, %s!', c_char_p(b'world'*100))
131+
self.assertEqual(stream.getvalue(), 'Hello, ' + 'world'*100 + '!')
132+
with support.captured_output(streamname) as stream:
133+
func(b'Hello, %s!', c_char_p(b'world'*200))
134+
out = stream.getvalue()
135+
self.assertEqual(out[:20], 'Hello, worldworldwor')
136+
self.assertEqual(out[-13:], '... truncated')
137+
self.assertGreater(len(out), 1000)
138+
139+
def test_sys_writestdout(self):
140+
# Test PySys_WriteStdout()
141+
self._test_sys_writestream('PySys_WriteStdout', 'stdout')
142+
143+
def test_sys_writestderr(self):
144+
# Test PySys_WriteStderr()
145+
self._test_sys_writestream('PySys_WriteStderr', 'stderr')
146+
147+
148+
if __name__ == "__main__":
149+
unittest.main()

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
169169
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
170170
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
171-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c
171+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c
172172
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
173173

174174
# Some testing modules MUST be built as shared libraries.

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ int _PyTestCapi_Init_Buffer(PyObject *module);
4545
int _PyTestCapi_Init_PyOS(PyObject *module);
4646
int _PyTestCapi_Init_Immortal(PyObject *module);
4747
int _PyTestCapi_Init_GC(PyObject *mod);
48+
int _PyTestCapi_Init_Sys(PyObject *);
4849

4950
#ifdef LIMITED_API_AVAILABLE
5051
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);

Modules/_testcapi/sys.c

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#define PY_SSIZE_T_CLEAN
2+
#include "parts.h"
3+
#include "util.h"
4+
5+
6+
static PyObject *
7+
sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
8+
{
9+
const char *name;
10+
Py_ssize_t size;
11+
if (!PyArg_Parse(arg, "z#", &name, &size)) {
12+
return NULL;
13+
}
14+
PyObject *result = PySys_GetObject(name);
15+
if (result == NULL) {
16+
result = PyExc_AttributeError;
17+
}
18+
return Py_NewRef(result);
19+
}
20+
21+
static PyObject *
22+
sys_setobject(PyObject *Py_UNUSED(module), PyObject *args)
23+
{
24+
const char *name;
25+
Py_ssize_t size;
26+
PyObject *value;
27+
if (!PyArg_ParseTuple(args, "z#O", &name, &size, &value)) {
28+
return NULL;
29+
}
30+
NULLABLE(value);
31+
RETURN_INT(PySys_SetObject(name, value));
32+
}
33+
34+
static PyObject *
35+
sys_getxoptions(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(ignored))
36+
{
37+
PyObject *result = PySys_GetXOptions();
38+
return Py_XNewRef(result);
39+
}
40+
41+
42+
static PyMethodDef test_methods[] = {
43+
{"sys_getobject", sys_getobject, METH_O},
44+
{"sys_setobject", sys_setobject, METH_VARARGS},
45+
{"sys_getxoptions", sys_getxoptions, METH_NOARGS},
46+
{NULL},
47+
};
48+
49+
int
50+
_PyTestCapi_Init_Sys(PyObject *m)
51+
{
52+
if (PyModule_AddFunctions(m, test_methods) < 0) {
53+
return -1;
54+
}
55+
56+
return 0;
57+
}

Modules/_testcapimodule.c

+3-31
Original file line numberDiff line numberDiff line change
@@ -3243,35 +3243,6 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
32433243
}
32443244

32453245

3246-
static PyObject *
3247-
sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
3248-
{
3249-
const char *name;
3250-
Py_ssize_t size;
3251-
if (!PyArg_Parse(arg, "z#", &name, &size)) {
3252-
return NULL;
3253-
}
3254-
PyObject *result = PySys_GetObject(name);
3255-
if (result == NULL) {
3256-
result = PyExc_AttributeError;
3257-
}
3258-
return Py_NewRef(result);
3259-
}
3260-
3261-
static PyObject *
3262-
sys_setobject(PyObject *Py_UNUSED(module), PyObject *args)
3263-
{
3264-
const char *name;
3265-
Py_ssize_t size;
3266-
PyObject *value;
3267-
if (!PyArg_ParseTuple(args, "z#O", &name, &size, &value)) {
3268-
return NULL;
3269-
}
3270-
NULLABLE(value);
3271-
RETURN_INT(PySys_SetObject(name, value));
3272-
}
3273-
3274-
32753246
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
32763247

32773248
static PyMethodDef TestMethods[] = {
@@ -3410,8 +3381,6 @@ static PyMethodDef TestMethods[] = {
34103381
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
34113382
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
34123383
{"test_atexit", test_atexit, METH_NOARGS},
3413-
{"sys_getobject", sys_getobject, METH_O},
3414-
{"sys_setobject", sys_setobject, METH_VARARGS},
34153384
{NULL, NULL} /* sentinel */
34163385
};
34173386

@@ -4058,6 +4027,9 @@ PyInit__testcapi(void)
40584027
if (_PyTestCapi_Init_PyOS(m) < 0) {
40594028
return NULL;
40604029
}
4030+
if (_PyTestCapi_Init_Sys(m) < 0) {
4031+
return NULL;
4032+
}
40614033
if (_PyTestCapi_Init_Immortal(m) < 0) {
40624034
return NULL;
40634035
}

PCbuild/_testcapi.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
<ClCompile Include="..\Modules\_testcapi\code.c" />
116116
<ClCompile Include="..\Modules\_testcapi\buffer.c" />
117117
<ClCompile Include="..\Modules\_testcapi\pyos.c" />
118+
<ClCompile Include="..\Modules\_testcapi\sys.c" />
118119
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
119120
<ClCompile Include="..\Modules\_testcapi\gc.c" />
120121
</ItemGroup>

PCbuild/_testcapi.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
<ClCompile Include="..\Modules\_testcapi\pyos.c">
7373
<Filter>Source Files</Filter>
7474
</ClCompile>
75+
<ClCompile Include="..\Modules\_testcapi\sys.c">
76+
<Filter>Source Files</Filter>
77+
</ClCompile>
7578
<ClCompile Include="..\Modules\_testcapi\gc.c">
7679
<Filter>Source Files</Filter>
7780
</ClCompile>

0 commit comments

Comments
 (0)