]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-152132: Test all PyRun functions in test_capi.test_run (#153352)
authorVictor Stinner <vstinner@python.org>
Thu, 9 Jul 2026 10:50:40 +0000 (12:50 +0200)
committerGitHub <noreply@github.com>
Thu, 9 Jul 2026 10:50:40 +0000 (12:50 +0200)
PyRun_InteractiveOneObjectEx() now raises TypeError if the second
parameter type is not str.

Lib/test/test_capi/test_run.py
Modules/_testcapi/run.c
Python/pythonrun.c

index 894f66b437a39c2c8cefdfa1e22b08d4810a3f11..d0418cdee6286776262be8a402cd2005aeaca2ee 100644 (file)
@@ -1,48 +1,72 @@
 import os
+import sys
+import tempfile
 import unittest
 from collections import UserDict
+from test import support
 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
+STDIN = '<stdin>'
+STDERR_FD = 2
+
+# Code raising a SyntaxError
+SYNTAX_ERROR = 'True = 1'
+
+# Raise a SystemExit with exit code 42
+CODE_EXIT_42 = 'raise SystemExit(42)'
+
+
+def create_text_file(filename, text):
+    with open(filename, 'w', encoding='utf8') as fp:
+        fp.write(text)
 
 
 class DictSubclass(dict):
     pass
 
 
-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)
+class capture_excepthook:
+    def __init__(self):
+        self.exc = None
+        self._old_hook = None
 
-    def test_run_stringflags(self):
-        # Test PyRun_StringFlags().
-        # XXX: fopen() uses different path encoding than Python on Windows.
+    def _hook(self, exc_type, exc_value, exc_tb):
+        # Storing the exception instance creates a reference cycle.
+        self.exc = exc_value
+
+    def __enter__(self):
+        self._old_hook = sys.excepthook
+        sys.excepthook = self._hook
+        return self
+
+    def __exit__(self, *exc_info):
+        sys.excepthook = self._old_hook
+        self.exc = None
+
+
+class CAPITest(unittest.TestCase):
+    def check_run_string(self, func):
         def run(s, *args):
-            return _testcapi.run_stringflags(s, Py_file_input, *args)
-        source = b'a\n'
+            return func(s, Py_file_input, *args)
+
+        ns = {}
+        self.assertIsNone(run(b'x = 1', ns, ns))
+        self.assertEqual(ns['x'], 1)
+
+        with self.assertRaises(SyntaxError) as cm:
+            run(SYNTAX_ERROR.encode(), {})
+        self.assertEqual(cm.exception.filename, '<string>')
+
+        with self.assertRaises(ValueError) as cm:
+            run(b'raise ValueError("BUG")', {})
+        self.assertEqual(str(cm.exception), 'BUG')
 
         self.assertIsNone(run(b'a\n', dict(a=1)))
         self.assertIsNone(run(b'a\n', dict(a=1), {}))
@@ -70,20 +94,29 @@ class CAPITest(unittest.TestCase):
 
         # CRASHES run(NULL, {})
 
-    def test_run_fileexflags(self):
-        # Test PyRun_FileExFlags().
+    def test_run_string(self):
+        # Test PyRun_String()
+        self.check_run_string(_testcapi.run_string)
+
+    def test_run_stringflags(self):
+        # Test PyRun_StringFlags().
+        self.check_run_string(_testcapi.run_stringflags)
+
+    def check_run_file(self, func, support_closeit):
+        # 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')
+        create_text_file(filename, 'a\n')
         self.addCleanup(unlink, filename)
         def run(*args):
