Skip to content

Commit b2ba298

Browse files
gh-111065: Add more tests for the C API with the PySys_ prefix (GH-111067)
* 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.
1 parent 0d1cbff commit b2ba298

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
@@ -1098,46 +1098,6 @@ class Data(_testcapi.ObjExtraData):
10981098
del d.extra
10991099
self.assertIsNone(d.extra)
11001100

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

11421102
@requires_limited_api
11431103
class TestHeapTypeRelative(unittest.TestCase):

Lib/test/test_capi/test_sys.py

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

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
160160
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
161161
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c
162-
@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/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/pyatomic.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c
162+
@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/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/pyatomic.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c
163163
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
164164
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
165165

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ int _PyTestCapi_Init_PyAtomic(PyObject *module);
4949
int _PyTestCapi_Init_PyOS(PyObject *module);
5050
int _PyTestCapi_Init_Immortal(PyObject *module);
5151
int _PyTestCapi_Init_GC(PyObject *mod);
52+
int _PyTestCapi_Init_Sys(PyObject *);
5253

5354
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
5455
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);

Modules/_testcapi/sys.c

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

Modules/_testcapimodule.c

+3-31
Original file line numberDiff line numberDiff line change
@@ -3231,35 +3231,6 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
32313231
}
32323232

32333233

3234-
static PyObject *
3235-
sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
3236-
{
3237-
const char *name;
3238-
Py_ssize_t size;
3239-
if (!PyArg_Parse(arg, "z#", &name, &size)) {
3240-
return NULL;
3241-
}
3242-
PyObject *result = PySys_GetObject(name);
3243-
if (result == NULL) {
3244-
result = PyExc_AttributeError;
3245-
}
3246-
return Py_NewRef(result);
3247-
}
3248-
3249-
static PyObject *
3250-
sys_setobject(PyObject *Py_UNUSED(module), PyObject *args)
3251-
{
3252-
const char *name;
3253-
Py_ssize_t size;
3254-
PyObject *value;
3255-
if (!PyArg_ParseTuple(args, "z#O", &name, &size, &value)) {
3256-
return NULL;
3257-
}
3258-
NULLABLE(value);
3259-
RETURN_INT(PySys_SetObject(name, value));
3260-
}
3261-
3262-
32633234
static PyMethodDef TestMethods[] = {
32643235
{"set_errno", set_errno, METH_VARARGS},
32653236
{"test_config", test_config, METH_NOARGS},
@@ -3392,8 +3363,6 @@ static PyMethodDef TestMethods[] = {
33923363
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
33933364
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
33943365
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
3395-
{"sys_getobject", sys_getobject, METH_O},
3396-
{"sys_setobject", sys_setobject, METH_VARARGS},
33973366
{NULL, NULL} /* sentinel */
33983367
};
33993368

@@ -4038,6 +4007,9 @@ PyInit__testcapi(void)
40384007
if (_PyTestCapi_Init_PyOS(m) < 0) {
40394008
return NULL;
40404009
}
4010+
if (_PyTestCapi_Init_Sys(m) < 0) {
4011+
return NULL;
4012+
}
40414013
if (_PyTestCapi_Init_Immortal(m) < 0) {
40424014
return NULL;
40434015
}

PCbuild/_testcapi.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
<ClCompile Include="..\Modules\_testcapi\buffer.c" />
116116
<ClCompile Include="..\Modules\_testcapi\pyatomic.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)