Count occupied restarts as failed missions; sync published Workshop version
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
+38
-7
@@ -3,10 +3,10 @@ from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Telemetry.c'
|
||||
s=p.read_text()
|
||||
def cpp(v):
|
||||
for a,b in [('protected ',''),('m_Config.', 'config.'),('Replication.IsServer()', 'authority'),('m_Mode.Settings()', 'settings'),('FileIO.DeleteFile(', 'DeleteFile('),('ERR_Phase.','Phase::'),('ERR_TelemetryRules.','Rules::'),('ERR_MissionRules.','MissionRules::'),('Math.Min(', 'std::min('),('Math.Pow(', 'power('),('System.GetTickCount()', '::now'),('LogLevel.WARNING','0'),('m_State.', 'm_State->'),('m_Callback = null', 'm_Callback = nullptr'),('m_Context = null', 'm_Context = nullptr'),('m_Mode = null', 'm_Mode = nullptr'),('RestCallback callback', 'RestCallback* callback'),('callback.GetHttpCode()', 'callback->GetHttpCode()'),('GetGame().GetCallqueue().Remove(Pulse)', 'removed++')]: v=v.replace(a,b)
|
||||
for a,b in [('protected ',''),('JsonLoadContext queued = new JsonLoadContext();', 'JsonLoadContext queued;'),('GetGame().GetPlayerManager().GetPlayerCount()', 'connections'),('m_Mode.Phase()', 'phase'),('m_Config.', 'config.'),('Replication.IsServer()', 'authority'),('m_Mode.Settings()', 'settings'),('FileIO.DeleteFile(', 'DeleteFile('),('ERR_Phase.','Phase::'),('ERR_TelemetryRules.','Rules::'),('ERR_MissionRules.','MissionRules::'),('Math.Min(', 'std::min('),('Math.Pow(', 'power('),('System.GetTickCount()', '::now'),('LogLevel.WARNING','0'),('m_State.', 'm_State->'),('m_Callback = null', 'm_Callback = nullptr'),('m_Context = null', 'm_Context = nullptr'),('m_Mode = null', 'm_Mode = nullptr'),('RestCallback callback', 'RestCallback* callback'),('callback.GetHttpCode()', 'callback->GetHttpCode()'),('GetGame().GetCallqueue().Remove(Pulse)', 'removed++')]: v=v.replace(a,b)
|
||||
return v
|
||||
rules='\n'.join(cpp(body(p,x)) for x in ['static bool Acknowledged(', 'static int RetrySeconds(', 'static string Outcome('])
|
||||
methods='\n'.join(cpp(body(p,x)) for x in ['bool Enabled(', 'void Refresh(', 'protected void Purge(', 'void Finish(', 'void PhaseChanged(', 'protected void Delivered(', 'protected void Failed(', 'void Close('])
|
||||
methods='\n'.join(cpp(body(p,x)) for x in ['bool Enabled(', 'void Refresh(', 'protected void Purge(', 'void Finish(', 'protected void RecoverRestart(', 'protected void Enqueue(', 'protected void Checkpoint(', 'void RosterChanged(', 'void PhaseChanged(', 'protected void Delivered(', 'protected void Failed(', 'void Close('])
|
||||
methods=methods.replace('GetGame().GetCallqueue().CallLater(Pulse, 1, false)', 'pulses++')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -21,15 +21,16 @@ namespace MissionRules {bool Terminal(int p){return p==Phase::VICTORY||p==Phase:
|
||||
struct Rules {
|
||||
'''+rules+r'''
|
||||
};
|
||||
struct Pending:std::vector<string>{using std::vector<string>::operator=;void Clear(){clear();}bool IsEmpty(){return empty();}void RemoveItem(string x){erase(std::remove(begin(),end(),x),end());}};
|
||||
struct State{string previousOutcome,previousMissionId;Pending pending;};
|
||||
struct Pending:std::vector<string>{using std::vector<string>::operator=;int Count(){return size();}void Insert(string x){push_back(x);}void InsertAt(string x,int at){insert(begin()+at,x);}void Remove(int at){erase(begin()+at);}void Clear(){clear();}bool IsEmpty(){return empty();}void RemoveItem(string x){erase(std::remove(begin(),end(),x),end());}};
|
||||
struct JsonLoadContext{string value;bool LoadFromString(string x){value=x;return true;}bool ReadValue(string key,string& type){type=value;return key=="type";}};
|
||||
struct State{int droppedEvents=0;string previousOutcome,previousMissionId,restartFailure;Pending pending;};
|
||||
struct RestCallback{int code=200;int GetHttpCode(){return code;}};
|
||||
struct Service {
|
||||
struct Config{string Endpoint="https://fixture.invalid/";}config;
|
||||
struct Settings{int enabled=1;int Get(string){return enabled;}explicit operator bool()const{return true;}}settings;
|
||||
Pending m_Players;bool authority=true,m_Collected=false,m_Partial=false,m_TruncatedPlayers=false,current=true;
|
||||
string m_MissionId="same-round";string STATE="queue";
|
||||
int m_Started=now,m_LastSample=0,m_NextHeartbeat=0,m_Peak=0,deletes=0;
|
||||
int connections=0,phase=Phase::PRISON;int m_Started=now,m_LastSample=0,m_NextHeartbeat=0,m_Peak=0,deletes=0;
|
||||
void DeleteFile(std::string){deletes++;}
|
||||
bool m_Enabled=true,m_Closed=false,m_Ended=false,m_Dirty=false,desired=true,saveOK=true;
|
||||
State state;State* m_State=&state;RestCallback* m_Callback=nullptr;void* m_Context=nullptr;void* m_Mode=nullptr;
|
||||
@@ -38,7 +39,10 @@ struct Service {
|
||||
int pulses=0;void UpdateCommunity(RestCallback*){}void CommunityUnavailable(){}
|
||||
bool Current(){return current;}
|
||||
void Sample(){samples++;}
|
||||
void Event(string t,string o="",string p="",string prior=""){type=t;result=o;events++;}
|
||||
void Event(string t,string o="",string p="",string prior="",bool checkpoint=false){
|
||||
if(checkpoint){state.restartFailure="snapshot";Save();return;}type=t;result=o;events++;
|
||||
}
|
||||
|
||||
bool Save(){saves++;if(saveOK)m_Dirty=false;return saveOK;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
@@ -59,6 +63,33 @@ int main(){
|
||||
Service loss;loss.Finish("preparation_failed");loss.Close();loss.Close();
|
||||
assert(loss.events==1&&loss.result=="preparation_failed"&&loss.removed==1);
|
||||
Service abandoned;abandoned.Close();assert(abandoned.result=="interrupted");
|
||||
Service occupied;occupied.connections=2;occupied.Close();
|
||||
assert(occupied.events==1&&occupied.result=="defeat"&&occupied.state.previousOutcome=="defeat");
|
||||
occupied.Close();assert(occupied.events==1);
|
||||
Service emptyRestart;emptyRestart.Finish("restarted_empty");emptyRestart.Close();assert(emptyRestart.result=="restarted_empty");
|
||||
Service checkpoint;checkpoint.connections=1;checkpoint.RosterChanged(1);
|
||||
assert(checkpoint.state.restartFailure=="snapshot"&&checkpoint.events==0&&checkpoint.saves==1);
|
||||
checkpoint.connections=0;checkpoint.RosterChanged(1);
|
||||
assert(checkpoint.state.restartFailure.IsEmpty()&&checkpoint.events==0&&checkpoint.saves==2);
|
||||
checkpoint.connections=1;checkpoint.Checkpoint();checkpoint.Finish("victory");checkpoint.Checkpoint();
|
||||
assert(checkpoint.state.restartFailure.IsEmpty()&&checkpoint.result=="victory");
|
||||
Service crash;crash.m_Enabled=false;crash.state.previousMissionId="crashed-round";
|
||||
crash.state.previousOutcome="unfinished";crash.state.restartFailure="persisted-event-with-stable-id";
|
||||
crash.Refresh();assert(crash.state.pending.size()==1&&crash.state.pending[0]=="persisted-event-with-stable-id");
|
||||
assert(crash.state.restartFailure.IsEmpty()&&crash.state.previousMissionId=="same-round");
|
||||
crash.RecoverRestart();assert(crash.state.pending.size()==1);
|
||||
Service full;full.m_InFlight="in-flight";full.state.pending.push_back("in-flight");
|
||||
for(int i=1;i<64;i++)full.state.pending.push_back(i==10?"heartbeat":"old");
|
||||
full.state.previousOutcome="unfinished";full.state.restartFailure="recovered";full.RecoverRestart();
|
||||
assert(full.state.pending.size()==64&&full.state.pending[0]=="recovered"&&full.state.pending[1]=="in-flight");
|
||||
assert(std::count(full.state.pending.begin(),full.state.pending.end(),"heartbeat")==0&&full.state.droppedEvents==1);
|
||||
Service legacy;legacy.state.previousOutcome="unfinished";legacy.RecoverRestart();assert(legacy.state.pending.empty());
|
||||
for(string result:{"victory","defeat","preparation_failed","restarted_empty","interrupted"}){
|
||||
Service completed;completed.state.previousOutcome=result;completed.state.restartFailure="stale";completed.RecoverRestart();
|
||||
assert(completed.state.pending.empty()&&completed.state.previousOutcome==result&&completed.state.restartFailure.IsEmpty());
|
||||
}
|
||||
Service optout;optout.state.restartFailure="private-data";optout.state.previousMissionId="old";
|
||||
optout.settings.enabled=0;optout.Refresh();assert(optout.state.restartFailure.IsEmpty()&&optout.state.pending.empty());
|
||||
Service disabled;disabled.settings.enabled=0;disabled.Finish("victory");assert(disabled.events==0);
|
||||
RestCallback old,current;Service delivery;
|
||||
delivery.state.pending={"first","second"};delivery.m_InFlight="first";delivery.m_Callback=¤t;
|
||||
@@ -94,4 +125,4 @@ assert 'ReadValue("token"' in s and 'WriteValue("token", m_Config.Token)' in s
|
||||
event=body(p,'protected void Event(')
|
||||
assert 'Token' not in event and 'Endpoint' not in event
|
||||
assert 'DidEscape' in body(p,'protected void Sample(')
|
||||
print('PASS: telemetry outcomes, exactly-once finish, opt-out gate, stale callbacks, HTTP status handling and bounded retry')
|
||||
print('PASS: telemetry outcomes, occupied/empty restarts, durable recovery, bounded queue, opt-out, exactly-once finish and HTTP retries')
|
||||
|
||||
Reference in New Issue
Block a user