-            return _testcapi.run_fileexflags(filename, Py_file_input, *args)
+            return func(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
+        if support_closeit:
+            closeit = 1
+            self.assertIsNone(run(dict(a=1), {}, closeit))
 
         self.assertRaises(NameError, run, {})
         self.assertRaises(NameError, run, {}, {})
@@ -101,17 +134,228 @@ class CAPITest(unittest.TestCase):
         self.assertRaises(SystemError, run, UserDict(), {})
         self.assertRaises(SystemError, 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
+        # syntax error
+        create_text_file(filename, SYNTAX_ERROR)
+        with self.assertRaises(SyntaxError) as cm:
+            self.assertIsNone(run({}))
+        self.assertEqual(cm.exception.filename, os.fsdecode(filename))
+
+        # raise exception
+        create_text_file(filename, 'raise ValueError("BUG")')
+        with self.assertRaises(ValueError) as cm:
+            self.assertIsNone(run({}))
+        self.assertEqual(str(cm.exception), 'BUG')
+
+        # Test undecodable filename
+        if TESTFN_UNDECODABLE and os.name != 'nt':
+            try:
+                create_text_file(TESTFN_UNDECODABLE, 'a\n')
+                self.addCleanup(unlink, TESTFN_UNDECODABLE)
+            except OSError:
+                # undecodable paths are not supported
+                pass
+            else:
+                self.assertIsNone(func(TESTFN_UNDECODABLE, Py_file_input, dict(a=1)))
+
+    def test_run_file(self):
+        # Test PyRun_File().
+        self.check_run_file(_testcapi.run_file, False)
+
+    def test_run_fileex(self):
+        # Test PyRun_FileEx().
+        self.check_run_file(_testcapi.run_fileex, True)
+
+    def test_run_fileflags(self):
+        # Test PyRun_FileFlags().
+        self.check_run_file(_testcapi.run_fileflags, True)
+
+    def test_run_fileexflags(self):
+        # Test PyRun_FileExFlags().
+        self.check_run_file(_testcapi.run_fileexflags, True)
+
+    def check_run_simplefile(self, func_name, have_closeit):
+        run = getattr(_testcapi, func_name)
+
+        open_filename = TESTFN
+        with open(open_filename, 'w') as fp:
+            print('import sys', file=fp)
+            print('print(__file__)', file=fp)
+            print('mod = sys.modules["__main__"]', file=fp)
+            print('print(type(mod.__loader__).__name__)', file=fp)
+        self.addCleanup(unlink, open_filename)
+
+        class MockLoader:
+            pass
+        loader = MockLoader()
+
+        for closeit in (0, 1):
+            for set_file in (False, True):
+                for filename in (open_filename, 'custom filename', STDIN):
+                    set_loader = (filename != STDIN)
+                    with self.subTest(closeit=closeit,
+                                      set_file=set_file,
+                                      filename=filename):
+                        mod = sys.modules['__main__']
+                        mod_file = mod.__file__
+                        with (support.captured_stdout() as stdout,
+                              support.swap_attr(mod, '__loader__', loader)):
+                            try:
+                                if set_file:
+                                    del mod.__file__
+                                filename_arg = os.fsencode(filename)
+                                if have_closeit:
+                                    res = run(open_filename, filename_arg, closeit)
+                                else:
+                                    res = run(open_filename, filename_arg)
+                                if set_file:
+                                    self.assertFalse(hasattr(mod, '__file__'))
+                            finally:
+                                mod.__file__ = mod_file
+
+                        self.assertEqual(res, 0)
+                        expected = [filename if set_file else mod_file]
+                        if set_loader:
+                            expected.append('SourceFileLoader')
+                        else:
+                            expected.append('MockLoader')
+                        self.assertEqual(stdout.getvalue().splitlines(),
+                                         expected)
+
+    def test_run_simplefile(self):
+        # Test PyRun_SimpleFile()
+        self.check_run_simplefile('run_simplefile', False)
+
+    def test_run_simplefileex(self):
+        # Test PyRun_SimpleFileEx()
+        self.check_run_simplefile('run_simplefileex', True)
+
+    def test_run_simplefileexflags(self):
+        # Test PyRun_SimpleFileExFlags()
+        self.check_run_simplefile('run_simplefileexflags', True)
+
+    def test_run_anyfile(self):
+        # Test PyRun_AnyFile()
+        self.check_run_simplefile('run_anyfile', False)
+
+    def test_run_anyfileex(self):
+        # Test PyRun_AnyFileEx()
+        self.check_run_simplefile('run_anyfileex', True)
+
+    def test_run_anyfileflags(self):
+        # Test PyRun_AnyFileFlags()
+        self.check_run_simplefile('run_anyfileflags', True)
+
+    def _check_run_interactive(self, run, encode_filename, use_loop):
+        open_filename = TESTFN
+        if use_loop:
+            with open(open_filename, 'w') as fp:
+                print('welcome = "hello REPL"', file=fp)
+                print('print(welcome)', file=fp)
+        else:
+            create_text_file(open_filename, 'print("hello REPL")')
+        self.addCleanup(unlink, open_filename)
+
+        for filename in ('filename', STDIN):
+            with self.subTest(filename=filename):
+                with support.captured_stdout() as stdout:
+                    if encode_filename:
+                        filename = filename.encode()
+                    self.assertEqual(run(open_filename, filename), 0)
+                self.assertEqual(stdout.getvalue(), 'hello REPL\n')
+
+        create_text_file(open_filename, SYNTAX_ERROR)
+        filename = 'custom filename'
+        if encode_filename:
+            filename_arg = filename.encode()
+        else:
+            filename_arg = filename
+        with capture_excepthook() as excepthook:
+            expected = (0 if use_loop else -1)
+            self.assertEqual(run(open_filename, filename_arg), expected)
+            self.assertIsInstance(excepthook.exc, SyntaxError)
+            self.assertEqual(excepthook.exc.filename, filename)
+
+        create_text_file(open_filename, 'raise ValueError("BUG")')
+        with capture_excepthook() as excepthook:
+            expected = (0 if use_loop else -1)
+            self.assertEqual(run(open_filename, filename_arg), expected)
+            self.assertIsInstance(excepthook.exc, ValueError)
+            self.assertEqual(str(excepthook.exc), 'BUG')
+
+        if not encode_filename:
+            # wrong type for the second parameter (filename)
+            with capture_excepthook() as excepthook:
+                self.assertEqual(run(open_filename, b'bytes'), -1)
+                self.assertIsInstance(excepthook.exc, TypeError)
+
+    def check_run_interactive(self, run, encode_filename, use_loop=False):
+        # Redirect stderr to a temporary file to hide '>>> ' from the REPL
         try:
-            with open(TESTFN_UNDECODABLE, 'wb') as fp:
-                fp.write(b'a\n')
-            self.addCleanup(unlink, TESTFN_UNDECODABLE)
+            stderr_copy = os.dup(STDERR_FD)
         except OSError:
-            self.skipTest('undecodable paths are not supported')
-        self.assertIsNone(run(TESTFN_UNDECODABLE, Py_file_input, dict(a=1)))
+            # On WASI, dup(STDERR_FD) fails with "OSError: [Errno 58] Not
+            # supported". In this case, run the test without redirecting
+            # stderr to a temporary file.
+            self._check_run_interactive(run, encode_filename, use_loop)
+        else:
+            with tempfile.TemporaryFile() as tmp:
+                try:
+                    os.dup2(tmp.fileno(), STDERR_FD)
+                    self._check_run_interactive(run, encode_filename, use_loop)
+                finally:
+                    os.dup2(stderr_copy, STDERR_FD)
+
+    def test_run_interactiveone(self):
+        # Test PyRun_InteractiveOne()
+        run = _testcapi.run_interactiveone
+        self.check_run_interactive(run, True)
+
+    def test_run_interactiveoneflags(self):
+        # Test PyRun_InteractiveOneFlags()
+        run = _testcapi.run_interactiveoneflags
+        self.check_run_interactive(run, True)
+
+    def test_run_interactiveoneobject(self):
+        # Test PyRun_InteractiveOneObject()
+        run = _testcapi.run_interactiveoneobject
+        self.check_run_interactive(run, False)
+
+    def test_run_interactiveloop(self):
+        # Test PyRun_InteractiveLoop()
+        run = _testcapi.run_interactiveloop
+        self.check_run_interactive(run, True, use_loop=True)
+
+    def test_run_interactiveloopflags(self):
+        # Test PyRun_InteractiveLoopFlags()
+        run = _testcapi.run_interactiveloopflags
+        self.check_run_interactive(run, True, use_loop=True)
+
+    def test_run_anyfileexflags(self):
+        # Test PyRun_AnyFileExFlags()
+        self.check_run_simplefile('run_anyfileexflags', True)
+
+    def check_run_simplestring(self, run):
+        with support.captured_stdout() as stdout:
+            run(b'print("simple hello")')
+        self.assertEqual(stdout.getvalue(), 'simple hello\n')
+
+        with capture_excepthook() as excepthook:
+            self.assertEqual(run(SYNTAX_ERROR.encode()), -1)
+            self.assertIsInstance(excepthook.exc, SyntaxError)
+            self.assertEqual(excepthook.exc.filename, '<string>')
+
+        with capture_excepthook() as excepthook:
+            self.assertEqual(run(b'raise ValueError("BUG")'), -1)
+            self.assertIsInstance(excepthook.exc, ValueError)
+            self.assertEqual(str(excepthook.exc), 'BUG')
+
+    def test_run_simplestring(self):
+        # Test PyRun_SimpleString()
+        self.check_run_simplestring(_testcapi.run_simplestring)
+
+    def test_run_simplestringflags(self):
+        # Test PyRun_SimpleStringFlags()
+        self.check_run_simplestring(_testcapi.run_simplestringflags)
 
 
 if __name__ == '__main__':
index c9db7ccdd965a2acb3f94ab091a851be6ce914d3..4a63c0f98a74d4c8dadc4f81949fcb6cfd872962 100644 (file)
@@ -7,8 +7,62 @@
 #include <errno.h>
 
 
-static PyObject *
-run_stringflags(PyObject *mod, PyObject *pos_args)
+// Test functions, not macros
+#undef PyRun_AnyFile
+#undef PyRun_AnyFileEx
+#undef PyRun_AnyFileFlags
+#undef PyRun_File
+#undef PyRun_FileEx
+#undef PyRun_FileFlags
+#undef PyRun_SimpleFile
+#undef PyRun_SimpleFileEx
+#undef PyRun_String
+#undef PyRun_SimpleString
+#undef Py_CompileString
+#undef Py_CompileStringFlags
+#undef PyRun_InteractiveOne
+#undef PyRun_InteractiveLoop
+
+
+// Some PyRun functions crash if start is invalid,
+// so validate the start argument.
+static int
+check_start(int start)
+{
+    if (start == Py_single_input || start == Py_file_input
+        || start == Py_eval_input || start == Py_func_type_input)
+    {
+        return 0;
+    }
+    PyErr_SetString(PyExc_ValueError, "invalid start argument");
+    return -1;
+}
+
+
+// Test PyRun_String()
+static PyObject*
+run_string(PyObject *mod, PyObject *args)
+{
+    const char *str;
+    int start = 0;
+    PyObject *globals = NULL;
+    PyObject *locals = NULL;
+
+    if (!PyArg_ParseTuple(args, "y|iOO", &str, &start, &globals, &locals)) {
+        return NULL;
+    }
+    if (check_start(start) < 0) {
+        return NULL;
+    }
+    NULLABLE(globals);
+    NULLABLE(locals);
+
+    return PyRun_String(str, start, globals, locals);
+}
+
+// Test PyRun_StringFlags()
+static PyObject*
+run_stringflags(PyObject *mod, PyObject *args)
 {
     const char *str;
     Py_ssize_t size;
@@ -20,12 +74,14 @@ run_stringflags(PyObject *mod, PyObject *pos_args)
     int cf_flags = 0;
     int cf_feature_version = 0;
 
-    if (!PyArg_ParseTuple(pos_args, "z#iO|Oii",
+    if (!PyArg_ParseTuple(args, "z#iO|Oii",
                           &str, &size, &start, &globals, &locals,
                           &cf_flags, &cf_feature_version)) {
         return NULL;
     }
-
+    if (check_start(start) < 0) {
+        return NULL;
+    }
     NULLABLE(globals);
     NULLABLE(locals);
     if (cf_flags || cf_feature_version) {
@@ -37,8 +93,402 @@ run_stringflags(PyObject *mod, PyObject *pos_args)
     return PyRun_StringFlags(str, start, globals, locals, pflags);
 }
 
-static PyObject *
-run_fileexflags(PyObject *mod, PyObject *pos_args)
+static FILE*
+open_file(PyObject *filename, int *closeit)
+{
+    if (PyUnicode_EqualToUTF8(filename, "<stdin>")) {
+        if (closeit) {
+            // override closeit
+            *closeit = 0;
+        }
+        return stdin;
+    }
+    return Py_fopen(filename, "r");
+}
+
+
+// Test PyRun_SimpleFile()
+static PyObject*
+run_simplefile(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+
+    if (!PyArg_ParseTuple(args, "Oy", &open_filename, &filename)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    int res = PyRun_SimpleFile(fp, filename);
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+static int
+close_fd(FILE *fp, int fd, int closeit)
+{
+    if (closeit && _Py_IsValidFD(fd)) {
+        PyErr_SetString(PyExc_AssertionError, "File was not closed after execution");
+        fclose(fp);
+        return -1;
+    }
+
+    if (!closeit && !_Py_IsValidFD(fd)) {
+        PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after execution");
+        return -1;
+    }
+
+    if (!closeit) {
+        fclose(fp);
+    }
+    return 0;
+}
+
+static int
+close_fd_result(FILE *fp, int fd, int closeit, PyObject *result)
+{
+    if (closeit && result && _Py_IsValidFD(fd)) {
+        PyErr_SetString(PyExc_AssertionError, "File was not closed after execution");
+        Py_DECREF(result);
+        fclose(fp);
+        return -1;
+    }
+
+    if (!closeit && !_Py_IsValidFD(fd)) {
+        PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after execution");
+        Py_XDECREF(result);
+        return -1;
+    }
+
+    if (!closeit) {
+        fclose(fp);
+    }
+    return 0;
+}
+
+// Test PyRun_SimpleFileEx()
+static PyObject*
+run_simplefileex(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+    int closeit = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|i",
+                          &open_filename, &filename, &closeit)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, &closeit);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    int res = PyRun_SimpleFileEx(fp, filename, closeit);
+
+    if (close_fd(fp, fd, closeit) < 0) {
+        return NULL;
+    }
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_SimpleFileExFlags()
+static PyObject*
+run_simplefileexflags(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+    int closeit = 0;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|ii",
+                          &open_filename, &filename, &closeit, &cf_flags)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, &closeit);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_SimpleFileExFlags(fp, filename, closeit, pflags);
+
+    if (close_fd(fp, fd, closeit) < 0) {
+        return NULL;
+    }
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_AnyFile()
+static PyObject*
+run_anyfile(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+
+    if (!PyArg_ParseTuple(args, "Oy", &open_filename, &filename)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    int res = PyRun_AnyFile(fp, filename);
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_AnyFileFlags()
+static PyObject*
+run_anyfileflags(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|i",
+                          &open_filename, &filename, &cf_flags)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_AnyFileFlags(fp, filename, pflags);
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_AnyFileEx()
+static PyObject*
+run_anyfileex(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+    int closeit = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|i",
+                          &open_filename, &filename, &closeit)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, &closeit);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    int res = PyRun_AnyFileEx(fp, filename, closeit);
+
+    if (close_fd(fp, fd, closeit) < 0) {
+        return NULL;
+    }
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_AnyFileExFlags()
+static PyObject*
+run_anyfileexflags(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    const char *filename;
+    int closeit = 0;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|ii",
+                          &open_filename, &filename, &closeit, &cf_flags)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, &closeit);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_AnyFileExFlags(fp, filename, closeit, pflags);
+
+    if (close_fd(fp, fd, closeit) < 0) {
+        return NULL;
+    }
+
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
+
+
+// Test PyRun_InteractiveOne()
+static PyObject*
+run_interactiveone(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    char *filename;
+
+    if (!PyArg_ParseTuple(args, "Oy", &open_filename, &filename)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    int res = PyRun_InteractiveOne(fp, filename);
+    assert(!PyErr_Occurred());
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_InteractiveOneFlags()
+static PyObject*
+run_interactiveoneflags(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    char *filename;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|i",
+                          &open_filename, &filename, &cf_flags)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_InteractiveOneFlags(fp, filename, pflags);
+    assert(!PyErr_Occurred());
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_InteractiveOneObject()
+static PyObject*
+run_interactiveoneobject(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    PyObject *filename;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "OO|i",
+                          &open_filename, &filename, &cf_flags)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_InteractiveOneObject(fp, filename, pflags);
+    assert(!PyErr_Occurred());
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return PyLong_FromLong(res);
+}
+
+// Test PyRun_File()
+static PyObject*
+run_file(PyObject *mod, PyObject *args)
 {
     PyObject *result = NULL;
     const char *filename = NULL;
@@ -46,20 +496,140 @@ run_fileexflags(PyObject *mod, PyObject *pos_args)
     int start;
     PyObject *globals = NULL;
     PyObject *locals = NULL;
+
+    if (!PyArg_ParseTuple(args, "z#iO|O",
+                          &filename, &filename_size, &start,
+                          &globals, &locals)) {
+        return NULL;
+    }
+    if (check_start(start) < 0) {
+        return NULL;
+    }
+    NULLABLE(globals);
+    NULLABLE(locals);
+
+    FILE *fp = fopen(filename, "r");
+    if (fp == NULL) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    result = PyRun_File(fp, filename, start, globals, locals);
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return result;
+}
+
+// Test PyRun_FileEx()
+static PyObject*
+run_fileex(PyObject *mod, PyObject *args)
+{
+    const char *filename = NULL;
+    Py_ssize_t filename_size;
+    int start;
+    PyObject *globals = NULL;
+    PyObject *locals = NULL;
     int closeit = 0;
+
+    if (!PyArg_ParseTuple(args, "z#iO|Oi",
+                          &filename, &filename_size, &start, &globals, &locals,
+                          &closeit)) {
+        return NULL;
+    }
+    if (check_start(start) < 0) {
+        return NULL;
+    }
+    NULLABLE(globals);
+    NULLABLE(locals);
+
+    FILE *fp = fopen(filename, "r");
+    if (fp == NULL) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    PyObject *result = PyRun_FileEx(fp, filename, start, globals, locals,
+                                    closeit);
+
+    if (close_fd_result(fp, fd, closeit, result) < 0) {
+        return NULL;
+    }
+
+    return result;
+}
+
+// Test PyRun_FileFlags
+static PyObject*
+run_fileflags(PyObject *mod, PyObject *args)
+{
+    const char *filename = NULL;
+    Py_ssize_t filename_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;
 
-    FILE *fp = NULL;
+    if (!PyArg_ParseTuple(args, "z#iO|Oii",
+                          &filename, &filename_size, &start, &globals, &locals,
+                          &cf_flags, &cf_feature_version)) {
+        return NULL;
+    }
+    if (check_start(start) < 0) {
+        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;
+    }
+
+    FILE *fp = fopen(filename, "r");
+    if (fp == NULL) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    PyObject *result = PyRun_FileFlags(fp, filename, start, globals, locals,
+                                       pflags);
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return result;
+}
+
+static PyObject*
+run_fileexflags(PyObject *mod, PyObject *args)
+{
+    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;
 
-    if (!PyArg_ParseTuple(pos_args, "z#iO|Oiii",
+    if (!PyArg_ParseTuple(args, "z#iO|Oiii",
                           &filename, &filename_size, &start, &globals, &locals,
                           &closeit, &cf_flags, &cf_feature_version)) {
         return NULL;
     }
-
+    if (check_start(start) < 0) {
+        return NULL;
+    }
     NULLABLE(globals);
     NULLABLE(locals);
     if (cf_flags || cf_feature_version) {
@@ -68,38 +638,149 @@ run_fileexflags(PyObject *mod, PyObject *pos_args)
         pflags = &flags;
     }
 
-    fp = fopen(filename, "r");
+    FILE *fp = fopen(filename, "r");
     if (fp == NULL) {
         PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
         return NULL;
     }
     int fd = fileno(fp);
 
-    result = PyRun_FileExFlags(fp, filename, start, globals, locals, closeit, pflags);
+    PyObject *result = PyRun_FileExFlags(fp, filename, start, globals, locals,
+                                         closeit, pflags);
 
-    if (closeit && result && _Py_IsValidFD(fd)) {
-        PyErr_SetString(PyExc_AssertionError, "File was not closed after execution");
-        Py_DECREF(result);
-        fclose(fp);
+    if (close_fd_result(fp, fd, closeit, result) < 0) {
         return NULL;
     }
 
-    if (!closeit && !_Py_IsValidFD(fd)) {
-        PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after execution");
-        Py_XDECREF(result);
+    return result;
+}
+
+static PyObject*
+run_simplestring(PyObject *mod, PyObject *args)
+{
+    const char *str;
+    if (!PyArg_ParseTuple(args, "y", &str)) {
         return NULL;
     }
 
-    if (!closeit) {
-        fclose(fp); /* don't need open file any more*/
+    int res = PyRun_SimpleString(str);
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
     }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
+}
 
-    return result;
+static PyObject*
+run_simplestringflags(PyObject *mod, PyObject *args)
+{
+    const char *str;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "y|i", &str, &flags)) {
+        return NULL;
+    }
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_SimpleStringFlags(str, pflags);
+    if (res == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    assert(!PyErr_Occurred());
+    return PyLong_FromLong(res);
 }
 
+
+// Test PyRun_InteractiveLoop()
+static PyObject*
+run_interactiveloop(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    char *filename;
+
+    if (!PyArg_ParseTuple(args, "Oy|i", &open_filename, &filename)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    int res = PyRun_InteractiveLoop(fp, filename);
+    assert(!PyErr_Occurred());
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return PyLong_FromLong(res);
+}
+
+
+// Test PyRun_InteractiveLoopFlags()
+static PyObject*
+run_interactiveloopflags(PyObject *mod, PyObject *args)
+{
+    PyObject *open_filename;
+    char *filename;
+    PyCompilerFlags flags = _PyCompilerFlags_INIT;
+    PyCompilerFlags *pflags = NULL;
+    int cf_flags = 0;
+
+    if (!PyArg_ParseTuple(args, "Oy|i",
+                          &open_filename, &filename, &cf_flags)) {
+        return NULL;
+    }
+
+    FILE *fp = open_file(open_filename, NULL);
+    if (fp == NULL) {
+        return NULL;
+    }
+    int fd = fileno(fp);
+
+    if (cf_flags) {
+        flags.cf_flags = cf_flags;
+        pflags = &flags;
+    }
+
+    int res = PyRun_InteractiveLoopFlags(fp, filename, pflags);
+    assert(!PyErr_Occurred());
+
+    assert(_Py_IsValidFD(fd));
+    fclose(fp);
+
+    return PyLong_FromLong(res);
+}
+
+
 static PyMethodDef test_methods[] = {
+    {"run_string", run_string, METH_VARARGS},
     {"run_stringflags", run_stringflags, METH_VARARGS},
+    {"run_simplefile", run_simplefile, METH_VARARGS},
+    {"run_simplefileex", run_simplefileex, METH_VARARGS},
+    {"run_simplefileexflags", run_simplefileexflags, METH_VARARGS},
+    {"run_anyfile", run_anyfile, METH_VARARGS},
+    {"run_anyfileflags", run_anyfileflags, METH_VARARGS},
+    {"run_anyfileex", run_anyfileex, METH_VARARGS},
+    {"run_anyfileexflags", run_anyfileexflags, METH_VARARGS},
+    {"run_interactiveone", run_interactiveone, METH_VARARGS},
+    {"run_interactiveoneflags", run_interactiveoneflags, METH_VARARGS},
+    {"run_interactiveoneobject", run_interactiveoneobject, METH_VARARGS},
+    {"run_file", run_file, METH_VARARGS},
+    {"run_fileex", run_fileex, METH_VARARGS},
+    {"run_fileflags", run_fileflags, METH_VARARGS},
     {"run_fileexflags", run_fileexflags, METH_VARARGS},
+    {"run_simplestring", run_simplestring, METH_VARARGS},
+    {"run_simplestringflags", run_simplestringflags, METH_VARARGS},
+    {"run_interactiveloop", run_interactiveloop, METH_VARARGS},
+    {"run_interactiveloopflags", run_interactiveloopflags, METH_VARARGS},
     {NULL},
 };
 
index 971ab064777a4186d140b47ff4416b8896e1d86f..8c441e7ebd3c79f4c83c8454d04a25a69c228595 100644 (file)
@@ -290,6 +290,12 @@ static int
 PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
                              PyCompilerFlags *flags)
 {
+    if (!PyUnicode_Check(filename)) {
+        PyErr_Format(PyExc_TypeError, "expect str for filename, got %T",
+                     filename);
+        return -1;
+    }
+
     PyArena *arena = _PyArena_New();
     if (arena == NULL) {
         return -1;