<commit_before>#include <SofaPython/PythonScriptEvent.h>

#include "Python_test.h"

#include <sofa/helper/system/PluginManager.h>

#include <sofa/simulation/Simulation.h>
#include <SofaSimulationGraph/DAGSimulation.h>

#include <sofa/helper/logging/Messaging.h>
#include <sofa/helper/system/FileSystem.h>

#include <SofaPython/PythonMacros.h>
#include <SofaPython/PythonFactory.h>

#include <SofaPython/helper.hpp>

namespace sofa {



Python_test::Python_test()
{
    static const std::string plugin = "SofaPython";
    sofa::helper::system::PluginManager::getInstance().loadPlugin(plugin);
}



void Python_test::run( const Python_test_data& data ) {

    msg_info("Python_test") << "running " << data.filepath;

    {
        // Check the file exists
        std::ifstream file(data.filepath.c_str());
        bool scriptFound = file.good();
        ASSERT_TRUE(scriptFound) << " ("<<data.filepath<<")";
    }

    ASSERT_TRUE( loader.loadTestWithArguments(data.filepath.c_str(),data.arguments) ) << " ("<<data.filepath<<")";

}


static bool ends_with(const std::string& suffix, const std::string& full){
    const std::size_t lf = full.length();
    const std::size_t ls = suffix.length();

    if(lf < ls) return false;

    return (0 == full.compare(lf - ls, ls, suffix));
}

static bool starts_with(const std::string& prefix, const std::string& full){
    const std::size_t lf = full.length();
    const std::size_t lp = prefix.length();

    if(lf < lp) return false;

    return (0 == full.compare(0, lp, prefix));
}




void Python_test_list::addTestDir(const std::string& dir, const std::string& prefix) {

    std::vector<std::string> files;
    helper::system::FileSystem::listDirectory(dir, files);

    for(const std::string& file : files) {
        if( starts_with(prefix, file) && ends_with(".py", file) ) {
            addTest(file, dir);
        }
    }

}



////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////


struct Listener : core::objectmodel::BaseObject {

    Listener() {
        f_listening = true;
    }

    virtual void handleEvent(core::objectmodel::Event * event) {
        if (core::objectmodel::PythonScriptEvent::checkEventType(event)
              || core::objectmodel::ScriptEvent::checkEventType(event) )
       {
            core::objectmodel::ScriptEvent* e = static_cast<core::objectmodel::ScriptEvent*>(event);
            std::string name = e->getEventName();
            if( name == "success" ) {
                throw Python_scene_test::result(true);
            } else if (name == "failure") {
                throw Python_scene_test::result(false);
            }
        }
    }

};



struct fail {
    const char* message;
    fail(const char* message)
        : message(message) { }
};

static PyObject* operator||(PyObject* obj, const fail& error) {
    if(obj) return obj;
    throw std::runtime_error(error.message);
    return nullptr;
}

static void operator||(int code, const fail& error) {
    if(code >= 0) return;
    throw std::runtime_error(error.message);
}


enum flag : char {
    stop = 1 << 1,
    test_failure = 1 << 2,
    python_error = 1 << 3
};





// shared data between except_hook and simulation loop
struct shared_type {
    char flags = 0;
    std::string message;
    PyObject* default_excepthook = nullptr;
};


// TODO FIXME: there's probably a MEMLEAK hiding in there, figure it out
static PyObject* except_hook(PyObject* self, PyObject* args) {

    // raise the stop flag
    shared_type* shared = reinterpret_cast<shared_type*>(PyCapsule_GetPointer(self, NULL));
    
    assert(shared && "cannot get shared_type pointer (wtf?)");
    if(!shared) std::exit(1);

    shared->flags |= flag::stop;
    
    // switch on exception type
    PyObject* type;
    PyObject* value;
    PyObject* traceback;

    if( !PyArg_ParseTuple(args, "OOO", &type, &value, &traceback) ) {
        assert(false && "cannot parse excepthook args (wtf?)");
        std::exit(1);
    }

    if( type == PyExc_AssertionError ) {
        // test failure
        shared->flags |= flag::test_failure;
    } else {
        // other python error
        shared->flags |= flag::python_error;
    }

    {
        // TODO find a way to push traceback as well
        const py::object value_string = py::ptr(value).str();
        assert(value_string.c_str());        
        shared->message += value_string.c_str();
    }
    
    // call default excepthook to get traceback etc
    return PyObject_CallObject(shared->default_excepthook, args);
}

static PyMethodDef except_hook_def = {
    "sofa_excepthook",
    except_hook,
    METH_VARARGS,
    NULL
};


// save/restore excepthook
class excepthook_installer {<commit_msg>[SofaTest] unduplicated python error message in gtest<commit_after>#include <SofaPython/PythonScriptEvent.h>

#include "Python_test.h"

#include <sofa/helper/system/PluginManager.h>

#include <sofa/simulation/Simulation.h>
#include <SofaSimulationGraph/DAGSimulation.h>

#include <sofa/helper/logging/Messaging.h>
#include <sofa/helper/system/FileSystem.h>

#include <SofaPython/PythonMacros.h>
#include <SofaPython/PythonFactory.h>

#include <SofaPython/helper.hpp>

namespace sofa {



Python_test::Python_test()
{
    static const std::string plugin = "SofaPython";
    sofa::helper::system::PluginManager::getInstance().loadPlugin(plugin);
}



void Python_test::run( const Python_test_data& data ) {

    msg_info("Python_test") << "running " << data.filepath;

    {
        // Check the file exists
        std::ifstream file(data.filepath.c_str());
        bool scriptFound = file.good();
        ASSERT_TRUE(scriptFound) << " ("<<data.filepath<<")";
    }

    ASSERT_TRUE( loader.loadTestWithArguments(data.filepath.c_str(),data.arguments) ) << " ("<<data.filepath<<")";

}


static bool ends_with(const std::string& suffix, const std::string& full){
    const std::size_t lf = full.length();
    const std::size_t ls = suffix.length();

    if(lf < ls) return false;

    return (0 == full.compare(lf - ls, ls, suffix));
}

static bool starts_with(const std::string& prefix, const std::string& full){
    const std::size_t lf = full.length();
    const std::size_t lp = prefix.length();

    if(lf < lp) return false;

    return (0 == full.compare(0, lp, prefix));
}




void Python_test_list::addTestDir(const std::string& dir, const std::string& prefix) {

    std::vector<std::string> files;
    helper::system::FileSystem::listDirectory(dir, files);

    for(const std::string& file : files) {
        if( starts_with(prefix, file) && ends_with(".py", file) ) {
            addTest(file, dir);
        }
    }

}



////////////////////////////////////////////////////
////////////////////////////////////////////////////
////////////////////////////////////////////////////


struct Listener : core::objectmodel::BaseObject {

