Files
EscapistsReintroduced/tests/test_native_shared_map_ownership.py
T

124 lines
7.9 KiB
Python

"""Execute fixture's actual ownership/cleanup and callback-evidence decisions.
Explicit native boundary doubles; this is not a native rendering/camera test.
"""
import re
from policy_harness import ROOT, body, run
SOURCE = ROOT / 'addons/map/Scripts/Game/ERRMap/ERRM_NativeMapOwnershipProbe.c'
def method(signature):
text = body(SOURCE, signature).replace('protected ', '')
text = re.sub(r'^ Print\(string\.Format[^\n]+\n', '', text, flags=re.M)
text = text.replace('SCR_MapEntity.GetMapInstance()', 'Map::current')
text = text.replace('SCR_MapEntity.GetOnMapClose().Remove(OnMapClosed)', 'closeObserver.Remove(this)')
text = text.replace('SCR_MapEntity.GetOnMapOpenComplete().Remove(OnMapReady)', 'readyObserver.Remove(this)')
text = text.replace('SCR_EntityHelper.DeleteEntityAndChildren(', 'DeleteEntityAndChildren(')
for field in ['m_Map', 'm_ReplacementConfig', 'm_ReplacementRoot', 'm_Capture', 'm_Spare']:
text = text.replace(field + '.', field + '->')
return re.sub(r'\bnull\b', 'nullptr', text)
methods = '\n'.join(method(x) for x in [
'protected bool ReplacementIsCurrent(', 'void Stop()',
'protected void SaveCounters(', 'protected bool VerifyStaleAndStop(',
'protected bool DeletionComplete(', 'protected void RequestOwnedDeletion(', 'void ObserveDeletion('])
# The first Stop in this file is the fixture method; adapters are named distinctly.
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
#include <string>
struct Probe;
struct Widget { int removes=0;void RemoveFromHierarchy(){removes++;} };
struct Config { Widget* RootWidgetRef=nullptr; };
struct Entity { int deletes=0; };
void DeleteEntityAndChildren(Entity*& value){value->deletes++;value=nullptr;}
struct Map { static Map* current;bool opened=true;Config* config=nullptr;Widget* root=nullptr;
int closes=0;float pan=0,zoom=0;bool IsOpen(){return opened;}Config* GetMapConfig(){return config;}
Widget* GetMapMenuRoot(){return root;}void CloseMap(){closes++;opened=false;}
float GetCurrentPan(){return pan;}float GetTargetZoomPPU(){return zoom;}
};Map* Map::current=nullptr;
struct Capture {int stops=0;void ERRM_StopOwnershipCapture(){stops++;}};
struct Observer {std::vector<Probe*> owners;void Remove(Probe* p){owners.erase(std::remove(owners.begin(),owners.end(),p),owners.end());}};
Observer closeObserver,readyObserver;
struct Probe {
Map* m_Map=nullptr;Widget* m_ReplacementRoot=nullptr;Config* m_ReplacementConfig=nullptr;Config* m_ReadyConfig=nullptr;
Capture *m_Capture=nullptr,*m_Spare=nullptr;Entity *m_CaptureEntity=nullptr,*m_SpareEntity=nullptr,*m_Holder=nullptr;
bool m_DeleteRequested=false,m_DeleteStateFailed=false;int m_CaptureDeleted=0,m_SpareDeleted=0;
bool m_Stopped=false;int m_CloseEvents=0,m_RestoreCalls=0,m_RestoreAuthorized=0,m_Completions=0,m_CenterCalls=0,m_FinishCalls=0;
int m_CloseBaseline=0,m_RestoreBaseline=0,m_AuthorizedBaseline=0,m_CompletionBaseline=0,m_CenterBaseline=0,m_FinishBaseline=0;
float m_PanBaseline=0,m_ZoomBaseline=0;
bool Check(bool result,const std::string&){if(!result)Stop();return result;}
'''+methods+r'''
void Bind(Map& map,Config& cfg,Widget& root,Capture& c,Capture& s,Entity& ce,Entity& se){
m_Map=&map;Map::current=&map;m_ReplacementConfig=&cfg;m_ReplacementRoot=&root;
map.opened=true;map.config=&cfg;map.root=&root;cfg.RootWidgetRef=&root;
m_Capture=&c;m_Spare=&s;m_CaptureEntity=&ce;m_SpareEntity=&se;
closeObserver.owners.push_back(this);readyObserver.owners.push_back(this);
}
};
int main(){
Map map;Config cfg,foreignCfg;Widget root,foreignRoot;Capture capture,spare;Entity ce,se;
Probe foreign;foreign.Bind(map,cfg,root,capture,spare,ce,se);
Probe other;closeObserver.owners.push_back(&other);readyObserver.owners.push_back(&other);
// Same object reused with another native root must not be closed in cleanup.
cfg.RootWidgetRef=&foreignRoot;map.root=&foreignRoot;
assert(!foreign.ReplacementIsCurrent());foreign.Stop();
assert(map.closes==0&&map.opened&&root.removes==1&&foreignRoot.removes==0);
assert(ce.deletes==1&&se.deletes==1&&capture.stops==1&&spare.stops==1);
assert(closeObserver.owners.size()==1&&closeObserver.owners[0]==&other);
assert(readyObserver.owners.size()==1&&readyObserver.owners[0]==&other);
foreign.Stop();assert(root.removes==1&&ce.deletes==1); // no duplicate cleanup
Probe changed;changed.Bind(map,cfg,root,capture,spare,ce,se);map.config=&foreignCfg;
changed.Stop();assert(map.closes==0); // distinct active config also preserved
Probe oldWorld;oldWorld.Bind(map,cfg,root,capture,spare,ce,se);Map replacementWorld;Map::current=&replacementWorld;
oldWorld.Stop();assert(map.closes==0); // old singleton cannot be closed
Probe exact;exact.Bind(map,cfg,root,capture,spare,ce,se);exact.Stop();assert(map.closes==1&&!map.opened);
// A correct view without callbacks running must not pass native evidence.
Probe missed;missed.Bind(map,cfg,root,capture,spare,ce,se);missed.SaveCounters();missed.m_RestoreCalls++;
assert(!missed.VerifyStaleAndStop()&&missed.m_Stopped);
Probe observed;observed.Bind(map,cfg,root,capture,spare,ce,se);observed.SaveCounters();
observed.m_CenterCalls+=2;observed.m_FinishCalls++;observed.m_RestoreCalls++;
assert(observed.VerifyStaleAndStop()&&!observed.m_Stopped&&map.opened);
observed.Stop();
Probe moved;moved.Bind(map,cfg,root,capture,spare,ce,se);moved.SaveCounters();
moved.m_CenterCalls+=2;moved.m_FinishCalls++;moved.m_RestoreCalls++;map.pan=1;
assert(!moved.VerifyStaleAndStop()); // actual changed map pan invalidates evidence
map.pan=0;
Probe closed;closed.Bind(map,cfg,root,capture,spare,ce,se);closed.SaveCounters();
closed.m_CenterCalls+=2;closed.m_FinishCalls++;closed.m_RestoreCalls++;closed.m_CloseEvents++;
assert(!closed.VerifyStaleAndStop()); // even a close/reopen to same view fails
// Request return is not deletion completion. Native refs and superclass
// completion are separate observations, all required, without timeout waiver.
Probe delayed;delayed.Bind(map,cfg,root,capture,spare,ce,se);
assert(!delayed.DeletionComplete());delayed.RequestOwnedDeletion();
int ceDeletes=ce.deletes,seDeletes=se.deletes;delayed.RequestOwnedDeletion();
assert(ce.deletes==ceDeletes&&se.deletes==seDeletes); // exactly one request
assert(!delayed.m_CaptureEntity&&!delayed.m_SpareEntity&&!delayed.DeletionComplete());
delayed.ObserveDeletion(1,true);delayed.ObserveDeletion(2,true);
assert(!delayed.DeletionComplete()); // callbacks alone don't invalidate native refs
delayed.m_Capture=nullptr;delayed.m_Spare=nullptr; // explicit native-boundary simulation
assert(delayed.DeletionComplete());delayed.ObserveDeletion(2,true);
assert(!delayed.DeletionComplete()); // duplicate cleanup is never accepted
Probe unreleased;unreleased.m_DeleteRequested=true;
unreleased.ObserveDeletion(1,false);unreleased.ObserveDeletion(2,true);
assert(!unreleased.DeletionComplete()); // a post-super leftover field fails
Probe premature;premature.ObserveDeletion(1,true);premature.m_DeleteRequested=true;
premature.ObserveDeletion(2,true);assert(!premature.DeletionComplete());
}
''')
text = SOURCE.read_text()
assert text.startswith('#ifdef WORKBENCH\n') and text.rstrip().endswith('#endif')
for name in ('CenterMapOnHolder', 'FinishMapCapture', 'RestoreCharacterCamera', 'CompleteMapModeSwitch'):
assert 'super.' + name + '();' in body(SOURCE, 'protected override void ' + name + '(')
deletion = body(SOURCE, 'protected override void OnDelete(')
assert deletion.index('super.OnDelete(owner)') < deletion.index('probe.ObserveDeletion(')
assert 'now >= m_DeleteDeadline' in text and 'm_DeleteDeadline = now + 3000' in text
assert 'SetCharacterCameraRenderActive(' not in text # no guessed getter or explicit write
assert 'GetOnMapOpenComplete().Insert(OnMapReady)' in text
assert 'm_ReadyConfig == m_ReplacementConfig' in text
assert 'CallLater(CenterMapOnHolder, 100, false)' in text
assert 'CallLater(FinishMapCapture, 200, false)' in text
print('PASS: actual native-map fixture ownership, cleanup and callback evidence fail closed; native execution remains separate')