diff --git a/Lib/test/test_capi/test_run.py b/Lib/test/test_capi/test_run.py index 894f66b437a39c..d0418cdee62867 100644 --- a/Lib/test/test_capi/test_run.py +++ b/Lib/test/test_capi/test_run.py @@ -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 = '' +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, '') + + 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 @@ def run(s, *args): # 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 @@ def run(*args): 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, '') + + 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__': diff --git a/Modules/_testcapi/run.c b/Modules/_testcapi/run.c index c9db7ccdd965a2..4a63c0f98a74d4 100644 --- a/Modules/_testcapi/run.c +++ b/Modules/_testcapi/run.c @@ -7,8 +7,62 @@ #include -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, "")) { + 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}, }; diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 971ab064777a41..8c441e7ebd3c79 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -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;