diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index ee55a2bef63..66d7ae09831 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -1098,46 +1098,6 @@ class CAPITest(unittest.TestCase):
del d.extra
self.assertIsNone(d.extra)
- def test_sys_getobject(self):
- getobject = _testcapi.sys_getobject
-
- self.assertIs(getobject(b'stdout'), sys.stdout)
- with support.swap_attr(sys, '\U0001f40d', 42):
- self.assertEqual(getobject('\U0001f40d'.encode()), 42)
-
- self.assertIs(getobject(b'nonexisting'), AttributeError)
- self.assertIs(getobject(b'\xff'), AttributeError)
- # CRASHES getobject(NULL)
-
- def test_sys_setobject(self):
- setobject = _testcapi.sys_setobject
-
- value = ['value']
- value2 = ['value2']
- try:
- self.assertEqual(setobject(b'newattr', value), 0)
- self.assertIs(sys.newattr, value)
- self.assertEqual(setobject(b'newattr', value2), 0)
- self.assertIs(sys.newattr, value2)
- self.assertEqual(setobject(b'newattr', NULL), 0)
- self.assertFalse(hasattr(sys, 'newattr'))
- self.assertEqual(setobject(b'newattr', NULL), 0)
- finally:
- with contextlib.suppress(AttributeError):
- del sys.newattr
- try:
- self.assertEqual(setobject('\U0001f40d'.encode(), value), 0)
- self.assertIs(getattr(sys, '\U0001f40d'), value)
- self.assertEqual(setobject('\U0001f40d'.encode(), NULL), 0)
- self.assertFalse(hasattr(sys, '\U0001f40d'))
- finally:
- with contextlib.suppress(AttributeError):
- delattr(sys, '\U0001f40d')
-
- with self.assertRaises(UnicodeDecodeError):
- setobject(b'\xff', value)
- # CRASHES setobject(NULL, value)
-
@requires_limited_api
class TestHeapTypeRelative(unittest.TestCase):
diff --git a/Lib/test/test_capi/test_sys.py b/Lib/test/test_capi/test_sys.py
new file mode 100644
index 00000000000..b83a0a1604d
--- /dev/null
+++ b/Lib/test/test_capi/test_sys.py
@@ -0,0 +1,150 @@
+import unittest
+import contextlib
+import sys
+from test import support
+from test.support import import_helper
+
+try:
+ import _testcapi
+except ImportError:
+ _testcapi = None
+
+NULL = None
+
+class CAPITest(unittest.TestCase):
+ # TODO: Test the following functions:
+ #
+ # PySys_Audit()
+ # PySys_AuditTuple()
+
+ maxDiff = None
+
+ @support.cpython_only
+ @unittest.skipIf(_testcapi is None, 'need _testcapi module')
+ def test_sys_getobject(self):
+ # Test PySys_GetObject()
+ getobject = _testcapi.sys_getobject
+
+ self.assertIs(getobject(b'stdout'), sys.stdout)
+ with support.swap_attr(sys, '\U0001f40d', 42):
+ self.assertEqual(getobject('\U0001f40d'.encode()), 42)
+
+ self.assertIs(getobject(b'nonexisting'), AttributeError)
+ self.assertIs(getobject(b'\xff'), AttributeError)
+ # CRASHES getobject(NULL)
+
+ @support.cpython_only
+ @unittest.skipIf(_testcapi is None, 'need _testcapi module')
+ def test_sys_setobject(self):
+ # Test PySys_SetObject()
+ setobject = _testcapi.sys_setobject
+
+ value = ['value']
+ value2 = ['value2']
+ try:
+ self.assertEqual(setobject(b'newattr', value), 0)
+ self.assertIs(sys.newattr, value)
+ self.assertEqual(setobject(b'newattr', value2), 0)
+ self.assertIs(sys.newattr, value2)
+ self.assertEqual(setobject(b'newattr', NULL), 0)
+ self.assertFalse(hasattr(sys, 'newattr'))
+ self.assertEqual(setobject(b'newattr', NULL), 0)
+ finally:
+ with contextlib.suppress(AttributeError):
+ del sys.newattr
+ try:
+ self.assertEqual(setobject('\U0001f40d'.encode(), value), 0)
+ self.assertIs(getattr(sys, '\U0001f40d'), value)
+ self.assertEqual(setobject('\U0001f40d'.encode(), NULL), 0)
+ self.assertFalse(hasattr(sys, '\U0001f40d'))
+ finally:
+ with contextlib.suppress(AttributeError):
+ delattr(sys, '\U0001f40d')
+
+ with self.assertRaises(UnicodeDecodeError):
+ setobject(b'\xff', value)
+ # CRASHES setobject(NULL, value)
+
+ @support.cpython_only
+ @unittest.skipIf(_testcapi is None, 'need _testcapi module')
+ def test_sys_getxoptions(self):
+ # Test PySys_GetXOptions()
+ getxoptions = _testcapi.sys_getxoptions
+
+ self.assertIs(getxoptions(), sys._xoptions)
+
+ xoptions = sys._xoptions
+ try:
+ sys._xoptions = 'non-dict'
+ self.assertEqual(getxoptions(), {})
+ self.assertIs(getxoptions(), sys._xoptions)
+
+ del sys._xoptions
+ self.assertEqual(getxoptions(), {})
+ self.assertIs(getxoptions(), sys._xoptions)
+ finally:
+ sys._xoptions = xoptions
+ self.assertIs(getxoptions(), sys._xoptions)
+
+ def _test_sys_formatstream(self, funname, streamname):
+ import_helper.import_module('ctypes')
+ from ctypes import pythonapi, c_char_p, py_object
+ func = getattr(pythonapi, funname)
+ func.argtypes = (c_char_p,)
+
+ # Supports plain C types.
+ with support.captured_output(streamname) as stream:
+ func(b'Hello, %s!', c_char_p(b'world'))
+ self.assertEqual(stream.getvalue(), 'Hello, world!')
+
+ # Supports Python objects.
+ with support.captured_output(streamname) as stream:
+ func(b'Hello, %R!', py_object('world'))
+ self.assertEqual(stream.getvalue(), "Hello, 'world'!")
+
+ # The total length is not limited.
+ with support.captured_output(streamname) as stream:
+ func(b'Hello, %s!', c_char_p(b'world'*200))
+ self.assertEqual(stream.getvalue(), 'Hello, ' + 'world'*200 + '!')
+
+ def test_sys_formatstdout(self):
+ # Test PySys_FormatStdout()
+ self._test_sys_formatstream('PySys_FormatStdout', 'stdout')
+
+ def test_sys_formatstderr(self):
+ # Test PySys_FormatStderr()
+ self._test_sys_formatstream('PySys_FormatStderr', 'stderr')
+
+ def _test_sys_writestream(self, funname, streamname):
+ import_helper.import_module('ctypes')
+ from ctypes import pythonapi, c_char_p
+ func = getattr(pythonapi, funname)
+ func.argtypes = (c_char_p,)
+
+ # Supports plain C types.
+ with support.captured_output(streamname) as stream:
+ func(b'Hello, %s!', c_char_p(b'world'))
+ self.assertEqual(stream.getvalue(), 'Hello, world!')
+
+ # There is a limit on the total length.
+ with support.captured_output(streamname) as stream:
+ func(b'Hello, %s!', c_char_p(b'world'*100))
+ self.assertEqual(stream.getvalue(), 'Hello, ' + 'world'*100 + '!')
+ with support.captured_output(streamname) as stream:
+ func(b'Hello, %s!', c_char_p(b'world'*200))
+ out = stream.getvalue()
+ self.assertEqual(out[:20], 'Hello, worldworldwor')
+ self.assertEqual(out[-13:], '... truncated')
+ self.assertGreater(len(out), 1000)
+
+ def test_sys_writestdout(self):
+ # Test PySys_WriteStdout()
+ self._test_sys_writestream('PySys_WriteStdout', 'stdout')
+
+ def test_sys_writestderr(self):
+ # Test PySys_WriteStderr()
+ self._test_sys_writestream('PySys_WriteStderr', 'stderr')
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 647f44280b9..c73522b8ecf 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -159,7 +159,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
-@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
+@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
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index e5dbb9cc49f..4fa77a8844e 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -49,6 +49,7 @@ int _PyTestCapi_Init_PyAtomic(PyObject *module);
int _PyTestCapi_Init_PyOS(PyObject *module);
int _PyTestCapi_Init_Immortal(PyObject *module);
int _PyTestCapi_Init_GC(PyObject *mod);
+int _PyTestCapi_Init_Sys(PyObject *);
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);
diff --git a/Modules/_testcapi/sys.c b/Modules/_testcapi/sys.c
new file mode 100644
index 00000000000..aa40e3cd5b9
--- /dev/null
+++ b/Modules/_testcapi/sys.c
@@ -0,0 +1,56 @@
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
+{
+ const char *name;
+ Py_ssize_t size;
+ if (!PyArg_Parse(arg, "z#", &name, &size)) {
+ return NULL;
+ }
+ PyObject *result = PySys_GetObject(name);
+ if (result == NULL) {
+ result = PyExc_AttributeError;
+ }
+ return Py_NewRef(result);
+}
+
+static PyObject *
+sys_setobject(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ const char *name;
+ Py_ssize_t size;
+ PyObject *value;
+ if (!PyArg_ParseTuple(args, "z#O", &name, &size, &value)) {
+ return NULL;
+ }
+ NULLABLE(value);
+ RETURN_INT(PySys_SetObject(name, value));
+}
+
+static PyObject *
+sys_getxoptions(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(ignored))
+{
+ PyObject *result = PySys_GetXOptions();
+ return Py_XNewRef(result);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"sys_getobject", sys_getobject, METH_O},
+ {"sys_setobject", sys_setobject, METH_VARARGS},
+ {"sys_getxoptions", sys_getxoptions, METH_NOARGS},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Sys(PyObject *m)
+{
+ if (PyModule_AddFunctions(m, test_methods) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 577fea35d97..dc9a25b6c9f 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3231,35 +3231,6 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
}
-static PyObject *
-sys_getobject(PyObject *Py_UNUSED(module), PyObject *arg)
-{
- const char *name;
- Py_ssize_t size;
- if (!PyArg_Parse(arg, "z#", &name, &size)) {
- return NULL;
- }
- PyObject *result = PySys_GetObject(name);
- if (result == NULL) {
- result = PyExc_AttributeError;
- }
- return Py_NewRef(result);
-}
-
-static PyObject *
-sys_setobject(PyObject *Py_UNUSED(module), PyObject *args)
-{
- const char *name;
- Py_ssize_t size;
- PyObject *value;
- if (!PyArg_ParseTuple(args, "z#O", &name, &size, &value)) {
- return NULL;
- }
- NULLABLE(value);
- RETURN_INT(PySys_SetObject(name, value));
-}
-
-
static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
@@ -3392,8 +3363,6 @@ static PyMethodDef TestMethods[] = {
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
- {"sys_getobject", sys_getobject, METH_O},
- {"sys_setobject", sys_setobject, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
@@ -4038,6 +4007,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_PyOS(m) < 0) {
return NULL;
}
+ if (_PyTestCapi_Init_Sys(m) < 0) {
+ return NULL;
+ }
if (_PyTestCapi_Init_Immortal(m) < 0) {
return NULL;
}
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 0f33c5a76ad..ae97c34eed5 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -115,6 +115,7 @@
+
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 4ba6011d8af..c3ad2564edd 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -72,6 +72,9 @@
Source Files
+
+ Source Files
+
Source Files