134 lines
5.1 KiB
Lua
134 lines
5.1 KiB
Lua
-- WoW API stubs ------------------------------------------------------------
|
|
format = string.format
|
|
local now = 1000
|
|
function GetTime() return now end
|
|
local combat, afk = false, false
|
|
function UnitAffectingCombat() return combat end
|
|
function UnitIsAFK() return afk end
|
|
local log = {}
|
|
DEFAULT_CHAT_FRAME = { AddMessage = function(self, m) table.insert(log, m); print(" chat: " .. m) end }
|
|
SlashCmdList = {}
|
|
FRAMES = {}
|
|
function CreateFrame()
|
|
local F = { scripts = {}, events = {} }
|
|
function F:RegisterEvent(e) self.events[e] = true end
|
|
function F:UnregisterEvent(e) self.events[e] = nil end
|
|
function F:SetScript(k, f) self.scripts[k] = f end
|
|
table.insert(FRAMES, F)
|
|
return F
|
|
end
|
|
local function fire(e, a1)
|
|
assert(F.events[e], "not registered: " .. e)
|
|
event, arg1 = e, a1; this = F
|
|
F.scripts.OnEvent()
|
|
end
|
|
local function update(sec) -- advance time in 0.1s frames
|
|
local n = sec * 10
|
|
for i = 1, n do now = now + 0.1; arg1 = 0.1; this = F; F.scripts.OnUpdate() end
|
|
end
|
|
|
|
-- live data (~8 MB) and a garbage generator -------------------------------
|
|
LIVE = {}
|
|
for i = 1, 60000 do LIVE[i] = { i, "x" .. i } end
|
|
local function makeGarbage(mb)
|
|
local t = {}
|
|
for i = 1, mb * 6000 do t[i] = { i, i } end -- roughly 1 MB per 6000 small tables
|
|
end
|
|
|
|
collectgarbage()
|
|
dofile(arg[1])
|
|
F = FRAMES[1]
|
|
HF = FRAMES[2]
|
|
fire("ADDON_LOADED", "GentleGC")
|
|
local cmd = SlashCmdList.GENTLEGC
|
|
cmd("debug 2")
|
|
|
|
local function count() local n = 0; for _, m in ipairs(log) do if string.find(m, "%->") then n = n + 1 end end; return n end
|
|
local function check(name, cond) print((cond and "PASS " or "FAIL ") .. name); if not cond then FAILED = true end end
|
|
|
|
print("1. login loading screen collects")
|
|
makeGarbage(6); fire("PLAYER_ENTERING_WORLD"); check("collected at login", count() == 1)
|
|
|
|
print("2. small garbage is skipped")
|
|
makeGarbage(1); now = now + 60; fire("MERCHANT_SHOW"); check("skipped (little garbage)", count() == 1)
|
|
|
|
print("3. interval respected")
|
|
makeGarbage(6); fire("BANKFRAME_OPENED"); fire("MAIL_SHOW")
|
|
check("one collection for two quick events", count() == 2)
|
|
makeGarbage(6); fire("MAIL_SHOW"); check("within minInterval -> no collect", count() == 2)
|
|
|
|
print("4. combat: threshold raised, no automatic GC, deferred collection after")
|
|
now = now + 100
|
|
local used0, thr0 = gcinfo()
|
|
fire("PLAYER_REGEN_DISABLED"); combat = true
|
|
local _, thr1 = gcinfo()
|
|
check("threshold raised by headroom", thr1 >= used0 + 32 * 1024 - 1)
|
|
-- allocate past the ORIGINAL threshold but keep it alive so we can see it
|
|
local keep = {}
|
|
local target = thr0 + 1024
|
|
while gcinfo() < target do table.insert(keep, { 1, 2, 3 }) end
|
|
local usedMid = gcinfo()
|
|
check("heap went past old threshold without auto-GC", usedMid > thr0)
|
|
update(20); check("no collection in combat (fallback timer)", count() == 2)
|
|
fire("ZONE_CHANGED_NEW_AREA"); check("no collection on safe event in combat", count() == 2)
|
|
keep = nil
|
|
fire("PLAYER_REGEN_ENABLED"); combat = false
|
|
check("collected after combat", count() == 3)
|
|
local u2, t2 = gcinfo(); check("threshold back to normal (2x live)", t2 <= u2 * 2 + 64)
|
|
|
|
print("5. short fight with nothing to collect restores threshold")
|
|
now = now + 100
|
|
local _, tb = gcinfo(); fire("PLAYER_REGEN_DISABLED"); fire("PLAYER_REGEN_ENABLED")
|
|
local _, ta = gcinfo(); check("threshold restored", ta == tb); check("no collection", count() == 3)
|
|
|
|
print("6. pressure: collect pre-emptively near threshold, out of combat")
|
|
now = now + 40
|
|
local keep2 = {}
|
|
local _, thr = gcinfo()
|
|
while gcinfo() < thr * 0.86 do table.insert(keep2, { 1, 2 }) end
|
|
keep2 = nil
|
|
update(1.5); check("pressure collection", count() == 4)
|
|
|
|
print("7. fallback idle collection after maxInterval")
|
|
makeGarbage(8); update(310); check("idle collection", count() == 5)
|
|
|
|
print("8. AFK")
|
|
makeGarbage(8); now = now + 60; afk = true; fire("PLAYER_FLAGS_CHANGED"); afk = false
|
|
check("afk collection", count() == 6)
|
|
|
|
print("8b. automatic GC is detected and resynced")
|
|
cmd("debug 2")
|
|
local nAuto = 0
|
|
for _, m in ipairs(log) do if string.find(m, "automatic GC detected") then nAuto = nAuto + 1 end end
|
|
-- the heap grows frame by frame until Lua collects on its own
|
|
local SF = FRAMES[4]
|
|
local _, t0 = gcinfo()
|
|
local junk = {}
|
|
local t = t0
|
|
while t == t0 do -- (a local declared inside repeat isn't visible to until in Lua 5.0)
|
|
for i = 1, 2000 do table.insert(junk, { i }) end
|
|
if table.getn(junk) > 20000 then junk = {} end
|
|
SF.scripts.OnUpdate()
|
|
_, t = gcinfo()
|
|
end
|
|
junk = nil
|
|
update(1.1)
|
|
local nAuto2 = 0
|
|
for _, m in ipairs(log) do if string.find(m, "automatic GC detected") then nAuto2 = nAuto2 + 1 end end
|
|
check("automatic GC detected", nAuto2 > nAuto)
|
|
local u, t = gcinfo()
|
|
check("pressure not falsely triggered after auto GC", u < t * 0.85)
|
|
|
|
print("9. slash commands")
|
|
cmd("interval 45"); check("interval set", GentleGCDB.minInterval == 45)
|
|
cmd("now"); check("manual collect", count() >= 7)
|
|
cmd(""); cmd("bogus"); cmd("reset"); check("reset", GentleGCDB.minInterval == 30)
|
|
|
|
print("10. summary report")
|
|
cmd("debug 1"); update(301)
|
|
local found = false
|
|
for _, m in ipairs(log) do if string.find(m, "last %d+ min") then found = true end end
|
|
check("summary printed", found)
|
|
|
|
print(FAILED and "SOME TESTS FAILED" or "ALL TESTS PASSED")
|