Hyprland/tests/config/lua/LuaBindingsInternal.cpp
Vaxry 5ba33f8461
config/lua: init lua config manager, use lua if available (#13817)
Adds a new lua config manager, which will automatically be used if there is a lua config present.
2026-04-26 15:16:36 +01:00

256 lines
7.8 KiB
C++

#include <config/lua/bindings/LuaBindingsInternal.hpp>
#include <config/lua/ConfigManager.hpp>
#include <Compositor.hpp>
#include <config/lua/types/LuaConfigInt.hpp>
#include <gtest/gtest.h>
extern "C" {
#include <lualib.h>
#include <lauxlib.h>
}
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
namespace Config::Lua {
class CConfigManagerPluginLuaTestAccessor {
public:
static void initializeLuaState(CConfigManager& mgr, lua_State* L) {
mgr.m_lua = L;
lua_pushlightuserdata(L, &mgr);
lua_setfield(L, LUA_REGISTRYINDEX, "hl_lua_manager");
}
};
}
namespace {
class CLuaState {
public:
CLuaState() : m_lua(luaL_newstate()) {
luaL_openlibs(m_lua);
}
~CLuaState() {
if (m_lua)
lua_close(m_lua);
}
lua_State* get() const {
return m_lua;
}
private:
lua_State* m_lua = nullptr;
};
int testPluginFn(lua_State* L) {
lua_pushstring(L, "pong");
return 1;
}
}
TEST(ConfigLuaBindingsInternal, parseDirectionAliases) {
EXPECT_EQ(Internal::parseDirectionStr("left"), Math::DIRECTION_LEFT);
EXPECT_EQ(Internal::parseDirectionStr("l"), Math::DIRECTION_LEFT);
EXPECT_EQ(Internal::parseDirectionStr("right"), Math::DIRECTION_RIGHT);
EXPECT_EQ(Internal::parseDirectionStr("r"), Math::DIRECTION_RIGHT);
EXPECT_EQ(Internal::parseDirectionStr("up"), Math::DIRECTION_UP);
EXPECT_EQ(Internal::parseDirectionStr("t"), Math::DIRECTION_UP);
EXPECT_EQ(Internal::parseDirectionStr("down"), Math::DIRECTION_DOWN);
EXPECT_EQ(Internal::parseDirectionStr("b"), Math::DIRECTION_DOWN);
EXPECT_EQ(Internal::parseDirectionStr("???"), Math::DIRECTION_DEFAULT);
}
TEST(ConfigLuaBindingsInternal, parseToggleAliases) {
EXPECT_EQ(Internal::parseToggleStr(""), Config::Actions::TOGGLE_ACTION_TOGGLE);
EXPECT_EQ(Internal::parseToggleStr("toggle"), Config::Actions::TOGGLE_ACTION_TOGGLE);
EXPECT_EQ(Internal::parseToggleStr("enable"), Config::Actions::TOGGLE_ACTION_ENABLE);
EXPECT_EQ(Internal::parseToggleStr("on"), Config::Actions::TOGGLE_ACTION_ENABLE);
EXPECT_EQ(Internal::parseToggleStr("disable"), Config::Actions::TOGGLE_ACTION_DISABLE);
EXPECT_EQ(Internal::parseToggleStr("off"), Config::Actions::TOGGLE_ACTION_DISABLE);
}
TEST(ConfigLuaBindingsInternal, argStrConvertsStringsAndNumbers) {
CLuaState S;
const auto L = S.get();
lua_pushstring(L, "abc");
EXPECT_EQ(Internal::argStr(L, -1), "abc");
lua_pop(L, 1);
lua_pushnumber(L, 42);
EXPECT_EQ(Internal::argStr(L, -1), "42");
lua_pop(L, 1);
}
TEST(ConfigLuaBindingsInternal, tableOptHelpersReadOptionalFields) {
CLuaState S;
const auto L = S.get();
lua_createtable(L, 0, 5);
lua_pushstring(L, "value");
lua_setfield(L, -2, "s");
lua_pushnumber(L, 5.5);
lua_setfield(L, -2, "n");
lua_pushboolean(L, true);
lua_setfield(L, -2, "b");
lua_pushstring(L, "not-number");
lua_setfield(L, -2, "n2");
lua_pushnil(L);
lua_setfield(L, -2, "nilv");
EXPECT_EQ(Internal::tableOptStr(L, -1, "s").value_or(""), "value");
EXPECT_DOUBLE_EQ(Internal::tableOptNum(L, -1, "n").value_or(0), 5.5);
EXPECT_EQ(Internal::tableOptBool(L, -1, "b").value_or(false), true);
EXPECT_FALSE(Internal::tableOptNum(L, -1, "n2").has_value());
EXPECT_FALSE(Internal::tableOptStr(L, -1, "missing").has_value());
EXPECT_FALSE(Internal::tableOptBool(L, -1, "nilv").has_value());
lua_pop(L, 1);
}
TEST(ConfigLuaBindingsInternal, selectorHelpersAcceptStringAndNumberSelectors) {
CLuaState S;
const auto L = S.get();
lua_createtable(L, 0, 4);
lua_pushstring(L, "DP-1");
lua_setfield(L, -2, "monitor");
lua_pushnumber(L, 7);
lua_setfield(L, -2, "workspace");
lua_pushnumber(L, 1337);
lua_setfield(L, -2, "window");
EXPECT_EQ(Internal::tableOptMonitorSelector(L, -1, "monitor", "test.fn").value_or(""), "DP-1");
EXPECT_EQ(Internal::tableOptWorkspaceSelector(L, -1, "workspace", "test.fn").value_or(""), "7");
EXPECT_EQ(Internal::tableOptWindowSelector(L, -1, "window", "test.fn").value_or(""), "1337");
EXPECT_FALSE(Internal::tableOptMonitorSelector(L, -1, "missing", "test.fn").has_value());
EXPECT_FALSE(Internal::tableOptWorkspaceSelector(L, -1, "missing", "test.fn").has_value());
EXPECT_FALSE(Internal::tableOptWindowSelector(L, -1, "missing", "test.fn").has_value());
EXPECT_EQ(Internal::requireTableFieldMonitorSelector(L, -1, "monitor", "test.fn"), "DP-1");
EXPECT_EQ(Internal::requireTableFieldWorkspaceSelector(L, -1, "workspace", "test.fn"), "7");
EXPECT_EQ(Internal::requireTableFieldWindowSelector(L, -1, "window", "test.fn"), "1337");
lua_pop(L, 1);
}
TEST(ConfigLuaBindingsInternal, pushWindowUpvalAcceptsNumberAndStringSelectors) {
CLuaState S;
const auto L = S.get();
lua_createtable(L, 0, 1);
lua_pushnumber(L, 42);
lua_setfield(L, -2, "window");
Internal::pushWindowUpval(L, -1);
ASSERT_TRUE(lua_isstring(L, -1));
EXPECT_STREQ(lua_tostring(L, -1), "42");
lua_pop(L, 1);
lua_pushstring(L, "0xabc");
lua_setfield(L, -2, "window");
Internal::pushWindowUpval(L, -1);
ASSERT_TRUE(lua_isstring(L, -1));
EXPECT_STREQ(lua_tostring(L, -1), "0xabc");
lua_pop(L, 1);
lua_pushnil(L);
lua_setfield(L, -2, "window");
Internal::pushWindowUpval(L, -1);
EXPECT_TRUE(lua_isnil(L, -1));
lua_pop(L, 1);
lua_pop(L, 1);
}
TEST(ConfigLuaBindingsInternal, parseTableFieldMissingFieldAndPrefixedErrors) {
CLuaState S;
const auto L = S.get();
CLuaConfigInt parser(0);
lua_newtable(L);
auto err = Internal::parseTableField(L, -1, "required", parser);
EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE);
EXPECT_NE(err.message.find("missing required field"), std::string::npos);
lua_pop(L, 1);
lua_createtable(L, 0, 1);
lua_pushstring(L, "bad");
lua_setfield(L, -2, "count");
err = Internal::parseTableField(L, -1, "count", parser);
EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_TYPE);
EXPECT_NE(err.message.find("field \"count\":"), std::string::npos);
lua_pop(L, 1);
}
TEST(ConfigLuaBindingsInternal, pluginBindingIsTableWithLoadFunction) {
CLuaState S;
const auto L = S.get();
lua_newtable(L);
Internal::registerConfigRuleBindings(L, nullptr);
lua_getfield(L, -1, "plugin");
ASSERT_TRUE(lua_istable(L, -1));
lua_getfield(L, -1, "load");
EXPECT_TRUE(lua_isfunction(L, -1));
lua_pop(L, 1);
lua_pop(L, 2);
}
TEST(ConfigLuaBindingsInternal, pluginLuaFnIsUnloadedWithoutDanglingCall) {
CLuaState S;
const auto L = S.get();
auto PREVCOMPOSITOR = std::move(g_pCompositor);
g_pCompositor = makeUnique<CCompositor>(true);
CConfigManager mgr;
CConfigManagerPluginLuaTestAccessor::initializeLuaState(mgr, L);
lua_newtable(L);
Internal::registerConfigRuleBindings(L, &mgr);
lua_setglobal(L, "hl");
const auto HANDLE = reinterpret_cast<void*>(0x1BADB002);
const auto regResult = mgr.registerPluginLuaFunction(HANDLE, "demo", "ping", testPluginFn);
ASSERT_TRUE(regResult.has_value()) << regResult.error();
ASSERT_EQ(luaL_dostring(L, R"(
local f = hl.plugin.demo.ping
assert(type(f) == "function")
captured = f
local v = f()
assert(v == "pong")
)"),
LUA_OK);
mgr.onPluginUnload(HANDLE);
ASSERT_EQ(luaL_dostring(L, R"(
assert(hl.plugin.demo == nil)
)"),
LUA_OK);
ASSERT_EQ(luaL_dostring(L, R"(
local ok, err = pcall(captured)
assert(ok == false)
assert(type(err) == "string")
assert(string.find(err, "no longer available", 1, true) ~= nil)
)"),
LUA_OK);
g_pCompositor = std::move(PREVCOMPOSITOR);
}