89 lines
5.4 KiB
Plaintext
89 lines
5.4 KiB
Plaintext
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using game.PlayerDataStorage;
|
|
if(UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || asap.core.GContext.Ready) throw new InvalidOperationException("Idle editor required.");
|
|
var asm=typeof(PlayerKVGameSessionBridge).Assembly;
|
|
var loop=asm.GetType("game.PlayerDataStorage.PlayerKVGameplayLoop",true);
|
|
var flags=BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Static;
|
|
var saved=loop.GetFields(flags).Where(f=>!f.IsInitOnly).ToDictionary(f=>f,f=>f.GetValue(null));
|
|
if((bool)loop.GetProperty("Enabled",flags).GetValue(null)) throw new InvalidOperationException("Real loop is active.");
|
|
var runType=asm.GetType("game.PlayerDataStorage.PlayerKVSaveRouteEditorValidationHost+Run",true);
|
|
var instanceFlags=BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance;
|
|
int checks=0,faults=0;
|
|
void Check(bool yes,string why) { checks++; if(!yes) throw new InvalidOperationException(why); }
|
|
void Set(string name,object value) { var f=loop.GetField(name,flags) ?? loop.GetField("<"+name+">k__BackingField",flags); f.SetValue(null,value); }
|
|
object Call(string name,params object[] args) { try { return loop.GetMethod(name,flags).Invoke(null,args); } catch(TargetInvocationException e) { throw e.InnerException; } }
|
|
var folder=Path.GetFullPath(Path.Combine(UnityEngine.Application.dataPath,"../../CodexOutput/upserver/KVGameplayLoopTests/unity-synthetic",Guid.NewGuid().ToString("N")));
|
|
Directory.CreateDirectory(folder);
|
|
var permitType=asm.GetType("game.PlayerDataStorage.PlayerKVLoopPermit",true);
|
|
var permit=Activator.CreateInstance(permitType,true);
|
|
permitType.GetProperty("PlayerId").SetValue(permit,"101");
|
|
permitType.GetProperty("AllowedKeys").SetValue(permit,new[]{"k","NewPlayerDataDic"});
|
|
object fixture=null;
|
|
async Task<PlayerKVGameSessionHandle> Start()
|
|
{
|
|
fixture=Activator.CreateInstance(runType,true);
|
|
var handler=(Func<CookieContainer,HttpMessageHandler>)Delegate.CreateDelegate(typeof(Func<CookieContainer,HttpMessageHandler>),fixture,runType.GetMethod("KV",instanceFlags));
|
|
var bridge=(PlayerKVGameSessionBridge)Activator.CreateInstance(typeof(PlayerKVGameSessionBridge),instanceFlags,null,new object[]{handler},null);
|
|
runType.GetField("bridge",instanceFlags).SetValue(fixture,bridge);
|
|
var handle=await (Task<PlayerKVGameSessionHandle>)runType.GetMethod("Open",instanceFlags).Invoke(fixture,new object[]{"101"});
|
|
Set("Enabled",true); Set("Ready",false); Set("failed",0); Set("Error",null); Set("handle",null);Set("collecting",null);
|
|
Set("Permit",permit);Set("RunPath",folder);Set("mainThread",Thread.CurrentThread.ManagedThreadId);Set("mainContext",SynchronizationContext.Current);
|
|
Set("onFault",new Action<string>(_=>faults++));Set("onClose",null);
|
|
Call("Publish",handle);
|
|
return handle;
|
|
}
|
|
async Task CloseFixture()
|
|
{
|
|
if(fixture!=null) await (Task)runType.GetMethod("Close",instanceFlags).Invoke(fixture,null);
|
|
fixture=null;
|
|
}
|
|
try
|
|
{
|
|
await Start();
|
|
Check((string)Call("Read","missing")==null,"valid missing key remains null");
|
|
var batch=Call("BeginEquip");
|
|
var nested=(PlayerKVEditResult)Call("Write","k","nested",true);
|
|
var primary=(PlayerKVEditResult)Call("Write","NewPlayerDataDic","whole-save",true);
|
|
((IDisposable)batch).Dispose();
|
|
await (Task)Call("ConfirmEquipAsync",batch,primary);
|
|
Check(nested.Ticket!=null && primary.Ticket!=null,"both exact tickets exist");
|
|
var remote=(Dictionary<string,Dictionary<string,string>>)runType.GetField("remote",instanceFlags).GetValue(fixture);
|
|
Check(remote["101"]["k"]=="nested" && remote["101"]["NewPlayerDataDic"]=="whole-save","nested and primary both remotely confirmed");
|
|
Check(faults==0,"successful equipment remains ready");
|
|
await CloseFixture();
|
|
await Start();
|
|
batch=Call("BeginEquip");
|
|
var superseded=(PlayerKVEditResult)Call("Write","k","first",true);
|
|
Call("Write","k","second",true);
|
|
primary=(PlayerKVEditResult)Call("Write","NewPlayerDataDic","whole-save",true);
|
|
((IDisposable)batch).Dispose();
|
|
bool rejected=false;try { await (Task)Call("ConfirmEquipAsync",batch,primary); } catch(InvalidOperationException e) { rejected=e.Message.Contains("Superseded"); }
|
|
Check(rejected && faults==1,"nested Superseded stops the entire equip chain");
|
|
Check(!(bool)loop.GetProperty("Ready",flags).GetValue(null),"fault synchronously revokes facade");
|
|
await CloseFixture();
|
|
await Start();
|
|
var before=(int)runType.GetField("writes",instanceFlags).GetValue(fixture);
|
|
rejected=false;try { Call("Write","unbacked-key","x",true); } catch(InvalidOperationException e) { rejected=e.Message.Contains("permission"); }
|
|
Check(rejected && (int)runType.GetField("writes",instanceFlags).GetValue(fixture)==before,"unauthorized key rejected before network");
|
|
await CloseFixture();
|
|
var closed=await Start();
|
|
await CloseFixture();
|
|
rejected=false;try { Call("Read","k"); } catch(InvalidOperationException) { rejected=true; }
|
|
Check(rejected,"failed stale read is not missing key");
|
|
Check(faults==3,"each separate synthetic run reports one fault");
|
|
return new{success=true,assertions=checks,formalRequests=0,synthetic=true,restored=true,folder};
|
|
}
|
|
finally
|
|
{
|
|
await CloseFixture(); // Do not restore over an undrained failure.
|
|
foreach(var pair in saved) pair.Key.SetValue(null,pair.Value);
|
|
}
|