<commit_before>#include <xkbcommon/xkbcommon-keysyms.h>

#include "rwte/window.h"
#include "rwte/luastate.h"
#include "rwte/luaterm.h"
#include "rwte/logging.h"
#include "rwte/term.h"

#define LOGGER() (logging::get("luaterm"))

static int term_mouse_press_ref = LUA_NOREF;
static int term_key_press_ref = LUA_NOREF;

static int luaterm_mode(lua_State *l)
{
    LuaState L(l);
    auto mode = (term_mode_enum) L.checkinteger(1);
    L.pushbool(g_term->mode()[mode]);
    return 1;
}

static int luaterm_send(lua_State *l)
{
    LuaState L(l);
    size_t len = 0;
    const char * buffer = L.checklstring(1, &len);
    g_term->send(buffer, len);
    return 0;
}

static int luaterm_mouse_press(lua_State *l)
{
    LuaState L(l);
    term_mouse_press_ref = L.setfuncref(1, term_mouse_press_ref);
    return 0;
}

static int luaterm_key_press(lua_State *l)
{
    LuaState L(l);
    term_key_press_ref = L.setfuncref(1, term_key_press_ref);
    return 0;
}

static int luaterm_clipcopy(lua_State *l)
{
    g_term->clipcopy();
    return 0;
}

static int luaterm_clippaste(lua_State *l)
{
    // todo: seems clumsy to initiate paste w/ window
    // (weird abstraction)
    window.clippaste();
    return 0;
}

static int luaterm_selpaste(lua_State *l)
{
    // todo: seems clumsy to initiate paste w/ window
    // (weird abstraction)
    window.selpaste();
    return 0;
}

// functions for term library
static const luaL_Reg term_funcs[] = {
    {"mode", luaterm_mode},
    {"send", luaterm_send},
    {"mouse_press", luaterm_mouse_press},
    {"key_press", luaterm_key_press},
    {"clipcopy", luaterm_clipcopy},
    {"clippaste", luaterm_clippaste},
    {"selpaste", luaterm_selpaste},
    {NULL, NULL}
};

