Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[3.12] gh-117968: Add tests for the part of the PyRun family of the C API (GH-117982) #118011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions Lib/test/test_capi/test_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import os
import unittest
from collections import UserDict
from test.support import import_helper
from test.support.os_helper import unlink, TESTFN, TESTFN_ASCII, TESTFN_UNDECODABLE

NULL = None
_testcapi = import_helper.import_module('_testcapi')
Py_single_input = _testcapi.Py_single_input
Py_file_input = _testcapi.Py_file_input
Py_eval_input = _testcapi.Py_eval_input


class CAPITest(unittest.TestCase):
# TODO: Test the following functions:
#
# PyRun_SimpleStringFlags
# PyRun_AnyFileExFlags
# PyRun_SimpleFileExFlags
# PyRun_InteractiveOneFlags
# PyRun_InteractiveOneObject
# PyRun_InteractiveLoopFlags
# PyRun_String (may be a macro)
# PyRun_AnyFile (may be a macro)
# PyRun_AnyFileEx (may be a macro)
# PyRun_AnyFileFlags (may be a macro)
# PyRun_SimpleString (may be a macro)
# PyRun_SimpleFile (may be a macro)
# PyRun_SimpleFileEx (may be a macro)
# PyRun_InteractiveOne (may be a macro)
# PyRun_InteractiveLoop (may be a macro)
# PyRun_File (may be a macro)
# PyRun_FileEx (may be a macro)
# PyRun_FileFlags (may be a macro)

def test_run_stringflags(self):
# Test PyRun_StringFlags().
def run(s, *args):
return _testcapi.run_stringflags(s, Py_file_input, *args)
source = b'a\n'

self.assertIsNone(run(b'a\n', dict(a=1)))
self.assertIsNone(run(b'a\n', dict(a=1), {}))
self.assertIsNone(run(b'a\n', {}, dict(a=1)))
self.assertIsNone(run(b'a\n', {}, UserDict(a=1)))

self.assertRaises(NameError, run, b'a\n', {})
self.assertRaises(NameError, run, b'a\n', {}, {})
self.assertRaises(TypeError, run, b'a\n', dict(a=1), [])
self.assertRaises(TypeError, run, b'a\n', dict(a=1), 1)

self.assertIsNone(run(b'\xc3\xa4\n', {'\xe4': 1}))
self.assertRaises(SyntaxError, run, b'\xe4\n', {})

# CRASHES run(b'a\n', NULL)
# CRASHES run(b'a\n', NULL, {})
# CRASHES run(b'a\n', NULL, dict(a=1))
# CRASHES run(b'a\n', UserDict())
# CRASHES run(b'a\n', UserDict(), {})
# CRASHES run(b'a\n', UserDict(), dict(a=1))

# CRASHES run(NULL, {})

def test_run_fileexflags(self):
# Test PyRun_FileExFlags().
# XXX: fopen() uses different path encoding than Python on Windows.
filename = os.fsencode(TESTFN if os.name != 'nt' else TESTFN_ASCII)
with open(filename, 'wb') as fp:
fp.write(b'a\n')
self.addCleanup(unlink, filename)
def run(*args):
return _testcapi.run_fileexflags(filename, Py_file_input, *args)

self.assertIsNone(run(dict(a=1)))
self.assertIsNone(run(dict(a=1), {}))
self.assertIsNone(run({}, dict(a=1)))
self.assertIsNone(run({}, UserDict(a=1)))
self.assertIsNone(run(dict(a=1), {}, 1)) # closeit = True

self.assertRaises(NameError, run, {})
self.assertRaises(NameError, run, {}, {})
self.assertRaises(TypeError, run, dict(a=1), [])
self.assertRaises(TypeError, run, dict(a=1), 1)

# CRASHES run(NULL)
# CRASHES run(NULL, {})
# CRASHES run(NULL, dict(a=1))
# CRASHES run(UserDict())
# CRASHES run(UserDict(), {})
# CRASHES run(UserDict(), dict(a=1))

@unittest.skipUnless(TESTFN_UNDECODABLE, 'only works if there are undecodable paths')
@unittest.skipIf(os.name == 'nt', 'does not work on Windows')
def test_run_fileexflags_with_undecodable_filename(self):
run = _testcapi.run_fileexflags
try:
with open(TESTFN_UNDECODABLE, 'wb') as fp:
fp.write(b'a\n')
self.addCleanup(unlink, TESTFN_UNDECODABLE)
except OSError:
self.skipTest('undecodable paths are not supported')
self.assertIsNone(run(TESTFN_UNDECODABLE, Py_file_input, dict(a=1)))


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 @@ -168,7 +168,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.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/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.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/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c

