// Tests para lua_engine_cpp_core. // Catch2 amalgamated. No requiere ImGui context. #include #include "core/lua_engine.h" // Helper: compila + eval con RowCtx vacia (sin columnas). static std::string quick_eval(const std::string& formula, std::string* err = nullptr) { lua_engine::Engine* e = lua_engine::get(); std::string compile_err; int id = lua_engine::compile(e, formula, &compile_err); if (id < 0) { if (err) *err = compile_err; return ""; } lua_engine::RowCtx ctx; std::string eval_err; std::string result = lua_engine::eval(e, id, ctx, &eval_err); lua_engine::release(e, id); if (err) *err = eval_err; return result; } TEST_CASE("lua_engine: eval expr simple", "[lua_engine]") { std::string err; std::string result = quick_eval("2 + 3", &err); REQUIRE(err.empty()); REQUIRE(result == "5"); } TEST_CASE("lua_engine: eval con vars via RowCtx", "[lua_engine]") { // Prepara tabla: 1 fila, 2 columnas (price=10, qty=3). const char* price_str = "10"; const char* qty_str = "3"; const char* cells[] = { price_str, qty_str }; std::vector headers = { "price", "qty" }; std::unordered_map name_to_col = { {"price", 0}, {"qty", 1} }; data_table::ColumnType types[] = { data_table::ColumnType::Float, data_table::ColumnType::Float }; lua_engine::RowCtx ctx; ctx.cells = cells; ctx.orig_cols = 2; ctx.row = 0; ctx.header_names = &headers; ctx.name_to_col = &name_to_col; ctx.types_orig = types; ctx.n_types_orig = 2; lua_engine::Engine* e = lua_engine::get(); std::string err; int id = lua_engine::compile(e, "[price] * [qty]", &err); REQUIRE(err.empty()); REQUIRE(id >= 0); std::string result = lua_engine::eval(e, id, ctx, &err); lua_engine::release(e, id); REQUIRE(err.empty()); // 10 * 3 = 30 (integer) REQUIRE(result == "30"); } TEST_CASE("lua_engine: error en expr invalida", "[lua_engine]") { std::string err; int id = lua_engine::compile(lua_engine::get(), "this is not lua !!!", &err); REQUIRE(id < 0); REQUIRE(!err.empty()); } TEST_CASE("lua_engine: sandbox bloquea io.open", "[lua_engine]") { std::string err; // io fue eliminado del sandbox; acceder a io.open debe producir error en runtime. std::string result = quick_eval("io.open('/etc/passwd', 'r')", &err); // Debe fallar (io es nil -> intento de indexar nil -> runtime error). REQUIRE(!err.empty()); REQUIRE(result.empty()); } TEST_CASE("lua_engine: preprocess convierte [col] a row[\"col\"]", "[lua_engine]") { std::string out = lua_engine::preprocess("[price] * [qty]"); // Debe contener row["price"] y row["qty"], y auto-return al ser expresion. REQUIRE(out.find("row[\"price\"]") != std::string::npos); REQUIRE(out.find("row[\"qty\"]") != std::string::npos); REQUIRE(out.rfind("return", 0) == 0); } TEST_CASE("lua_engine: fn.* builtins disponibles", "[lua_engine]") { std::string err; std::string result = quick_eval("fn.upper('hello')", &err); REQUIRE(err.empty()); REQUIRE(result == "HELLO"); } TEST_CASE("lua_engine: shutdown y reinicio", "[lua_engine]") { lua_engine::shutdown(); // Tras shutdown, get() debe crear un nuevo estado limpio. lua_engine::Engine* e = lua_engine::get(); REQUIRE(e != nullptr); std::string err; std::string result = quick_eval("1 + 1", &err); REQUIRE(err.empty()); REQUIRE(result == "2"); lua_engine::shutdown(); }