static int term_openf(lua_State *l)
{
    LuaState L(l);

    // make the lib (4 funcs, 2 values)
    L.newlib(term_funcs, 6);

    // add modes table
    L.newtable();
#define PUSH_ENUM_FIELD(nm)\
    L.pushinteger(nm); L.setfield(-2, #nm)
    PUSH_ENUM_FIELD(MODE_WRAP);
    PUSH_ENUM_FIELD(MODE_INSERT);
    PUSH_ENUM_FIELD(MODE_APPKEYPAD);
    PUSH_ENUM_FIELD(MODE_ALTSCREEN);
    PUSH_ENUM_FIELD(MODE_CRLF);
    PUSH_ENUM_FIELD(MODE_MOUSEBTN);
    PUSH_ENUM_FIELD(MODE_MOUSEMOTION);
    PUSH_ENUM_FIELD(MODE_REVERSE);
    PUSH_ENUM_FIELD(MODE_KBDLOCK);
    PUSH_ENUM_FIELD(MODE_HIDE);
    PUSH_ENUM_FIELD(MODE_ECHO);
    PUSH_ENUM_FIELD(MODE_APPCURSOR);
    PUSH_ENUM_FIELD(MODE_MOUSESGR);
    PUSH_ENUM_FIELD(MODE_8BIT);
    PUSH_ENUM_FIELD(MODE_BLINK);
    PUSH_ENUM_FIELD(MODE_FBLINK);
    PUSH_ENUM_FIELD(MODE_FOCUS);
    PUSH_ENUM_FIELD(MODE_MOUSEX10);
    PUSH_ENUM_FIELD(MODE_MOUSEMANY);
    PUSH_ENUM_FIELD(MODE_BRCKTPASTE);
    PUSH_ENUM_FIELD(MODE_PRINT);
    PUSH_ENUM_FIELD(MODE_UTF8);
    PUSH_ENUM_FIELD(MODE_SIXEL);
#undef PUSH_ENUM_FIELD
    L.setfield(-2, "modes");

    // add keys table. Note that this table is quite
    // incomplete; there's no reason for this other than
    // laziness
    L.newtable();
#define PUSH_ENUM_FIELD(nm)\
    L.pushinteger(XKB_KEY_##nm); L.setfield(-2, #nm)
    // latin 1 keys (note that keys above U+007E are missing)
    PUSH_ENUM_FIELD(space);
    PUSH_ENUM_FIELD(exclam);
    PUSH_ENUM_FIELD(quotedbl);
    PUSH_ENUM_FIELD(numbersign);
    PUSH_ENUM_FIELD(dollar);
    PUSH_ENUM_FIELD(percent);
    PUSH_ENUM_FIELD(ampersand);
    PUSH_ENUM_FIELD(apostrophe);
    PUSH_ENUM_FIELD(parenleft);
    PUSH_ENUM_FIELD(parenright);
    PUSH_ENUM_FIELD(asterisk);
    PUSH_ENUM_FIELD(plus);
    PUSH_ENUM_FIELD(comma);
    PUSH_ENUM_FIELD(minus);
    PUSH_ENUM_FIELD(period);
    PUSH_ENUM_FIELD(slash);
    PUSH_ENUM_FIELD(0);
    PUSH_ENUM_FIELD(1);
    PUSH_ENUM_FIELD(2);
    PUSH_ENUM_FIELD(3);
    PUSH_ENUM_FIELD(4);
    PUSH_ENUM_FIELD(5);
    PUSH_ENUM_FIELD(6);
    PUSH_ENUM_FIELD(7);
    PUSH_ENUM_FIELD(8);
    PUSH_ENUM_FIELD(9);
    PUSH_ENUM_FIELD(colon);
    PUSH_ENUM_FIELD(semicolon);
    PUSH_ENUM_FIELD(less);
    PUSH_ENUM_FIELD(equal);
    PUSH_ENUM_FIELD(greater);
    PUSH_ENUM_FIELD(question);
    PUSH_ENUM_FIELD(at);
    PUSH_ENUM_FIELD(A);
    PUSH_ENUM_FIELD(B);
    PUSH_ENUM_FIELD(C);
    PUSH_ENUM_FIELD(D);
    PUSH_ENUM_FIELD(E);
    PUSH_ENUM_FIELD(F);
    PUSH_ENUM_FIELD(G);
    PUSH_ENUM_FIELD(H);
    PUSH_ENUM_FIELD(I);
    PUSH_ENUM_FIELD(J);
    PUSH_ENUM_FIELD(K);
    PUSH_ENUM_FIELD(L);
    PUSH_ENUM_FIELD(M);
    PUSH_ENUM_FIELD(N);
    PUSH_ENUM_FIELD(O);
    PUSH_ENUM_FIELD(P);
    PUSH_ENUM_FIELD(Q);
    PUSH_ENUM_FIELD(R);
    PUSH_ENUM_FIELD(S);
    PUSH_ENUM_FIELD(T);
    PUSH_ENUM_FIELD(U);
    PUSH_ENUM_FIELD(V);
    PUSH_ENUM_FIELD(W);
    PUSH_ENUM_FIELD(X);
    PUSH_ENUM_FIELD(Y);
    PUSH_ENUM_FIELD(Z);
    PUSH_ENUM_FIELD(bracketleft);
    PUSH_ENUM_FIELD(backslash);
    PUSH_ENUM_FIELD(bracketright);
    PUSH_ENUM_FIELD(asciicircum);
    PUSH_ENUM_FIELD(underscore);
    PUSH_ENUM_FIELD(grave);
    PUSH_ENUM_FIELD(a);
    PUSH_ENUM_FIELD(b);
    PUSH_ENUM_FIELD(c);
    PUSH_ENUM_FIELD(d);
    PUSH_ENUM_FIELD(e);
    PUSH_ENUM_FIELD(f);
    PUSH_ENUM_FIELD(g);
    PUSH_ENUM_FIELD(h);
    PUSH_ENUM_FIELD(i);
    PUSH_ENUM_FIELD(j);
    PUSH_ENUM_FIELD(k);
    PUSH_ENUM_FIELD(l);
    PUSH_ENUM_FIELD(m);
    PUSH_ENUM_FIELD(n);
    PUSH_ENUM_FIELD(o);
    PUSH_ENUM_FIELD(p);
    PUSH_ENUM_FIELD(q);
    PUSH_ENUM_FIELD(r);
    PUSH_ENUM_FIELD(s);
    PUSH_ENUM_FIELD(t);
    PUSH_ENUM_FIELD(u);
    PUSH_ENUM_FIELD(v);
    PUSH_ENUM_FIELD(w);
    PUSH_ENUM_FIELD(x);
    PUSH_ENUM_FIELD(y);
    PUSH_ENUM_FIELD(z);
    PUSH_ENUM_FIELD(braceleft);
    PUSH_ENUM_FIELD(bar);
    PUSH_ENUM_FIELD(braceright);
    PUSH_ENUM_FIELD(asciitilde);
    // cusor motion and func keys
    PUSH_ENUM_FIELD(Home);
    PUSH_ENUM_FIELD(Insert);
    PUSH_ENUM_FIELD(Delete);
    PUSH_ENUM_FIELD(End);
    PUSH_ENUM_FIELD(Page_Up); // Prior
    PUSH_ENUM_FIELD(Page_Down); // Next
    PUSH_ENUM_FIELD(Return);
    PUSH_ENUM_FIELD(ISO_Left_Tab);
    PUSH_ENUM_FIELD(BackSpace);
    // F keys
    PUSH_ENUM_FIELD(F1);
    PUSH_ENUM_FIELD(F2);
    PUSH_ENUM_FIELD(F3);
    PUSH_ENUM_FIELD(F4);
    PUSH_ENUM_FIELD(F5);
    PUSH_ENUM_FIELD(F6);
    PUSH_ENUM_FIELD(F7);
    PUSH_ENUM_FIELD(F8);
    PUSH_ENUM_FIELD(F9);
    PUSH_ENUM_FIELD(F10);
    PUSH_ENUM_FIELD(F11);
    PUSH_ENUM_FIELD(F12);
    // KP keys
    PUSH_ENUM_FIELD(KP_Home);
    PUSH_ENUM_FIELD(KP_Up);
    PUSH_ENUM_FIELD(KP_Down);
    PUSH_ENUM_FIELD(KP_Left);
    PUSH_ENUM_FIELD(KP_Right);
    PUSH_ENUM_FIELD(KP_Prior);
    PUSH_ENUM_FIELD(KP_Begin);
    PUSH_ENUM_FIELD(KP_End);
    PUSH_ENUM_FIELD(KP_Next);
    PUSH_ENUM_FIELD(KP_Insert);
    PUSH_ENUM_FIELD(KP_Delete);
    PUSH_ENUM_FIELD(KP_Multiply);
    PUSH_ENUM_FIELD(KP_Add);
    PUSH_ENUM_FIELD(KP_Enter);
    PUSH_ENUM_FIELD(KP_Subtract);
    PUSH_ENUM_FIELD(KP_Decimal);
    PUSH_ENUM_FIELD(KP_Divide);
    PUSH_ENUM_FIELD(KP_0);
    PUSH_ENUM_FIELD(KP_1);
    PUSH_ENUM_FIELD(KP_2);
    PUSH_ENUM_FIELD(KP_3);
    PUSH_ENUM_FIELD(KP_4);
    PUSH_ENUM_FIELD(KP_5);
    PUSH_ENUM_FIELD(KP_6);
    PUSH_ENUM_FIELD(KP_7);
    PUSH_ENUM_FIELD(KP_8);
    PUSH_ENUM_FIELD(KP_9);
#undef PUSH_ENUM_FIELD
    L.setfield(-2, "keys");

	return 1;
}

void register_luaterm(LuaState *L)
{
    L->requiref("term", term_openf, true);
    L->pop();
}

bool luaterm_mouse_press(LuaState *L, int col, int row, int button,
        const keymod_state& mods)
{
    // first, try to push the mouse_press ref
    if (!L->pushfuncref(term_mouse_press_ref))
        return false;

    L->pushinteger(col);
    L->pushinteger(row);
    L->pushinteger(button);
    L->newtable();
    L->pushbool(mods[MOD_SHIFT]);
    L->setfield(-2, "shift");
    L->pushbool(mods[MOD_CTRL]);
    L->setfield(-2, "ctrl");
    L->pushbool(mods[MOD_ALT]);
    L->setfield(-2, "alt");
    L->pushbool(mods[MOD_LOGO]);
    L->setfield(-2, "logo");
    // todo: include num

    if (L->pcall(4, 1, 0) == LUA_OK)
    {
        bool result = L->tobool(-1);
        L->pop(1);
        return result;
    }
    else
    {
        LOGGER()->error("mouse_press: {}", L->tostring(-1));
        L->pop(1);
        return false;
    }
}

bool luaterm_key_press(LuaState *L, int keysym,
        const keymod_state& mods)
{
    // first, try to push the key_press ref
    if (!L->pushfuncref(term_key_press_ref))
        return false;

    L->pushinteger(keysym);
    L->newtable();
    L->pushbool(mods[MOD_SHIFT]);
    L->setfield(-2, "shift");
    L->pushbool(mods[MOD_CTRL]);
    L->setfield(-2, "ctrl");
    L->pushbool(mods[MOD_ALT]);
    L->setfield(-2, "alt");
    L->pushbool(mods[MOD_LOGO]);
    L->setfield(-2, "logo");
    // todo: include num

    if (L->pcall(2, 1, 0) == LUA_OK)
    {
        bool result = L->tobool(-1);
        L->pop(1);
        return result;
    }
    else
    {
        LOGGER()->error("key_press: {}", L->tostring(-1));
        L->pop(1);
        return false;
    }
}

<commit_msg>Renamed lua keys KP_Prior -> KP_Page_Up, KP_Next -> KP_Page_Down<commit_after>#include <xkbcommon/xkbcommon-keysyms.h>

#include "rwte/window.h"
#include "rwte/luastate.h"
#include "rwte/luaterm.h"
#include "rwte/logging.h"
#include "rwte/term.h"

#define LOGGER() (logging::get("luaterm"))

static int term_mouse_press_ref = LUA_NOREF;
static int term_key_press_ref = LUA_NOREF;

static int luaterm_mode(lua_State *l)
{
    LuaState L(l);
    auto mode = (term_mode_enum) L.checkinteger(1);
    L.pushbool(g_term->mode()[mode]);
    return 1;
}

static int luaterm_send(lua_State *l)
{
    LuaState L(l);
    size_t len = 0;
    const char * buffer = L.checklstring(1, &len);
    g_term->send(buffer, len);
    return 0;
}

static int luaterm_mouse_press(lua_State *l)
{
    LuaState L(l);
    term_mouse_press_ref = L.setfuncref(1, term_mouse_press_ref);
    return 0;
}

static int luaterm_key_press(lua_State *l)
{
    LuaState L(l);
    term_key_press_ref = L.setfuncref(1, term_key_press_ref);
    return 0;
}

static int luaterm_clipcopy(lua_State *l)
{
    g_term->clipcopy();
    return 0;
}

static int luaterm_clippaste(lua_State *l)
{
    // todo: seems clumsy to initiate paste w/ window
    // (weird abstraction)
    window.clippaste();
    return 0;
}

static int luaterm_selpaste(lua_State *l)
{
    // todo: seems clumsy to initiate paste w/ window
    // (weird abstraction)
    window.selpaste();
    return 0;
}

// functions for term library
static const luaL_Reg term_funcs[] = {
    {"mode", luaterm_mode},
    {"send", luaterm_send},
    {"mouse_press", luaterm_mouse_press},
    {"key_press", luaterm_key_press},
    {"clipcopy", luaterm_clipcopy},
    {"clippaste", luaterm_clippaste},
    {"selpaste", luaterm_selpaste},
    {NULL, NULL}
};

static int term_openf(lua_State *l)
{
    LuaState L(l);

    // make the lib (4 funcs, 2 values)
    L.newlib(term_funcs, 6);

    // add modes table
    L.newtable();
#define PUSH_ENUM_FIELD(nm)\
    L.pushinteger(nm); L.setfield(-2, #nm)
    PUSH_ENUM_FIELD(MODE_WRAP);
    PUSH_ENUM_FIELD(MODE_INSERT);
    PUSH_ENUM_FIELD(MODE_APPKEYPAD);
    PUSH_ENUM_FIELD(MODE_ALTSCREEN);
    PUSH_ENUM_FIELD(MODE_CRLF);
    PUSH_ENUM_FIELD(MODE_MOUSEBTN);
    PUSH_ENUM_FIELD(MODE_MOUSEMOTION);
    PUSH_ENUM_FIELD(MODE_REVERSE);
    PUSH_ENUM_FIELD(MODE_KBDLOCK);
    PUSH_ENUM_FIELD(MODE_HIDE);
    PUSH_ENUM_FIELD(MODE_ECHO);
    PUSH_ENUM_FIELD(MODE_APPCURSOR);
    PUSH_ENUM_FIELD(MODE_MOUSESGR);
    PUSH_ENUM_FIELD(MODE_8BIT);
    PUSH_ENUM_FIELD(MODE_BLINK);
    PUSH_ENUM_FIELD(MODE_FBLINK);
    PUSH_ENUM_FIELD(MODE_FOCUS);
    PUSH_ENUM_FIELD(MODE_MOUSEX10);
    PUSH_ENUM_FIELD(MODE_MOUSEMANY);
    PUSH_ENUM_FIELD(MODE_BRCKTPASTE);
    PUSH_ENUM_FIELD(MODE_PRINT);
    PUSH_ENUM_FIELD(MODE_UTF8);
    PUSH_ENUM_FIELD(MODE_SIXEL);
#undef PUSH_ENUM_FIELD
    L.setfield(-2, "modes");

    // add keys table. Note that this table is quite
    // incomplete; there's no reason for this other than
    // laziness
    L.newtable();
#define PUSH_ENUM_FIELD(nm)\
    L.pushinteger(XKB_KEY_##nm); L.setfield(-2, #nm)
    // latin 1 keys (note that keys above U+007E are missing)
    PUSH_ENUM_FIELD(space);
    PUSH_ENUM_FIELD(exclam);
    PUSH_ENUM_FIELD(quotedbl);
    PUSH_ENUM_FIELD(numbersign);
    PUSH_ENUM_FIELD(dollar);
    PUSH_ENUM_FIELD(percent);
    PUSH_ENUM_FIELD(ampersand);
    PUSH_ENUM_FIELD(apostrophe);
    PUSH_ENUM_FIELD(parenleft);
    PUSH_ENUM_FIELD(parenright);
    PUSH_ENUM_FIELD(asterisk);
    PUSH_ENUM_FIELD(plus);
    PUSH_ENUM_FIELD(comma);
    PUSH_ENUM_FIELD(minus);
    PUSH_ENUM_FIELD(period);
    PUSH_ENUM_FIELD(slash);
    PUSH_ENUM_FIELD(0);
    PUSH_ENUM_FIELD(1);
    PUSH_ENUM_FIELD(2);
    PUSH_ENUM_FIELD(3);
    PUSH_ENUM_FIELD(4);
    PUSH_ENUM_FIELD(5);
    PUSH_ENUM_FIELD(6);
    PUSH_ENUM_FIELD(7);
    PUSH_ENUM_FIELD(8);
    PUSH_ENUM_FIELD(9);
    PUSH_ENUM_FIELD(colon);
    PUSH_ENUM_FIELD(semicolon);
    PUSH_ENUM_FIELD(less);
    PUSH_ENUM_FIELD(equal);
    PUSH_ENUM_FIELD(greater);
    PUSH_ENUM_FIELD(question);
    PUSH_ENUM_FIELD(at);
    PUSH_ENUM_FIELD(A);
    PUSH_ENUM_FIELD(B);
    PUSH_ENUM_FIELD(C);
    PUSH_ENUM_FIELD(D);
    PUSH_ENUM_FIELD(E);
    PUSH_ENUM_FIELD(F);
    PUSH_ENUM_FIELD(G);
    PUSH_ENUM_FIELD(H);
    PUSH_ENUM_FIELD(I);
    PUSH_ENUM_FIELD(J);
    PUSH_ENUM_FIELD(K);
    PUSH_ENUM_FIELD(L);
    PUSH_ENUM_FIELD(M);
    PUSH_ENUM_FIELD(N);
    PUSH_ENUM_FIELD(O);
    PUSH_ENUM_FIELD(P);
    PUSH_ENUM_FIELD(Q);
    PUSH_ENUM_FIELD(R);
    PUSH_ENUM_FIELD(S);
    PUSH_ENUM_FIELD(T);
    PUSH_ENUM_FIELD(U);
    PUSH_ENUM_FIELD(V);
    PUSH_ENUM_FIELD(W);
    PUSH_ENUM_FIELD(X);
    PUSH_ENUM_FIELD(Y);
    PUSH_ENUM_FIELD(Z);
    PUSH_ENUM_FIELD(bracketleft);
    PUSH_ENUM_FIELD(backslash);
    PUSH_ENUM_FIELD(bracketright);
    PUSH_ENUM_FIELD(asciicircum);
    PUSH_ENUM_FIELD(underscore);
    PUSH_ENUM_FIELD(grave);
    PUSH_ENUM_FIELD(a);
    PUSH_ENUM_FIELD(b);
    PUSH_ENUM_FIELD(c);
    PUSH_ENUM_FIELD(d);
    PUSH_ENUM_FIELD(e);
    PUSH_ENUM_FIELD(f);
    PUSH_ENUM_FIELD(g);
    PUSH_ENUM_FIELD(h);
    PUSH_ENUM_FIELD(i);
    PUSH_ENUM_FIELD(j);
    PUSH_ENUM_FIELD(k);
    PUSH_ENUM_FIELD(l);
    PUSH_ENUM_FIELD(m);
    PUSH_ENUM_FIELD(n);
    PUSH_ENUM_FIELD(o);
    PUSH_ENUM_FIELD(p);
    PUSH_ENUM_FIELD(q);
    PUSH_ENUM_FIELD(r);
    PUSH_ENUM_FIELD(s);
    PUSH_ENUM_FIELD(t);
    PUSH_ENUM_FIELD(u);
    PUSH_ENUM_FIELD(v);
    PUSH_ENUM_FIELD(w);
    PUSH_ENUM_FIELD(x);
    PUSH_ENUM_FIELD(y);
    PUSH_ENUM_FIELD(z);
    PUSH_ENUM_FIELD(braceleft);
    PUSH_ENUM_FIELD(bar);
    PUSH_ENUM_FIELD(braceright);
    PUSH_ENUM_FIELD(asciitilde);
    // cusor motion and func keys
    PUSH_ENUM_FIELD(Home);
    PUSH_ENUM_FIELD(Insert);
    PUSH_ENUM_FIELD(Delete);
    PUSH_ENUM_FIELD(End);
    PUSH_ENUM_FIELD(Page_Up); // Prior
    PUSH_ENUM_FIELD(Page_Down); // Next
    PUSH_ENUM_FIELD(Return);
    PUSH_ENUM_FIELD(ISO_Left_Tab);
    PUSH_ENUM_FIELD(BackSpace);
    // F keys
    PUSH_ENUM_FIELD(F1);
    PUSH_ENUM_FIELD(F2);
    PUSH_ENUM_FIELD(F3);
    PUSH_ENUM_FIELD(F4);
    PUSH_ENUM_FIELD(F5);
    PUSH_ENUM_FIELD(F6);
    PUSH_ENUM_FIELD(F7);
    PUSH_ENUM_FIELD(F8);
    PUSH_ENUM_FIELD(F9);
    PUSH_ENUM_FIELD(F10);
    PUSH_ENUM_FIELD(F11);
    PUSH_ENUM_FIELD(F12);
    // KP keys
    PUSH_ENUM_FIELD(KP_Home);
    PUSH_ENUM_FIELD(KP_Up);
    PUSH_ENUM_FIELD(KP_Down);
    PUSH_ENUM_FIELD(KP_Left);
    PUSH_ENUM_FIELD(KP_Right);
    PUSH_ENUM_FIELD(KP_Page_Up); //Prior
    PUSH_ENUM_FIELD(KP_Begin);
    PUSH_ENUM_FIELD(KP_End);
    PUSH_ENUM_FIELD(KP_Page_Down); // Next
    PUSH_ENUM_FIELD(KP_Insert);
    PUSH_ENUM_FIELD(KP_Delete);
    PUSH_ENUM_FIELD(KP_Multiply);
    PUSH_ENUM_FIELD(KP_Add);
    PUSH_ENUM_FIELD(KP_Enter);
    PUSH_ENUM_FIELD(KP_Subtract);
    PUSH_ENUM_FIELD(KP_Decimal);
    PUSH_ENUM_FIELD(KP_Divide);
    PUSH_ENUM_FIELD(KP_0);
    PUSH_ENUM_FIELD(KP_1);
    PUSH_ENUM_FIELD(KP_2);
    PUSH_ENUM_FIELD(KP_3);
    PUSH_ENUM_FIELD(KP_4);
    PUSH_ENUM_FIELD(KP_5);
    PUSH_ENUM_FIELD(KP_6);
    PUSH_ENUM_FIELD(KP_7);
    PUSH_ENUM_FIELD(KP_8);
    PUSH_ENUM_FIELD(KP_9);
#undef PUSH_ENUM_FIELD
    L.setfield(-2, "keys");

	return 1;
}

void register_luaterm(LuaState *L)
{
    L->requiref("term", term_openf, true);
    L->pop();
}

bool luaterm_mouse_press(LuaState *L, int col, int row, int button,
        const keymod_state& mods)
{
    // first, try to push the mouse_press ref
    if (!L->pushfuncref(term_mouse_press_ref))
        return false;

    L->pushinteger(col);
    L->pushinteger(row);
    L->pushinteger(button);
    L->newtable();
    L->pushbool(mods[MOD_SHIFT]);
    L->setfield(-2, "shift");
    L->pushbool(mods[MOD_CTRL]);
    L->setfield(-2, "ctrl");
    L->pushbool(mods[MOD_ALT]);
    L->setfield(-2, "alt");
    L->pushbool(mods[MOD_LOGO]);
    L->setfield(-2, "logo");
    // todo: include num

    if (L->pcall(4, 1, 0) == LUA_OK)
    {
        bool result = L->tobool(-1);
        L->pop(1);
        return result;
    }
    else
    {
        LOGGER()->error("mouse_press: {}", L->tostring(-1));
        L->pop(1);
        return false;
    }
}

bool luaterm_key_press(LuaState *L, int keysym,
        const keymod_state& mods)
{
    // first, try to push the key_press ref
    if (!L->pushfuncref(term_key_press_ref))
        return false;

    L->pushinteger(keysym);
    L->newtable();
    L->pushbool(mods[MOD_SHIFT]);
    L->setfield(-2, "shift");
    L->pushbool(mods[MOD_CTRL]);
    L->setfield(-2, "ctrl");
    L->pushbool(mods[MOD_ALT]);
    L->setfield(-2, "alt");
    L->pushbool(mods[MOD_LOGO]);
    L->setfield(-2, "logo");
    // todo: include num

    if (L->pcall(2, 1, 0) == LUA_OK)
    {
        bool result = L->tobool(-1);
        L->pop(1);
        return result;
    }
    else
    {
        LOGGER()->error("key_press: {}", L->tostring(-1));
        L->pop(1);
        return false;
    }
}

<|endoftext|>


<commit_before>//===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This defines ObjCSuperDeallocChecker, a builtin check that warns when
// self is used after a call to [super dealloc] in MRR mode.
//
//===----------------------------------------------------------------------===//

#include "ClangSACheckers.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"

using namespace clang;
using namespace ento;

namespace {
class ObjCSuperDeallocChecker
    : public Checker<check::PostObjCMessage, check::PreObjCMessage,
                     check::PreCall, check::Location> {

  mutable IdentifierInfo *IIdealloc, *IINSObject;
  mutable Selector SELdealloc;

  std::unique_ptr<BugType> DoubleSuperDeallocBugType;

  void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;

  bool isSuperDeallocMessage(const ObjCMethodCall &M) const;

public:
  ObjCSuperDeallocChecker();
  void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
  void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;

  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;

  void checkLocation(SVal l, bool isLoad, const Stmt *S,
                     CheckerContext &C) const;

private:

  void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const;

  void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S,
                             CheckerContext &C) const;
};

} // End anonymous namespace.

// Remember whether [super dealloc] has previously been called on the
// SymbolRef for the receiver.
REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)

class SuperDeallocBRVisitor final
    : public BugReporterVisitorImpl<SuperDeallocBRVisitor> {

  SymbolRef ReceiverSymbol;
  bool Satisfied;

public:
  SuperDeallocBRVisitor(SymbolRef ReceiverSymbol)
      : ReceiverSymbol(ReceiverSymbol),
        Satisfied(false) {}

  PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
                                 const ExplodedNode *Pred,
                                 BugReporterContext &BRC,
                                 BugReport &BR) override;

  void Profile(llvm::FoldingSetNodeID &ID) const override {
    ID.Add(ReceiverSymbol);
  }
};

void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
                                                  CheckerContext &C) const {

  ProgramStateRef State = C.getState();
  SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol();
  if (!ReceiverSymbol) {
    diagnoseCallArguments(M, C);
    return;
  }

  bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol);
  if (!AlreadyCalled)
    return;

  StringRef Desc;

  if (isSuperDeallocMessage(M)) {
    Desc = "[super dealloc] should not be called multiple times";
  } else {
    Desc = StringRef();
  }

  reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C);

  return;
}

