Files
ft/CodexOutput/upserver/KVGameLoadTests/AggregateTests.cs

84 lines
7.2 KiB
C#

using game.PlayerDataStorage;
static partial class Tests
{
static PlayerKVSchemaResult Aggregate(Dictionary<string,string> raw=null,bool fresh=false,bool current=true,string version="synthetic-v1")
{
var prepared=new PlayerKVLoginPrepareResult(PlayerKVLoginPrepareError.None,"101",7,data:raw ?? ModernRaw());
var account=new PlayerKVAccountDescriptor("101",7,17,fresh,SchemaSnapshot().NowUtc,SchemaSnapshot().NowUtc);
return PlayerKVSaveSchema.Decode(prepared,account,"101",7,current,SchemaSnapshot(),version,Identity());
}
static async Task RunAggregateTests()
{
await Run("transaction-every-reset-replace-revocation-boundary",()=>
{
foreach(int boundary in Enumerable.Range(1,31)) foreach(int mode in new[]{0,1,2,3})
{
bool current=true;int notified=0;var values=new int[31];
var modules=Enumerable.Range(1,31).Select(id=>(IPlayerKVLoadModule)new PlayerKVLoadModule(id,
()=>{values[id-1]=0;if(id==boundary && mode==0)throw new Exception();if(id==boundary && mode==1)current=false;},
_=>{values[id-1]=id;if(id==boundary && mode==2)current=false;if(id==boundary && mode==3)throw new Exception();}));
var transaction=new PlayerKVGameLoadTransaction(modules,Environment.CurrentManagedThreadId,()=>{});
var result=transaction.Apply(Aggregate().Data,()=>current,()=>true,()=>notified++);
Check(result==(mode==0?PlayerKVApplyError.Poisoned:mode==3?PlayerKVApplyError.Apply:PlayerKVApplyError.Stale),"failure classification at every module");
Check(notified==0 && values.All(v=>v==0) && transaction.AppliedModules==0,"no publication, all reset");
if(mode==0)Check(transaction.Apply(Aggregate().Data,()=>true,()=>true,()=>{})==PlayerKVApplyError.Poisoned,"failed reset cannot reuse container");
}
return Task.CompletedTask;
});
await Run("aggregate-31-source-priority-no-partial-state",()=>
{
var raw=ModernRaw(); raw["extension"]="uninterpreted";
var a=Aggregate(raw); Check(a.Verdict==PlayerKVSchemaVerdict.Valid && a.Data.ModuleCount==31,"all31 decoded");
Check(a.Data.UnconsumedCount==1 && a.Data.UnconsumedTopLevelCount==1,"unknown top-level retained and counted");
var extended=ModernRaw(); var inner=Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string,string>>(extended["NewPlayerDataDic"]);
inner["unknown"]="opaque"; extended["NewPlayerDataDic"]=Newtonsoft.Json.JsonConvert.SerializeObject(inner);extended["Map_not_a_number"]="opaque";
var counted=Aggregate(extended).Data;Check(counted.UnconsumedModernCount==1 && counted.UnconsumedTopLevelCount==1,"unknown modern and nonmatching map prefixes counted separately");
raw.Clear(); Check(a.Data.Raw.Count==2 && a.Data.Module<PlayerKVPlayerState>(1).Core==a.Data.Module<PlayerKVCoreState>(2),"source copied, consistent shared immutable core");
Check(Aggregate(new(),true).Verdict==PlayerKVSchemaVerdict.EmptyNew && Aggregate(new()).Verdict==PlayerKVSchemaVerdict.EmptyExisting,"S101 empty classification");
Check(Aggregate(new(),true,false).Verdict==PlayerKVSchemaVerdict.Stale,"stale before empty");
Check(Aggregate(version:"other").Verdict==PlayerKVSchemaVerdict.UnsupportedSchema,"version locked");
Check(Aggregate(new(){["FootPrint"]="1"}).Verdict==PlayerKVSchemaVerdict.MissingCore,"optional data not core");
raw=ModernRaw(); raw["EventPartnerGatherData"]="null"; raw["TapJoy_Diamond"]="bad";
var bad=Aggregate(raw); Check(bad.Data==null && bad.Diagnostics.Count(18,PlayerKVSchemaError.Malformed)==1 && bad.Diagnostics.Count(31,PlayerKVSchemaError.Malformed)==1,"last module visited despite earlier optional failure");
raw["NewPlayerDataDic"]="null"; Check(Aggregate(raw).Verdict==PlayerKVSchemaVerdict.Malformed,"outer parse failure");
return Task.CompletedTask;
});
await Run("transaction-all-reset-before-replace-failure-clears",()=>
{
var order=new List<string>(); var values=new int[31]; int failReplace=0,failReset=0,discard=0,notify=0;
var modules=Enumerable.Range(1,31).Select(id=>(IPlayerKVLoadModule)new PlayerKVLoadModule(id,
()=>{order.Add("R"+id); values[id-1]=0; if(failReset==id) throw new Exception();},
_=>{order.Add("A"+id); values[id-1]=id; if(failReplace==id) throw new Exception();})).ToArray();
var transaction=new PlayerKVGameLoadTransaction(modules,Environment.CurrentManagedThreadId,()=>discard++);
Check(transaction.Apply(Aggregate().Data,()=>true,()=>true,()=>notify++)==PlayerKVApplyError.None,"apply succeeded");
Check(order.Take(31).SequenceEqual(Enumerable.Range(1,31).Select(id=>"R"+id)) && order.Skip(31).SequenceEqual(Enumerable.Range(1,31).Select(id=>"A"+id)),"exact reset-then-replace sequence");
Check(transaction.AppliedModules==31 && notify==1 && discard==0,"notification only complete");
foreach(var failed in Enumerable.Range(1,31))
{
failReplace=failed; order.Clear();
Check(transaction.Apply(Aggregate().Data,()=>true,()=>true,()=>notify++)==PlayerKVApplyError.Apply && values.All(v=>v==0),"partial materialization cleared");
Check(order.TakeLast(31).SequenceEqual(Enumerable.Range(1,31).Select(id=>"R"+id)),"all resets attempted after failure");
}
failReplace=0; failReset=8;
Check(transaction.Apply(Aggregate().Data,()=>true,()=>true,()=>notify++)==PlayerKVApplyError.Poisoned && transaction.Poisoned,"reset failure poisons");
failReset=0; Check(transaction.Apply(Aggregate().Data,()=>true,()=>true,()=>notify++)==PlayerKVApplyError.Poisoned,"poison cannot retry");
Check(notify==1,"no failure notifications");
return Task.CompletedTask;
});
await Run("transaction-notification-stale-sideeffect-and-main-thread",async()=>
{
bool current=true,clean=true; int resets=0;
var modules=Enumerable.Range(1,31).Select(id=>(IPlayerKVLoadModule)new PlayerKVLoadModule(id,()=>resets++,_=>{})).ToArray();
var transaction=new PlayerKVGameLoadTransaction(modules,Environment.CurrentManagedThreadId,()=>{});
Check(transaction.Apply(Aggregate().Data,()=>current,()=>clean,()=>current=false)==PlayerKVApplyError.Stale,"notification revocation");
current=true; Check(transaction.Apply(Aggregate().Data,()=>current,()=>clean,()=>clean=false)==PlayerKVApplyError.SideEffect,"notification sideeffect");
clean=true; Check(transaction.Apply(Aggregate().Data,()=>current,()=>clean,()=>throw new Exception())==PlayerKVApplyError.Apply,"receiver failure");
int before=resets;
var other=new PlayerKVGameLoadTransaction(modules,-1,()=>{});
Check(other.Apply(Aggregate().Data,()=>true,()=>true,()=>{})==PlayerKVApplyError.Input && resets==before,"wrong thread never mutates");
await Throws<ArgumentException>(()=>{new PlayerKVGameLoadTransaction(modules.Take(30),Environment.CurrentManagedThreadId,()=>{});return Task.CompletedTask;});
});
}
}