Files
AzerothAI/tests/lifecycle-lua.test.py
T
2026-09-24 05:35:53 +12:00

144 lines
6.7 KiB
Python

"""Run with python3 after installing lupa; no live server or database involved."""
from pathlib import Path
from lupa import LuaRuntime
lua = LuaRuntime()
setup='''
rows={{10,"Dead",1,0,"dead",2},{11,"Zaku",1,1,"active",5},
{12,"Fresh",2,0,"reserve",2},{13,"Human",3,0,"active",1},
{14,"Wronglevel",2,0,"reserve",2}}
players={}; writes={}; removed={}; values={}; events={}; deaths=0
function player(guid,name,account,level,dead)
local p={guid=guid,name=name,account=account,level=level,dead=dead}
function p:GetGUIDLow() return self.guid end
function p:GetName() return self.name end
function p:GetAccountId() return self.account end
function p:GetLevel() return self.level end
function p:GetClass() return self.class or 1 end
function p:GetRace() return self.race or 2 end
function p:IsDead() return self.dead end
players[name]=p; return p
end
function AuthDBQuery(sql)
if string.find(sql,"CREATE TABLE IF NOT EXISTS azerothai_realm_rules",1,true) then rulesReady=true;return nil end
if string.find(sql,"INSERT IGNORE INTO azerothai_realm_rules",1,true) then assert(rulesReady);return nil end
if string.find(sql,"SELECT hardcore FROM azerothai_realm_rules",1,true) then assert(rulesReady,'Schema must exist before synchronous mode reads');return {GetUInt32=function()return mode or 1 end} end
if string.find(sql,'SELECT id FROM account',1,true) then
if string.find(sql,'id=99 ',1,true) then return nil end
return {GetUInt32=function() return 2 end}
end
if string.find(sql,"SELECT guid,name,account_id FROM azerothai_lifecycle WHERE state='dead'",1,true) then return nil end
local q={i=1}
function q:GetUInt32(n) return rows[self.i][n+1] end
function q:GetString(n) return rows[self.i][n+1] end
function q:NextRow() self.i=self.i+1;return self.i<=#rows end
return q
end
function CharDBQuery(sql) return {GetUInt32=function() return 0 end} end
function AuthDBExecute(sql) table.insert(writes,sql) end
mgr={}
function mgr:Remove(p) table.insert(removed,p.guid);players[p.name]=nil end
function mgr:SetValue(guid,key,value) values[guid..key]=value end
function mgr:GetValue(guid,key) return values[guid..key] or 0 end
function GetRandomPlayerbotMgr() return mgr end
function GetPlayerByName(name) return players[name] end
function GetPlayersInWorld() return players end
function RegisterPlayerEvent(id,fn) events[id]=fn end
function CreateLuaEvent(fn) tick=fn end
function PrintError(text) end
function PrintInfo(text) end
function AzerothAIEmitPermanentDeath(p) deaths=deaths+1 end
function AzerothAIEmitPlayerSnapshot(name) end
player(10,"Dead",1,80,false)
player(11,"Zaku",1,7,true)
player(12,"Fresh",2,1,false)
-- A mismatched account must never be managed, even if the GUID/name matches.
player(13,"Human",99,20,true)
player(14,"Wronglevel",2,55,false)
'''
lua.execute(setup)
lua.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
lua.execute('''
assert(#removed==1 and removed[1]==10)
assert(values['10logout']==1 and values['10add']==0)
assert(players.Zaku and players.Human)
assert(deaths==0)
tick();assert(not players.Wronglevel)
local fresh=players.Fresh
events[8](8,nil,fresh);events[8](8,nil,fresh);events[6](6,nil,fresh)
assert(deaths==1,'Repeated death events must be idempotent')
tick();assert(not players.Fresh and values['12logout']==1)
events[8](8,nil,players.Zaku);events[8](8,nil,players.Human)
assert(deaths==1 and players.Zaku and players.Human)
local before=#writes;tick();assert(#writes==before,'No per-second database churn')
-- A retired identity accidentally logged in again must be evicted.
player(10,'Dead',1,80,false);tick();assert(not players.Dead)
player(15,'Factorynew',2,1,false);tick();assert(players.Factorynew)
player(16,'Realhuman',99,1,true);tick();assert(players.Realhuman)
assert(deaths==1)
events[8](8,nil,players.Factorynew);tick();assert(not players.Factorynew)
assert(deaths==2)
local fast=player(17,'Fastlearner',2,1,false)
events[3](3,fast);fast.level=2;tick();assert(players.Fastlearner)
''')
balanced=LuaRuntime()
balanced.execute(setup)
balanced.execute('''
players={};rows={}
for i=1,25 do
rows[#rows+1]={100+i,'Horde'..i,2,0,'active',2}
player(100+i,'Horde'..i,2,5,false)
end
rows[#rows+1]={201,'Alliancefresh',2,0,'reserve',1}
rows[#rows+1]={202,'Hordefresh',2,0,'reserve',2}
player(201,'Alliancefresh',2,1,false)
player(202,'Hordefresh',2,1,false)
''')
balanced.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
balanced.execute('''
assert(players.Alliancefresh,'Underrepresented faction must fill its vacancy')
assert(not players.Hordefresh,'Do not exceed 25 members of a faction')
assert(values['202logout']==1)
assert(deaths==0,'Faction deferral is not a death')
''')
returning=LuaRuntime()
returning.execute(setup)
returning.execute("""
players={};rows={}
for i=1,26 do
rows[#rows+1]={100+i,'Horde'..i,2,0,'active',2}
player(100+i,'Horde'..i,2,5,false)
end
rows[#rows+1]={999,'Zaku',2,1,'active',2}
player(999,'Zaku',2,7,false)
""")
returning.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
returning.execute("""
local n=0;for _ in pairs(players) do n=n+1 end
assert(n==25 and players.Zaku,'Returning population must obey faction cap and protect Zaku')
assert(deaths==0,'Holding returning residents offline must not retire them')
assert(values['125logout']==1 and values['126logout']==1)
""")
dk=LuaRuntime()
dk.execute(setup)
dk.execute("""
players={};rows={{201,'AllianceDK',2,0,'reserve',1},{202,'HordeDK',2,0,'reserve',2}}
local a=player(201,'AllianceDK',2,55,false);a.class=6;a.race=1
local h=player(202,'HordeDK',2,55,false);h.class=6
""")
dk.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text())
dk.execute("""
assert(players.AllianceDK and players.HordeDK)
local entries=0
for _,q in pairs(writes) do if string.find(q,'entry_level=55',1,true) then entries=entries+1 end end
assert(entries==2,'Death Knights must be audited at level 55')
events[8](8,nil,players.HordeDK);tick();assert(not players.HordeDK and deaths==1)
""")
print('PASS: retirement, protection, level-1 entry, replay safety, and 25/25 faction balancing')
normal=LuaRuntime();normal.execute(setup)
normal.execute("mode=0;players={};rows={};for i=1,25 do rows[#rows+1]={100+i,'Resident'..i,2,0,'active',2};player(100+i,'Resident'..i,2,5,i==1) end;rows[#rows+1]={200,'Reserve',2,0,'reserve',2};player(200,'Reserve',2,1,false)")
normal.execute((Path(__file__).parents[1]/'ale/azerothai_lifecycle.lua').read_text())
normal.execute("events[8](8,nil,players.Resident1);tick();assert(players.Resident1 and deaths==0);assert(not players.Reserve,'Recoverable death must retain its population slot');local n=0;for _ in pairs(players)do n=n+1 end;assert(n==25)")
print('Normal resurrection mode preserves identity and reserves the dead resident population slot')