Files
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

91 lines
4.8 KiB
Python

"""Run production bounded journal methods against clock/file/callqueue doubles."""
from policy_harness import ROOT, body, run
p = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_EquipmentLog.c'
s = '\n'.join(body(p, signature) for signature in (
'static string Clean(', 'protected void Enqueue(', 'protected void Schedule(',
'protected string Path(', 'protected void Pump('))
s = s.replace('protected ', '').replace('string.Format', 'Format')
s = s.replace('string.Empty', 'string()').replace('FileHandle file', 'File* file')
s = s.replace('FileMode.', 'FileMode::').replace('LogLevel.WARNING', '0').replace('level: ', '')
s = s.replace('GetGame().GetWorld().GetWorldTime()', 'worldTime')
s = s.replace('GetGame().GetCallqueue().CallLater(Pump, delay, false)', 'delays.push_back(delay)')
s = s.replace('System.GetTickCount()', 'ticks').replace('Replication.IsServer()', 'server')
s = s.replace('FileIO.OpenFile', 'OpenFile').replace('file.', 'file->')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
struct string:std::string {
using std::string::string;
string(std::string value):std::string(value){}
int Length(){return size();}
string Substring(int start,int count){return substr(start,count);}
void Replace(string before,string after){size_t i=0;while((i=find(before,i))!=npos){replace(i,before.size(),after);i+=after.size();}}
};
template<class T>std::string show(T value){std::ostringstream s;s<<value;return s.str();}
template<class... T>string Format(string pattern,T...args){
std::vector<std::string> values{show(args)...};
for(size_t i=0;i<values.size();i++) { pattern.Replace("%"+std::to_string(i+1),values[i]); }
return pattern;
}
template<class T>struct Array:std::vector<T>{void Resize(int n){this->resize(n);}};
int ticks=0,worldTime=17,writeCost=0,opens=0,warnings=0;
bool server=true,openOk=true;
std::vector<int> delays;
enum class FileMode {APPEND,WRITE};
struct File{
std::vector<string> rows;int length=0,closes=0;
void WriteLine(string line){rows.push_back(line);length+=line.size()+1;ticks+=writeCost;}
int GetLength(){return length;}void Close(){closes++;}
} disk;
string lastPath;FileMode lastMode;
File* OpenFile(string path,FileMode mode){opens++;lastPath=path;lastMode=mode;if(!openOk)return nullptr;if(mode==FileMode::WRITE)disk.length=0;return &disk;}
void PrintFormat(string,string,int){warnings++;}
struct Journal {
Array<string> m_Pending;
int m_Head=0,m_Count=0,m_Lost=0,m_Sequence=0,m_Part=0;
bool m_Truncate=false,m_Scheduled=false,m_Warned=false;
string m_Run="test";
Journal(){m_Pending.Resize(4096);}
''' + s + r'''
};
int main(){
// One wakeup for a burst; constant ring storage, no disk IO at enqueue time.
Journal journal;
for(int i=0;i<4097;i++)journal.Enqueue("ROW",std::to_string(i));
assert(journal.m_Count==4096&&journal.m_Lost==1&&journal.m_Pending.size()==4096);
assert(opens==0&&delays.size()==1&&delays.back()==500);
// File failure preserves rows; bounded retry and only one warning.
openOk=false;journal.Pump();journal.Pump();
assert(journal.m_Count==4096&&journal.m_Lost==1&&warnings==1&&delays.back()==30000);
openOk=true;journal.Pump();
assert(journal.m_Count==4064&&journal.m_Lost==0&&disk.rows.size()==33);
assert(disk.rows[0].find("LOG_GAP dropped=1")!=string::npos);
assert(disk.rows[1].find("seq=1 ")!=string::npos);
// Freed slots can immediately be reused without shifting the remaining ring.
journal.Enqueue("ROW","new");assert(journal.m_Count==4065);
while(journal.m_Count>0)journal.Pump();
assert(disk.rows.size()==4098&&disk.rows.back().find("seq=4098 ")!=string::npos);
int idleOpens=opens;journal.Pump();assert(opens==idleOpens&&!journal.m_Scheduled);
// Slow native IO ends the slice after one record, without losing following rows.
writeCost=4;Journal slow;slow.Enqueue("ROW","a");slow.Enqueue("ROW","b");
slow.Pump();assert(slow.m_Count==1&&slow.m_Scheduled);slow.Pump();assert(slow.m_Count==0);
writeCost=0;
// Four bounded file slots; truncate only when opening the next rotated slot.
Journal rotating;
for(int part=0;part<5;part++){
disk.length=2097152;rotating.Enqueue("ROW","rotate");rotating.m_Truncate=false;rotating.Pump();
assert(rotating.m_Part==(part+1)%4&&rotating.m_Truncate);
}
rotating.Enqueue("ROW","next");rotating.Pump();assert(lastMode==FileMode::WRITE&&!rotating.m_Truncate);
assert(lastPath=="$logs:rereintroduced-equipment-1.log");
assert(Journal::Clean("a\nb\rc\td")=="a b c d");
assert(Journal::Clean(string(2000,'x')).Length()==1035);
// A client cannot write even if an old callback fires.
server=false;Journal client;client.Enqueue("ROW","ignored");idleOpens=opens;client.Pump();assert(opens==idleOpens);
}
''')
print('PASS: production equipment journal ring bounds, gap notices, retries, flush budget, rotation, idle/server guards and sanitization')