]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-111065: Add more tests for the C API with the PySys_ prefix (GH-111067)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 25 Oct 2023 08:33:12 +0000 (11:33 +0300)
committerGitHub <noreply@github.com>
Wed, 25 Oct 2023 08:33:12 +0000 (11:33 +0300)
* 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.

Lib/test/test_capi/test_misc.py
Lib/test/test_capi/test_sys.py [new file with mode: 0644]
Modules/Setup.stdlib.in
Modules/_testcapi/parts.h
Modules/_testcapi/sys.c [new file with mode: 0644]
Modules/_testcapimodule.c
PCbuild/_testcapi.vcxproj
PCbuild/_testcapi.vcxproj.filters

index ee55a2bef63303f46aa7b0fb6fd40b198bfe7681..66d7ae098315dae4c86561f6bddb01a7c6f60f5c 100644 (file)
@@ -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 (file)
index 0000000..b83a0a1
--- /dev/null
@@ -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()
index 647f44280b9ea1db587ab4bd53bfd762ee67b7d5..c73522b8ecf426da7e5e94e65f1474a2bb4737b7 100644 (file)
 @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
 
index e5dbb9cc49ff4bcc494aa6b2c51b363b8a085def..4fa77a8844ee4d6105bad58add73fc698f97f795 100644 (file)
@@ -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 (file)
index 0000000..aa40e3c
--- /dev/null
@@ -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;
+}
index 577fea35d97b7ee5af434224d9287c4ddc9f42e5..dc9a25b6c9ff3ece1a5757a1d53eaa0ce202d0e7 100644 (file)
@@ -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;
     }
index 0f33c5a76ade9d22053261bb0b5442cfd1778533..ae97c34eed50e04e9e8576609060e43690305c28 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\buffer.c" />
     <ClCompile Include="..\Modules\_testcapi\pyatomic.c" />
     <ClCompile Include="..\Modules\_testcapi\pyos.c" />
+    <ClCompile Include="..\Modules\_testcapi\sys.c" />
     <ClCompile Include="..\Modules\_testcapi\immortal.c" />
     <ClCompile Include="..\Modules\_testcapi\gc.c" />
   </ItemGroup>
index 4ba6011d8af5b97f5b2b710a7f7cfdac17b17794..c3ad2564edd6860ddda3baad303be17ca91598cb 100644 (file)
@@ -72,6 +72,9 @@
     <ClCompile Include="..\Modules\_testcapi\pyos.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\sys.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\Modules\_testcapi\gc.c">
       <Filter>Source Files</Filter>
     </ClCompile>