diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index db3b9b3b6..60771ee60 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,3 +37,7 @@ jobs: run: | CONFIG_LOWER=$(echo "${{ matrix.configuration }}" | tr '[:upper:]' '[:lower:]') make -j$(nproc) config=${CONFIG_LOWER}_x64 + + - name: Run Lua API tests + working-directory: ${{env.GITHUB_WORKSPACE}} + run: ./Bin/${{ matrix.configuration }}/OvLuaApiTests/OvLuaApiTests diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1f5b7f1ce..5d917a76a 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,3 +35,7 @@ jobs: - name: Build working-directory: ${{env.GITHUB_WORKSPACE}} run: msbuild /m /p:Configuration=${{ matrix.configuration }} ${{env.SOLUTION_FILE_PATH}} /p:Platform=x64 + + - name: Run Lua API tests + working-directory: ${{env.GITHUB_WORKSPACE}} + run: .\Bin\${{ matrix.configuration }}\OvLuaApiTests\OvLuaApiTests.exe diff --git a/Sources/OvCore/include/OvCore/Scripting/Lua/LuaBindings.h b/Sources/OvCore/include/OvCore/Scripting/Lua/LuaBindings.h new file mode 100644 index 000000000..91a42f7ba --- /dev/null +++ b/Sources/OvCore/include/OvCore/Scripting/Lua/LuaBindings.h @@ -0,0 +1,51 @@ +/** +* @project: Overload +* @author: Overload Tech. +* @licence: MIT +*/ + +#pragma once + +namespace sol +{ + class state; +} + +namespace OvCore::Scripting::Lua +{ + /** + * Registers the standard Lua libraries and every symbol exposed by Overload. + * @param p_luaState + */ + void BindLuaApi(sol::state& p_luaState); + + /** + * Registers actor bindings exposed to Lua scripts. + * @param p_luaState + */ + void BindLuaActor(sol::state& p_luaState); + + /** + * Registers component bindings exposed to Lua scripts. + * @param p_luaState + */ + void BindLuaComponents(sol::state& p_luaState); + + /** + * Registers global engine bindings exposed to Lua scripts. + * @param p_luaState + */ + void BindLuaGlobal(sol::state& p_luaState); + + /** + * Registers math bindings exposed to Lua scripts. + * @param p_luaState + */ + void BindLuaMath(sol::state& p_luaState); + + /** + * Registers profiler bindings exposed to Lua scripts. + * @param p_luaState + */ + void BindLuaProfiler(sol::state& p_luaState); +} diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaActorBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaActorBindings.cpp index bc3ec5e11..0773d83bb 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaActorBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaActorBindings.cpp @@ -26,9 +26,10 @@ #include #include #include +#include #include -void BindLuaActor(sol::state& p_luaState) +void OvCore::Scripting::Lua::BindLuaActor(sol::state& p_luaState) { using namespace OvCore::ECS; using namespace OvCore::ECS::Components; diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp index 816842ff6..364ea8912 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaComponentsBindings.cpp @@ -24,8 +24,9 @@ #include #include #include +#include -void BindLuaComponents(sol::state& p_luaState) +void OvCore::Scripting::Lua::BindLuaComponents(sol::state& p_luaState) { using namespace OvMaths; using namespace OvCore::ECS; diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaGlobalBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaGlobalBindings.cpp index 30e443a72..a9a45f8ab 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaGlobalBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaGlobalBindings.cpp @@ -23,6 +23,7 @@ #include "OvCore/ResourceManagement/MaterialManager.h" #include "OvCore/ResourceManagement/SoundManager.h" #include "OvCore/Scripting/Common/ScriptPropertyValue.h" +#include #include @@ -30,7 +31,7 @@ #include -void BindLuaGlobal(sol::state& p_luaState) +void OvCore::Scripting::Lua::BindLuaGlobal(sol::state& p_luaState) { using namespace OvWindowing; using namespace OvWindowing::Inputs; diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaMathBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaMathBindings.cpp index 12178518c..43a6cae26 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaMathBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaMathBindings.cpp @@ -11,10 +11,11 @@ #include #include #include +#include #include -void BindLuaMath(sol::state& p_luaState) +void OvCore::Scripting::Lua::BindLuaMath(sol::state& p_luaState) { using namespace OvMaths; diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaProfilerBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaProfilerBindings.cpp index 8b748c1ff..2e80c80e4 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaProfilerBindings.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/Bindings/LuaProfilerBindings.cpp @@ -7,7 +7,9 @@ #include #include -void BindLuaProfiler(sol::state& p_luaState) +#include + +void OvCore::Scripting::Lua::BindLuaProfiler(sol::state& p_luaState) { tracy::LuaRegister(p_luaState.lua_state()); } diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/LuaBindings.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/LuaBindings.cpp new file mode 100644 index 000000000..a16a99e71 --- /dev/null +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/LuaBindings.cpp @@ -0,0 +1,20 @@ +/** +* @project: Overload +* @author: Overload Tech. +* @licence: MIT +*/ + +#include + +#include + +void OvCore::Scripting::Lua::BindLuaApi(sol::state& p_luaState) +{ + p_luaState.open_libraries(sol::lib::base, sol::lib::math); + + BindLuaActor(p_luaState); + BindLuaComponents(p_luaState); + BindLuaGlobal(p_luaState); + BindLuaMath(p_luaState); + BindLuaProfiler(p_luaState); +} diff --git a/Sources/OvCore/src/OvCore/Scripting/Lua/LuaScriptEngine.cpp b/Sources/OvCore/src/OvCore/Scripting/Lua/LuaScriptEngine.cpp index 74d8b038c..3bd68518a 100644 --- a/Sources/OvCore/src/OvCore/Scripting/Lua/LuaScriptEngine.cpp +++ b/Sources/OvCore/src/OvCore/Scripting/Lua/LuaScriptEngine.cpp @@ -17,14 +17,9 @@ #include #include #include +#include #include -void BindLuaActor(sol::state& p_state); -void BindLuaComponents(sol::state& p_state); -void BindLuaGlobal(sol::state& p_state); -void BindLuaMath(sol::state& p_state); -void BindLuaProfiler(sol::state& p_state); - namespace { template @@ -345,13 +340,7 @@ void OvCore::Scripting::LuaScriptEngine::CreateContext() OVASSERT(m_context.luaState == nullptr, "A Lua context already exists!"); m_context.luaState = std::make_unique(); - m_context.luaState->open_libraries(sol::lib::base, sol::lib::math); - - BindLuaActor(*m_context.luaState); - BindLuaComponents(*m_context.luaState); - BindLuaGlobal(*m_context.luaState); - BindLuaMath(*m_context.luaState); - BindLuaProfiler(*m_context.luaState); + OvCore::Scripting::Lua::BindLuaApi(*m_context.luaState); m_context.errorCount = 0; diff --git a/Tests/OvLuaApiTests/Lua/Globals/ApiSurface.test.lua b/Tests/OvLuaApiTests/Lua/Globals/ApiSurface.test.lua new file mode 100644 index 000000000..2430d56db --- /dev/null +++ b/Tests/OvLuaApiTests/Lua/Globals/ApiSurface.test.lua @@ -0,0 +1,38 @@ +AssertEquals("table", type(Debug), "Debug") +AssertCallable(Debug.Log, "Debug.Log") +AssertCallable(Debug.LogInfo, "Debug.LogInfo") +AssertCallable(Debug.LogWarning, "Debug.LogWarning") +AssertCallable(Debug.LogError, "Debug.LogError") + +AssertEquals("table", type(Inputs), "Inputs") +AssertCallable(Inputs.GetKeyDown, "Inputs.GetKeyDown") +AssertCallable(Inputs.GetMousePos, "Inputs.GetMousePos") + +AssertEquals("table", type(Scenes), "Scenes") +AssertCallable(Scenes.GetCurrentScene, "Scenes.GetCurrentScene") +AssertCallable(Scenes.Load, "Scenes.Load") + +AssertEquals("table", type(Resources), "Resources") +AssertCallable(Resources.GetModel, "Resources.GetModel") +AssertCallable(Resources.GetTexture, "Resources.GetTexture") +AssertCallable(Resources.GetSound, "Resources.GetSound") + +AssertEquals("table", type(Math), "Math") +AssertCallable(Math.Lerp, "Math.Lerp") +AssertNear(4, Math.Lerp(2, 6, 0.5), 0.0001, "Math.Lerp") + +AssertEquals("table", type(Physics), "Physics") +AssertCallable(Physics.Raycast, "Physics.Raycast") + +AssertEquals("table", type(Vector2), "Vector2") +AssertCallable(Vector2.new, "Vector2.new") +AssertEquals("table", type(Vector3), "Vector3") +AssertCallable(Vector3.new, "Vector3.new") +AssertEquals("table", type(Vector4), "Vector4") +AssertCallable(Vector4.new, "Vector4.new") +AssertEquals("table", type(Matrix3), "Matrix3") +AssertCallable(Matrix3.new, "Matrix3.new") +AssertEquals("table", type(Matrix4), "Matrix4") +AssertCallable(Matrix4.new, "Matrix4.new") +AssertEquals("table", type(Quaternion), "Quaternion") +AssertCallable(Quaternion.new, "Quaternion.new") diff --git a/Tests/OvLuaApiTests/Lua/Globals/Enums.test.lua b/Tests/OvLuaApiTests/Lua/Globals/Enums.test.lua new file mode 100644 index 000000000..e03d448f8 --- /dev/null +++ b/Tests/OvLuaApiTests/Lua/Globals/Enums.test.lua @@ -0,0 +1,30 @@ +AssertTrue(Key.UNKNOWN ~= nil, "Key.UNKNOWN is registered") +AssertTrue(Key.SPACE ~= nil, "Key.SPACE is registered") +AssertTrue(Key.A == Key.A, "Key equality is stable") +AssertTrue(Key.A ~= Key.B, "Key inequality is stable") + +AssertTrue(MouseButton.BUTTON_1 ~= nil, "MouseButton.BUTTON_1 is registered") +AssertTrue(MouseButton.BUTTON_LEFT == MouseButton.BUTTON_1, "left mouse button alias") +AssertTrue(MouseButton.BUTTON_RIGHT == MouseButton.BUTTON_2, "right mouse button alias") +AssertTrue(MouseButton.BUTTON_MIDDLE == MouseButton.BUTTON_3, "middle mouse button alias") + +AssertTrue(CollisionDetectionMode.DISCRETE ~= nil, "CollisionDetectionMode.DISCRETE is registered") +AssertTrue(CollisionDetectionMode.DISCRETE ~= CollisionDetectionMode.CONTINUOUS, "collision modes are distinct") + +AssertTrue(ProjectionMode.ORTHOGRAPHIC ~= nil, "ProjectionMode.ORTHOGRAPHIC is registered") +AssertTrue(ProjectionMode.ORTHOGRAPHIC ~= ProjectionMode.PERSPECTIVE, "projection modes are distinct") + +AssertTrue(FrustumBehaviour.DISABLED ~= nil, "FrustumBehaviour.DISABLED is registered") +AssertTrue(FrustumBehaviour.DISABLED ~= FrustumBehaviour.MESH_BOUNDS, "frustum behaviours are distinct") + +AssertTrue(TonemappingMode.NEUTRAL ~= nil, "TonemappingMode.NEUTRAL is registered") +AssertTrue(TonemappingMode.NEUTRAL ~= TonemappingMode.ACES, "tonemapping modes are distinct") + +AssertTrue(ReflectionProbeRefreshMode.REALTIME ~= nil, "ReflectionProbeRefreshMode.REALTIME is registered") +AssertTrue(ReflectionProbeRefreshMode.REALTIME ~= ReflectionProbeRefreshMode.ONCE, "reflection refresh modes are distinct") + +AssertTrue(ReflectionProbeCaptureSpeed.ONE_FACE ~= nil, "ReflectionProbeCaptureSpeed.ONE_FACE is registered") +AssertTrue(ReflectionProbeCaptureSpeed.ONE_FACE ~= ReflectionProbeCaptureSpeed.SIX_FACES, "reflection capture speeds are distinct") + +AssertTrue(ReflectionProbeInfluencePolicy.GLOBAL ~= nil, "ReflectionProbeInfluencePolicy.GLOBAL is registered") +AssertTrue(ReflectionProbeInfluencePolicy.GLOBAL ~= ReflectionProbeInfluencePolicy.LOCAL, "reflection influence policies are distinct") diff --git a/Tests/OvLuaApiTests/Lua/Globals/Factories.test.lua b/Tests/OvLuaApiTests/Lua/Globals/Factories.test.lua new file mode 100644 index 000000000..de7ad3e66 --- /dev/null +++ b/Tests/OvLuaApiTests/Lua/Globals/Factories.test.lua @@ -0,0 +1,31 @@ +local model = Model() +AssertEquals("userdata", type(model), "Model factory") +AssertEquals("", model.path, "Model default path") +model.path = "Assets/Models/Ship.fbx" +AssertEquals("Assets/Models/Ship.fbx", model.path, "Model path mutation") + +local texture = Texture() +AssertEquals("userdata", type(texture), "Texture factory") +AssertEquals("", texture.path, "Texture default path") + +local shader = Shader() +AssertEquals("userdata", type(shader), "Shader factory") +AssertEquals("", shader.path, "Shader default path") + +local material = Material() +AssertEquals("userdata", type(material), "Material factory") +AssertEquals("", material.path, "Material default path") + +local sound = Sound() +AssertEquals("userdata", type(sound), "Sound factory") +AssertEquals("", sound.path, "Sound default path") + +local prefab = Prefab() +AssertEquals("userdata", type(prefab), "Prefab factory") +AssertEquals("", prefab.path, "Prefab default path") + +local actor = Actor() +AssertEquals("userdata", type(actor), "Actor factory") +AssertEquals(0, actor.guid, "Actor default guid") +actor.guid = 42 +AssertEquals(42, actor.guid, "Actor guid mutation") diff --git a/Tests/OvLuaApiTests/Lua/Math/Vector2.test.lua b/Tests/OvLuaApiTests/Lua/Math/Vector2.test.lua new file mode 100644 index 000000000..7e143ad70 --- /dev/null +++ b/Tests/OvLuaApiTests/Lua/Math/Vector2.test.lua @@ -0,0 +1,29 @@ +local epsilon = 0.0001 + +local defaultVector = Vector2.new() +AssertVector2Equals(0, 0, defaultVector, epsilon, "default constructor") + +local a = Vector2.new(3, 4) +local b = Vector2.new(1, 2) + +AssertVector2Equals(3, 4, a, epsilon, "constructor") +AssertVector2Equals(1, 1, Vector2.One(), epsilon, "Vector2.One") +AssertVector2Equals(0, 0, Vector2.Zero(), epsilon, "Vector2.Zero") + +AssertVector2Equals(4, 6, a + b, epsilon, "addition") +AssertVector2Equals(2, 2, a - b, epsilon, "subtraction") +AssertVector2Equals(-3, -4, -a, epsilon, "unary minus") +AssertVector2Equals(6, 8, a * 2, epsilon, "scalar multiplication") +AssertVector2Equals(1.5, 2, a / 2, epsilon, "scalar division") + +AssertNear(5, a:Length(), epsilon, "length") +AssertNear(11, a:Dot(b), epsilon, "dot") +AssertVector2Equals(0.6, 0.8, a:Normalize(), epsilon, "normalize") +AssertVector2Equals(0, 0, Vector2.Zero():Normalize(), epsilon, "zero normalize") +AssertVector2Equals(2, 3, Vector2.Lerp(b, a, 0.5), epsilon, "lerp") + +local ok = pcall(function() + return a / 0 +end) + +AssertFalse(ok, "division by zero fails") diff --git a/Tests/OvLuaApiTests/Lua/Math/Vector3.test.lua b/Tests/OvLuaApiTests/Lua/Math/Vector3.test.lua new file mode 100644 index 000000000..9ed0e81e8 --- /dev/null +++ b/Tests/OvLuaApiTests/Lua/Math/Vector3.test.lua @@ -0,0 +1,37 @@ +local epsilon = 0.0001 + +local defaultVector = Vector3.new() +AssertVector3Equals(0, 0, 0, defaultVector, epsilon, "default constructor") + +local a = Vector3.new(1, 2, 3) +local b = Vector3.new(4, 5, 6) + +AssertVector3Equals(1, 2, 3, a, epsilon, "constructor") +AssertVector3Equals(1, 1, 1, Vector3.One(), epsilon, "Vector3.One") +AssertVector3Equals(0, 0, 0, Vector3.Zero(), epsilon, "Vector3.Zero") +AssertVector3Equals(0, 0, 1, Vector3.Forward(), epsilon, "Vector3.Forward") +AssertVector3Equals(0, 0, -1, Vector3.Backward(), epsilon, "Vector3.Backward") +AssertVector3Equals(0, 1, 0, Vector3.Up(), epsilon, "Vector3.Up") +AssertVector3Equals(0, -1, 0, Vector3.Down(), epsilon, "Vector3.Down") +AssertVector3Equals(1, 0, 0, Vector3.Right(), epsilon, "Vector3.Right") +AssertVector3Equals(-1, 0, 0, Vector3.Left(), epsilon, "Vector3.Left") + +AssertVector3Equals(5, 7, 9, a + b, epsilon, "addition") +AssertVector3Equals(-3, -3, -3, a - b, epsilon, "subtraction") +AssertVector3Equals(-1, -2, -3, -a, epsilon, "unary minus") +AssertVector3Equals(2, 4, 6, a * 2, epsilon, "scalar multiplication") +AssertVector3Equals(4, 10, 18, a * b, epsilon, "component multiplication") +AssertVector3Equals(0.5, 1, 1.5, a / 2, epsilon, "scalar division") + +AssertNear(math.sqrt(14), a:Length(), epsilon, "length") +AssertNear(32, a:Dot(b), epsilon, "dot") +AssertVector3Equals(-3, 6, -3, a:Cross(b), epsilon, "cross") +AssertVector3Equals(0, 0, 0, Vector3.Zero():Normalize(), epsilon, "zero normalize") +AssertVector3Equals(2.5, 3.5, 4.5, Vector3.Lerp(a, b, 0.5), epsilon, "lerp") +AssertNear(math.sqrt(27), Vector3.Distance(a, b), epsilon, "distance") + +local ok = pcall(function() + return a / 0 +end) + +AssertFalse(ok, "division by zero fails") diff --git a/Tests/OvLuaApiTests/Lua/Support.lua b/Tests/OvLuaApiTests/Lua/Support.lua new file mode 100644 index 000000000..609570bfc --- /dev/null +++ b/Tests/OvLuaApiTests/Lua/Support.lua @@ -0,0 +1,95 @@ +local kDefaultEpsilon = 0.0001 + +local function Fail(message) + error(message, 2) +end + +local function GetTypeDescription(value) + local valueType = type(value) + + if valueType ~= "userdata" and valueType ~= "table" then + return valueType + end + + local metatable = getmetatable(value) + if type(metatable) ~= "table" then + return valueType + end + + local callType = metatable and type(metatable.__call) or nil + + if callType then + return valueType .. " with __call " .. callType + end + + return valueType +end + +local function IsCallable(value) + local valueType = type(value) + + if valueType == "function" then + return true + end + + if valueType ~= "userdata" and valueType ~= "table" then + return false + end + + local metatable = getmetatable(value) + if type(metatable) ~= "table" then + return false + end + + local callType = type(metatable.__call) + return callType == "function" or callType == "userdata" +end + +function AssertTrue(condition, message) + if not condition then + Fail(message or "expected condition to be true") + end +end + +function AssertFalse(condition, message) + if condition then + Fail(message or "expected condition to be false") + end +end + +function AssertEquals(expected, actual, message) + if actual ~= expected then + Fail((message or "values differ") .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual)) + end +end + +function AssertCallable(value, message) + if not IsCallable(value) then + Fail((message or "value") .. ": expected callable, got " .. GetTypeDescription(value)) + end +end + +function AssertNear(expected, actual, epsilon, message) + local tolerance = epsilon or kDefaultEpsilon + + if math.abs(expected - actual) > tolerance then + Fail((message or "values differ") .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual)) + end +end + +function AssertVector2Equals(expectedX, expectedY, actual, epsilon, message) + local label = message or "Vector2" + + AssertEquals("userdata", type(actual), label .. " type") + AssertNear(expectedX, actual.x, epsilon, label .. ".x") + AssertNear(expectedY, actual.y, epsilon, label .. ".y") +end + +function AssertVector3Equals(expectedX, expectedY, expectedZ, actual, epsilon, message) + local label = message or "Vector3" + + AssertEquals("userdata", type(actual), label .. " type") + AssertNear(expectedX, actual.x, epsilon, label .. ".x") + AssertNear(expectedY, actual.y, epsilon, label .. ".y") + AssertNear(expectedZ, actual.z, epsilon, label .. ".z") +end diff --git a/Tests/OvLuaApiTests/README.md b/Tests/OvLuaApiTests/README.md new file mode 100644 index 000000000..0ab66b4e8 --- /dev/null +++ b/Tests/OvLuaApiTests/README.md @@ -0,0 +1,5 @@ +# Lua API tests + +`OvLuaApiTests` starts a fresh Lua state for each `*.test.lua` file, initializes it through the same `OvCore::Scripting::Lua::BindLuaApi` entry point as the engine, then executes assertions from `Lua/Support.lua`. + +Add focused tests under `Tests/OvLuaApiTests/Lua//.test.lua`. Tests should be independent because global state is reset before each file. diff --git a/Tests/OvLuaApiTests/premake5.lua b/Tests/OvLuaApiTests/premake5.lua new file mode 100644 index 000000000..3d643bc77 --- /dev/null +++ b/Tests/OvLuaApiTests/premake5.lua @@ -0,0 +1,103 @@ +project "OvLuaApiTests" + kind "ConsoleApp" + language "C++" + cppdialect "C++20" + targetdir (outputdir .. "%{cfg.buildcfg}/%{prj.name}") + objdir (objoutdir .. "%{cfg.buildcfg}/%{prj.name}") + debugdir "%{wks.location}" + fatalwarnings { "All" } + + files { + "**.h", + "**.inl", + "**.cpp", + "**.lua" + } + + includedirs { + -- Dependencies + dependdir .. "assimp/include", + dependdir .. "glad/include", + dependdir .. "ImGui/include", + dependdir .. "lua/include", + dependdir .. "sol/include", + dependdir .. "tinyxml2/include", + dependdir .. "tracy", + + -- Overload SDK + "%{wks.location}/Sources/OvAudio/include", + "%{wks.location}/Sources/OvCore/include", + "%{wks.location}/Sources/OvDebug/include", + "%{wks.location}/Sources/OvMaths/include", + "%{wks.location}/Sources/OvPhysics/include", + "%{wks.location}/Sources/OvRendering/include", + "%{wks.location}/Sources/OvTools/include", + "%{wks.location}/Sources/OvUI/include", + "%{wks.location}/Sources/OvWindowing/include", + + -- Current project + "include" + } + + links { + -- Dependencies + "bullet3", + "freetype", + "glad", + "ImGui", + "lua", + "soloud", + "tinyxml2", + "tracy", + + -- Overload SDK + "OvAudio", + "OvCore", + "OvDebug", + "OvMaths", + "OvPhysics", + "OvRendering", + "OvTools", + "OvUI", + "OvWindowing", + + -- Dependencies that others depend on + "assimp", + "glfw" + } + + filter { "configurations:Debug" } + defines { "DEBUG", "_DEBUG" } + symbols "On" + + filter { "configurations:Release or configurations:Publish" } + defines { "NDEBUG" } + optimize "Speed" + + filter { "system:windows" } + links { + "dbghelp.lib", + "opengl32.lib", + } + + filter { "system:linux" } + links { + "dl", + "pthread", + "GL", + "X11", + } + + linkoptions { + "-Wl,--whole-archive", + outputdir .. "%{cfg.buildcfg}/ImGui/libImGui.a", + outputdir .. "%{cfg.buildcfg}/bullet3/libbullet3.a", + outputdir .. "%{cfg.buildcfg}/lua/liblua.a", + outputdir .. "%{cfg.buildcfg}/soloud/libsoloud.a", + outputdir .. "%{cfg.buildcfg}/OvAudio/libOvAudio.a", + outputdir .. "%{cfg.buildcfg}/assimp/libassimp.a", + outputdir .. "%{cfg.buildcfg}/tinyxml2/libtinyxml2.a", + outputdir .. "%{cfg.buildcfg}/glad/libglad.a", + "-Wl,--no-whole-archive", + "-Wl,--allow-multiple-definition", + } diff --git a/Tests/OvLuaApiTests/src/Main.cpp b/Tests/OvLuaApiTests/src/Main.cpp new file mode 100644 index 000000000..4ad20559e --- /dev/null +++ b/Tests/OvLuaApiTests/src/Main.cpp @@ -0,0 +1,175 @@ +/** +* @project: Overload +* @author: Overload Tech. +* @licence: MIT +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + constexpr auto kDefaultTestRoot = "Tests/OvLuaApiTests/Lua"; + constexpr auto kLuaTestSuffix = ".test.lua"; + constexpr auto kSupportFileName = "Support.lua"; + + std::filesystem::path GetTestRoot(int p_argumentCount, char** p_arguments) + { + if (p_argumentCount > 1) + { + return p_arguments[1]; + } + + return kDefaultTestRoot; + } + + bool IsLuaTestFile(const std::filesystem::path& p_path) + { + const std::string filename = p_path.filename().string(); + return filename.ends_with(kLuaTestSuffix); + } + + std::vector CollectTestFiles(const std::filesystem::path& p_testRoot) + { + std::vector testFiles; + + for (const auto& entry : std::filesystem::recursive_directory_iterator(p_testRoot)) + { + if (!entry.is_regular_file()) + { + continue; + } + + if (!IsLuaTestFile(entry.path())) + { + continue; + } + + testFiles.push_back(entry.path()); + } + + std::sort(testFiles.begin(), testFiles.end(), [](const auto& p_left, const auto& p_right) + { + return p_left.generic_string() < p_right.generic_string(); + }); + + return testFiles; + } + + std::string GetDisplayPath(const std::filesystem::path& p_testRoot, const std::filesystem::path& p_path) + { + return std::filesystem::relative(p_path, p_testRoot).generic_string(); + } + + bool ExecuteLuaFile( + sol::state& p_luaState, + const std::filesystem::path& p_scriptPath, + const std::string& p_displayPath, + const std::string& p_phase + ) + { + const auto result = p_luaState.safe_script_file(p_scriptPath.string(), &sol::script_pass_on_error); + + if (result.valid()) + { + return true; + } + + const sol::error error = result; + std::cerr + << '\n' + << "[ERROR] Lua API test execution failed" << '\n' + << "File: " << p_displayPath << '\n' + << "Phase: " << p_phase << '\n' + << "Details:" << '\n' + << error.what() << '\n'; + return false; + } + + int HandleLuaException(lua_State* p_luaState, sol::optional, sol::string_view p_message) + { + lua_pushlstring(p_luaState, p_message.data(), p_message.size()); + return 1; + } + + bool RunTestFile( + const std::filesystem::path& p_testRoot, + const std::filesystem::path& p_testFile, + const std::optional& p_supportFile + ) + { + sol::state luaState; + luaState.set_exception_handler(HandleLuaException); + OvCore::Scripting::Lua::BindLuaApi(luaState); + + if (p_supportFile.has_value()) + { + if (!ExecuteLuaFile(luaState, p_supportFile.value(), kSupportFileName, "loading assertions")) + { + return false; + } + } + + return ExecuteLuaFile(luaState, p_testFile, GetDisplayPath(p_testRoot, p_testFile), "running test"); + } +} + +int main(int p_argumentCount, char** p_arguments) +{ + const std::filesystem::path testRoot = GetTestRoot(p_argumentCount, p_arguments); + + if (!std::filesystem::exists(testRoot) || !std::filesystem::is_directory(testRoot)) + { + std::cerr << "Lua API test root not found: " << testRoot.generic_string() << '\n'; + return EXIT_FAILURE; + } + + const std::vector testFiles = CollectTestFiles(testRoot); + + if (testFiles.empty()) + { + std::cerr << "No Lua API tests found under: " << testRoot.generic_string() << '\n'; + return EXIT_FAILURE; + } + + const std::filesystem::path supportFile = testRoot / kSupportFileName; + const std::optional supportPath = + std::filesystem::is_regular_file(supportFile) ? + std::optional(supportFile) : + std::nullopt; + + uint32_t failureCount = 0; + + for (const auto& testFile : testFiles) + { + const std::string displayPath = GetDisplayPath(testRoot, testFile); + + if (RunTestFile(testRoot, testFile, supportPath)) + { + std::cout << "[PASS] " << displayPath << '\n'; + continue; + } + + std::cerr << "[FAIL] " << displayPath << '\n'; + ++failureCount; + } + + if (failureCount > 0) + { + std::cerr << failureCount << " Lua API test(s) failed" << '\n'; + return EXIT_FAILURE; + } + + std::cout << testFiles.size() << " Lua API test(s) passed" << '\n'; + return EXIT_SUCCESS; +} diff --git a/premake5.lua b/premake5.lua index 921dc6a03..7ccd2234c 100644 --- a/premake5.lua +++ b/premake5.lua @@ -83,4 +83,8 @@ group "Overload Apps" include "Sources/OvGame" group "" +group "Overload Tests" + include "Tests/OvLuaApiTests" +group "" + include "Resources"