Skip to content
Snippets Groups Projects
Commit 2d72aca1 authored by Guilhem Saurel's avatar Guilhem Saurel
Browse files

fix interpreter tests

parent 7c44de53
No related branches found
No related tags found
No related merge requests found
// -*- mode: c++ -*-
// Copyright 2011, Florent Lamiraux, CNRS.
#ifndef DYNAMIC_GRAPH_PYTHON_INTERPRETER_H
#define DYNAMIC_GRAPH_PYTHON_INTERPRETER_H
#undef _POSIX_C_SOURCE
#undef _XOPEN_SOURCE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string>
#include "dynamic-graph/python/api.hh"
#include "dynamic-graph/python/deprecated.hh"
#ifndef DYNAMIC_GRAPH_PYTHON_INTERPRETER_H
#define DYNAMIC_GRAPH_PYTHON_INTERPRETER_H
#include "dynamic-graph/python/api.hh"
namespace dynamicgraph {
......@@ -54,7 +55,6 @@ class DYNAMIC_GRAPH_PYTHON_DLLAPI Interpreter {
/// Pointer to the dictionary of local variables
PyObject* locals_;
PyObject* mainmod_;
PyObject* traceback_format_exception_;
};
} // namespace python
} // namespace dynamicgraph
......
......@@ -20,8 +20,7 @@ std::ofstream dg_debugfile("/tmp/dynamic-graph-traces.txt", std::ios::trunc& std
// Python initialization commands
namespace dynamicgraph {
namespace python {
static const std::string pythonPrefix[5] = {"import traceback\n",
"def display(s): return str(s) if not s is None else None",
static const std::string pythonPrefix[7] = {"import traceback\n",
"class StdoutCatcher:\n"
" def __init__(self):\n"
" self.data = ''\n"
......@@ -30,77 +29,61 @@ static const std::string pythonPrefix[5] = {"import traceback\n",
" def fetch(self):\n"
" s = self.data[:]\n"
" self.data = ''\n"
" return s\n"
"stdout_catcher = StdoutCatcher()\n"
"import sys\n"
"sys.stdout = stdout_catcher"};
" return s\n",
"stdout_catcher = StdoutCatcher()\n",
"stderr_catcher = StdoutCatcher()\n",
"import sys\n",
"sys.stdout = stdout_catcher",
"sys.stderr = stderr_catcher"};
// Get any PyObject and get its str() representation as an std::string
std::string obj_to_str(PyObject* o) {
std::string ret;
PyObject* os;
#if PY_MAJOR_VERSION >= 3
os = PyObject_Str(o);
assert(os != NULL);
assert(PyUnicode_Check(os));
ret = PyUnicode_AsUTF8(os);
#else
os = PyObject_Unicode(o);
assert(os != NULL);
assert(PyUnicode_Check(os));
PyObject* oss = PyUnicode_AsUTF8String(os);
assert(oss != NULL);
ret = PyString_AsString(oss);
Py_DECREF(oss);
#endif
Py_DECREF(os);
return ret;
}
} // namespace dynamicgraph
namespace dynamicgraph {
namespace python {
// Parse a python exception and return the corresponding error message
// http://thejosephturner.com/blog/post/embedding-python-in-c-applications-with-boostpython-part-2/
std::string parse_python_exception();
bool HandleErr(std::string& err, PyObject* traceback_format_exception, PyObject* globals_, int PythonInputType) {
bool HandleErr(std::string& err, PyObject* globals_, int PythonInputType) {
dgDEBUGIN(15);
err = "";
bool lres = false;
if (PyErr_Occurred()) {
PyObject *ptype, *pvalue, *ptraceback, *pyerr;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
if (ptraceback == NULL) {
ptraceback = Py_None;
// increase the Py_None count, to avoid a crash at the tuple destruction
Py_INCREF(ptraceback);
}
PyObject* args = PyTuple_New(3);
PyTuple_SET_ITEM(args, 0, ptype);
PyTuple_SET_ITEM(args, 1, pvalue);
PyTuple_SET_ITEM(args, 2, ptraceback);
pyerr = PyObject_CallObject(traceback_format_exception, args);
assert(PyList_Check(pyerr));
Py_ssize_t size = PyList_GET_SIZE(pyerr);
std::string stringRes;
for (Py_ssize_t i = 0; i < size; ++i) stringRes += std::string(PyUnicode_AS_DATA(PyList_GET_ITEM(pyerr, i)));
Py_DecRef(pyerr);
pyerr = PyUnicode_FromString(stringRes.c_str());
err = PyUnicode_AS_DATA(pyerr);
dgDEBUG(15) << "err: " << err << std::endl;
Py_DecRef(pyerr);
if (PyErr_Occurred() != NULL) {
bool is_syntax_error = PyErr_ExceptionMatches(PyExc_SyntaxError);
PyErr_Print();
PyObject* stderr_obj = PyRun_String("stderr_catcher.fetch()", Py_eval_input, globals_, globals_);
err = obj_to_str(stderr_obj);
Py_DECREF(stderr_obj);
// Here if there is a syntax error and
// and the interpreter input is set to Py_eval_input,
// it is maybe a statement instead of an expression.
// Therefore we indicate to re-evaluate the command.
if (PyErr_GivenExceptionMatches(ptype, PyExc_SyntaxError) && (PythonInputType == Py_eval_input)) {
if (is_syntax_error && PythonInputType == Py_eval_input) {
dgDEBUG(15) << "Detected a syntax error " << std::endl;
lres = false;
} else
lres = true;
Py_CLEAR(args);
PyErr_Clear();
} else {
dgDEBUG(15) << "no object generated but no error occured." << std::endl;
}
PyObject* stdout_obj = PyRun_String("stdout_catcher.fetch()", Py_eval_input, globals_, globals_);
std::string out("");
out = PyUnicode_AS_DATA(stdout_obj);
// Local display for the robot (in debug mode or for the logs)
if (out.length() != 0) {
dgDEBUG(15) << std::endl;
} else {
dgDEBUG(15) << "No exception." << std::endl;
}
dgDEBUGOUT(15);
Py_DecRef(stdout_obj);
return lres;
}
......@@ -122,13 +105,10 @@ Interpreter::Interpreter() {
PyRun_SimpleString(pythonPrefix[2].c_str());
PyRun_SimpleString(pythonPrefix[3].c_str());
PyRun_SimpleString(pythonPrefix[4].c_str());
PyRun_SimpleString(pythonPrefix[5].c_str());
PyRun_SimpleString(pythonPrefix[6].c_str());
PyRun_SimpleString("import linecache");
traceback_format_exception_ =
PyDict_GetItemString(PyModule_GetDict(PyImport_AddModule("traceback")), "format_exception");
assert(PyCallable_Check(traceback_format_exception_));
Py_INCREF(traceback_format_exception_);
// Allow threads
_pyState = PyEval_SaveThread();
}
......@@ -146,7 +126,7 @@ Interpreter::~Interpreter() {
PyObject* poAttrName;
while ((poAttrName = PyIter_Next(poAttrIter)) != NULL) {
std::string oAttrName(PyUnicode_AS_DATA(poAttrName));
std::string oAttrName(obj_to_str(poAttrName));
// Make sure we don't delete any private objects.
if (oAttrName.compare(0, 2, "__") != 0 || oAttrName.compare(oAttrName.size() - 2, 2, "__") != 0) {
......@@ -155,19 +135,18 @@ Interpreter::~Interpreter() {
// Make sure we don't delete any module objects.
if (poAttr && poAttr->ob_type != mainmod_->ob_type) PyObject_SetAttr(mainmod_, poAttrName, NULL);
Py_DecRef(poAttr);
Py_DECREF(poAttr);
}
Py_DecRef(poAttrName);
Py_DECREF(poAttrName);
}
Py_DecRef(poAttrIter);
Py_DecRef(poAttrList);
Py_DECREF(poAttrIter);
Py_DECREF(poAttrList);
}
Py_DECREF(mainmod_);
Py_DECREF(globals_);
Py_DECREF(traceback_format_exception_);
// Py_Finalize();
}
......@@ -194,45 +173,41 @@ void Interpreter::python(const std::string& command, std::string& res, std::stri
std::cout << command.c_str() << std::endl;
PyObject* result = PyRun_String(command.c_str(), Py_eval_input, globals_, globals_);
// Check if the result is null.
if (!result) {
if (result == NULL) {
// Test if this is a syntax error (due to the evaluation of an expression)
// else just output the problem.
if (!HandleErr(err, traceback_format_exception_, globals_, Py_eval_input)) {
if (!HandleErr(err, globals_, Py_eval_input)) {
// If this is a statement, re-parse the command.
result = PyRun_String(command.c_str(), Py_single_input, globals_, globals_);
// If there is still an error build the appropriate err string.
if (result == NULL)
HandleErr(err, traceback_format_exception_, globals_, Py_single_input);
if (result == NULL) HandleErr(err, globals_, Py_single_input);
// If there is no error, make sure that the previous error message is erased.
else
// If there is no error, make sure that the previous error message is erased.
err = "";
} else {
} else
dgDEBUG(15) << "Do not try a second time." << std::endl;
}
}
PyObject* stdout_obj = 0;
stdout_obj = PyRun_String("stdout_catcher.fetch()", Py_eval_input, globals_, globals_);
out = PyUnicode_AS_DATA(stdout_obj);
out = obj_to_str(stdout_obj);
Py_DECREF(stdout_obj);
// Local display for the robot (in debug mode or for the logs)
if (out.size() != 0) std::cout << "Output:" << out << std::endl;
if (err.size() != 0) std::cout << "Error:" << err << std::endl;
PyObject* result2 = PyObject_Repr(result);
// If python cannot build a string representation of result
// then results is equal to NULL. This will trigger a SEGV
if (result2 != NULL) {
dgDEBUG(15) << "For command :" << command << std::endl;
res = PyUnicode_AS_DATA(result2);
dgDEBUG(15) << "For command: " << command << std::endl;
if (result != NULL) {
res = obj_to_str(result);
dgDEBUG(15) << "Result is: " << res << std::endl;
dgDEBUG(15) << "Out is: " << out << std::endl;
dgDEBUG(15) << "Err is :" << err << std::endl;
Py_DECREF(result);
} else {
dgDEBUG(15) << "Result is empty" << std::endl;
dgDEBUG(15) << "Result is: empty" << std::endl;
}
Py_DecRef(stdout_obj);
Py_DecRef(result2);
Py_DecRef(result);
dgDEBUG(15) << "Out is: " << out << std::endl;
dgDEBUG(15) << "Err is :" << err << std::endl;
_pyState = PyEval_SaveThread();
......@@ -256,24 +231,22 @@ void Interpreter::runPythonFile(std::string filename, std::string& err) {
PyEval_RestoreThread(_pyState);
err = "";
PyObject* pymainContext = globals_;
PyObject* run = PyRun_FileExFlags(pFile, filename.c_str(), Py_file_input, pymainContext, pymainContext, true, NULL);
if (PyErr_Occurred()) {
err = parse_python_exception();
PyObject* run = PyRun_File(pFile, filename.c_str(), Py_file_input, globals_, globals_);
if (run == NULL) {
HandleErr(err, globals_, Py_file_input);
std::cerr << err << std::endl;
;
}
Py_DecRef(run);
_pyState = PyEval_SaveThread();
fclose(pFile);
}
void Interpreter::runMain(void) {
PyEval_RestoreThread(_pyState);
#if PY_MAJOR_VERSION >= 3
const wchar_t* argv[] = {L"dg-embedded-pysh"};
Py_Main(1, const_cast<wchar_t**>(argv));
const Py_UNICODE* argv[] = {L"dg-embedded-pysh"};
Py_Main(1, const_cast<Py_UNICODE**>(argv));
#else
const char* argv[] = {"dg-embedded-pysh"};
Py_Main(1, const_cast<char**>(argv));
......@@ -299,44 +272,5 @@ std::string Interpreter::processStream(std::istream& stream, std::ostream& os) {
return command;
}
std::string parse_python_exception() {
PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL;
PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr);
std::string ret("Unfetchable Python error");
if (type_ptr != NULL) {
py::handle<> h_type(type_ptr);
py::str type_pstr(h_type);
py::extract<std::string> e_type_pstr(type_pstr);
if (e_type_pstr.check())
ret = e_type_pstr();
else
ret = "Unknown exception type";
}
if (value_ptr != NULL) {
py::handle<> h_val(value_ptr);
py::str a(h_val);
py::extract<std::string> returned(a);
if (returned.check())
ret += ": " + returned();
else
ret += std::string(": Unparseable Python error: ");
}
if (traceback_ptr != NULL) {
py::handle<> h_tb(traceback_ptr);
py::object tb(py::import("traceback"));
py::object fmt_tb(tb.attr("format_tb"));
py::object tb_list(fmt_tb(h_tb));
py::object tb_str(py::str("\n").join(tb_list));
py::extract<std::string> returned(tb_str);
if (returned.check())
ret += ": " + returned();
else
ret += std::string(": Unparseable Python traceback");
}
return ret;
}
} // namespace python
} // namespace dynamicgraph
......@@ -4,7 +4,7 @@ ADD_EXECUTABLE(${EXECUTABLE_NAME} interpreter-test.cc)
TARGET_LINK_LIBRARIES(${EXECUTABLE_NAME} dynamic-graph-python)
ADD_TEST(${EXECUTABLE_NAME} ${EXECUTABLE_NAME})
## Test runfile
# Test runfile
SET(EXECUTABLE_NAME interpreter-test-runfile)
ADD_EXECUTABLE(${EXECUTABLE_NAME} interpreter-test-runfile.cc)
TARGET_LINK_LIBRARIES(${EXECUTABLE_NAME} dynamic-graph-python)
......
......@@ -50,17 +50,20 @@ int main(int argc, char** argv) {
// because re as been imported in a previous test and it is not
// safe to delete imported module...
res = testFile("test_python-name_error.py",
std::string("<type 'exceptions.NameError'>: name 're' is not defined:") +
" File \"test_python-name_error.py\", line 6, in <module>\n" +
" pathList = re.split(':', pkgConfigPath)\n",
std::string("Traceback (most recent call last):\n"
" File \"test_python-name_error.py\", line 7, in <module>\n"
" pathList = re.split(':', pkgConfigPath) # noqa\n"
"NameError: name 're' is not defined\n"),
numTest) &&
res;
res = testFile("test_python-ok.py", "", numTest) && res;
res = testFile("unexistant_file.py", "unexistant_file.py cannot be open", numTest) && res;
res = testFile("test_python-syntax_error.py",
std::string("<type 'exceptions.SyntaxError'>: ('invalid syntax', ") +
"('test_python-syntax_error.py', 1, 11, " + "'hello world\\n'))",
std::string(" File \"test_python-syntax_error.py\", line 2\n"
" hello world\n"
" ^\n"
"SyntaxError: invalid syntax\n"),
numTest) &&
res;
res = testInterpreterDestructor("test_python-restart_interpreter.py", "") && res;
......
......@@ -14,7 +14,7 @@ int main(int argc, char** argv) {
for (int i = 0; i < numTest; ++i) {
// correct input
interp.python("print \"I am the interpreter\"", result, out, err);
interp.python("print('I am the interpreter')", result, out, err);
// incorrect input
interp.python("print I am the interpreter", result, out, err);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment