diff --git a/CMakeLists.txt b/CMakeLists.txt index a32ff0a735c029a1147628b252efa007d0a3882e..ae3402828411787d13eba17f128b2bf36ae0d283 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ ADD_REQUIRED_DEPENDENCY("dynamic-graph >= 2.5.5-6") PKG_CONFIG_APPEND_LIBS("dynamic-graph-python") # Search for Boost. +SET(BOOST_COMPONENTS python filesystem system thread program_options unit_test_framework) SEARCH_FOR_BOOST() # Make sure Boost.Filesystem v2 is used. diff --git a/include/dynamic-graph/python/interpreter.hh b/include/dynamic-graph/python/interpreter.hh index 5addec4c128ac4f95f2b1137fbdd2613c4e4c1ae..5d2e4f353a9920d77e5fc368b5ef5733697038d0 100644 --- a/include/dynamic-graph/python/interpreter.hh +++ b/include/dynamic-graph/python/interpreter.hh @@ -51,6 +51,7 @@ namespace dynamicgraph { /// \brief Method to exectue a python script. /// \param filename the filename void runPythonFile( std::string filename ); + void runPythonFile( std::string filename, std::string& err); void runMain( void ); /// \brief Process input stream to send relevant blocks to python diff --git a/src/interpreter.cc b/src/interpreter.cc index ba8930ae71d553f0246f49d33cbcfdbdbf552870..0b97823b9cd3552e8e7053940ce51e1b7530a460 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -19,6 +19,13 @@ #include "dynamic-graph/python/interpreter.hh" #include "link-to-python.hh" +#include <boost/python/errors.hpp> +#include <boost/python/object.hpp> +#include <boost/python/handle.hpp> +#include <boost/python/extract.hpp> + +using namespace boost::python; + std::ofstream dg_debugfile( "/tmp/dynamic-graph-traces.txt", std::ios::trunc&std::ios::out ); // Python initialization commands @@ -136,6 +143,8 @@ Interpreter::Interpreter() PyRun_SimpleString(pythonPrefix[2].c_str()); PyRun_SimpleString(pythonPrefix[3].c_str()); PyRun_SimpleString(pythonPrefix[4].c_str()); + PyRun_SimpleString("import linecache"); + traceback_format_exception_ = PyDict_GetItemString (PyModule_GetDict(PyImport_AddModule("traceback")), "format_exception"); assert(PyCallable_Check(traceback_format_exception_)); @@ -229,14 +238,66 @@ PyObject* Interpreter::globals() void Interpreter::runPythonFile( std::string filename ) { + std::string err = ""; + runPythonFile(filename, err); +} + + +void Interpreter::runPythonFile( std::string filename, std::string& err) +{ + err = ""; PyObject* pymainContext = globals_; - PyRun_File(fopen( filename.c_str(),"r" ), filename.c_str(), - Py_file_input, pymainContext,pymainContext); + PyObject* run = PyRun_FileExFlags(fopen( filename.c_str(),"r" ), filename.c_str(), + Py_file_input, pymainContext,pymainContext, true, NULL); if (PyErr_Occurred()) { - std::cout << "Error occures..." << std::endl; - PyErr_Print(); + 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; } + Py_DecRef(run); } void Interpreter::runMain( void ) diff --git a/unitTesting/CMakeLists.txt b/unitTesting/CMakeLists.txt index eca30b2584efd2cc5d643ae5db42645ee0d47b44..79a32e53c85648d7317bd38bd92a941a989dc349 100644 --- a/unitTesting/CMakeLists.txt +++ b/unitTesting/CMakeLists.txt @@ -26,10 +26,21 @@ ADD_DEFINITIONS(${DYNAMIC_GRAPH_CFLAGS}) SET(EXECUTABLE_NAME interpreter-test) - ADD_EXECUTABLE(${EXECUTABLE_NAME} interpreter-test.cc) TARGET_LINK_LIBRARIES(${EXECUTABLE_NAME} dynamic-graph-python) -TARGET_LINK_LIBRARIES(${EXECUTABLE_NAME} ${DYNAMIC_GRAPH_LIBRARIES} -lpthread -ldl -lutil) +ADD_TEST(${EXECUTABLE_NAME} ${EXECUTABLE_NAME}) + +## Test runfile +SET(EXECUTABLE_NAME interpreter-test-runfile) +ADD_EXECUTABLE(${EXECUTABLE_NAME} interpreter-test-runfile.cc) +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 + ${CMAKE_BINARY_DIR}/unitTesting + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python_error.py + ${CMAKE_BINARY_DIR}/unitTesting +) + diff --git a/unitTesting/interpreter-test-runfile.cc b/unitTesting/interpreter-test-runfile.cc new file mode 100644 index 0000000000000000000000000000000000000000..e1e508209a4d12b432aa2bea3092acdff511a8e0 --- /dev/null +++ b/unitTesting/interpreter-test-runfile.cc @@ -0,0 +1,48 @@ +// The purpose of this unit test is to check the interpreter::runPythonFile method +#include <cstring> +#include <iostream> + +#include "dynamic-graph/python/interpreter.hh" + + +int main(int argc, char ** argv) +{ + // execute numerous time the same file. + // While running 1025, we can notice a change in the error. + // unfortunately, it can not be shown using a redirection of the streams + int numTest = 1025; + 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; +} diff --git a/unitTesting/test_python_error.py b/unitTesting/test_python_error.py new file mode 100644 index 0000000000000000000000000000000000000000..7bf749deec4cf7ff01d2d58e76f8c662e51bb0b4 --- /dev/null +++ b/unitTesting/test_python_error.py @@ -0,0 +1,9 @@ +import sys, os + +pkgConfigPath = os.environ.get("PKG_CONFIG_PATH") +if pkgConfigPath == None: + pkgConfigPath = '' +pathList = re.split(':', pkgConfigPath) + +print pathList + diff --git a/unitTesting/test_python_ok.py b/unitTesting/test_python_ok.py new file mode 100644 index 0000000000000000000000000000000000000000..665398b68e3f56308d13ee3c6d27eb8c1038eebd --- /dev/null +++ b/unitTesting/test_python_ok.py @@ -0,0 +1,10 @@ +import sys, os +import re + +pkgConfigPath = os.environ.get("PKG_CONFIG_PATH") +if pkgConfigPath == None: + pkgConfigPath = '' +pathList = re.split(':', pkgConfigPath) + +print pathList +