Skip to content

Commit

Permalink
pythongh-111065: Add more tests for the C API with the PySys_ prefix (p…
Browse files Browse the repository at this point in the history
…ythonGH-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.
  • Loading branch information
serhiy-storchaka authored and aisk committed Feb 11, 2024
1 parent c589a91 commit c6b839e
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 72 deletions.
40 changes: 0 additions & 40 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,46 +1098,6 @@ class Data(_testcapi.ObjExtraData):
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):
Expand Down
150 changes: 150 additions & 0 deletions Lib/test/test_capi/test_sys.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
56 changes: 56 additions & 0 deletions Modules/_testcapi/sys.c
Original file line number Diff line number Diff line change
@@ -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;
}
34 changes: 3 additions & 31 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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 */
};

Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions PCbuild/_testcapi.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
<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>
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/_testcapi.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit c6b839e

Please sign in to comment.