You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.5 KiB

---@class DBMCoreNamespace
local private = select(2, ...)
---@class DBM
local DBM = private:GetPrototype("DBM")
local CL = DBM_COMMON_L
---@class DBMTest
local test = private:GetPrototype("DBMTest")
DBM.Test = test
-- Indicates whether a test is currently running.
test.testRunning = false
-- Overriden by DBM-Test once loaded.
-- This field is intentionally set in an odd way to prevent LuaLS from suggesting this function.
local traceField = "Trace"
test[traceField] = function() end
local LoadAddOn = _G.C_AddOns.LoadAddOn or LoadAddOn ---@diagnostic disable-line:deprecated
function test:Load()
local loaded, err = LoadAddOn("DBM-Test")
if not loaded then
DBM:AddMsg("Failed to load DBM-Test: " .. (_G["ADDON_" .. err] or CL.UNKNOWN))
return loaded, err
end
return true
end
function test:LoadAllTests()
local numTestAddOnsFound = 0
if not self:Load() then
return nil
end
for i = 1, C_AddOns.GetNumAddOns() do
if C_AddOns.GetAddOnMetadata(i, "X-DBM-Test") then
numTestAddOnsFound = numTestAddOnsFound + 1
C_AddOns.LoadAddOn(i)
end
end
return numTestAddOnsFound
end
function test:TestsLoaded()
return self.Registry ~= nil and #self.Registry.sortedTests > 0
end
function test:HandleCommand(testName, timeWarp)
timeWarp = timeWarp and tonumber(timeWarp:match("(%d+)"))
local numTestAddOnsFound = self:LoadAllTests()
if not numTestAddOnsFound then return end
if numTestAddOnsFound == 0 then
DBM:AddMsg("No test AddOns installed, install an alpha or dev version of DBM to get DBM-Test-* mods.")
end
if testName:lower() == "list" or testName:lower() == "help" then
DBM:AddMsg("Available tests:")
for _, v in ipairs(self.Registry.sortedTests) do
DBM:AddMsg(" " .. v)
end
if #self.Registry.sortedTests == 0 then
DBM:AddMsg(" (none)")
end
DBM:AddMsg("/dbm test <name> <time warp factor> -- execute a test")
DBM:AddMsg("/dbm test * <time warp factor> -- run all tests")
DBM:AddMsg("<name> can be a prefix, e.g., /dbm test Dragonflight runs all tests for Dragonflight.")
DBM:AddMsg("/dbm test clear -- clear exported test data")
elseif testName:lower() == "stop" then
test:StopTests()
DBM:AddMsg("Stopping running tests.")
elseif testName:lower() == "clear" then
DBM_TestResults_Export = {}
DBM:AddMsg("Cleared exported test results.")
else
local tests = {}
if testName == "*" then
testName = ""
end
for _, v in ipairs(self.Registry.sortedTests) do
if v:sub(1, #testName) == testName then
tests[#tests + 1] = v
end
end
if #tests == 0 then
DBM:AddMsg("No tests matching prefix " .. testName .. " found, run /dbm test list to see available tests.")
return
end
local successes = 0
self:RunTests(tests, timeWarp, nil, function(event, test, testOptions, report, count, totalTests)
if event ~= "TestFinish" then return end
DBM:AddMsg("Test " .. test.name .. " finished, result: " .. report:GetResult())
if report:GetResult() == "Success" then
successes = successes + 1
end
if report:HasDiff() then
report:ReportDiff()
end
if report:HasErrors() then
report:ReportErrors()
end
if count == totalTests and totalTests > 1 then
DBM:AddMsg(("%d/%d tests succeeded!"):format(successes, totalTests))
end
end)
end
end
---@type table<Frame>
test.framesForTimeWarp = {}
function test:RegisterTimeWarpFrame(frame)
-- Frames are registered on DBM load -- which is before the full testing module containing the time warper loads
if test.TimeWarper then
test.TimeWarper:RegisterFrame(frame)
else
test.framesForTimeWarp[frame] = true
end
end
-- Expose DBM private's namespace for tests.
-- These functions are intentionally disabled in release builds to prevent people from using these to tamper with DBM's private namespace in release builds.
local function checkDevBuild()
--@non-alpha@
error("this function is only available in dev and alpha builds of DBM", 3)
do return false end -- Safety to prevent people from just overriding error() to circumvent the alpha check.
--@end-non-alpha@
return true
end
local localHooks = {}
local restoreLocalHooks = {}
local restorePrivates = {}
-- Temporarily change the value of a field in DBM's private namespace or update a local variable in a file.
-- Updating locals works by files registering a callback via RegisterLocalHook below.
function test:HookPrivate(key, value)
if not checkDevBuild() then return end
if localHooks[key] then
restoreLocalHooks[key] = restoreLocalHooks[key] or {}
for _, v in ipairs(localHooks[key]) do
local old = v(value)
if restoreLocalHooks[key][v] == nil then -- handle nested hooking calls
restoreLocalHooks[key][v] = old
end
end
end
if private[key] ~= nil then -- FIXME: support privates that are currently nil
if restorePrivates[key] == nil then
restorePrivates[key] = private[key]
end
private[key] = value
end
end
-- Register a function to change a file-local variable temporarily via HookPrivate
function test:RegisterLocalHook(id, hookingFunc)
local entries = localHooks[id] or {}
localHooks[id] = entries
entries[#entries + 1] = hookingFunc
end
function test:UnhookPrivates()
if not checkDevBuild() then return end
for k, v in pairs(restorePrivates) do
private[k] = v
end
table.wipe(restorePrivates)
for _, v in pairs(restoreLocalHooks) do
for func, val in pairs(v) do
func(val)
end
end
table.wipe(restoreLocalHooks)
end
function test:GetPrivate()
if not checkDevBuild() then error() end
return private
end