https://github.com/python/cpython/commit/8ae70c87619393eef19c8ac01df81b7559d3d245
commit: 8ae70c87619393eef19c8ac01df81b7559d3d245
branch: 3.12
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-04-22T18:59:28Z
summary:

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

(cherry picked from commit 6078f2033ea15a16cf52fe8d644a95a3be72d2e3)

Co-authored-by: NGRsoftlab <[email protected]>
Co-authored-by: Erlend E. Aasland <[email protected]>

files:
A Lib/test/test_capi/test_run.py
A Modules/_testcapi/run.c
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters

diff --git a/Lib/test/test_capi/test_run.py b/Lib/test/test_capi/test_run.py
new file mode 100644
index 00000000000000..0141d3ea4456d1
--- /dev/null
+++ b/Lib/test/test_capi/test_run.py
@@ -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()
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 33608055962db5..b68b8e4e347bbd 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -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.
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 1dd0995b87fd9e..496dd38fbad0bc 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -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);
diff --git a/Modules/_testcapi/run.c b/Modules/_testcapi/run.c
new file mode 100644
index 00000000000000..baf728c2b632ad
--- /dev/null
+++ b/Modules/_testcapi/run.c
@@ -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;
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index bbfd3e8d337d49..edd137f30ca9ba 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -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);
@@ -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;
     }
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index e032e67c256426..257562c75d103b 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -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" />
diff --git a/PCbuild/_testcapi.vcxproj.filters 
b/PCbuild/_testcapi.vcxproj.filters
index e985856a6a92a5..4d1e4330d43989 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -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">

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to