Files
EscapistsReintroduced/tests/test_chat_client_ownership.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

63 lines
4.4 KiB
Python

"""Execute actual chat teardown/scope bodies against replaced local-controller ownership."""
import os
from pathlib import Path
from policy_harness import ROOT, body, run
source = Path(os.environ.get('ERR_CHAT_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ChatClient.c'))
def adapt(text):
text=text.replace('protected ', '').replace('ERR_GameMode mode', 'Mode* mode').replace('ERR_AdminMenu menu', 'Menu* menu')
for name in ('ERR_GameMode','ERR_AdminMenu'): text=text.replace(name+'.',name+'::')
text=text.replace('GetGame().','GetGame()->').replace('GetCallqueue().','GetCallqueue()->')
for name in ('m_Command','menu'): text=text.replace(name+'.',name+'->')
text=text.replace('Remove(Register)', 'Remove(this, 1)').replace('Remove(Poll)', 'Remove(this, 2)').replace('Remove(OnCommand)', 'Remove(this)').replace('null','nullptr')
return text
methods='\n'.join(adapt(body(source,s)) for s in ('protected void Poll(', 'int Scope(', 'bool Accept(', 'void Stop('))
run(r'''
#include <cassert>
#include <map>
#include <set>
#include <utility>
struct Controller {};
struct Mode {};
struct Queue {
std::set<std::pair<void*,int>> pending;
int removed=0;
void Remove(void* owner,int method){pending.erase({owner,method});removed++;}
};
struct Game {Controller* local=nullptr;Queue queue;Controller* GetPlayerController(){return local;}Queue* GetCallqueue(){return &queue;}} game;
Game* currentGame=&game;Game* GetGame(){return currentGame;}
struct Command {std::set<void*> listeners;int removals=0;void Remove(void* owner){listeners.erase(owner);removals++;}} command;
struct Menu {int closes=0;void Close(){closes++;}} oldMenu,newMenu;
namespace ERR_AdminMenu {Menu* open=nullptr;Menu* GetOpenMenu(){return open;}}
namespace ERR_GameMode {Mode* current=nullptr;Mode* Get(){return current;}}
struct Client {Controller* m_Controller=nullptr;Mode* m_Mode=nullptr;Command* m_Command=nullptr;bool m_Stopped=false;int m_Token=1;
'''+methods+r'''
};
void registerClient(Client& c){c.m_Command=&command;command.listeners.insert(&c);game.queue.pending.insert({&c,1});game.queue.pending.insert({&c,2});}
int main(){
Controller previous,replacement;Mode first,second;game.local=&previous;ERR_GameMode::current=&first;
Client old;old.m_Controller=&previous;old.m_Mode=&first;registerClient(old);
Client next;next.m_Controller=&replacement;next.m_Mode=&first;registerClient(next);
// The new controller has opened its own panel before the retained old callback fires.
game.local=&replacement;ERR_AdminMenu::open=&newMenu;old.Poll();
assert(old.m_Stopped && !old.m_Command);
assert(newMenu.closes==0); // old baseline wrongly closes the new owner's panel
assert(!command.listeners.count(&old) && command.listeners.count(&next));
assert(!game.queue.pending.count({&old,1})&&!game.queue.pending.count({&old,2}));
assert(game.queue.pending.count({&next,1})&&game.queue.pending.count({&next,2}));
int removed=game.queue.removed,unbound=command.removals;old.Stop();assert(game.queue.removed==removed&&command.removals==unbound&&newMenu.closes==0);
assert(!old.Accept(1)&&next.Accept(1));next.Poll();assert(!next.m_Stopped&&newMenu.closes==0);
// Current owner still closes its panel on a mission change and rotates its token once.
ERR_GameMode::current=&second;next.Poll();assert(next.m_Token==2&&newMenu.closes==1);
next.Poll();assert(next.m_Token==2&&newMenu.closes==1);assert(!next.Accept(1)&&next.Accept(2));
next.Stop();assert(next.m_Stopped&&newMenu.closes==2&&!command.listeners.count(&next));
next.Stop();assert(newMenu.closes==2);
// Retained controller destruction (direct Stop), null local gaps and shutdown are safe.
Client destroyed;destroyed.m_Controller=&previous;registerClient(destroyed);destroyed.Stop();assert(newMenu.closes==2);
Client gap;gap.m_Controller=&previous;registerClient(gap);game.local=nullptr;gap.Poll();assert(gap.m_Stopped&&newMenu.closes==2);
Client shutdown;shutdown.m_Controller=&previous;registerClient(shutdown);currentGame=nullptr;ERR_AdminMenu::open=nullptr;shutdown.Stop();assert(shutdown.m_Stopped&&!shutdown.m_Command&&!command.listeners.count(&shutdown));currentGame=&game;
// No open panel is also valid for the still-current owner.
Client noPanel;noPanel.m_Controller=&previous;registerClient(noPanel);game.local=&previous;noPanel.Stop();assert(noPanel.m_Stopped);
}
''')
print('PASS: former chat controller releases only its own callbacks and preserves replacement menu; current-owner stop/scope and repeated teardown remain intact')