void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call,
                                           CheckerContext &C) const {
  diagnoseCallArguments(Call, C);
}

void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
                                                   CheckerContext &C) const {
  // Check for [super dealloc] method call.
  if (!isSuperDeallocMessage(M))
    return;

  ProgramStateRef State = C.getState();
  SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol();
  assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");

  // We add this transition in checkPostObjCMessage to avoid warning when
  // we inline a call to [super dealloc] where the inlined call itself
  // calls [super dealloc].
  State = State->add<CalledSuperDealloc>(ReceiverSymbol);
  C.addTransition(State);
}

void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S,
                                  CheckerContext &C) const {
  SymbolRef BaseSym = L.getLocSymbolInBase();
  if (!BaseSym)
    return;

  ProgramStateRef State = C.getState();

  if (!State->contains<CalledSuperDealloc>(BaseSym))
    return;

  const MemRegion *R = L.getAsRegion();
  if (!R)
    return;

  // Climb the super regions to find the base symbol while recording
  // the second-to-last region for error reporting.
  const MemRegion *PriorSubRegion = nullptr;
  while (const SubRegion *SR = dyn_cast<SubRegion>(R)) {
    if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) {
      BaseSym = SymR->getSymbol();
      break;
    } else {
      R = SR->getSuperRegion();
      PriorSubRegion = SR;
    }
  }

  StringRef Desc = StringRef();
  auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(PriorSubRegion);

  if (IvarRegion) {
    std::string Buf;
    llvm::raw_string_ostream OS(Buf);
    OS << "use of instance variable '" << *IvarRegion->getDecl() <<
          "' after the instance has been freed with call to [super dealloc]";
    Desc = OS.str();
  }

  reportUseAfterDealloc(BaseSym, Desc, S, C);
}

/// Report a use-after-dealloc on \param Sym. If not empty,
/// \param Desc will be used to describe the error; otherwise,
/// a default warning will be used.
void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym,
                                                    StringRef Desc,
                                                    const Stmt *S,
                                                    CheckerContext &C) const {
  // We have a use of self after free.
  // This likely causes a crash, so stop exploring the
  // path by generating a sink.
  ExplodedNode *ErrNode = C.generateErrorNode();
  // If we've already reached this node on another path, return.
  if (!ErrNode)
    return;

  if (Desc.empty())
    Desc = "use of 'self' after it has been freed with call to [super dealloc]";

  // Generate the report.
  std::unique_ptr<BugReport> BR(
      new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode));
  BR->addRange(S->getSourceRange());
  BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(Sym));
  C.emitReport(std::move(BR));
}

/// Diagnose if any of the arguments to \param CE have already been
/// dealloc'd.
void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE,
                                                    CheckerContext &C) const {
  ProgramStateRef State = C.getState();
  unsigned ArgCount = CE.getNumArgs();
  for (unsigned I = 0; I < ArgCount; I++) {
    SymbolRef Sym = CE.getArgSVal(I).getAsSymbol();
    if (!Sym)
      continue;

    if (State->contains<CalledSuperDealloc>(Sym)) {
      reportUseAfterDealloc(Sym, StringRef(), CE.getArgExpr(I), C);
      return;
    }
  }
}

ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
    : IIdealloc(nullptr), IINSObject(nullptr) {

  DoubleSuperDeallocBugType.reset(
      new BugType(this, "[super dealloc] should not be called more than once",
                  categories::CoreFoundationObjectiveC));
}

void
ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const {
  if (IIdealloc)
    return;

  IIdealloc = &Ctx.Idents.get("dealloc");
  IINSObject = &Ctx.Idents.get("NSObject");

  SELdealloc = Ctx.Selectors.getSelector(0, &IIdealloc);
}

bool
ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const {
  if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance)
    return false;

  ASTContext &Ctx = M.getState()->getStateManager().getContext();
  initIdentifierInfoAndSelectors(Ctx);

  return M.getSelector() == SELdealloc;
}

PathDiagnosticPiece *SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
                                                      const ExplodedNode *Pred,
                                                      BugReporterContext &BRC,
                                                      BugReport &BR) {
  if (Satisfied)
    return nullptr;

  ProgramStateRef State = Succ->getState();

  bool CalledNow =
      Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
  bool CalledBefore =
      Pred->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);

  // Is Succ the node on which the analyzer noted that [super dealloc] was
  // called on ReceiverSymbol?
  if (CalledNow && !CalledBefore) {
    Satisfied = true;

    ProgramPoint P = Succ->getLocation();
    PathDiagnosticLocation L =
        PathDiagnosticLocation::create(P, BRC.getSourceManager());

    if (!L.isValid() || !L.asLocation().isValid())
      return nullptr;

    return new PathDiagnosticEventPiece(
        L, "[super dealloc] called here");
  }

  return nullptr;
}

//===----------------------------------------------------------------------===//
// Checker Registration.
//===----------------------------------------------------------------------===//

void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) {
  const LangOptions &LangOpts = Mgr.getLangOpts();
  if (LangOpts.getGC() == LangOptions::GCOnly || LangOpts.ObjCAutoRefCount)
    return;
  Mgr.registerChecker<ObjCSuperDeallocChecker>();
}
<commit_msg>[analyzer] Fix a memory error in r261935 caught by the Windows bots.<commit_after>//===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This defines ObjCSuperDeallocChecker, a builtin check that warns when
// self is used after a call to [super dealloc] in MRR mode.
//
//===----------------------------------------------------------------------===//

#include "ClangSACheckers.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"

using namespace clang;
using namespace ento;

