test(upserver): 归档存档迁移与玩法闭环回归工具
This commit is contained in:
83
CodexOutput/upserver/KVGameLoadTests/AggregateTests.cs
Normal file
83
CodexOutput/upserver/KVGameLoadTests/AggregateTests.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
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;});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user