Files
2026-10-30 14:19:39 +01:00

98 lines
7.4 KiB
Python

"""Execute production outcome, completion and HTTP callback logic with fake engine boundaries."""
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)
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=methods.replace('GetGame().GetCallqueue().CallLater(Pulse, 1, false)', 'pulses++')
run(r'''
#include <cassert>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>
struct string:std::string {using std::string::string;using std::string::operator=;bool IsEmpty()const{return empty();}}; using ERR_Phase=int;
namespace Phase {enum {PREPARING, PRISON, RADIO_SEARCH, RADIO_DEFENCE, EXTRACTION, VICTORY, DEFEAT};}
int now=123456; bool GetGame(){return true;} void Print(string,int){} int power(int a,int b){return std::pow(a,b);}
namespace MissionRules {bool Terminal(int p){return p==Phase::VICTORY||p==Phase::DEFEAT;}}
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 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;
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;
string m_InFlight;int m_Failures=0,m_Delivered=0,m_NextAttempt=0,samples=0,events=0,removed=0,saves=0;
string type,result;
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++;}
bool Save(){saves++;if(saveOK)m_Dirty=false;return saveOK;}
'''+methods+r'''
};
int main(){
for(int p=Phase::PREPARING;p<=Phase::EXTRACTION;p++) assert(Rules::Outcome(p,false,false)=="interrupted");
assert(Rules::Outcome(Phase::VICTORY,false,false)=="victory");
assert(Rules::Outcome(Phase::DEFEAT,false,false)=="defeat");
assert(Rules::Outcome(Phase::DEFEAT,true,false)=="restarted_empty");
assert(Rules::Outcome(Phase::PREPARING,false,true)=="preparation_failed");
for(int status:{200,201,202,204,299}) assert(Rules::Acknowledged(status));
for(int status:{0,199,300,400,401,429,500,503}) assert(!Rules::Acknowledged(status));
assert(Rules::RetrySeconds(1)==10&&Rules::RetrySeconds(2)==20);
assert(Rules::RetrySeconds(6)==300&&Rules::RetrySeconds(100000)==300);
Service win;win.PhaseChanged(Phase::EXTRACTION,Phase::VICTORY);
assert(win.events==1&&win.result=="victory"&&win.samples==1&&win.m_Ended);
win.Finish("interrupted");win.PhaseChanged(Phase::VICTORY,Phase::DEFEAT);
assert(win.events==1&&win.state.previousOutcome=="victory");
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 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=&current;
delivery.Delivered(&old);assert(delivery.state.pending.size()==2);
current.code=500;delivery.Delivered(&current);
assert(delivery.state.pending.size()==2&&delivery.m_Failures==1&&delivery.m_NextAttempt==now+10000);
delivery.m_Callback=&current;delivery.m_InFlight="first";current.code=204;delivery.Delivered(&current);
assert(delivery.state.pending.size()==1&&delivery.state.pending[0]=="second"&&delivery.m_Failures==0&&delivery.saves==1);
delivery.Delivered(&current);assert(delivery.saves==1);
delivery.m_Callback=&current;delivery.m_InFlight="second";delivery.m_Enabled=false;
delivery.Delivered(&current);assert(delivery.state.pending.size()==1);
delivery.m_Enabled=true;delivery.m_Closed=true;delivery.Failed(&current);assert(delivery.m_Failures==0);
Service resume;resume.m_Enabled=false;resume.Refresh();
assert(resume.events==1&&resume.type=="mission_started"&&!resume.m_Partial);
resume.settings.enabled=0;resume.Refresh();assert(!resume.Enabled()&&resume.deletes==3);
resume.settings.enabled=1;resume.Refresh();
assert(resume.events==2&&resume.type=="telemetry_resumed"&&resume.m_Partial&&resume.m_MissionId=="same-round");
Service unconfigured;unconfigured.m_Enabled=false;unconfigured.config.Endpoint="";unconfigured.Refresh();assert(!unconfigured.Enabled()&&unconfigured.events==0);
Service client;client.m_Enabled=false;client.authority=false;client.Refresh();assert(client.events==0);
Service stale;stale.current=false;stale.Close();assert(stale.events==0&&stale.removed==1);
Service restart;restart.m_Enabled=false;restart.state.previousMissionId="earlier";restart.state.previousOutcome="victory";restart.Refresh();assert(restart.events==2&&restart.type=="mission_started");
Service progress;progress.PhaseChanged(Phase::PRISON,Phase::RADIO_SEARCH);assert(progress.type=="phase_changed"&&!progress.m_Ended);
}
''')
lifecycle=(p.parent/'ERR_TelemetryLifecycle.c').read_text()
assert lifecycle.index('m_ERRTelemetry.Finish(ERR_TelemetryRules.Outcome') < lifecycle.index('super.OnGameModeEnd(endData)')
assert '#ifdef WORKBENCH' in lifecycle and 'errTelemetryTest' in lifecycle
assert 'GetRestApi' not in lifecycle
assert 'm_State.pending.Count() >= 64' in s and 'payload.Length() > 65536' in s
assert s.index('if (m_Dirty && !Save())') < s.index('m_Context.POST(')
assert 'ReadValue("token"' in s and 'WriteValue("token", m_Config.Token)' in s
# Persisted event data must not contain authentication material.
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')