124 lines
7.9 KiB
Python
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::current=↦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')
|