namespace {
class ObjCSuperDeallocChecker
    : public Checker<check::PostObjCMessage, check::PreObjCMessage,
                     check::PreCall, check::Location> {

  mutable IdentifierInfo *IIdealloc, *IINSObject;
  mutable Selector SELdealloc;

  std::unique_ptr<BugType> DoubleSuperDeallocBugType;

  void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;

  bool isSuperDeallocMessage(const ObjCMethodCall &M) const;

public:
  ObjCSuperDeallocChecker();
  void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
  void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;

  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;

  void checkLocation(SVal l, bool isLoad, const Stmt *S,
                     CheckerContext &C) const;

private:

  void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const;

  void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S,
                             CheckerContext &C) const;
};

} // End anonymous namespace.

// Remember whether [super dealloc] has previously been called on the
// SymbolRef for the receiver.
REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)

class SuperDeallocBRVisitor final
    : public BugReporterVisitorImpl<SuperDeallocBRVisitor> {

  SymbolRef ReceiverSymbol;
  bool Satisfied;

public:
  SuperDeallocBRVisitor(SymbolRef ReceiverSymbol)
      : ReceiverSymbol(ReceiverSymbol),
        Satisfied(false) {}

  PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
                                 const ExplodedNode *Pred,
                                 BugReporterContext &BRC,
                                 BugReport &BR) override;

  void Profile(llvm::FoldingSetNodeID &ID) const override {
    ID.Add(ReceiverSymbol);
  }
};

void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
                                                  CheckerContext &C) const {

  ProgramStateRef State = C.getState();
  SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol();
  if (!ReceiverSymbol) {
    diagnoseCallArguments(M, C);
    return;
  }

  bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol);
  if (!AlreadyCalled)
    return;

  StringRef Desc;

  if (isSuperDeallocMessage(M)) {
    Desc = "[super dealloc] should not be called multiple times";
  } else {
    Desc = StringRef();
  }

  reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C);

  return;
}

void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call,
                                           CheckerContext &C) const {
  diagnoseCallArguments(Call, C);
}

void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
                                                   CheckerContext &C) const {
  // Check for [super dealloc] method call.
  if (!isSuperDeallocMessage(M))
    return;

  ProgramStateRef State = C.getState();
  SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol();
  assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");

  // We add this transition in checkPostObjCMessage to avoid warning when
  // we inline a call to [super dealloc] where the inlined call itself
  // calls [super dealloc].
  State = State->add<CalledSuperDealloc>(ReceiverSymbol);
  C.addTransition(State);
}

void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S,
                                  CheckerContext &C) const {
  SymbolRef BaseSym = L.getLocSymbolInBase();
  if (!BaseSym)
    return;

  ProgramStateRef State = C.getState();

  if (!State->contains<CalledSuperDealloc>(BaseSym))
    return;

  const MemRegion *R = L.getAsRegion();
  if (!R)
    return;

  // Climb the super regions to find the base symbol while recording
  // the second-to-last region for error reporting.
  const MemRegion *PriorSubRegion = nullptr;
  while (const SubRegion *SR = dyn_cast<SubRegion>(R)) {
    if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) {
      BaseSym = SymR->getSymbol();
      break;
    } else {
      R = SR->getSuperRegion();
      PriorSubRegion = SR;
    }
  }

  StringRef Desc = StringRef();
  auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(PriorSubRegion);

  std::string Buf;
  llvm::raw_string_ostream OS(Buf);
  if (IvarRegion) {
    OS << "use of instance variable '" << *IvarRegion->getDecl() <<
          "' after the instance has been freed with call to [super dealloc]";
    Desc = OS.str();
  }

  reportUseAfterDealloc(BaseSym, Desc, S, C);
}

/// Report a use-after-dealloc on \param Sym. If not empty,
/// \param Desc will be used to describe the error; otherwise,
/// a default warning will be used.
void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym,
                                                    StringRef Desc,
                                                    const Stmt *S,
                                                    CheckerContext &C) const {
  // We have a use of self after free.
  // This likely causes a crash, so stop exploring the
  // path by generating a sink.
  ExplodedNode *ErrNode = C.generateErrorNode();
  // If we've already reached this node on another path, return.
  if (!ErrNode)
    return;

  if (Desc.empty())
    Desc = "use of 'self' after it has been freed with call to [super dealloc]";

  // Generate the report.
  std::unique_ptr<BugReport> BR(
      new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode));
  BR->addRange(S->getSourceRange());
  BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(Sym));
  C.emitReport(std::move(BR));
}

/// Diagnose if any of the arguments to \param CE have already been
/// dealloc'd.
void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE,
                                                    CheckerContext &C) const {
  ProgramStateRef State = C.getState();
  unsigned ArgCount = CE.getNumArgs();
  for (unsigned I = 0; I < ArgCount; I++) {
    SymbolRef Sym = CE.getArgSVal(I).getAsSymbol();
    if (!Sym)
      continue;

    if (State->contains<CalledSuperDealloc>(Sym)) {
      reportUseAfterDealloc(Sym, StringRef(), CE.getArgExpr(I), C);
      return;
    }
  }
}

ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
    : IIdealloc(nullptr), IINSObject(nullptr) {

  DoubleSuperDeallocBugType.reset(
      new BugType(this, "[super dealloc] should not be called more than once",
                  categories::CoreFoundationObjectiveC));
}

void
ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const {
  if (IIdealloc)
    return;

  IIdealloc = &Ctx.Idents.get("dealloc");
  IINSObject = &Ctx.Idents.get("NSObject");

  SELdealloc = Ctx.Selectors.getSelector(0, &IIdealloc);
}

bool
ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const {
  if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance)
    return false;

  ASTContext &Ctx = M.getState()->getStateManager().getContext();
  initIdentifierInfoAndSelectors(Ctx);

  return M.getSelector() == SELdealloc;
}

PathDiagnosticPiece *SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
                                                      const ExplodedNode *Pred,
                                                      BugReporterContext &BRC,
                                                      BugReport &BR) {
  if (Satisfied)
    return nullptr;

  ProgramStateRef State = Succ->getState();

  bool CalledNow =
      Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
  bool CalledBefore =
      Pred->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);

  // Is Succ the node on which the analyzer noted that [super dealloc] was
  // called on ReceiverSymbol?
  if (CalledNow && !CalledBefore) {
    Satisfied = true;

    ProgramPoint P = Succ->getLocation();
    PathDiagnosticLocation L =
        PathDiagnosticLocation::create(P, BRC.getSourceManager());

    if (!L.isValid() || !L.asLocation().isValid())
      return nullptr;

    return new PathDiagnosticEventPiece(
        L, "[super dealloc] called here");
  }

  return nullptr;
}

//===----------------------------------------------------------------------===//
// Checker Registration.
//===----------------------------------------------------------------------===//

void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) {
  const LangOptions &LangOpts = Mgr.getLangOpts();
  if (LangOpts.getGC() == LangOptions::GCOnly || LangOpts.ObjCAutoRefCount)
    return;
  Mgr.registerChecker<ObjCSuperDeallocChecker>();
}
<|endoftext|>


<commit_before>#include "xml_file_loader_tests.h"

#include <iostream>
#include <set>
#include <sstream>
#include <string>

#include "agent.h"
#include "dynamic_module.h"
#include "env.h"
#include "error.h"
#include "sqlite_back.h"

using namespace std;
using cyclus::XMLFileLoader;

void XMLFileLoaderTests::SetUp() {
  schema_path = cyclus::Env::GetInstallPath() + "/share/cyclus/cyclus.rng.in";
  b_ = new cyclus::SqliteBack("xmlfileloadtestdb.sqlite");
  rec_.RegisterBackend(b_);

  falseFile = "false.xml";
  CreateTestInputFile(falseFile, FalseSequence());

  controlFile = "control.xml";
  CreateTestInputFile(controlFile, ControlSequence());

  decayControlFile = "decaycontrol.xml";
  CreateTestInputFile(decayControlFile, ControlSequenceWithDecay());

  solver_control_file = "solvercontrol.xml";
  CreateTestInputFile(solver_control_file, ControlSequenceWithSolver());

  epsFile = "eps.xml";
  CreateTestInputFile(epsFile, ControlSequenceWithEps());

  recipeFile = "recipes.xml";
  CreateTestInputFile(recipeFile, RecipeSequence());

  moduleFile = "modules.xml";
  CreateTestInputFile(moduleFile, ModuleSequence());

}

void XMLFileLoaderTests::TearDown() {
  remove("xmlfileloadtestdb.sqlite");
  unlink(falseFile.c_str());
  unlink(controlFile.c_str());
  unlink(decayControlFile.c_str());
  unlink(epsFile.c_str());
  unlink(solver_control_file.c_str());
  unlink(recipeFile.c_str());
  unlink(moduleFile.c_str());
}

std::string XMLFileLoaderTests::FalseSequence() {
  return "XML is nice, but boooooooooooooooo";
}

std::string XMLFileLoaderTests::ControlSequence() {
  return  "<simulation>"
          " <control>"
          "  <duration>1200</duration>"
          "  <startmonth>1</startmonth>"
          "  <startyear>2000</startyear>"
          "  <simstart>0</simstart>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSequenceWithDecay() {
  return  "<simulation>"
          " <control>"
          "  <duration>1200</duration>"
          "  <startmonth>1</startmonth>"
          "  <startyear>2000</startyear>"
          "  <simstart>0</simstart>"
          "  <decay>never</decay>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSequenceWithSolver() {
  return  "<simulation>"
          " <control>"
          "  <duration>1200</duration>"
          "  <startmonth>1</startmonth>"
          "  <startyear>2000</startyear>"
          "  <simstart>0</simstart>"
          "  <solver>"
          "    <config>"
          "      <greedy>"
          "        <preconditioner>greedy</preconditioner>"
          "      </greedy>"
          "    </config>"
          "    <allow_exclusive_orders>true</allow_exclusive_orders>"
          "  </solver>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSequenceWithEps() {
  return
          "<simulation>"
          "  <control>"
          "    <duration>1</duration>"
          "    <startmonth>1</startmonth>"
          "    <startyear>2000</startyear>"
          "    <eps>0.5e-5</eps>"
          "    <eps_rsrc>3e-4</eps_rsrc>"
          "  </control>"
          "  <archetypes>"
          "    <spec><lib>agents</lib><name>Source</name></spec>"
          "    <spec><lib>agents</lib><name>Sink</name></spec>"
          "    <spec><lib>agents</lib><name>NullRegion</name></spec>"
          "    <spec><lib>agents</lib><name>NullInst</name></spec>"
          "  </archetypes>"
          "  <facility>"
          "    <name>src</name>"
          "    <lifetime>1</lifetime>"
          "    <config>"
          "      <Source>"
          "        <commod>commod</commod>"
          "        <capacity>1</capacity>"
          "      </Source>"
          "    </config>"
          "  </facility>"
          "  <facility>"
          "    <name>snk</name>"
          "    <config>"
          "      <Sink>"
          "        <in_commods><val>commod</val></in_commods>"
          "        <recipe_name>commod_recipe</recipe_name>"
          "        <capacity>1</capacity>"
          "      </Sink>"
          "    </config>"
          "  </facility>"
          "  <region>"
          "    <name>SingleRegion</name>"
          "    <config> <NullRegion/> </config>"
          "    <institution>"
          "      <name>SingleInstitution</name>"
          "      <initialfacilitylist>"
          "        <entry> <prototype>src</prototype> <number>1</number> </entry>"
          "        <entry> <prototype>snk</prototype> <number>1</number> </entry>"
          "      </initialfacilitylist>"
          "      <config> <NullInst/> </config>"
          "    </institution>"
          "  </region>"
          "  <recipe>"
          "    <name>commod_recipe</name>"
          "    <basis>mass</basis>"
          "    <nuclide> <id>cs137</id> <comp>1</comp> </nuclide>"
          "  </recipe>"
          "</simulation>";
}