# Some testing modules MUST be built as shared libraries.
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_Exceptions(PyObject *module);
int _PyTestCapi_Init_Code(PyObject *module);
int _PyTestCapi_Init_Buffer(PyObject *module);
int _PyTestCapi_Init_PyOS(PyObject *module);
int _PyTestCapi_Init_Run(PyObject *module);
int _PyTestCapi_Init_File(PyObject *module);
int _PyTestCapi_Init_Codec(PyObject *module);
int _PyTestCapi_Init_Immortal(PyObject *module);
Expand Down
113 changes: 113 additions & 0 deletions Modules/_testcapi/run.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#define PY_SSIZE_T_CLEAN
#include "parts.h"
#include "util.h"

#include <stdio.h>
#include <errno.h>


static PyObject *
run_stringflags(PyObject *mod, PyObject *pos_args)
{
const char *str;
Py_ssize_t size;
int start;
PyObject *globals = NULL;
PyObject *locals = NULL;
PyCompilerFlags flags = _PyCompilerFlags_INIT;
PyCompilerFlags *pflags = NULL;
int cf_flags = 0;
int cf_feature_version = 0;

if (!PyArg_ParseTuple(pos_args, "z#iO|Oii",
&str, &size, &start, &globals, &locals,
&cf_flags, &cf_feature_version)) {
return NULL;
}

NULLABLE(globals);
NULLABLE(locals);
if (cf_flags || cf_feature_version) {
flags.cf_flags = cf_flags;
flags.cf_feature_version = cf_feature_version;
pflags = &flags;
}

return PyRun_StringFlags(str, start, globals, locals, pflags);
}

static PyObject *
run_fileexflags(PyObject *mod, PyObject *pos_args)
{
PyObject *result = NULL;
const char *filename = NULL;
Py_ssize_t filename_size;
int start;
PyObject *globals = NULL;
PyObject *locals = NULL;
int closeit = 0;
PyCompilerFlags flags = _PyCompilerFlags_INIT;
PyCompilerFlags *pflags = NULL;
int cf_flags = 0;
int cf_feature_version = 0;

FILE *fp = NULL;

if (!PyArg_ParseTuple(pos_args, "z#iO|Oiii",
&filename, &filename_size, &start, &globals, &locals,
&closeit, &cf_flags, &cf_feature_version)) {
return NULL;
}

NULLABLE(globals);
NULLABLE(locals);
if (cf_flags || cf_feature_version) {
flags.cf_flags = cf_flags;
flags.cf_feature_version = cf_feature_version;
pflags = &flags;
}

fp = fopen(filename, "r");
if (fp == NULL) {
PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
return NULL;
}

result = PyRun_FileExFlags(fp, filename, start, globals, locals, closeit, pflags);

#if !defined(__wasi__)
/* The behavior of fileno() after fclose() is undefined. */
if (closeit && result && fileno(fp) >= 0) {
PyErr_SetString(PyExc_AssertionError, "File was not closed after excution");
Py_DECREF(result);
fclose(fp);
return NULL;
}
#endif
if (!closeit && fileno(fp) < 0) {
PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after excution");
Py_XDECREF(result);
return NULL;
}

if (!closeit) {
fclose(fp); /* don't need open file any more*/
}

return result;
}

static PyMethodDef test_methods[] = {
{"run_stringflags", run_stringflags, METH_VARARGS},
{"run_fileexflags", run_fileexflags, METH_VARARGS},
{NULL},
};

int
_PyTestCapi_Init_Run(PyObject *mod)
{
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
return 0;
}
13 changes: 13 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3946,6 +3946,16 @@ PyInit__testcapi(void)

PyModule_AddIntConstant(m, "the_number_three", 3);

if (PyModule_AddIntMacro(m, Py_single_input)) {
return NULL;
}
if (PyModule_AddIntMacro(m, Py_file_input)) {
return NULL;
}
if (PyModule_AddIntMacro(m, Py_eval_input)) {
return NULL;
}

TestError = PyErr_NewException("_testcapi.error", NULL, NULL);
Py_INCREF(TestError);
PyModule_AddObject(m, "error", TestError);
Expand Down Expand Up @@ -4034,6 +4044,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_PyOS(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_Run(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_File(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 @@ -126,6 +126,7 @@
<ClCompile Include="..\Modules\_testcapi\sys.c" />
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
<ClCompile Include="..\Modules\_testcapi\gc.c" />
<ClCompile Include="..\Modules\_testcapi\run.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/_testcapi.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
<ClCompile Include="..\Modules\_testcapi\gc.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Modules\_testcapi\run.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
Expand Down