diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60dad30..40efa24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ Thank you for your interest in contributing to **PhDamage** - a floating combat ## Prerequisites - World of Warcraft client (Retail, TBC Anniversary, MoP Classic, Cata, or Classic) -- [Lua 5.1](https://www.lua.org/) (for linting and testing) +- [Lua 5.1](https://www.lua.org/) or [Lua 5.4](https://www.lua.org/) (for linting and testing) - [Luacheck](https://github.com/mpeterv/luacheck) (for static analysis) - [Busted](https://olivinelabs.com/busted/) (for unit tests) - [Git](https://git-scm.com/) @@ -116,11 +116,18 @@ luacheck path/to/File.lua # single file for fast feedback ### Automated Tests PhDamage uses [busted](https://olivinelabs.com/busted/) for unit testing. Tests live in the `tests/` directory. +CI still runs tests with Lua 5.1. Local runs may use Lua 5.1 or Lua 5.4. + +Use the repo-local launcher instead of calling `busted` directly. Invoke it with whichever interpreter you want to verify, such as `lua`, `lua5.1`, or `lua5.4`. The launcher asks LuaRocks where `busted.runner` is installed and prepends that rock tree to `package.path` and `package.cpath` for the active interpreter. + ```bash -busted --verbose # run all tests -busted tests/some_spec.lua # run a single test file +lua tests/RunBusted.lua --verbose # run all tests +lua tests/RunBusted.lua tests/some_spec.lua # run a single test file +lua5.4 tests/RunBusted.lua --verbose # explicitly test under Lua 5.4 ``` +If `busted --verbose` fails with `module 'busted.runner' not found`, your `busted` wrapper and your active interpreter are using different module paths. The launcher above avoids that mismatch without hard-coded machine-specific paths. + ### Manual Testing 1. Load the addon in the target game version 2. Attack a target dummy to verify floating combat text diff --git a/Data/SpellData_Warlock.lua b/Data/SpellData_Warlock.lua index 4717c40..2bf4653 100644 --- a/Data/SpellData_Warlock.lua +++ b/Data/SpellData_Warlock.lua @@ -450,7 +450,8 @@ SpellData[1949] = { ------------------------------------------------------------------------------- -- Life Tap — instant, Shadow (converts health to mana) --- Coefficient: ~0.8 (gains spell power scaling) +-- Current addon model uses a 0.8 coefficient for the spell line, including Rank 6 at level 70. +-- Revisit only if stronger in-game evidence shows this model is wrong. SpellData[1454] = { name = "Life Tap", school = SCHOOL_SHADOW, diff --git a/tests/RunBusted.lua b/tests/RunBusted.lua new file mode 100644 index 0000000..12d24c7 --- /dev/null +++ b/tests/RunBusted.lua @@ -0,0 +1,98 @@ +------------------------------------------------------------------------------- +-- RunBusted.lua +-- Repo-local Busted launcher that bootstraps LuaRocks paths for the active Lua +-- +-- Supported versions: Retail, MoP Classic, TBC Anniversary, Cata, Classic +------------------------------------------------------------------------------- + +local io = io +local package = package +local table = table + +local ACTIVE_LUA_VERSION = (_VERSION and _VERSION:match("(%d+%.%d+)")) + or error("Could not detect active Lua version", 0) + +local function Trim(value) + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function ReadFirstLine(command) + local handle = io.popen(command) + if not handle then + return nil, "Failed to start command: " .. command + end + + local output = handle:read("*a") or "" + local ok, _, code = handle:close() + local firstLine = Trim((output:match("([^\r\n]+)") or "")) + + if firstLine == "" then + if ok or code == 0 then + return nil, "Command returned no output: " .. command + end + + return nil, "Command failed: " .. command + end + + return firstLine +end + +local function PrependPath(currentValue, entries) + return table.concat(entries, ";") .. ";" .. currentValue +end + +local function BootstrapLuaRocksPaths() + local runnerPath, runnerErr = ReadFirstLine( + "luarocks --lua-version " .. ACTIVE_LUA_VERSION .. " which busted.runner" + ) + if not runnerPath then + return nil, runnerErr + end + + runnerPath = runnerPath:gsub("\\", "/") + + local rocksRoot, luaVersion = runnerPath:match("^(.*)/share/lua/(%d+%.%d+)/busted/runner%.lua$") + if not rocksRoot then + return nil, "Could not derive LuaRocks tree from: " .. runnerPath + end + + if luaVersion ~= ACTIVE_LUA_VERSION then + return nil, "LuaRocks resolved busted for Lua " .. luaVersion + .. " but active lua is " .. ACTIVE_LUA_VERSION + end + + package.path = PrependPath(package.path, { + rocksRoot .. "/share/lua/" .. luaVersion .. "/?.lua", + rocksRoot .. "/share/lua/" .. luaVersion .. "/?/init.lua", + }) + + package.cpath = PrependPath(package.cpath, { + rocksRoot .. "/lib/lua/" .. luaVersion .. "/?.dll", + rocksRoot .. "/lib/lua/" .. luaVersion .. "/?.so", + }) + + return true +end + +local function RunBusted() + local ok, runner = pcall(require, "busted.runner") + if ok then + return runner({ standalone = false }) + end + + local originalError = runner + local _, bootstrapErr = BootstrapLuaRocksPaths() + if bootstrapErr then + error("Failed to load busted.runner. Original error: " .. tostring(originalError) + .. " | Bootstrap error: " .. tostring(bootstrapErr), 0) + end + + ok, runner = pcall(require, "busted.runner") + if not ok then + error("Failed to load busted.runner after LuaRocks bootstrap: " .. tostring(runner), 0) + end + + return runner({ standalone = false }) +end + +RunBusted() diff --git a/tests/test_spellcalc.lua b/tests/test_spellcalc.lua index 6b711a0..1837f16 100644 --- a/tests/test_spellcalc.lua +++ b/tests/test_spellcalc.lua @@ -293,6 +293,19 @@ describe("SpellCalc", function() assert.is_near(0.8, result.coefficient, 0.0001) end) + + it("should preserve the current modeled 0.8 scaling for Rank 6", function() + local spellData = ns.SpellData[1454] + local rankData = spellData.ranks[6] + local result = SpellCalc.ComputeBase(spellData, rankData, playerState) + + local expectedSpBonus = 0.8 * 1000 -- 800 + local expectedManaGain = 420 + expectedSpBonus -- 1220 + + assert.is_near(expectedSpBonus, result.spellPowerBonus, 0.01) + assert.is_near(expectedManaGain, result.manaGain, 0.01) + assert.is_near(0.8, result.coefficient, 0.0001) + end) end) it("should return nil for an unknown spell type", function()