std::string XMLFileLoaderTests::RecipeSequence() {
  return  "<simulation>"
          " <control>"
          "  <recipe>"
          "    <name>used_uo2_50gwd</name>"
          "    <basis>mass</basis>"
          "    <unit>assembly</unit>"
          "    <total>1000</total>"
          "    <nuclide>"
          "      <id>922340000</id>"
          "      <comp>0.01</comp>"
          "    </nuclide>"
          "    <nuclide>"
          "      <id>U-235</id>"
          "      <comp>0.02</comp>"
          "    </nuclide>"
          "    <nuclide>"
          "      <id>922380000</id>"
          "      <comp>0.97</comp>"
          "    </nuclide>"
          "    <nuclide>"
          "      <id>080160000</id>"
          "      <comp>0.13</comp>"
          "    </nuclide>"
          "  </recipe>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ModuleSequence() {
  return  "<simulation>"
          "  <!-- facility prototypes -->"
          "  <facility>"
          "    <name>fac</name>"
          "    <config>"
          "      <TestFacility/>"
          "    </config>"
          "  </facility>"
          "  <!-- region definitions -->"
          "  <region>"
          "    <name>reg</name>"
          "    <config>"
          "      <TestRegion/>"
          "    </config>"
          "    <!-- institution definitions -->"
          "    <institution>"
          "      <name>inst</name>"
          "      <config>"
          "        <TestInst/>"
          "      </config>"
          "    </institution>"
          "    <!-- end institution definitions -->"
          "  </region>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSchema() {
  return
    "<grammar xmlns=\"http://relaxng.org/ns/structure/1.0\""
    "datatypeLibrary=\"http://www.w3.org/2001/XMLSchema-datatypes\">"
    "<start>"
    "<element name=\"simulation\">"
    "<element name=\"control\">"
    "<element name=\"duration\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "<element name=\"startmonth\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "<element name=\"startyear\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "<element name=\"simstart\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "</element>"
    "</element>"
    "</start>"
    "</grammar>";
}

TEST_F(XMLFileLoaderTests, openfile) {
  EXPECT_NO_THROW(XMLFileLoader file(&rec_, b_, schema_path, controlFile));
}

TEST_F(XMLFileLoaderTests, decayfile) {
  EXPECT_NO_THROW(XMLFileLoader file(&rec_, b_, schema_path, decayControlFile));
}

TEST_F(XMLFileLoaderTests, solverfile) {
  EXPECT_NO_THROW(XMLFileLoader file(&rec_, b_, schema_path, solver_control_file));
}

// Checking default values
TEST_F(XMLFileLoaderTests, EpsDefault) {
  XMLFileLoader file(&rec_, b_, schema_path, controlFile);

  EXPECT_DOUBLE_EQ(1e-6, cyclus::eps());
  EXPECT_DOUBLE_EQ(1e-6, cyclus::eps_rsrc());
}

// Checking default values
TEST_F(XMLFileLoaderTests, EpsFile) {
  XMLFileLoader file(&rec_, b_, schema_path, epsFile);
  file.LoadSim();

  EXPECT_DOUBLE_EQ(0.5e-5, cyclus::eps());
  EXPECT_DOUBLE_EQ(3e-4, cyclus::eps_rsrc());
}

TEST_F(XMLFileLoaderTests, throws) {
  EXPECT_THROW(XMLFileLoader file(&rec_, b_, schema_path, "blah"), cyclus::IOError);
}
<commit_msg>update comment<commit_after>#include "xml_file_loader_tests.h"

#include <iostream>
#include <set>
#include <sstream>
#include <string>

#include "agent.h"
#include "dynamic_module.h"
#include "env.h"
#include "error.h"
#include "sqlite_back.h"

using namespace std;
using cyclus::XMLFileLoader;

void XMLFileLoaderTests::SetUp() {
  schema_path = cyclus::Env::GetInstallPath() + "/share/cyclus/cyclus.rng.in";
  b_ = new cyclus::SqliteBack("xmlfileloadtestdb.sqlite");
  rec_.RegisterBackend(b_);

  falseFile = "false.xml";
  CreateTestInputFile(falseFile, FalseSequence());

  controlFile = "control.xml";
  CreateTestInputFile(controlFile, ControlSequence());

  decayControlFile = "decaycontrol.xml";
  CreateTestInputFile(decayControlFile, ControlSequenceWithDecay());

  solver_control_file = "solvercontrol.xml";
  CreateTestInputFile(solver_control_file, ControlSequenceWithSolver());

  epsFile = "eps.xml";
  CreateTestInputFile(epsFile, ControlSequenceWithEps());

  recipeFile = "recipes.xml";
  CreateTestInputFile(recipeFile, RecipeSequence());

  moduleFile = "modules.xml";
  CreateTestInputFile(moduleFile, ModuleSequence());

}

void XMLFileLoaderTests::TearDown() {
  remove("xmlfileloadtestdb.sqlite");
  unlink(falseFile.c_str());
  unlink(controlFile.c_str());
  unlink(decayControlFile.c_str());
  unlink(epsFile.c_str());
  unlink(solver_control_file.c_str());
  unlink(recipeFile.c_str());
  unlink(moduleFile.c_str());
}

std::string XMLFileLoaderTests::FalseSequence() {
  return "XML is nice, but boooooooooooooooo";
}

std::string XMLFileLoaderTests::ControlSequence() {
  return  "<simulation>"
          " <control>"
          "  <duration>1200</duration>"
          "  <startmonth>1</startmonth>"
          "  <startyear>2000</startyear>"
          "  <simstart>0</simstart>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSequenceWithDecay() {
  return  "<simulation>"
          " <control>"
          "  <duration>1200</duration>"
          "  <startmonth>1</startmonth>"
          "  <startyear>2000</startyear>"
          "  <simstart>0</simstart>"
          "  <decay>never</decay>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSequenceWithSolver() {
  return  "<simulation>"
          " <control>"
          "  <duration>1200</duration>"
          "  <startmonth>1</startmonth>"
          "  <startyear>2000</startyear>"
          "  <simstart>0</simstart>"
          "  <solver>"
          "    <config>"
          "      <greedy>"
          "        <preconditioner>greedy</preconditioner>"
          "      </greedy>"
          "    </config>"
          "    <allow_exclusive_orders>true</allow_exclusive_orders>"
          "  </solver>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSequenceWithEps() {
  return
          "<simulation>"
          "  <control>"
          "    <duration>1</duration>"
          "    <startmonth>1</startmonth>"
          "    <startyear>2000</startyear>"
          "    <eps>0.5e-5</eps>"
          "    <eps_rsrc>3e-4</eps_rsrc>"
          "  </control>"
          "  <archetypes>"
          "    <spec><lib>agents</lib><name>Source</name></spec>"
          "    <spec><lib>agents</lib><name>Sink</name></spec>"
          "    <spec><lib>agents</lib><name>NullRegion</name></spec>"
          "    <spec><lib>agents</lib><name>NullInst</name></spec>"
          "  </archetypes>"
          "  <facility>"
          "    <name>src</name>"
          "    <lifetime>1</lifetime>"
          "    <config>"
          "      <Source>"
          "        <commod>commod</commod>"
          "        <capacity>1</capacity>"
          "      </Source>"
          "    </config>"
          "  </facility>"
          "  <facility>"
          "    <name>snk</name>"
          "    <config>"
          "      <Sink>"
          "        <in_commods><val>commod</val></in_commods>"
          "        <recipe_name>commod_recipe</recipe_name>"
          "        <capacity>1</capacity>"
          "      </Sink>"
          "    </config>"
          "  </facility>"
          "  <region>"
          "    <name>SingleRegion</name>"
          "    <config> <NullRegion/> </config>"
          "    <institution>"
          "      <name>SingleInstitution</name>"
          "      <initialfacilitylist>"
          "        <entry> <prototype>src</prototype> <number>1</number> </entry>"
          "        <entry> <prototype>snk</prototype> <number>1</number> </entry>"
          "      </initialfacilitylist>"
          "      <config> <NullInst/> </config>"
          "    </institution>"
          "  </region>"
          "  <recipe>"
          "    <name>commod_recipe</name>"
          "    <basis>mass</basis>"
          "    <nuclide> <id>cs137</id> <comp>1</comp> </nuclide>"
          "  </recipe>"
          "</simulation>";
}

std::string XMLFileLoaderTests::RecipeSequence() {
  return  "<simulation>"
          " <control>"
          "  <recipe>"
          "    <name>used_uo2_50gwd</name>"
          "    <basis>mass</basis>"
          "    <unit>assembly</unit>"
          "    <total>1000</total>"
          "    <nuclide>"
          "      <id>922340000</id>"
          "      <comp>0.01</comp>"
          "    </nuclide>"
          "    <nuclide>"
          "      <id>U-235</id>"
          "      <comp>0.02</comp>"
          "    </nuclide>"
          "    <nuclide>"
          "      <id>922380000</id>"
          "      <comp>0.97</comp>"
          "    </nuclide>"
          "    <nuclide>"
          "      <id>080160000</id>"
          "      <comp>0.13</comp>"
          "    </nuclide>"
          "  </recipe>"
          " </control>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ModuleSequence() {
  return  "<simulation>"
          "  <!-- facility prototypes -->"
          "  <facility>"
          "    <name>fac</name>"
          "    <config>"
          "      <TestFacility/>"
          "    </config>"
          "  </facility>"
          "  <!-- region definitions -->"
          "  <region>"
          "    <name>reg</name>"
          "    <config>"
          "      <TestRegion/>"
          "    </config>"
          "    <!-- institution definitions -->"
          "    <institution>"
          "      <name>inst</name>"
          "      <config>"
          "        <TestInst/>"
          "      </config>"
          "    </institution>"
          "    <!-- end institution definitions -->"
          "  </region>"
          "</simulation>";
}

std::string XMLFileLoaderTests::ControlSchema() {
  return
    "<grammar xmlns=\"http://relaxng.org/ns/structure/1.0\""
    "datatypeLibrary=\"http://www.w3.org/2001/XMLSchema-datatypes\">"
    "<start>"
    "<element name=\"simulation\">"
    "<element name=\"control\">"
    "<element name=\"duration\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "<element name=\"startmonth\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "<element name=\"startyear\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "<element name=\"simstart\">"
    "  <data type=\"nonNegativeInteger\"/>"
    "</element>"
    "</element>"
    "</element>"
    "</start>"
    "</grammar>";
}

TEST_F(XMLFileLoaderTests, openfile) {
  EXPECT_NO_THROW(XMLFileLoader file(&rec_, b_, schema_path, controlFile));
}

TEST_F(XMLFileLoaderTests, decayfile) {
  EXPECT_NO_THROW(XMLFileLoader file(&rec_, b_, schema_path, decayControlFile));
}

TEST_F(XMLFileLoaderTests, solverfile) {
  EXPECT_NO_THROW(XMLFileLoader file(&rec_, b_, schema_path, solver_control_file));
}

// Checking default eps values
TEST_F(XMLFileLoaderTests, EpsDefault) {
  XMLFileLoader file(&rec_, b_, schema_path, controlFile);

  EXPECT_DOUBLE_EQ(1e-6, cyclus::eps());
  EXPECT_DOUBLE_EQ(1e-6, cyclus::eps_rsrc());
}

// Changing eps values
TEST_F(XMLFileLoaderTests, EpsFile) {
  XMLFileLoader file(&rec_, b_, schema_path, epsFile);
  file.LoadSim();

  EXPECT_DOUBLE_EQ(0.5e-5, cyclus::eps());
  EXPECT_DOUBLE_EQ(3e-4, cyclus::eps_rsrc());
}

TEST_F(XMLFileLoaderTests, throws) {
  EXPECT_THROW(XMLFileLoader file(&rec_, b_, schema_path, "blah"), cyclus::IOError);
}
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Kaveh Vahedipour
////////////////////////////////////////////////////////////////////////////////

#include "Inception.h"

#include "Agency/Agent.h"
#include "Agency/GossipCallback.h"
#include "Agency/MeasureCallback.h"
#include "Basics/ConditionLocker.h"
#include "Cluster/ClusterComm.h"

