Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
91 lines
4.8 KiB
Python
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')
|