Skip to content

Commit 6078f20

Browse files
serhiy-storchakaNGRsoftlaberlend-aasland
authored
pythongh-117968: Add tests for the part of the PyRun family of the C API (pythonGH-117982)
Co-authored-by: NGRsoftlab <[email protected]> Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent c1d7147 commit 6078f20

File tree

7 files changed

+235
-1
lines changed

7 files changed

+235
-1
lines changed

Lib/test/test_capi/test_run.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os
2+
import unittest
3+
from collections import UserDict
4+
from test.support import import_helper
5+
from test.support.os_helper import unlink, TESTFN, TESTFN_UNDECODABLE
6+
7+
NULL = None
8+
_testcapi = import_helper.import_module('_testcapi')
9+
Py_single_input = _testcapi.Py_single_input
10+
Py_file_input = _testcapi.Py_file_input
11+
Py_eval_input = _testcapi.Py_eval_input
12+
13+
14+
class CAPITest(unittest.TestCase):
15+
# TODO: Test the following functions:
16+
#
17+
# PyRun_SimpleStringFlags
18+
# PyRun_AnyFileExFlags
19+
# PyRun_SimpleFileExFlags
20+
# PyRun_InteractiveOneFlags
21+
# PyRun_InteractiveOneObject
22+
# PyRun_InteractiveLoopFlags
23+
# PyRun_String (may be a macro)
24+
# PyRun_AnyFile (may be a macro)
25+
# PyRun_AnyFileEx (may be a macro)
26+
# PyRun_AnyFileFlags (may be a macro)
27+
# PyRun_SimpleString (may be a macro)
28+
# PyRun_SimpleFile (may be a macro)
29+
# PyRun_SimpleFileEx (may be a macro)
30+
# PyRun_InteractiveOne (may be a macro)
31+
# PyRun_InteractiveLoop (may be a macro)
32+
# PyRun_File (may be a macro)
33+
# PyRun_FileEx (may be a macro)
34+
# PyRun_FileFlags (may be a macro)
35+
36+
def test_run_stringflags(self):
37+
# Test PyRun_StringFlags().
38+
def run(s, *args):
39+
return _testcapi.run_stringflags(s, Py_file_input, *args)
40+
source = b'a\n'
41+
42+
self.assertIsNone(run(b'a\n', dict(a=1)))
43+
self.assertIsNone(run(b'a\n', dict(a=1), {}))
44+
self.assertIsNone(run(b'a\n', {}, dict(a=1)))
45+
self.assertIsNone(run(b'a\n', {}, UserDict(a=1)))
46+
47+
self.assertRaises(NameError, run, b'a\n', {})
48+
self.assertRaises(NameError, run, b'a\n', {}, {})
49+
self.assertRaises(TypeError, run, b'a\n', dict(a=1), [])
50+
self.assertRaises(TypeError, run, b'a\n', dict(a=1), 1)
51+
52+
self.assertIsNone(run(b'\xc3\xa4\n', {'\xe4': 1}))
53+
self.assertRaises(SyntaxError, run, b'\xe4\n', {})
54+
55+
# CRASHES run(b'a\n', NULL)
56+
# CRASHES run(b'a\n', NULL, {})
57+
# CRASHES run(b'a\n', NULL, dict(a=1))
58+
# CRASHES run(b'a\n', UserDict())
59+
# CRASHES run(b'a\n', UserDict(), {})
60+
# CRASHES run(b'a\n', UserDict(), dict(a=1))
61+
62+
# CRASHES run(NULL, {})
63+
64+
def test_run_fileexflags(self):
65+
# Test PyRun_FileExFlags().
66+
filename = os.fsencode(TESTFN)
67+
with open(filename, 'wb') as fp:
68+
fp.write(b'a\n')
69+
self.addCleanup(unlink, filename)
70+
def run(*args):
71+
return _testcapi.run_fileexflags(filename, Py_file_input, *args)
72+
73+
self.assertIsNone(run(dict(a=1)))
74+
self.assertIsNone(run(dict(a=1), {}))
75+
self.assertIsNone(run({}, dict(a=1)))
76+
self.assertIsNone(run({}, UserDict(a=1)))
77+
self.assertIsNone(run(dict(a=1), {}, 1)) # closeit = True
78+
79+
self.assertRaises(NameError, run, {})
80+
self.assertRaises(NameError, run, {}, {})
81+
self.assertRaises(TypeError, run, dict(a=1), [])
82+
self.assertRaises(TypeError, run, dict(a=1), 1)
83+
84+
# CRASHES run(NULL)
85+
# CRASHES run(NULL, {})
86+
# CRASHES run(NULL, dict(a=1))
87+
# CRASHES run(UserDict())
88+
# CRASHES run(UserDict(), {})
89+
# CRASHES run(UserDict(), dict(a=1))
90+
91+
@unittest.skipUnless(TESTFN_UNDECODABLE, 'only works if there are undecodable paths')
92+
def test_run_fileexflags_with_undecodable_filename(self):
93+
run = _testcapi.run_fileexflags
94+
try:
95+
with open(TESTFN_UNDECODABLE, 'wb') as fp:
96+
fp.write(b'a\n')
97+
self.addCleanup(unlink, TESTFN_UNDECODABLE)
98+
except OSError:
99+
self.skipTest('undecodable paths are not supported')
100+
self.assertIsNone(run(TESTFN_UNDECODABLE, Py_file_input, dict(a=1)))
101+
102+
103+
if __name__ == '__main__':
104+
unittest.main()

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
163163
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
164164
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
165-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.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/pyatomic.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c
165+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.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/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c
166166
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
167167
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
168168
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c