#include <chrono>
#include <numeric>
#include <thread>

using namespace arangodb::consensus;

Inception::Inception() : Thread("Inception"), _agent(nullptr) {}

Inception::Inception(Agent* agent) : Thread("Inception"), _agent(agent) {}

// Shutdown if not already
Inception::~Inception() { shutdown(); }

/// Gossip to others
/// - Get snapshot of gossip peers and agent pool
/// - Create outgoing gossip.
/// - Send to all peers
void Inception::gossip() {

  auto s = std::chrono::system_clock::now();
  std::chrono::seconds timeout(120);
  size_t i = 0;

  CONDITION_LOCKER(guard, _cv);

  while (!this->isStopping() && !_agent->isStopping()) {

    config_t config = _agent->config();  // get a copy of conf

    // Build gossip message
    query_t out = std::make_shared<Builder>();
    out->openObject();
    out->add("endpoint", VPackValue(config.endpoint()));
    out->add("id", VPackValue(config.id()));
    out->add("pool", VPackValue(VPackValueType::Object));
    for (auto const& i : config.pool()) {
      out->add(i.first, VPackValue(i.second));
    }
    out->close();
    out->close();

    std::string path = privApiPrefix + "gossip";

    // gossip peers
    for (auto const& p : config.gossipPeers()) {
      if (p != config.endpoint()) {
        std::string clientid = config.id() + std::to_string(i++);
        auto hf =
            std::make_unique<std::unordered_map<std::string, std::string>>();
        arangodb::ClusterComm::instance()->asyncRequest(
          clientid, 1, p, rest::RequestType::POST, path,
          std::make_shared<std::string>(out->toJson()), hf,
          std::make_shared<GossipCallback>(_agent), 1.0, true, 0.5);
      }
    }

    // pool entries
    for (auto const& pair : config.pool()) {
      if (pair.second != config.endpoint()) {
        std::string clientid = config.id() + std::to_string(i++);
        auto hf =
            std::make_unique<std::unordered_map<std::string, std::string>>();
        arangodb::ClusterComm::instance()->asyncRequest(
          clientid, 1, pair.second, rest::RequestType::POST, path,
          std::make_shared<std::string>(out->toJson()), hf,
          std::make_shared<GossipCallback>(_agent), 1.0, true, 0.5);
      }
    }

    // don't panic
    _cv.wait(100000);

    // Timed out? :(
    if ((std::chrono::system_clock::now() - s) > timeout) {
      if (config.poolComplete()) {
        LOG_TOPIC(DEBUG, Logger::AGENCY) << "Stopping active gossipping!";
      } else {
        LOG_TOPIC(ERR, Logger::AGENCY)
          << "Failed to find complete pool of agents. Giving up!";
      }
      break;
    }

    // We're done
    if (config.poolComplete()) {
      _agent->startConstituent();
      break;
    }

  }

}

// @brief Active agency from persisted database
bool Inception::activeAgencyFromPersistence() {

  auto myConfig = _agent->config();
  std::string const path = pubApiPrefix + "config";

  // Can only be done responcibly, if we are complete
  if (myConfig.poolComplete()) {

    // Contact hosts on pool in hopes of finding a leader Id
    for (auto const& pair : myConfig.pool()) {

      if (pair.first != myConfig.id()) {

        auto comres = arangodb::ClusterComm::instance()->syncRequest(
          myConfig.id(), 1, pair.second, rest::RequestType::GET, path,
          std::string(), std::unordered_map<std::string, std::string>(), 1.0);

        if (comres->status == CL_COMM_SENT) {

          auto body = comres->result->getBodyVelocyPack();
          auto theirConfig = body->slice();

          std::string leaderId;

          // LeaderId in configuration?
          try {
            leaderId = theirConfig.get("leaderId").copyString();
          } catch (std::exception const& e) {
            LOG_TOPIC(DEBUG, Logger::AGENCY)
              << "Failed to get leaderId from" << pair.second << ": "
              << e.what();
          }

          if (leaderId != "") { // Got leaderId. Let's get do it.

            try {
              LOG_TOPIC(DEBUG, Logger::AGENCY)
                << "Found active agency with leader " << leaderId
                << " at endpoint "
                << theirConfig.get("configuration").get(
                  "pool").get(leaderId).copyString();
            } catch (std::exception const& e) {
              LOG_TOPIC(DEBUG, Logger::AGENCY)
                << "Failed to get leaderId from" << pair.second << ": "
                << e.what();
            }

            auto agency = std::make_shared<Builder>();
            agency->openObject();
            agency->add("term", theirConfig.get("term"));
            agency->add("id", VPackValue(leaderId));
            agency->add("active", theirConfig.get("configuration").get("active"));
            agency->add("pool", theirConfig.get("configuration").get("pool"));
            agency->close();
            _agent->notify(agency);

            return true;

          } else { // No leaderId. Move on.

            LOG_TOPIC(DEBUG, Logger::AGENCY)
              << "Failed to get leaderId from" << pair.second;

          }

        }

      }

    }
  }

  return false;

}

bool Inception::restartingActiveAgent() {

  auto myConfig = _agent->config();
  std::string const path = pubApiPrefix + "config";

  auto s = std::chrono::system_clock::now();
  std::chrono::seconds timeout(60);

  // Can only be done responcibly, if we are complete
  if (myConfig.poolComplete()) {

    auto pool = myConfig.pool();
    auto active = myConfig.active();

    CONDITION_LOCKER(guard, _cv);

    while (!this->isStopping() && !_agent->isStopping()) {

      active.erase(
        std::remove(active.begin(), active.end(), myConfig.id()), active.end());
      active.erase(
        std::remove(active.begin(), active.end(), ""), active.end());

      if (active.empty()) {
        return true;
      }

      for (auto& i : active) {

        if (i != myConfig.id() && i != "") {

          auto clientId = myConfig.id();
          auto comres   = arangodb::ClusterComm::instance()->syncRequest(
            clientId, 1, pool.at(i), rest::RequestType::GET, path, std::string(),
            std::unordered_map<std::string, std::string>(), 2.0);

          if (comres->status == CL_COMM_SENT) {

            try {

              auto theirActive = comres->result->getBodyVelocyPack()->
                slice().get("configuration").get("active").toJson();
              auto myActive = myConfig.activeToBuilder()->toJson();

              if (theirActive != myActive) {
                LOG_TOPIC(FATAL, Logger::AGENCY)
                  << "Assumed active RAFT peer and I disagree on active membership."
                  << "Administrative intervention needed.";
                FATAL_ERROR_EXIT();
                return false;
              } else {
                i = "";
              }

            } catch (std::exception const& e) {
              LOG_TOPIC(FATAL, Logger::AGENCY)
                << "Assumed active RAFT peer has no active agency list: " << e.what()
                << "Administrative intervention needed.";
              FATAL_ERROR_EXIT();
              return false;
            }
          }
        }

      }

      // Timed out? :(
      if ((std::chrono::system_clock::now() - s) > timeout) {
        if (myConfig.poolComplete()) {
          LOG_TOPIC(DEBUG, Logger::AGENCY) << "Stopping active gossipping!";
        } else {
          LOG_TOPIC(ERR, Logger::AGENCY)
            << "Failed to find complete pool of agents. Giving up!";
        }
        break;
      }

      _cv.wait(500000);

    }
  }

  return false;

}

inline static int64_t timeStamp() {
  using namespace std::chrono;
  return duration_cast<microseconds>(
    steady_clock::now().time_since_epoch()).count();
}

void Inception::reportIn(std::string const& peerId, uint64_t start) {
  MUTEX_LOCKER(lock, _pLock);
  _pings.push_back(1.0e-3*(double)(timeStamp()-start));
}

void Inception::reportIn(query_t const& query) {

  VPackSlice slice = query->slice();

  TRI_ASSERT(slice.isObject());
  TRI_ASSERT(slice.hasKey("mean"));
  TRI_ASSERT(slice.hasKey("stdev"));
  TRI_ASSERT(slice.hasKey("min"));
  TRI_ASSERT(slice.hasKey("max"));

  MUTEX_LOCKER(lock, _mLock);
  _measurements.push_back(
    std::vector<double>(
      {slice.get("mean").getDouble(), slice.get("stdev").getDouble(),
          slice.get("max").getDouble(), slice.get("min").getDouble()} ));

}

bool Inception::estimateRAFTInterval() {

  using namespace std::chrono;

  std::string path("/_api/agency/config");
  auto pool = _agent->config().pool();
  auto myid = _agent->id();

  for (size_t i = 0; i < 25; ++i) {
    for (auto const& peer : pool) {
      if (peer.first != myid) {
        std::string clientid = peer.first + std::to_string(i);
        auto hf =
          std::make_unique<std::unordered_map<std::string, std::string>>();
        arangodb::ClusterComm::instance()->asyncRequest(
          clientid, 1, peer.second, rest::RequestType::GET, path,
          std::make_shared<std::string>(), hf,
          std::make_shared<MeasureCallback>(this, peer.second, timeStamp()),
          2.0, true);
      }
    }
    std::this_thread::sleep_for(std::chrono::duration<double,std::milli>(5));
  }

  auto s = system_clock::now();
  seconds timeout(3);

  CONDITION_LOCKER(guard, _cv);

  while (true) {

    _cv.wait(50000);

    {
      MUTEX_LOCKER(lock, _pLock);
      if (_pings.size() == 25*(pool.size()-1)) {
        LOG_TOPIC(DEBUG, Logger::AGENCY) << "All pings are in";
        break;
      }
    }

    if ((system_clock::now() - s) > timeout) {
      LOG_TOPIC(DEBUG, Logger::AGENCY) << "Timed out waiting for pings";
      break;
    }

  }

  if (! _ping.empty()) {

    double sum, mean = 0., sq_sum, stdev = 0., mx = 0., mn = 0.;

    MUTEX_LOCKER(lock, _pLock);
    size_t num = _pings.size();
    mean   = std::accumulate(_pings.begin(), _pings.end(), 0.0) / num;
    mx     = *std::max_element(_pings.begin(), _pings.end());
    mn     = *std::min_element(_pings.begin(), _pings.end());
    std::transform(_pings.begin(), _pings.end(), _pings.begin(),
                   std::bind2nd(std::minus<double>(), mean));
    stdev =
      std::sqrt(
        std::inner_product(
          _pings.begin(), _pings.end(), _pings.begin(), 0.0) / num);

    LOG_TOPIC(DEBUG, Logger::AGENCY)
      << "mean(" << mean << ") stdev(" << stdev<< ")";

    Builder measurement;
    measurement.openObject();
    measurement.add("mean", VPackValue(mean));
    measurement.add("stdev", VPackValue(stdev));
    measurement.add("min", VPackValue(mn));
    measurement.add("max", VPackValue(mx));
    measurement.close();
    std::string measjson = measurement.toJson();

    path = privApiPrefix + "measure";
    for (auto const& peer : pool) {
      if (peer.first != myid) {
        auto clientId = "1";
        auto comres   = arangodb::ClusterComm::instance()->syncRequest(
          clientId, 1, peer.second, rest::RequestType::POST, path,
          measjson, std::unordered_map<std::string, std::string>(), 2.0);
      }
    }

    {
      MUTEX_LOCKER(lock, _mLock);
      _measurements.push_back(std::vector<double>({mean, stdev, mx, mn}));
    }
    s = system_clock::now();
    while (true) {

      _cv.wait(50000);

      {
        MUTEX_LOCKER(lock, _mLock);
        if (_measurements.size() == pool.size()) {
          LOG_TOPIC(DEBUG, Logger::AGENCY) << "All measurements are in";
          break;
        }
      }

      if ((system_clock::now() - s) > timeout) {
        LOG_TOPIC(WARN, Logger::AGENCY)
          << "Timed out waiting for other measurements. Auto-adaptation failed! Will stick to command line arguments";
        return false;
      }

    }

    double maxmean  = .0;
    double maxstdev = .0;
    for (auto const& meas : _measurements) {
      if (maxmean < meas[0]) {
        maxmean = meas[0];
      }
      if (maxstdev < meas[1]) {
        maxstdev = meas[1];
      }
    }

    maxmean = 1.0e-2*std::ceil(100*(.15 + 1.0e-3*maxmean));

    LOG_TOPIC(INFO, Logger::AGENCY)
      << "Auto-adapting RAFT timing to: {" << maxmean
      << ", " << 5.0*maxmean << "}s";

    _agent->resetRAFTTimes(maxmean, 5.0*maxmean);

  }

  return true;

}