    Listener() {
        f_listening = true;
    }

    virtual void handleEvent(core::objectmodel::Event * event) {
        if (core::objectmodel::PythonScriptEvent::checkEventType(event)
              || core::objectmodel::ScriptEvent::checkEventType(event) )
       {
            core::objectmodel::ScriptEvent* e = static_cast<core::objectmodel::ScriptEvent*>(event);
            std::string name = e->getEventName();
            if( name == "success" ) {
                throw Python_scene_test::result(true);
            } else if (name == "failure") {
                throw Python_scene_test::result(false);
            }
        }
    }

};



struct fail {
    const char* message;
    fail(const char* message)
        : message(message) { }
};

static PyObject* operator||(PyObject* obj, const fail& error) {
    if(obj) return obj;
    throw std::runtime_error(error.message);
    return nullptr;
}

static void operator||(int code, const fail& error) {
    if(code >= 0) return;
    throw std::runtime_error(error.message);
}


enum flag : char {
    stop = 1 << 1,
    test_failure = 1 << 2,
    python_error = 1 << 3
};





// shared data between except_hook and simulation loop
struct shared_type {
    char flags = 0;
    std::string message;
    PyObject* default_excepthook = nullptr;
};


// TODO FIXME: there's probably a MEMLEAK hiding in there, figure it out
static PyObject* except_hook(PyObject* self, PyObject* args) {

    // raise the stop flag
    shared_type* shared = reinterpret_cast<shared_type*>(PyCapsule_GetPointer(self, NULL));
    
    assert(shared && "cannot get shared_type pointer (wtf?)");
    if(!shared) std::exit(1);

    shared->flags |= flag::stop;
    
    // switch on exception type
    PyObject* type;
    PyObject* value;
    PyObject* traceback;

    if( !PyArg_ParseTuple(args, "OOO", &type, &value, &traceback) ) {
        assert(false && "cannot parse excepthook args (wtf?)");
        std::exit(1);
    }

    if( type == PyExc_AssertionError ) {
        // test failure
        shared->flags |= flag::test_failure;
    } else {
        // other python error
        shared->flags |= flag::python_error;
    }

    {
        // TODO find a way to push traceback as well
        const py::object value_string = py::ptr(value).str();
        assert(value_string.c_str());        
        shared->message = value_string.c_str();
    }
    
    // call default excepthook to get traceback etc
    return PyObject_CallObject(shared->default_excepthook, args);
}

static PyMethodDef except_hook_def = {
    "sofa_excepthook",
    except_hook,
    METH_VARARGS,
    NULL
};


// save/restore excepthook
class excepthook_installer {
{'fraction_non_alphanumeric': 0.16588663546541862, 'fraction_numerical': 0.002704108164326573, 'mean_word_length': 2.7547832877782117, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3861145', 'n_tokens_mistral': 3055, 'n_tokens_neox': 2933, 'n_words': 664}