Modules/_testcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ int _PyTestCapi_Init_Exceptions(PyObject *module);
5151
int _PyTestCapi_Init_Code(PyObject *module);
5252
int _PyTestCapi_Init_Buffer(PyObject *module);
5353
int _PyTestCapi_Init_PyAtomic(PyObject *module);
54+
int _PyTestCapi_Init_Run(PyObject *module);
5455
int _PyTestCapi_Init_File(PyObject *module);
5556
int _PyTestCapi_Init_Codec(PyObject *module);
5657
int _PyTestCapi_Init_Immortal(PyObject *module);

Modules/_testcapi/run.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include "parts.h"
2+
#include "util.h"
3+
4+
#include <stdio.h>
5+
#include <errno.h>
6+
7+
8+
static PyObject *
9+
run_stringflags(PyObject *mod, PyObject *pos_args)
10+
{
11+
const char *str;
12+
Py_ssize_t size;
13+
int start;
14+
PyObject *globals = NULL;
15+
PyObject *locals = NULL;
16+
PyCompilerFlags flags = _PyCompilerFlags_INIT;
17+
PyCompilerFlags *pflags = NULL;
18+
int cf_flags = 0;
19+
int cf_feature_version = 0;
20+
21+
if (!PyArg_ParseTuple(pos_args, "z#iO|Oii",
22+
&str, &size, &start, &globals, &locals,
23+
&cf_flags, &cf_feature_version)) {
24+
return NULL;
25+
}
26+
27+
NULLABLE(globals);
28+
NULLABLE(locals);
29+
if (cf_flags || cf_feature_version) {
30+
flags.cf_flags = cf_flags;
31+
flags.cf_feature_version = cf_feature_version;
32+
pflags = &flags;
33+
}
34+
35+
return PyRun_StringFlags(str, start, globals, locals, pflags);
36+
}
37+
38+
static PyObject *
39+
run_fileexflags(PyObject *mod, PyObject *pos_args)
40+
{
41+
PyObject *result = NULL;
42+
const char *filename = NULL;
43+
Py_ssize_t filename_size;
44+
int start;
45+
PyObject *globals = NULL;
46+
PyObject *locals = NULL;
47+
int closeit = 0;
48+
PyCompilerFlags flags = _PyCompilerFlags_INIT;
49+
PyCompilerFlags *pflags = NULL;
50+
int cf_flags = 0;
51+
int cf_feature_version = 0;
52+
53+
FILE *fp = NULL;
54+
55+
if (!PyArg_ParseTuple(pos_args, "z#iO|Oiii",
56+
&filename, &filename_size, &start, &globals, &locals,
57+
&closeit, &cf_flags, &cf_feature_version)) {
58+
return NULL;
59+
}
60+
61+
NULLABLE(globals);
62+
NULLABLE(locals);
63+
if (cf_flags || cf_feature_version) {
64+
flags.cf_flags = cf_flags;
65+
flags.cf_feature_version = cf_feature_version;
66+
pflags = &flags;
67+
}
68+
69+
fp = fopen(filename, "r");
70+
if (fp == NULL) {
71+
PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
72+
return NULL;
73+
}
74+
75+
result = PyRun_FileExFlags(fp, filename, start, globals, locals, closeit, pflags);
76+
77+
#if !defined(__wasi__)
78+
/* The behavior of fileno() after fclose() is undefined. */
79+
if (closeit && result && fileno(fp) >= 0) {
80+
PyErr_SetString(PyExc_AssertionError, "File was not closed after excution");
81+
Py_DECREF(result);
82+
fclose(fp);
83+
return NULL;
84+
}
85+
#endif
86+
if (!closeit && fileno(fp) < 0) {
87+
PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after excution");
88+
Py_XDECREF(result);
89+
return NULL;
90+
}
91+
92+
if (!closeit) {
93+
fclose(fp); /* don't need open file any more*/
94+
}
95+
96+
return result;
97+
}
98+
99+
static PyMethodDef test_methods[] = {
100+
{"run_stringflags", run_stringflags, METH_VARARGS},
101+
{"run_fileexflags", run_fileexflags, METH_VARARGS},
102+
{NULL},
103+
};
104+
105+
int
106+
_PyTestCapi_Init_Run(PyObject *mod)
107+
{
108+
if (PyModule_AddFunctions(mod, test_methods) < 0) {
109+
return -1;
110+
}
111+
return 0;
112+
}

