Commit da8d9931 authored by Francois Keith's avatar Francois Keith
Browse files

A better way to handle and display the error in a python file.

Former version crashed in case of syntax error
(cf test_syntax_error.py)
parent 42506315
......@@ -23,8 +23,11 @@
#include <boost/python/object.hpp>
#include <boost/python/handle.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/str.hpp>
#include <boost/python/import.hpp>
using namespace boost::python;
namespace py=boost::python;
std::ofstream dg_debugfile( "/tmp/dynamic-graph-traces.txt", std::ios::trunc&std::ios::out );
......@@ -53,6 +56,10 @@ static const std::string pythonPrefix[5] = {
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_,
......@@ -63,7 +70,6 @@ bool HandleErr(std::string & err,
bool lres=false;
if (PyErr_Occurred()) {
PyObject *ptype, *pvalue, *ptraceback, *pyerr;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
if (ptraceback == NULL) {
......@@ -242,60 +248,24 @@ void Interpreter::runPythonFile( std::string filename )
runPythonFile(filename, err);
}
void Interpreter::runPythonFile( std::string filename, std::string& err)
{
FILE* pFile = fopen( filename.c_str(),"r" );
if (pFile==0x0)
{
err = filename + " cannot be open";
return;
}
err = "";
PyObject* pymainContext = globals_;
PyObject* run = PyRun_FileExFlags(fopen( filename.c_str(),"r" ), filename.c_str(),
PyObject* run = PyRun_FileExFlags(pFile, filename.c_str(),
Py_file_input, pymainContext,pymainContext, true, NULL);
if (PyErr_Occurred())
{
PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
handle<> hTraceback(ptraceback);
object traceback(hTraceback);
//Extract error message
std::string strErrorMessage = extract<std::string>(pvalue);
std::ostringstream errstream;
//TODO does not work for now for a single command.
do
{
//Extract line number (top entry of call stack)
// if you want to extract another levels of call stack
// also process traceback.attr("tb_next") recurently
long lineno = extract<long> (traceback.attr("tb_lineno"));
std::string filename = extract<std::string>
(traceback.attr("tb_frame").attr("f_code").attr("co_filename"));
std::string funcname = extract<std::string>
(traceback.attr("tb_frame").attr("f_code").attr("co_name"));
errstream << " File \"" << filename <<"\", line "
<< lineno << ", in "<< funcname << std::endl;
// get the corresponding line.
std::ostringstream cmd;
cmd << "linecache.getline('"<<filename<<"', "<<lineno <<")";
PyObject* line_obj = PyRun_String(cmd.str().c_str(),
Py_eval_input, globals_, globals_);
std::string line = PyString_AsString(line_obj);
Py_DecRef(line_obj);
// remove the spaces at the beginning of the line.
size_t index = line.find_first_not_of (" \t");
errstream << " " << line.substr(index, line.size()-index);
// go to the next line.
traceback = traceback.attr("tb_next");
}
while (traceback);
// recreate the error message
errstream << strErrorMessage << std::endl;
err =errstream.str();
std::cerr << err;
err = parse_python_exception();
std::cerr << err << std::endl;;
}
Py_DecRef(run);
}
......@@ -323,5 +293,49 @@ std::string Interpreter::processStream(std::istream& stream, std::ostream& os)
#endif
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
......@@ -38,9 +38,11 @@ TARGET_LINK_LIBRARIES(${EXECUTABLE_NAME} dynamic-graph-python)
ADD_TEST(${EXECUTABLE_NAME} ${EXECUTABLE_NAME})
ADD_CUSTOM_COMMAND(TARGET interpreter-test-runfile POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python_ok.py
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python-ok.py
${CMAKE_BINARY_DIR}/unitTesting
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python_error.py
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python-name_error.py
${CMAKE_BINARY_DIR}/unitTesting
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python-syntax_error.py
${CMAKE_BINARY_DIR}/unitTesting
)
......@@ -4,6 +4,25 @@
#include "dynamic-graph/python/interpreter.hh"
bool testFile(const std::string & filename,
const std::string & expectedOutput,
int numTest)
{
std::string err = "";
dynamicgraph::python::Interpreter interp;
for (int i=0; i<numTest; ++i)
{
interp.runPythonFile(filename, err);
if (err != expectedOutput)
{
std::cerr << "At iteration " << i << ", the output was not the one expected:" << std::endl;
std::cerr << " expected: " << expectedOutput << std::endl;
std::cerr << " err: " << err << std::endl;
return false;
}
}
return true;
}
int main(int argc, char ** argv)
{
......@@ -14,35 +33,18 @@ int main(int argc, char ** argv)
if (argc > 1)
numTest = atoi(argv[1]);
std::string empty_err = "";
dynamicgraph::python::Interpreter interp;
for (int i=0; i<numTest; ++i)
{
interp.runPythonFile("test_python_ok.py", empty_err);
if (empty_err != "")
{
std::cerr << "At iteration " << i << ", the error was not empty:" << std::endl;
std::cerr << " err " << empty_err << std::endl;
return -1;
}
}
// check that the error remains the same, despite of the number of executions
std::string old_err;
interp.runPythonFile("test_python_error.py", old_err);
std::string new_err = old_err;
for (int i=0; i<numTest; ++i)
{
interp.runPythonFile("test_python_error.py", new_err);
if (old_err != new_err)
{
std::cerr << "At iteration " << i << ", the error changed:" << std::endl;
std::cerr << " old " << old_err << std::endl;
std::cerr << " new " << new_err << std::endl;
return -1;
}
}
return 0;
bool res = true;
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-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", 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'))", numTest) && res;
return (res?0:1);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment