using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; using Xunit; using P = AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class RuntimeBudgetTests { sealed class Rig { public readonly P Program = TestRig.Program(); public readonly Stub Runtime = new Stub(); public readonly Queue Incoming = new Queue(); public readonly List Sent = new List(); public Rig() { Runtime.Set("MaxInstructionCount", 50000).Set("CurrentInstructionCount", 0).Set("TimeSinceLastRun", TimeSpan.FromSeconds(1.0 / 60)); TestRig.SetBase(Program, "Runtime", Runtime.Value); var broadcast = new Stub(); var unicast = new Stub().Method("get_HasPendingMessage", a => Incoming.Count > 0) .Method("AcceptMessage", a => Incoming.Dequeue()); var igc = new Stub().Set("UnicastListener", unicast.Value) .Method("RegisterBroadcastListener", a => broadcast.Value) .Method("SendBroadcastMessage", a => Capture(a[1] as string)) .Method("SendUnicastMessage", a => { Capture(a[2] as string); return true; }); TestRig.SetBase(Program, "IGC", igc.Value); Program.Bus = new P.Wire(Program); } object Capture(string value) { P.Packet packet; Assert.True(P.Packet.TryDecode(value, "mining", out packet)); Sent.Add(packet); return null; } public void Add(string session, long seq, string kind = "TELEMETRY",string peer="peer",long address=123) { var packet = new P.Packet { From = peer, Session = session, Seq = seq, Kind = kind, Id = session + seq, Body = new MyIni() }; Incoming.Enqueue(new MyIGCMessage(packet.Encode("mining"), "AMS2/mining", address)); } } sealed class BusyRole : P.RoleLogic { readonly Rig rig; public bool Busy = true; public int Ticks, Received; public readonly List Jobs = new List { new P.Job { Id = "active", Owner = "miner-1", Progress = 12 } }; public BusyRole(Rig r) : base(r.Program) { rig = r; } public override void Tick(double dt) { Ticks++; if (Busy) rig.Runtime.Set("CurrentInstructionCount", 34000); } public override void Receive(P.Packet packet) { Received++; } public override List GetJobs() { return Jobs; } } static void Set(P p, string name, object value) { typeof(P).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(p, value); } [Fact] public void BusyFrameDefersBackgroundWithoutErasingActiveProgressAndNextFrameContinues() { var rig = new Rig(); var role = new BusyRole(rig); Set(rig.Program, "role", role); Set(rig.Program, "dashboard", new P.Dashboard(rig.Program)); Set(rig.Program, "nextDraw", double.MaxValue); Set(rig.Program, "nextSave", double.MaxValue); rig.Add("session", 1); rig.Program.Main("", UpdateType.Update1); Assert.Equal(1, role.Ticks); Assert.Equal(0, role.Received); Assert.Single(rig.Incoming); Assert.Equal(1, rig.Program.BudgetDeferrals); Assert.Equal(12, role.Jobs[0].Progress); Assert.Equal("miner-1", role.Jobs[0].Owner); role.Busy = false; rig.Runtime.Set("CurrentInstructionCount", 0); rig.Program.Main("", UpdateType.Update1); Assert.Equal(2, role.Ticks); Assert.Equal(1, role.Received); Assert.Empty(rig.Incoming); } [Fact] public void FleetHandlesAnIncomingRequestBeforeItsPlannerConsumesTheFrameBudget() { var rig = new Rig(); rig.Program.Config = new P.Settings("[System]\nId=test\nFleetId=mining\nBaseId=base\n", "fleet", "test"); var role = new BusyRole(rig); Set(rig.Program, "role", role); Set(rig.Program, "dashboard", new P.Dashboard(rig.Program)); Set(rig.Program, "nextDraw", double.MaxValue); Set(rig.Program, "nextSave", double.MaxValue); rig.Add("session", 1, "DOCK_REQUEST"); rig.Program.Main("", UpdateType.Update1); Assert.Equal(1, role.Received); Assert.Empty(rig.Incoming); Assert.Equal(1, role.Ticks); Assert.Equal(34000, rig.Program.Runtime.CurrentInstructionCount); // At this instruction count Drain cannot process another message. // Receiving the request proves it ran before the busy role tick. rig.Add("session", 2, "DOCK_REQUEST"); rig.Program.Bus.Drain(role.Receive); Assert.Equal(1, role.Received); Assert.Single(rig.Incoming); Assert.Equal(12, role.Jobs[0].Progress); } [Fact] public void IncomingBurstIsBoundedAndUnprocessedPacketsStayQueued() { var rig = new Rig(); for (int n = 1; n <= 10; n++) rig.Add("session", n); int received = 0; rig.Program.Bus.Drain(p => received++); Assert.Equal(4, received); Assert.Equal(6, rig.Incoming.Count); rig.Runtime.Set("CurrentInstructionCount", 28000); rig.Program.Bus.Drain(p => received++); Assert.Equal(4, received); Assert.Equal(6, rig.Incoming.Count); rig.Runtime.Set("CurrentInstructionCount", 0); rig.Program.Bus.Drain(p => received++); rig.Program.Bus.Drain(p => received++); Assert.Equal(10, received); Assert.Empty(rig.Incoming); } [Fact] public void ReceiverCostStopsFurtherProcessingInTheSameFrame() { var rig = new Rig(); rig.Add("s", 1); rig.Add("s", 2); int received = 0; rig.Program.Bus.Drain(p => { received++; rig.Runtime.Set("CurrentInstructionCount", 28000); }); Assert.Equal(1, received); Assert.Single(rig.Incoming); } [Fact] public void RetriesAreLimitedPerFrameWithoutDroppingPendingMessages() { var rig = new Rig(); for (int n = 0; n < 8; n++) rig.Program.Bus.Send("JOB", "peer", new MyIni(), true, 123); rig.Sent.Clear(); rig.Program.Now = 2; rig.Program.Bus.Tick(); Assert.Equal(2, rig.Sent.Count); Assert.Equal(8, rig.Program.Bus.PendingCount); rig.Program.Bus.Tick(); rig.Program.Bus.Tick(); rig.Program.Bus.Tick(); Assert.Equal(8, rig.Sent.Count); Assert.Equal(8, rig.Program.Bus.PendingCount); } [Fact] public void ForgettingPeerStopsRetriesAndDuplicatesButPermitsFreshReconnection() { var rig=new Rig();int received=0;rig.Add("old",1,"HELLO");rig.Program.Bus.Drain(p=>received++); rig.Program.Bus.Send("JOB","peer",new MyIni(),true);rig.Program.Bus.Send("COMMAND","peer",new MyIni(),true); rig.Program.Bus.ForgetPeer("peer");Assert.Equal(0,rig.Program.Bus.PendingCount); var peers=(IDictionary)typeof(P.Wire).GetField("peers",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(rig.Program.Bus); Assert.Empty(peers);rig.Add("old",1,"HELLO");rig.Program.Bus.Drain(p=>received++);Assert.Equal(1,received);Assert.Empty(peers); rig.Add("old",2,"HELLO");rig.Program.Bus.Drain(p=>received++);Assert.Equal(2,received);Assert.Single(peers); rig.Program.Bus.ForgetPeer("peer");rig.Add("replacement",1,"HELLO","peer",456);rig.Program.Bus.Drain(p=>received++);Assert.Equal(3,received); Assert.Equal(456L,peers["peer"]); } [Fact] public void DelayedAckCannotRecreateAForgottenAddressBinding() { var rig=new Rig();int received=0;rig.Add("original",1,"HELLO");rig.Program.Bus.Drain(p=>received++); rig.Program.Bus.ForgetPeer("peer");rig.Add("original",2,"ACK");rig.Program.Bus.Drain(p=>received++); var peers=(IDictionary)typeof(P.Wire).GetField("peers",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(rig.Program.Bus); Assert.Empty(peers);rig.Add("replacement",1,"HELLO","peer",456);rig.Program.Bus.Drain(p=>received++);Assert.Equal(2,received); } [Fact] public void AckRequiresTheExpectedAddressAndDoesNotRegisterAPeer() { var rig=new Rig();rig.Program.Bus.Send("COMMAND","peer",new MyIni(),true,123);P.Packet sent=rig.Sent[0]; var body=new MyIni();body.Set("ack","Id",sent.Id);var ack=new P.Packet {From="peer",Session="s",Seq=1,Kind="ACK",Body=body}; rig.Incoming.Enqueue(new MyIGCMessage(ack.Encode("mining"),"AMS2/mining",456));rig.Program.Bus.Drain(p=>{});Assert.Equal(1,rig.Program.Bus.PendingCount); rig.Incoming.Enqueue(new MyIGCMessage(ack.Encode("mining"),"AMS2/mining",123));rig.Program.Bus.Drain(p=>{});Assert.Equal(0,rig.Program.Bus.PendingCount); var peers=(IDictionary)typeof(P.Wire).GetField("peers",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(rig.Program.Bus); Assert.Empty(peers); } [Fact] public void RepeatedCleanupReleasesPeerLimitAndBoundsRetainedReplayStreams() { var rig=new Rig();int received=0; for(int n=0;n<300;n++) { string name="renamed-"+n;rig.Add("session",1,"HELLO",name);rig.Program.Bus.Drain(p=>received++);rig.Program.Bus.ForgetPeer(name); } Assert.Equal(300,received); var peers=(IDictionary)typeof(P.Wire).GetField("peers",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(rig.Program.Bus); var streams=(IDictionary)typeof(P.Wire).GetField("streams",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(rig.Program.Bus); Assert.Empty(peers);Assert.InRange(streams.Count,1,128); } [Fact] public void RepeatedSessionChangesAndCleanupKeepRetiredSessionsBounded() { var rig=new Rig();int received=0; for(int n=0;n<1300;n++) {rig.Add("s"+n,1);rig.Program.Bus.Drain(p=>received++);rig.Program.Bus.ForgetPeer("peer");} Assert.Equal(1300,received); var retired=(ICollection)typeof(P.Wire).GetField("retired",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(rig.Program.Bus); Assert.InRange(retired.Count,1,1024); rig.Add("s1298",2);rig.Program.Bus.Drain(p=>received++);Assert.Equal(1300,received); } [Fact] public void ProtectedOccupantRejectsReusedIdBeforeRetiringItsRealSession() { var rig=new Rig();int received=0;rig.Add("original",1);rig.Program.Bus.Drain(p=>received++); rig.Program.Bus.ProtectPeer("peer",123);rig.Program.Bus.ForgetPeer("peer"); rig.Add("replacement",1,"TELEMETRY","peer",456);rig.Program.Bus.Drain(p=>received++);Assert.Equal(1,received); rig.Add("original",2);rig.Program.Bus.Drain(p=>received++);Assert.Equal(2,received); rig.Program.Bus.ProtectPeer("peer",0);rig.Program.Bus.ForgetPeer("peer");rig.Add("replacement",2,"TELEMETRY","peer",456);rig.Program.Bus.Drain(p=>received++);Assert.Equal(3,received); } [Fact] public void SessionCacheAutomaticallyReplacesOldStreamsButRejectsRetiredSessionReplay() { var rig = new Rig(); int received = 0; for (int session = 0; session < 80; session++) { for (int kind = 0; kind < 32; kind++) { rig.Add("s" + session, kind + 1, "K" + kind); rig.Program.Bus.Drain(p => received++); } } Assert.Equal(2560, received); var streams = (IDictionary)typeof(P.Wire).GetField("streams", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(rig.Program.Bus); Assert.Single(streams); Assert.Equal(32, ((IDictionary)streams["peer"]).Count); rig.Add("s0", 1000, "K0"); rig.Program.Bus.Drain(p => received++); Assert.Equal(2560, received); rig.Add("s79", 32, "K31"); rig.Program.Bus.Drain(p => received++); Assert.Equal(2560, received); rig.Add("s80", 1, "K0"); rig.Program.Bus.Drain(p => received++); Assert.Equal(2561, received); } [Fact] public void ClearingAndReplacingTasksDropsAnUnfinishedMapAndPublishedTotals() { var rig=new Rig();var role=new BusyRole(rig);role.Jobs.Clear(); for(int n=0;n<128;n++)role.Jobs.Add(new P.Job {Id="old"+n,RegionId="old-region",Outcome="Complete"}); var dashboard=new P.Dashboard(rig.Program);dashboard.Update(role); role.Jobs.Clear();role.Jobs.Add(new P.Job {Id="new",RegionId="new-region",Outcome="Complete"});dashboard.Update(role); var count=typeof(P.Dashboard).GetField("completedJobs",BindingFlags.Instance|BindingFlags.NonPublic); Assert.Equal(1,(int)count.GetValue(dashboard)); role.Jobs.Clear();dashboard.Update(role);Assert.Equal(0,(int)count.GetValue(dashboard)); } [Fact] public void DisplayBuildsLargeMapAcrossFramesAndKeepsJobsUntouched() { var rig = new Rig(); var role = new BusyRole(rig); role.Jobs.Clear(); for (int n = 0; n < 2048; n++) role.Jobs.Add(new P.Job { Id = "job" + n, RegionId = "r", Outcome = "Complete" }); var dashboard = new P.Dashboard(rig.Program); var count = typeof(P.Dashboard).GetField("completedJobs", BindingFlags.Instance | BindingFlags.NonPublic); dashboard.Update(role); Assert.Equal(0, (int)count.GetValue(dashboard)); for (int n = 0; n < 31; n++) dashboard.Update(role); Assert.Equal(2048, (int)count.GetValue(dashboard)); Assert.Equal(2048, role.Jobs.Count); rig.Runtime.Set("CurrentInstructionCount", 30000); dashboard.ClearTransientCache(); dashboard.Update(role); Assert.Equal(2048, (int)count.GetValue(dashboard)); } } }