diff options
| author | Vaxry <43317083+vaxerski@users.noreply.github.com> | 2026-04-26 15:16:36 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-26 15:16:36 +0100 |
| commit | 5ba33f846129f3e6c0505b19f94634b24505e828 (patch) | |
| tree | aaacabe678006040c342935481a86607c66607fd /tests | |
| parent | 329b2b356dd92ac07f4e2aa69cbce82ee9ba9d19 (diff) | |
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.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/config/MonitorParser.cpp | 2 | ||||
| -rw-r--r-- | tests/config/lua/ConfigValueTypes.cpp | 347 | ||||
| -rw-r--r-- | tests/config/lua/LuaBindingsInternal.cpp | 256 | ||||
| -rw-r--r-- | tests/config/lua/LuaConfigUtils.cpp | 105 | ||||
| -rw-r--r-- | tests/config/lua/LuaEventHandler.cpp | 115 | ||||
| -rw-r--r-- | tests/config/lua/LuaObjectsBasic.cpp | 266 | ||||
| -rw-r--r-- | tests/helpers/MathTransform.cpp | 7 |
7 files changed, 1094 insertions, 4 deletions
diff --git a/tests/config/MonitorParser.cpp b/tests/config/MonitorParser.cpp index 29bd8db1..1d8fe664 100644 --- a/tests/config/MonitorParser.cpp +++ b/tests/config/MonitorParser.cpp @@ -128,7 +128,7 @@ TEST(Config, monitorParserTransformValid) { for (int i = 0; i <= 7; i++) { CMonitorRuleParser parser("DP-1"); EXPECT_TRUE(parser.parseTransform(std::to_string(i))); - EXPECT_EQ(parser.rule().m_transform, static_cast<wl_output_transform>(i)); + EXPECT_EQ(parser.rule().m_transform, sc<wl_output_transform>(i)); } } diff --git a/tests/config/lua/ConfigValueTypes.cpp b/tests/config/lua/ConfigValueTypes.cpp new file mode 100644 index 00000000..90e97520 --- /dev/null +++ b/tests/config/lua/ConfigValueTypes.cpp @@ -0,0 +1,347 @@ +#include <config/lua/types/LuaConfigBool.hpp> +#include <config/lua/types/LuaConfigColor.hpp> +#include <config/lua/types/LuaConfigCssGap.hpp> +#include <config/lua/types/LuaConfigFloat.hpp> +#include <config/lua/types/LuaConfigFontWeight.hpp> +#include <config/lua/types/LuaConfigGradient.hpp> +#include <config/lua/types/LuaConfigInt.hpp> +#include <config/lua/types/LuaConfigString.hpp> +#include <config/lua/types/LuaConfigVec2.hpp> + +#include <macros.hpp> + +#include <gtest/gtest.h> + +#include <numbers> + +extern "C" { +#include <lualib.h> +#include <lauxlib.h> +} + +using namespace Config; +using namespace Config::Lua; + +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; + }; + + void pushVec2Table(lua_State* L, double x, double y) { + lua_createtable(L, 2, 2); + lua_pushnumber(L, x); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, y); + lua_rawseti(L, -2, 2); + } +} + +TEST(ConfigLuaValueTypes, boolParseAndReset) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigBool value(false); + EXPECT_FALSE(value.parsed()); + EXPECT_FALSE(value.setByUser()); + + lua_pushboolean(L, true); + const auto err = value.parse(L); + lua_pop(L, 1); + + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_TRUE(value.parsed()); + EXPECT_TRUE(value.setByUser()); + + value.reset(); + EXPECT_FALSE(value.parsed()); + + value.resetSetByUser(); + EXPECT_FALSE(value.setByUser()); +} + +TEST(ConfigLuaValueTypes, boolBadType) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigBool value(false); + + lua_pushinteger(L, 1); + const auto err = value.parse(L); + lua_pop(L, 1); + + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_TYPE); + EXPECT_NE(err.message.find("requires a bool"), std::string::npos); +} + +TEST(ConfigLuaValueTypes, intBooleanCastAndRangeAndMap) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigInt ranged(0, 0, 10); + + lua_pushboolean(L, true); + auto err = ranged.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(ranged.parsed(), 1); + + lua_pushinteger(L, 11); + err = ranged.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OUT_OF_RANGE); + + CLuaConfigInt mapped(0, std::nullopt, std::nullopt, std::unordered_map<std::string, INTEGER>{{"auto", -1}, {"on", 1}}); + + lua_pushstring(L, "auto"); + err = mapped.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(mapped.parsed(), -1); + + lua_pushstring(L, "missing"); + err = mapped.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); + EXPECT_NE(err.message.find("unknown string value"), std::string::npos); +} + +TEST(ConfigLuaValueTypes, floatParseAndRange) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigFloat value(0.F, -1.F, 1.F); + + lua_pushnumber(L, 0.5); + auto err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_FLOAT_EQ(value.parsed(), 0.5F); + + lua_pushinteger(L, 2); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OUT_OF_RANGE); +} + +TEST(ConfigLuaValueTypes, stringValidatorAndEmptyPush) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigString validated("default", std::optional<std::function<std::expected<void, std::string>(const STRING&)>>{[](const STRING& s) -> std::expected<void, std::string> { + if (s.starts_with("ok:")) + return {}; + return std::unexpected("must start with ok:"); + }}); + + lua_pushstring(L, "ok:value"); + auto err = validated.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(validated.parsed(), "ok:value"); + + lua_pushstring(L, "bad"); + err = validated.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); + EXPECT_NE(err.message.find("must start with ok:"), std::string::npos); + + CLuaConfigString emptyDefault(STRVAL_EMPTY); + emptyDefault.push(L); + ASSERT_TRUE(lua_isstring(L, -1)); + EXPECT_STREQ(lua_tostring(L, -1), ""); + lua_pop(L, 1); +} + +TEST(ConfigLuaValueTypes, colorParseAndPush) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigColor value(0); + + lua_pushstring(L, "0xFF00FF00"); + auto err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(value.parsed(), 0xFF00FF00); + + value.push(L); + ASSERT_TRUE(lua_isstring(L, -1)); + EXPECT_STREQ(lua_tostring(L, -1), "0xFF00FF00"); + lua_pop(L, 1); + + lua_pushstring(L, "@@@notacolor@@@"); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); +} + +TEST(ConfigLuaValueTypes, vec2ParseValidateAndPush) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigVec2 value({0, 0}, std::optional<std::function<std::expected<void, std::string>(const VEC2&)>>{[](const VEC2& v) -> std::expected<void, std::string> { + if (v.x < 0 || v.y < 0) + return std::unexpected("must be non-negative"); + return {}; + }}); + + pushVec2Table(L, 10, 20); + auto err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_FLOAT_EQ(value.parsed().x, 10.F); + EXPECT_FLOAT_EQ(value.parsed().y, 20.F); + + value.push(L); + ASSERT_TRUE(lua_istable(L, -1)); + lua_rawgeti(L, -1, 1); + EXPECT_DOUBLE_EQ(lua_tonumber(L, -1), 10); + lua_pop(L, 1); + lua_getfield(L, -1, "y"); + EXPECT_DOUBLE_EQ(lua_tonumber(L, -1), 20); + lua_pop(L, 2); + + pushVec2Table(L, -1, 0); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); + EXPECT_NE(err.message.find("must be non-negative"), std::string::npos); + + lua_createtable(L, 3, 0); + lua_pushnumber(L, 1); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, 2); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, 3); + lua_rawseti(L, -2, 3); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); +} + +TEST(ConfigLuaValueTypes, cssGapParseFormsAndRange) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigCssGap value(1, 0, 10); + + lua_pushinteger(L, 7); + auto err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(value.parsed().m_top, 7); + EXPECT_EQ(value.parsed().m_right, 7); + EXPECT_EQ(value.parsed().m_bottom, 7); + EXPECT_EQ(value.parsed().m_left, 7); + + lua_createtable(L, 0, 2); + lua_pushinteger(L, 3); + lua_setfield(L, -2, "top"); + lua_pushinteger(L, 4); + lua_setfield(L, -2, "left"); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(value.parsed().m_top, 3); + EXPECT_EQ(value.parsed().m_right, 0); + EXPECT_EQ(value.parsed().m_bottom, 0); + EXPECT_EQ(value.parsed().m_left, 4); + + lua_createtable(L, 0, 1); + lua_pushinteger(L, 100); + lua_setfield(L, -2, "top"); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OUT_OF_RANGE); + + lua_createtable(L, 0, 1); + lua_pushstring(L, "bad"); + lua_setfield(L, -2, "top"); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_TYPE); +} + +TEST(ConfigLuaValueTypes, fontWeightParse) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigFontWeight value(400); + + lua_pushstring(L, "bold"); + auto err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + EXPECT_EQ(value.parsed().m_value, 700); + + lua_pushinteger(L, -1); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OUT_OF_RANGE); + + lua_pushstring(L, "definitely-not-a-weight"); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); +} + +TEST(ConfigLuaValueTypes, gradientParseAndPush) { + CLuaState S; + const auto L = S.get(); + + CLuaConfigGradient value(CHyprColor(0xFF000000)); + + lua_pushstring(L, "0xFF112233"); + auto err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + ASSERT_EQ(value.parsed().m_colors.size(), 1); + + lua_createtable(L, 0, 2); + lua_createtable(L, 2, 0); + lua_pushstring(L, "0xFF0000FF"); + lua_rawseti(L, -2, 1); + lua_pushstring(L, "0xFFFF0000"); + lua_rawseti(L, -2, 2); + lua_setfield(L, -2, "colors"); + lua_pushnumber(L, 90); + lua_setfield(L, -2, "angle"); + + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_OK); + ASSERT_EQ(value.parsed().m_colors.size(), 2); + EXPECT_NEAR(value.parsed().m_angle, std::numbers::pi_v<float> / 2.F, 0.0001F); + + value.push(L); + ASSERT_TRUE(lua_istable(L, -1)); + lua_getfield(L, -1, "colors"); + ASSERT_TRUE(lua_istable(L, -1)); + lua_rawgeti(L, -1, 1); + EXPECT_STREQ(lua_tostring(L, -1), "0xFF0000FF"); + lua_pop(L, 1); + lua_pop(L, 1); + lua_getfield(L, -1, "angle"); + EXPECT_NEAR(lua_tonumber(L, -1), 90.0, 0.001); + lua_pop(L, 2); + + lua_createtable(L, 0, 0); + err = value.parse(L); + lua_pop(L, 1); + EXPECT_EQ(err.errorCode, PARSE_ERROR_BAD_VALUE); +} diff --git a/tests/config/lua/LuaBindingsInternal.cpp b/tests/config/lua/LuaBindingsInternal.cpp new file mode 100644 index 00000000..d94ea2dc --- /dev/null +++ b/tests/config/lua/LuaBindingsInternal.cpp @@ -0,0 +1,256 @@ +#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); +} diff --git a/tests/config/lua/LuaConfigUtils.cpp b/tests/config/lua/LuaConfigUtils.cpp new file mode 100644 index 00000000..a165755e --- /dev/null +++ b/tests/config/lua/LuaConfigUtils.cpp @@ -0,0 +1,105 @@ +#include <config/lua/types/LuaConfigUtils.hpp> + +#include <config/lua/types/LuaConfigBool.hpp> +#include <config/lua/types/LuaConfigColor.hpp> +#include <config/lua/types/LuaConfigCssGap.hpp> +#include <config/lua/types/LuaConfigFloat.hpp> +#include <config/lua/types/LuaConfigFontWeight.hpp> +#include <config/lua/types/LuaConfigGradient.hpp> +#include <config/lua/types/LuaConfigInt.hpp> +#include <config/lua/types/LuaConfigString.hpp> +#include <config/lua/types/LuaConfigVec2.hpp> + +#include <config/values/types/BoolValue.hpp> +#include <config/values/types/ColorValue.hpp> +#include <config/values/types/CssGapValue.hpp> +#include <config/values/types/FloatValue.hpp> +#include <config/values/types/FontWeightValue.hpp> +#include <config/values/types/GradientValue.hpp> +#include <config/values/types/IntValue.hpp> +#include <config/values/types/StringValue.hpp> +#include <config/values/types/Vec2Value.hpp> + +#include <gtest/gtest.h> + +using namespace Config; +using namespace Config::Lua; + +namespace { + class CUnsupportedValue : public Values::IValue { + public: + CUnsupportedValue() : Values::IValue(0) { + m_name = "unsupported"; + m_description = "unsupported"; + } + + const std::type_info* underlying() const override { + return &typeid(void); + } + + void commence() override { + ; + } + }; +} + +TEST(ConfigLuaUtils, fromGenericValueMapsSupportedTypes) { + { + auto out = fromGenericValue(makeShared<Values::CIntValue>("a", "", 1)); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigInt*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CFloatValue>("a", "", 1.F)); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigFloat*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CBoolValue>("a", "", true)); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigBool*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CStringValue>("a", "", "x")); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigString*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CColorValue>("a", "", 0xFF000000)); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigColor*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CVec2Value>("a", "", VEC2{1, 2})); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigVec2*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CCssGapValue>("a", "", 5)); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigCssGap*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CFontWeightValue>("a", "", 500)); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigFontWeight*>(out.get()), nullptr); + } + { + auto out = fromGenericValue(makeShared<Values::CGradientValue>("a", "", CHyprColor(0xFF112233))); + ASSERT_NE(out.get(), nullptr); + EXPECT_NE(dynamic_cast<CLuaConfigGradient*>(out.get()), nullptr); + } +} + +TEST(ConfigLuaUtils, fromGenericValueReturnsNullForUnsupportedType) { + auto out = fromGenericValue(makeShared<CUnsupportedValue>()); + EXPECT_EQ(out.get(), nullptr); +} + +TEST(ConfigLuaUtils, fromGenericValueCopiesRefreshBits) { + const auto REFRESH = Config::Supplementary::REFRESH_LAYOUTS | Config::Supplementary::REFRESH_WINDOW_STATES; + + auto out = fromGenericValue(makeShared<Values::CIntValue>("a", "", 1, Values::SIntValueOptions{.refresh = REFRESH})); + ASSERT_NE(out.get(), nullptr); + EXPECT_EQ(out->refreshBits(), REFRESH); +} diff --git a/tests/config/lua/LuaEventHandler.cpp b/tests/config/lua/LuaEventHandler.cpp new file mode 100644 index 00000000..570014d3 --- /dev/null +++ b/tests/config/lua/LuaEventHandler.cpp @@ -0,0 +1,115 @@ +#include <config/lua/LuaEventHandler.hpp> + +#include <event/EventBus.hpp> + +#include <gtest/gtest.h> + +extern "C" { +#include <lualib.h> +#include <lauxlib.h> +} + +using namespace Config::Lua; + +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 refGlobalFunction(lua_State* L, const char* name) { + lua_getglobal(L, name); + EXPECT_TRUE(lua_isfunction(L, -1)); + return luaL_ref(L, LUA_REGISTRYINDEX); + } + + int getGlobalInt(lua_State* L, const char* name) { + lua_getglobal(L, name); + const int val = sc<int>(lua_tointeger(L, -1)); + lua_pop(L, 1); + return val; + } +} + +TEST(ConfigLuaEventHandler, knownEventsContainsExpectedEntries) { + const auto& known = CLuaEventHandler::knownEvents(); + EXPECT_TRUE(known.contains("config.reloaded")); + EXPECT_TRUE(known.contains("keybinds.submap")); + EXPECT_TRUE(known.contains("window.open")); +} + +TEST(ConfigLuaEventHandler, registerDispatchAndUnregister) { + CLuaState S; + const auto L = S.get(); + CLuaEventHandler handler(L); + + ASSERT_EQ(luaL_dostring(L, R"( + count = 0 + lastSubmap = "" + function onSubmap(name) + count = count + 1 + lastSubmap = name + end + )"), + LUA_OK); + + const int ref = refGlobalFunction(L, "onSubmap"); + const auto handle = handler.registerEvent("keybinds.submap", ref); + ASSERT_TRUE(handle.has_value()); + + Event::bus()->m_events.keybinds.submap.emit("main"); + EXPECT_EQ(getGlobalInt(L, "count"), 1); + + lua_getglobal(L, "lastSubmap"); + ASSERT_TRUE(lua_isstring(L, -1)); + EXPECT_STREQ(lua_tostring(L, -1), "main"); + lua_pop(L, 1); + + EXPECT_TRUE(handler.unregisterEvent(*handle)); + + Event::bus()->m_events.keybinds.submap.emit("other"); + EXPECT_EQ(getGlobalInt(L, "count"), 1); +} + +TEST(ConfigLuaEventHandler, registerUnknownEventReturnsNullopt) { + CLuaState S; + const auto L = S.get(); + CLuaEventHandler handler(L); + + ASSERT_EQ(luaL_dostring(L, "function cb() end"), LUA_OK); + + const int ref = refGlobalFunction(L, "cb"); + const auto handle = handler.registerEvent("totally.unknown", ref); + + EXPECT_FALSE(handle.has_value()); + luaL_unref(L, LUA_REGISTRYINDEX, ref); +} + +TEST(ConfigLuaEventHandler, callbackLuaErrorDoesNotCrashDispatch) { + CLuaState S; + const auto L = S.get(); + CLuaEventHandler handler(L); + + ASSERT_EQ(luaL_dostring(L, "function broken() error('boom') end"), LUA_OK); + + const int ref = refGlobalFunction(L, "broken"); + const auto h = handler.registerEvent("config.reloaded", ref); + ASSERT_TRUE(h.has_value()); + + Event::bus()->m_events.config.reloaded.emit(); + SUCCEED(); +} diff --git a/tests/config/lua/LuaObjectsBasic.cpp b/tests/config/lua/LuaObjectsBasic.cpp new file mode 100644 index 00000000..10758b7d --- /dev/null +++ b/tests/config/lua/LuaObjectsBasic.cpp @@ -0,0 +1,266 @@ +#include <config/lua/LuaEventHandler.hpp> + +#include <config/lua/objects/LuaEventSubscription.hpp> +#include <config/lua/objects/LuaKeybind.hpp> + +#include <managers/KeybindManager.hpp> +#include <event/EventBus.hpp> + +#include <gtest/gtest.h> + +extern "C" { +#include <lualib.h> +#include <lauxlib.h> +} + +using namespace Config::Lua; + +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 getGlobalInt(lua_State* L, const char* name) { + lua_getglobal(L, name); + const int v = sc<int>(lua_tointeger(L, -1)); + lua_pop(L, 1); + return v; + } + + bool getGlobalBool(lua_State* L, const char* name) { + lua_getglobal(L, name); + const bool v = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + return v; + } +} + +TEST(ConfigLuaObjects, keybindCanToggleEnabledFromLua) { + CLuaState S; + const auto L = S.get(); + + Objects::CLuaKeybind{}.setup(L); + + auto keybind = makeShared<SKeybind>(); + keybind->enabled = true; + + Objects::CLuaKeybind::push(L, keybind); + lua_setglobal(L, "kb"); + + ASSERT_EQ(luaL_dostring(L, R"( + assert(kb:is_enabled() == true) + kb:set_enabled(false) + assert(kb:is_enabled() == false) + )"), + LUA_OK); + + EXPECT_FALSE(keybind->enabled); +} + +TEST(ConfigLuaObjects, keybindExposesMetadataAndRemoveMethods) { + CLuaState S; + const auto L = S.get(); + + Objects::CLuaKeybind{}.setup(L); + + auto keybind = makeShared<SKeybind>(); + keybind->enabled = true; + keybind->description = "Close active window"; + keybind->hasDescription = true; + keybind->displayKey = "SUPER + Q"; + keybind->submap.name = "default"; + keybind->handler = "exec"; + keybind->arg = "kitty"; + keybind->modmask = HL_MODIFIER_META; + keybind->key = "Q"; + keybind->keycode = 24; + keybind->repeat = true; + keybind->locked = true; + keybind->release = false; + keybind->nonConsuming = true; + keybind->transparent = false; + keybind->ignoreMods = false; + keybind->longPress = false; + keybind->dontInhibit = true; + keybind->click = false; + keybind->drag = false; + keybind->submapUniversal = false; + keybind->deviceInclusive = true; + keybind->devices = {"kbd-a", "kbd-b"}; + + Objects::CLuaKeybind::push(L, keybind); + lua_setglobal(L, "kb"); + + ASSERT_EQ(luaL_dostring(L, R"( + assert(kb.enabled == true) + assert(kb.description == "Close active window") + assert(kb.display_key == "SUPER + Q") + assert(kb.submap == "default") + assert(kb.handler == "exec") + assert(kb.arg == "kitty") + assert(kb.modmask ~= nil) + assert(kb.key == "Q") + assert(kb.keycode == 24) + assert(kb.repeating == true) + assert(kb.locked == true) + assert(kb.non_consuming == true) + assert(kb.dont_inhibit == true) + assert(type(kb.devices) == "table") + + kb:remove() + kb:unbind() + )"), + LUA_OK); +} + +TEST(ConfigLuaObjects, objectsAreReadOnlyFromLua) { + CLuaState S; + const auto L = S.get(); + + Objects::CLuaKeybind{}.setup(L); + + auto keybind = makeShared<SKeybind>(); + Objects::CLuaKeybind::push(L, keybind); + lua_setglobal(L, "kb"); + + EXPECT_NE(luaL_dostring(L, "kb.foo = 1"), LUA_OK); + ASSERT_TRUE(lua_isstring(L, -1)); + EXPECT_NE(std::string(lua_tostring(L, -1)).find("read-only"), std::string::npos); + lua_pop(L, 1); +} + +TEST(ConfigLuaObjects, keybindSupportsEqAndToString) { + CLuaState S; + const auto L = S.get(); + + Objects::CLuaKeybind{}.setup(L); + + auto keybindA = makeShared<SKeybind>(); + auto keybindB = makeShared<SKeybind>(); + + Objects::CLuaKeybind::push(L, keybindA); + lua_setglobal(L, "kb1"); + + Objects::CLuaKeybind::push(L, keybindA); + lua_setglobal(L, "kb2"); + + Objects::CLuaKeybind::push(L, keybindB); + lua_setglobal(L, "kb3"); + + ASSERT_EQ(luaL_dostring(L, R"( + eq12 = (kb1 == kb2) + neq13 = (kb1 ~= kb3) + + local s = tostring(kb1) + hasPrefix = type(s) == "string" and string.sub(s, 1, 11) == "HL.Keybind(" + )"), + LUA_OK); + + EXPECT_TRUE(getGlobalBool(L, "eq12")); + EXPECT_TRUE(getGlobalBool(L, "neq13")); + EXPECT_TRUE(getGlobalBool(L, "hasPrefix")); +} + +TEST(ConfigLuaObjects, eventSubscriptionRemoveAndIsActive) { + CLuaState S; + const auto L = S.get(); + + Objects::CLuaEventSubscription{}.setup(L); + CLuaEventHandler handler(L); + + ASSERT_EQ(luaL_dostring(L, R"( + count = 0 + function onReload() + count = count + 1 + end + )"), + LUA_OK); + + lua_getglobal(L, "onReload"); + ASSERT_TRUE(lua_isfunction(L, -1)); + const int ref = luaL_ref(L, LUA_REGISTRYINDEX); + + const auto handle = handler.registerEvent("config.reloaded", ref); + ASSERT_TRUE(handle.has_value()); + + Objects::CLuaEventSubscription::push(L, &handler, *handle); + lua_setglobal(L, "sub"); + + Event::bus()->m_events.config.reloaded.emit(); + EXPECT_EQ(getGlobalInt(L, "count"), 1); + + ASSERT_EQ(luaL_dostring(L, R"( + assert(sub:is_active() == true) + sub:remove() + assert(sub:is_active() == false) + sub:remove() + )"), + LUA_OK); + + Event::bus()->m_events.config.reloaded.emit(); + EXPECT_EQ(getGlobalInt(L, "count"), 1); +} + +TEST(ConfigLuaObjects, eventSubscriptionSupportsEqAndToString) { + CLuaState S; + const auto L = S.get(); + + Objects::CLuaEventSubscription{}.setup(L); + CLuaEventHandler handler(L); + + ASSERT_EQ(luaL_dostring(L, R"( + function cbA() end + function cbB() end + )"), + LUA_OK); + + lua_getglobal(L, "cbA"); + ASSERT_TRUE(lua_isfunction(L, -1)); + const int refA = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_getglobal(L, "cbB"); + ASSERT_TRUE(lua_isfunction(L, -1)); + const int refB = luaL_ref(L, LUA_REGISTRYINDEX); + + const auto handleA = handler.registerEvent("config.reloaded", refA); + const auto handleB = handler.registerEvent("config.reloaded", refB); + ASSERT_TRUE(handleA.has_value()); + ASSERT_TRUE(handleB.has_value()); + + Objects::CLuaEventSubscription::push(L, &handler, *handleA); + lua_setglobal(L, "sub1"); + + Objects::CLuaEventSubscription::push(L, &handler, *handleA); + lua_setglobal(L, "sub2"); + + Objects::CLuaEventSubscription::push(L, &handler, *handleB); + lua_setglobal(L, "sub3"); + + ASSERT_EQ(luaL_dostring(L, R"( + eq12 = (sub1 == sub2) + neq13 = (sub1 ~= sub3) + + local s = tostring(sub1) + hasPrefix = type(s) == "string" and string.sub(s, 1, 21) == "HL.EventSubscription(" + )"), + LUA_OK); + + EXPECT_TRUE(getGlobalBool(L, "eq12")); + EXPECT_TRUE(getGlobalBool(L, "neq13")); + EXPECT_TRUE(getGlobalBool(L, "hasPrefix")); +} diff --git a/tests/helpers/MathTransform.cpp b/tests/helpers/MathTransform.cpp index 686721b9..fe935d16 100644 --- a/tests/helpers/MathTransform.cpp +++ b/tests/helpers/MathTransform.cpp @@ -1,4 +1,5 @@ #include <helpers/math/Math.hpp> +#include <helpers/memory/Memory.hpp> #include <gtest/gtest.h> @@ -19,7 +20,7 @@ TEST(Helpers, mathWlTransformToHyprutils) { TEST(Helpers, mathWlTransformToHyprutilsInvalid) { // Invalid value falls back to NORMAL - EXPECT_EQ(wlTransformToHyprutils(static_cast<wl_output_transform>(99)), eTransform::HYPRUTILS_TRANSFORM_NORMAL); + EXPECT_EQ(wlTransformToHyprutils(sc<wl_output_transform>(99)), eTransform::HYPRUTILS_TRANSFORM_NORMAL); } // invertTransform @@ -47,7 +48,7 @@ TEST(Helpers, mathInvertTransformFlippedRotated) { TEST(Helpers, mathInvertTransformDoubleInvert) { // Double invert returns original for all transforms for (int i = 0; i <= 7; i++) { - auto t = static_cast<wl_output_transform>(i); + auto t = sc<wl_output_transform>(i); EXPECT_EQ(invertTransform(invertTransform(t)), t); } } @@ -57,7 +58,7 @@ TEST(Helpers, mathInvertTransformDoubleInvert) { TEST(Helpers, mathComposeTransformIdentity) { // Composing with NORMAL is identity for (int i = 0; i <= 7; i++) { - auto t = static_cast<eTransform>(i); + auto t = sc<eTransform>(i); EXPECT_EQ(composeTransform(t, eTransform::HYPRUTILS_TRANSFORM_NORMAL), t); EXPECT_EQ(composeTransform(eTransform::HYPRUTILS_TRANSFORM_NORMAL, t), t); } |