// @brief Active agency from persisted database
bool Inception::activeAgencyFromCommandLine() {
  return false;
}

// @brief Thread main
void Inception::run() {

  // 1. If active agency, do as you're told
  if (activeAgencyFromPersistence()) {
    _agent->ready(true);
  }

  // 2. If we think that we used to be active agent
  if (!_agent->ready() && restartingActiveAgent()) {
    _agent->ready(true);
  }

  // 3. Else gossip
  config_t config = _agent->config();
  if (!_agent->ready() && !config.poolComplete()) {
    gossip();
  }

  // 4. If still incomplete bail out :(
  config = _agent->config();
  if (!_agent->ready() && !config.poolComplete()) {
    LOG_TOPIC(FATAL, Logger::AGENCY)
      << "Failed to build environment for RAFT algorithm. Bailing out!";
    FATAL_ERROR_EXIT();
  }

  estimateRAFTInterval();

  _agent->ready(true);

}

// @brief Graceful shutdown
void Inception::beginShutdown() {
  Thread::beginShutdown();
  CONDITION_LOCKER(guard, _cv);
  guard.broadcast();
}
<commit_msg>RAFT auto-timing warnings<commit_after>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Kaveh Vahedipour
////////////////////////////////////////////////////////////////////////////////

#include "Inception.h"

#include "Agency/Agent.h"
#include "Agency/GossipCallback.h"
#include "Agency/MeasureCallback.h"
#include "Basics/ConditionLocker.h"
#include "Cluster/ClusterComm.h"

#include <chrono>
#include <numeric>
#include <thread>

using namespace arangodb::consensus;

Inception::Inception() : Thread("Inception"), _agent(nullptr) {}

Inception::Inception(Agent* agent) : Thread("Inception"), _agent(agent) {}

// Shutdown if not already
Inception::~Inception() { shutdown(); }

/// Gossip to others
/// - Get snapshot of gossip peers and agent pool
/// - Create outgoing gossip.
/// - Send to all peers
void Inception::gossip() {

  auto s = std::chrono::system_clock::now();
  std::chrono::seconds timeout(120);
  size_t i = 0;

  CONDITION_LOCKER(guard, _cv);

  while (!this->isStopping() && !_agent->isStopping()) {

    config_t config = _agent->config();  // get a copy of conf

    // Build gossip message
    query_t out = std::make_shared<Builder>();
    out->openObject();
    out->add("endpoint", VPackValue(config.endpoint()));
    out->add("id", VPackValue(config.id()));
    out->add("pool", VPackValue(VPackValueType::Object));
    for (auto const& i : config.pool()) {
      out->add(i.first, VPackValue(i.second));
    }
    out->close();
    out->close();

    std::string path = privApiPrefix + "gossip";

    // gossip peers
    for (auto const& p : config.gossipPeers()) {
      if (p != config.endpoint()) {
        std::string clientid = config.id() + std::to_string(i++);
        auto hf =
            std::make_unique<std::unordered_map<std::string, std::string>>();
        arangodb::ClusterComm::instance()->asyncRequest(
          clientid, 1, p, rest::RequestType::POST, path,
          std::make_shared<std::string>(out->toJson()), hf,
          std::make_shared<GossipCallback>(_agent), 1.0, true, 0.5);
      }
    }

    // pool entries
    for (auto const& pair : config.pool()) {
      if (pair.second != config.endpoint()) {
        std::string clientid = config.id() + std::to_string(i++);
        auto hf =
            std::make_unique<std::unordered_map<std::string, std::string>>();
        arangodb::ClusterComm::instance()->asyncRequest(
          clientid, 1, pair.second, rest::RequestType::POST, path,
          std::make_shared<std::string>(out->toJson()), hf,
          std::make_shared<GossipCallback>(_agent), 1.0, true, 0.5);
      }
    }

    // don't panic
    _cv.wait(100000);

    // Timed out? :(
    if ((std::chrono::system_clock::now() - s) > timeout) {
      if (config.poolComplete()) {
        LOG_TOPIC(DEBUG, Logger::AGENCY) << "Stopping active gossipping!";
      } else {
        LOG_TOPIC(ERR, Logger::AGENCY)
          << "Failed to find complete pool of agents. Giving up!";
      }
      break;
    }

    // We're done
    if (config.poolComplete()) {
      _agent->startConstituent();
      break;
    }

  }

}

// @brief Active agency from persisted database
bool Inception::activeAgencyFromPersistence() {

  auto myConfig = _agent->config();
  std::string const path = pubApiPrefix + "config";

  // Can only be done responcibly, if we are complete
  if (myConfig.poolComplete()) {

    // Contact hosts on pool in hopes of finding a leader Id
    for (auto const& pair : myConfig.pool()) {

      if (pair.first != myConfig.id()) {

        auto comres = arangodb::ClusterComm::instance()->syncRequest(
          myConfig.id(), 1, pair.second, rest::RequestType::GET, path,
          std::string(), std::unordered_map<std::string, std::string>(), 1.0);

        if (comres->status == CL_COMM_SENT) {

          auto body = comres->result->getBodyVelocyPack();
          auto theirConfig = body->slice();

          std::string leaderId;

          // LeaderId in configuration?
          try {
            leaderId = theirConfig.get("leaderId").copyString();
          } catch (std::exception const& e) {
            LOG_TOPIC(DEBUG, Logger::AGENCY)
              << "Failed to get leaderId from" << pair.second << ": "
              << e.what();
          }

          if (leaderId != "") { // Got leaderId. Let's get do it.

            try {
              LOG_TOPIC(DEBUG, Logger::AGENCY)
                << "Found active agency with leader " << leaderId
                << " at endpoint "
                << theirConfig.get("configuration").get(
                  "pool").get(leaderId).copyString();
            } catch (std::exception const& e) {
              LOG_TOPIC(DEBUG, Logger::AGENCY)
                << "Failed to get leaderId from" << pair.second << ": "
                << e.what();
            }

            auto agency = std::make_shared<Builder>();
            agency->openObject();
            agency->add("term", theirConfig.get("term"));
            agency->add("id", VPackValue(leaderId));
            agency->add("active", theirConfig.get("configuration").get("active"));
            agency->add("pool", theirConfig.get("configuration").get("pool"));
            agency->close();
            _agent->notify(agency);

            return true;

          } else { // No leaderId. Move on.

            LOG_TOPIC(DEBUG, Logger::AGENCY)
              << "Failed to get leaderId from" << pair.second;

          }

        }

      }

    }
  }

  return false;

}

bool Inception::restartingActiveAgent() {

  auto myConfig = _agent->config();
  std::string const path = pubApiPrefix + "config";

  auto s = std::chrono::system_clock::now();
  std::chrono::seconds timeout(60);

  // Can only be done responcibly, if we are complete
  if (myConfig.poolComplete()) {

    auto pool = myConfig.pool();
    auto active = myConfig.active();

    CONDITION_LOCKER(guard, _cv);

    while (!this->isStopping() && !_agent->isStopping()) {

      active.erase(
        std::remove(active.begin(), active.end(), myConfig.id()), active.end());
      active.erase(
        std::remove(active.begin(), active.end(), ""), active.end());

      if (active.empty()) {
        return true;
      }

      for (auto& i : active) {

        if (i != myConfig.id() && i != "") {

          auto clientId = myConfig.id();
          auto comres   = arangodb::ClusterComm::instance()->syncRequest(
            clientId, 1, pool.at(i), rest::RequestType::GET, path, std::string(),
            std::unordered_map<std::string, std::string>(), 2.0);

          if (comres->status == CL_COMM_SENT) {

            try {

              auto theirActive = comres->result->getBodyVelocyPack()->
                slice().get("configuration").get("active").toJson();
              auto myActive = myConfig.activeToBuilder()->toJson();

              if (theirActive != myActive) {
                LOG_TOPIC(FATAL, Logger::AGENCY)
                  << "Assumed active RAFT peer and I disagree on active membership."
                  << "Administrative intervention needed.";
                FATAL_ERROR_EXIT();
                return false;
              } else {
                i = "";
              }

            } catch (std::exception const& e) {
              LOG_TOPIC(FATAL, Logger::AGENCY)
                << "Assumed active RAFT peer has no active agency list: " << e.what()
                << "Administrative intervention needed.";
              FATAL_ERROR_EXIT();
              return false;
            }
          }
        }

      }

      // Timed out? :(
      if ((std::chrono::system_clock::now() - s) > timeout) {
        if (myConfig.poolComplete()) {
          LOG_TOPIC(DEBUG, Logger::AGENCY) << "Stopping active gossipping!";
        } else {
          LOG_TOPIC(ERR, Logger::AGENCY)
            << "Failed to find complete pool of agents. Giving up!";
        }
        break;
      }

      _cv.wait(500000);

    }
  }

  return false;

}

inline static int64_t timeStamp() {
  using namespace std::chrono;
  return duration_cast<microseconds>(
    steady_clock::now().time_since_epoch()).count();
}

void Inception::reportIn(std::string const& peerId, uint64_t start) {
  MUTEX_LOCKER(lock, _pLock);
  _pings.push_back(1.0e-3*(double)(timeStamp()-start));
}

void Inception::reportIn(query_t const& query) {

  VPackSlice slice = query->slice();

  TRI_ASSERT(slice.isObject());
  TRI_ASSERT(slice.hasKey("mean"));
  TRI_ASSERT(slice.hasKey("stdev"));
  TRI_ASSERT(slice.hasKey("min"));
  TRI_ASSERT(slice.hasKey("max"));

  MUTEX_LOCKER(lock, _mLock);
  _measurements.push_back(
    std::vector<double>(
      {slice.get("mean").getDouble(), slice.get("stdev").getDouble(),
          slice.get("max").getDouble(), slice.get("min").getDouble()} ));

}