Modules/_testcapimodule.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3904,6 +3904,16 @@ PyInit__testcapi(void)
39043904
PyModule_AddIntConstant(m, "the_number_three", 3);
39053905
PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT);
39063906

3907+
if (PyModule_AddIntMacro(m, Py_single_input)) {
3908+
return NULL;
3909+
}
3910+
if (PyModule_AddIntMacro(m, Py_file_input)) {
3911+
return NULL;
3912+
}
3913+
if (PyModule_AddIntMacro(m, Py_eval_input)) {
3914+
return NULL;
3915+
}
3916+
39073917
testcapistate_t *state = get_testcapi_state(m);
39083918
state->error = PyErr_NewException("_testcapi.error", NULL, NULL);
39093919
PyModule_AddObject(m, "error", state->error);
@@ -3998,6 +4008,9 @@ PyInit__testcapi(void)
39984008
if (_PyTestCapi_Init_PyAtomic(m) < 0) {
39994009
return NULL;
40004010
}
4011+
if (_PyTestCapi_Init_Run(m) < 0) {
4012+
return NULL;
4013+
}
40014014
if (_PyTestCapi_Init_Hash(m) < 0) {
40024015
return NULL;
40034016
}

PCbuild/_testcapi.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<ClCompile Include="..\Modules\_testcapi\object.c" />
125125
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
126126
<ClCompile Include="..\Modules\_testcapi\gc.c" />
127+
<ClCompile Include="..\Modules\_testcapi\run.c" />
127128
</ItemGroup>
128129
<ItemGroup>
129130
<ResourceCompile Include="..\PC\python_nt.rc" />

PCbuild/_testcapi.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@
105105
<ClCompile Include="..\Modules\_testcapi\gc.c">
106106
<Filter>Source Files</Filter>
107107
</ClCompile>
108+
<ClCompile Include="..\Modules\_testcapi\run.c">
109+
<Filter>Source Files</Filter>
110+
</ClCompile>
108111
</ItemGroup>
109112
<ItemGroup>
110113
<ResourceCompile Include="..\PC\python_nt.rc">

0 commit comments

Comments
 (0)