bool Inception::estimateRAFTInterval() {

  using namespace std::chrono;

  std::string path("/_api/agency/config");
  auto pool = _agent->config().pool();
  auto myid = _agent->id();

  for (size_t i = 0; i < 25; ++i) {
    for (auto const& peer : pool) {
      if (peer.first != myid) {
        std::string clientid = peer.first + std::to_string(i);
        auto hf =
          std::make_unique<std::unordered_map<std::string, std::string>>();
        arangodb::ClusterComm::instance()->asyncRequest(
          clientid, 1, peer.second, rest::RequestType::GET, path,
          std::make_shared<std::string>(), hf,
          std::make_shared<MeasureCallback>(this, peer.second, timeStamp()),
          2.0, true);
      }
    }
    std::this_thread::sleep_for(std::chrono::duration<double,std::milli>(5));
  }

  auto s = system_clock::now();
  seconds timeout(3);

  CONDITION_LOCKER(guard, _cv);

  while (true) {

    _cv.wait(50000);

    {
      MUTEX_LOCKER(lock, _pLock);
      if (_pings.size() == 25*(pool.size()-1)) {
        LOG_TOPIC(DEBUG, Logger::AGENCY) << "All pings are in";
        break;
      }
    }

    if ((system_clock::now() - s) > timeout) {
      LOG_TOPIC(DEBUG, Logger::AGENCY) << "Timed out waiting for pings";
      break;
    }

  }

  if (! _pings.empty()) {

    double sum, mean = 0., sq_sum, stdev = 0., mx = 0., mn = 0.;

    MUTEX_LOCKER(lock, _pLock);
    size_t num = _pings.size();
    mean   = std::accumulate(_pings.begin(), _pings.end(), 0.0) / num;
    mx     = *std::max_element(_pings.begin(), _pings.end());
    mn     = *std::min_element(_pings.begin(), _pings.end());
    std::transform(_pings.begin(), _pings.end(), _pings.begin(),
                   std::bind2nd(std::minus<double>(), mean));
    stdev =
      std::sqrt(
        std::inner_product(
          _pings.begin(), _pings.end(), _pings.begin(), 0.0) / num);

    LOG_TOPIC(DEBUG, Logger::AGENCY)
      << "mean(" << mean << ") stdev(" << stdev<< ")";

    Builder measurement;
    measurement.openObject();
    measurement.add("mean", VPackValue(mean));
    measurement.add("stdev", VPackValue(stdev));
    measurement.add("min", VPackValue(mn));
    measurement.add("max", VPackValue(mx));
    measurement.close();
    std::string measjson = measurement.toJson();

    path = privApiPrefix + "measure";
    for (auto const& peer : pool) {
      if (peer.first != myid) {
        auto clientId = "1";
        auto comres   = arangodb::ClusterComm::instance()->syncRequest(
          clientId, 1, peer.second, rest::RequestType::POST, path,
          measjson, std::unordered_map<std::string, std::string>(), 2.0);
      }
    }

    {
      MUTEX_LOCKER(lock, _mLock);
      _measurements.push_back(std::vector<double>({mean, stdev, mx, mn}));
    }
    s = system_clock::now();
    while (true) {

      _cv.wait(50000);

      {
        MUTEX_LOCKER(lock, _mLock);
        if (_measurements.size() == pool.size()) {
          LOG_TOPIC(DEBUG, Logger::AGENCY) << "All measurements are in";
          break;
        }
      }

      if ((system_clock::now() - s) > timeout) {
        LOG_TOPIC(WARN, Logger::AGENCY)
          << "Timed out waiting for other measurements. Auto-adaptation failed! Will stick to command line arguments";
        return false;
      }

    }

    double maxmean  = .0;
    double maxstdev = .0;
    for (auto const& meas : _measurements) {
      if (maxmean < meas[0]) {
        maxmean = meas[0];
      }
      if (maxstdev < meas[1]) {
        maxstdev = meas[1];
      }
    }

    maxmean = 1.0e-2*std::ceil(100*(.15 + 1.0e-3*maxmean));

    LOG_TOPIC(INFO, Logger::AGENCY)
      << "Auto-adapting RAFT timing to: {" << maxmean
      << ", " << 5.0*maxmean << "}s";

    _agent->resetRAFTTimes(maxmean, 5.0*maxmean);

  }

  return true;

}

// @brief Active agency from persisted database
bool Inception::activeAgencyFromCommandLine() {
  return false;
}

// @brief Thread main
void Inception::run() {

  // 1. If active agency, do as you're told
  if (activeAgencyFromPersistence()) {
    _agent->ready(true);
  }

  // 2. If we think that we used to be active agent
  if (!_agent->ready() && restartingActiveAgent()) {
    _agent->ready(true);
  }

  // 3. Else gossip
  config_t config = _agent->config();
  if (!_agent->ready() && !config.poolComplete()) {
    gossip();
  }

  // 4. If still incomplete bail out :(
  config = _agent->config();
  if (!_agent->ready() && !config.poolComplete()) {
    LOG_TOPIC(FATAL, Logger::AGENCY)
      << "Failed to build environment for RAFT algorithm. Bailing out!";
    FATAL_ERROR_EXIT();
  }

  estimateRAFTInterval();

  _agent->ready(true);

}

// @brief Graceful shutdown
void Inception::beginShutdown() {
  Thread::beginShutdown();
  CONDITION_LOCKER(guard, _cv);
  guard.broadcast();
}
<|endoftext|>


<commit_before>//
// Test Suite for geos::operation::overlayng::OverlayNGRobust class.
//
// Useful place for test cases raised by 3rd party software, that will be using
// Geometry->Intersection(), Geometry->Union(), etc, that call into the
// OverlayNGRobust utility class that bundles up different precision models
// and noders to provide a "best case" overlay for all inputs.
//

#include <tut/tut.hpp>
#include <utility.h>

// geos
#include <geos/operation/overlayng/OverlayNGRobust.h>

// std
#include <memory>

using namespace geos::geom;
using namespace geos::operation::overlayng;
using geos::io::WKTReader;
using geos::io::WKTWriter;

namespace tut {
//
// Test Group
//

// Common data used by all tests
struct test_overlayngrobust_data {

    WKTReader r;
    WKTWriter w;

    void
    testOverlay(const std::string& a, const std::string& b, const std::string& expected, int opCode)
    {
        std::unique_ptr<Geometry> geom_a = r.read(a);
        std::unique_ptr<Geometry> geom_b = r.read(b);
        std::unique_ptr<Geometry> geom_expected = r.read(expected);
        std::unique_ptr<Geometry> geom_result = OverlayNGRobust::Overlay(geom_a.get(), geom_b.get(), opCode);
        // std::string wkt_result = w.write(geom_result.get());
        // std::cout << std::endl << wkt_result << std::endl;
        ensure_equals_geometry(geom_expected.get(), geom_result.get());
    }

    void
    checkOverlaySuccess(const std::string& a, const std::string& b, int opCode)
    {
        std::unique_ptr<Geometry> geom_a = r.read(a);
        std::unique_ptr<Geometry> geom_b = r.read(b);
        std::unique_ptr<Geometry> geom_expected = r.read(expected);
		ensure_nothrow( OverlayNGRobust::Overlay(geom_a.get(), geom_b.get(), opCode) );
    }

};

typedef test_group<test_overlayngrobust_data> group;
typedef group::object object;

group test_overlayngrobust_data("geos::operation::overlayng::OverlayNGRobust");

//
// Test Cases
//

// 2020-09-17, GDAL exposed error in result clipping routine
template<>
template<>
void object::test<1> ()
{
    std::string a = "LINESTRING(832864.275023695 0,835092.849076364 0)";
    std::string b = "MULTIPOLYGON (((832864.275023695 0.0,833978.556808034 -0.000110682755987,833978.556808034 0.0,833978.556808034 0.000110682755987,832864.275023695 0.0,832864.275023695 0.0)),((835092.849076364 0.0,833978.557030887 -0.000110682755987,833978.557030887 0.0,833978.557030887 0.000110682755987,835092.849076364 0.0,835092.849076364 0.0)))";
    std::string exp = "MULTILINESTRING ((832864.275023695 0.0,833978.556808034 0.0),(833978.557030887 0.0,835092.849076364 0.0))";
    testOverlay(a, b, exp, OverlayNG::INTERSECTION);
}

/**
 * Tests correct ordering of {@link SegmentNode#compareTo(Object)}.
 *
 * See https://trac.osgeo.org/geos/ticket/1051
 *
 * JTS equivalent: testSegmentNodeOrderingIntersection()
 */
template<>
template<>
void object::test<2> ()
{
    std::string a = "POLYGON ((654948.3853299792 1794977.105854025, 655016.3812220972 1794939.918901604, 655016.2022581929 1794940.1099794197, 655014.9264068712 1794941.4254068714, 655014.7408834674 1794941.6101225375, 654948.3853299792 1794977.105854025))";
	std::string b = "POLYGON ((655103.6628454948 1794805.456674405, 655016.20226 1794940.10998, 655014.8317182435 1794941.5196832407, 655014.8295602322 1794941.5218318563, 655014.740883467 1794941.610122538, 655016.6029214273 1794938.7590508445, 655103.6628454948 1794805.456674405))";
    checkOverlaySuccess(a, b, OverlayNG::INTERSECTION);
}

} // namespace tut
<commit_msg>Fix compilation<commit_after>//
// Test Suite for geos::operation::overlayng::OverlayNGRobust class.
//
// Useful place for test cases raised by 3rd party software, that will be using
// Geometry->Intersection(), Geometry->Union(), etc, that call into the
// OverlayNGRobust utility class that bundles up different precision models
// and noders to provide a "best case" overlay for all inputs.
//

#include <tut/tut.hpp>
#include <tut/tut_macros.hpp>
#include <utility.h>

// geos
#include <geos/operation/overlayng/OverlayNGRobust.h>
#include <geos/operation/overlayng/OverlayNG.h>

// std
#include <memory>

using namespace geos::geom;
using geos::io::WKTReader;
using geos::io::WKTWriter;
using geos::operation::overlayng::OverlayNGRobust;
using geos::operation::overlayng::OverlayNG;

namespace tut {
//
// Test Group
//

// Common data used by all tests
struct test_overlayngrobust_data {

    WKTReader r;
    WKTWriter w;

    void
    testOverlay(const std::string& a, const std::string& b, const std::string& expected, int opCode)
    {
        std::unique_ptr<Geometry> geom_a = r.read(a);
        std::unique_ptr<Geometry> geom_b = r.read(b);
        std::unique_ptr<Geometry> geom_expected = r.read(expected);
        std::unique_ptr<Geometry> geom_result = OverlayNGRobust::Overlay(geom_a.get(), geom_b.get(), opCode);
        // std::string wkt_result = w.write(geom_result.get());
        // std::cout << std::endl << wkt_result << std::endl;
        ensure_equals_geometry(geom_expected.get(), geom_result.get());
    }

    void
    checkOverlaySuccess(const std::string& a, const std::string& b, int opCode)
    {
        std::unique_ptr<Geometry> geom_a = r.read(a);
        std::unique_ptr<Geometry> geom_b = r.read(b);
        ensure_NO_THROW( OverlayNGRobust::Overlay(geom_a.get(), geom_b.get(), opCode) );
    }

};

typedef test_group<test_overlayngrobust_data> group;
typedef group::object object;

group test_overlayngrobust_data("geos::operation::overlayng::OverlayNGRobust");

//
// Test Cases
//

// 2020-09-17, GDAL exposed error in result clipping routine
template<>
template<>
void object::test<1> ()
{
    std::string a = "LINESTRING(832864.275023695 0,835092.849076364 0)";
    std::string b = "MULTIPOLYGON (((832864.275023695 0.0,833978.556808034 -0.000110682755987,833978.556808034 0.0,833978.556808034 0.000110682755987,832864.275023695 0.0,832864.275023695 0.0)),((835092.849076364 0.0,833978.557030887 -0.000110682755987,833978.557030887 0.0,833978.557030887 0.000110682755987,835092.849076364 0.0,835092.849076364 0.0)))";
    std::string exp = "MULTILINESTRING ((832864.275023695 0.0,833978.556808034 0.0),(833978.557030887 0.0,835092.849076364 0.0))";
    testOverlay(a, b, exp, OverlayNG::INTERSECTION);
}

/**
 * Tests correct ordering of {@link SegmentNode#compareTo(Object)}.
 *
 * See https://trac.osgeo.org/geos/ticket/1051
 *
 * JTS equivalent: testSegmentNodeOrderingIntersection()
 */
template<>
template<>
void object::test<2> ()
{
    std::string a = "POLYGON ((654948.3853299792 1794977.105854025, 655016.3812220972 1794939.918901604, 655016.2022581929 1794940.1099794197, 655014.9264068712 1794941.4254068714, 655014.7408834674 1794941.6101225375, 654948.3853299792 1794977.105854025))";
	std::string b = "POLYGON ((655103.6628454948 1794805.456674405, 655016.20226 1794940.10998, 655014.8317182435 1794941.5196832407, 655014.8295602322 1794941.5218318563, 655014.740883467 1794941.610122538, 655016.6029214273 1794938.7590508445, 655103.6628454948 1794805.456674405))";
    checkOverlaySuccess(a, b, OverlayNG::INTERSECTION);
}

} // namespace tut
<|endoftext|>