using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Sandbox.ModAPI.Ingame; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class RebuiltMinerTests { // The 13:43/13:46 saves retained the deleted miner's PB and connector, // while the replacement used the same logical ID with both entities new. const string MinerId="深空工业3号"; const long OldAddress=73380066387127517,NewAddress=100758657065621036; const long OldConnector=95696644539555323,NewConnector=75739440036816648; const long Berth=86881199841431342,Epoch=639249584512672040; static readonly Vector3D PortPosition=new Vector3D(-67770.011285779881,-123675.8598834305,-162150.0515711247); sealed class Delivery { public long Address; public P.Packet Packet; } sealed class Rig { public readonly FleetStateTests.Rig FleetRig=new FleetStateTests.Rig(1,false,"[Dock]\nApproachDistance=130\n[Display]\nOfflineSeconds=5\n"); public readonly Queue Incoming=new Queue(); public readonly List Sent=new List(); public P Program=>FleetRig.Program; public P.FleetController Fleet=>FleetRig.Fleet; public Rig() { var ports=(List>)typeof(FleetStateTests.Rig).GetField("portStubs",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(FleetRig); ports[0].Set("EntityId",Berth);FleetRig.SetPort(0,PortPosition,Vector3D.Forward); typeof(P.FleetController).GetField("nextWaitingSlot",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(Fleet,2); 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("SendBroadcastMessage",a=>Capture(0,(string)a[1])) .Method("SendUnicastMessage",a=>{Capture((long)a[0],(string)a[2]);return true;}); // Keep the Fleet-created Wire and its real AllowRebind callback. TestRig.SetBase(Program,"IGC",igc.Value);Program.Bus.Epoch=Epoch; } object Capture(long address,string text) { P.Packet packet;Assert.True(P.Packet.TryDecode(text,"mining",out packet)); Sent.Add(new Delivery {Address=address,Packet=packet});return null; } public P.Telemetry Miner(bool replacement=true) { return new P.Telemetry {Id=MinerId,ConnectorId=replacement?NewConnector:OldConnector,State=P.FlightState.Returning, Position=replacement?new Vector3D(-67812.941594717908,-123673.67683211257,-162086.70801528683):new Vector3D(-67606.2248054574,-122641.64258946766,-162455.13400703063), Radius=4.6314600797496155,Battery=1,Hydrogen=1,WaitingSlot=999}; } public P.Packet Request(P.Telemetry miner,bool replacement=true,string kind="DOCK_REQUEST",long sequence=1) { var address=replacement?NewAddress:OldAddress;var session=replacement?"replacement-pb-session":"deleted-pb-session"; var packet=new P.Packet {Kind=kind,From=MinerId,Target="base",Session=session,Seq=sequence,Id=session+"-"+kind+"-"+sequence, Epoch=Epoch,Reliable=true,Body=miner.ToIni()}; Incoming.Enqueue(new MyIGCMessage(packet.Encode("mining"),"AMS2/mining",address));Program.Bus.Drain(Fleet.Receive);return packet; } public Delivery InitialGrant() { Request(Miner(false),false);var grant=Assert.Single(Sent,d=>d.Packet.Kind=="DOCK_GRANT"); Assert.Equal(OldAddress,grant.Address);Assert.Equal(Berth,P.DockFrame.FromIni(grant.Packet.Body).ConnectorId); Assert.Equal(2,Assert.Single(Fleet.GetTelemetry()).WaitingSlot);Sent.Clear();return grant; } public P.Telemetry Registered=>Assert.Single(Fleet.GetTelemetry()); public Dictionary Holds=>(Dictionary)typeof(P.FleetController).GetField("clearanceHolds",BindingFlags.Instance|BindingFlags.NonPublic).GetValue(Fleet); } [Fact] public void SavedReplacementReceivesItsExistingBerthDuringTheSameWireDrainAndOldRetriesStop() { var r=new Rig();var old=r.InitialGrant();var oldFrame=P.DockFrame.FromIni(old.Packet.Body); r.Program.Bus.Send("COMMAND",MinerId,new MyIni(),true,OldAddress);var oldCommand=r.Sent.Last().Packet; r.Program.Bus.Send("COMMAND","unrelated-miner",new MyIni(),true,4242);Assert.Equal(3,r.Program.Bus.PendingCount); r.Sent.Clear();r.Program.Now+=6;var now=r.Program.Now;var request=r.Request(r.Miner()); var delivered=Assert.Single(r.Sent,d=>d.Packet.Kind=="DOCK_GRANT");var frame=P.DockFrame.FromIni(delivered.Packet.Body); Assert.Equal(now,r.Program.Now);Assert.Equal(NewAddress,delivered.Address);Assert.Equal(Berth,frame.ConnectorId); Assert.NotEmpty(frame.Token);Assert.NotEqual(oldFrame.Token,frame.Token);Assert.Equal(130,frame.ApproachDistance); Assert.Equal(NewAddress,r.Registered.Address);Assert.Equal(NewConnector,r.Registered.ConnectorId);Assert.Equal(2,r.Registered.WaitingSlot); Assert.Contains(r.Sent,d=>d.Address==NewAddress && d.Packet.Kind=="ACK" && P.Data.Text(d.Packet.Body,"ack","Id")==request.Id); Assert.Equal(1,P.Data.ReadLong(r.FleetRig.Save(),"fleet","LaneCount"));Assert.Equal(2,r.Program.Bus.PendingCount); r.Sent.Clear();r.Program.Now+=2;r.Program.Bus.Tick(); Assert.DoesNotContain(r.Sent,d=>d.Address==OldAddress || d.Packet.Id==old.Packet.Id || d.Packet.Id==oldCommand.Id); Assert.Contains(r.Sent,d=>d.Address==NewAddress && d.Packet.Id==delivered.Packet.Id); Assert.Contains(r.Sent,d=>d.Address==4242); } [Fact] public void ALiveDifferentShipCannotTakeTheSameIdOrBerth() { var r=new Rig();var old=r.InitialGrant();r.Program.Now+=4;r.Request(r.Miner()); Assert.Equal(OldAddress,r.Registered.Address);Assert.Equal(OldConnector,r.Registered.ConnectorId); Assert.Empty(r.Sent);Assert.Equal(P.DockFrame.FromIni(old.Packet.Body).Token,P.Data.Text(r.FleetRig.Save(),"fleet","LaneToken")); Assert.Equal(1,r.Program.Bus.PendingCount); } [Fact] public void AReplacementProgrammableBlockWithTheSameConnectorCanRecoverWithoutAnOfflineDelay() { var r=new Rig();var old=r.InitialGrant();var current=r.Miner();current.ConnectorId=OldConnector; r.Request(current);var delivery=Assert.Single(r.Sent,d=>d.Packet.Kind=="DOCK_GRANT"); Assert.Equal(NewAddress,delivery.Address);Assert.Equal(NewAddress,r.Registered.Address);Assert.Equal(2,r.Registered.WaitingSlot); Assert.Equal(Berth,P.DockFrame.FromIni(delivery.Packet.Body).ConnectorId); Assert.NotEqual(P.DockFrame.FromIni(old.Packet.Body).Token,P.DockFrame.FromIni(delivery.Packet.Body).Token); } [Theory] [InlineData(false)] [InlineData(true)] public void ARebuiltShipCannotClaimAnOldMiningTaskOrClearanceHold(bool clearanceHold) { var r=new Rig();r.InitialGrant(); P.Job held; if(clearanceHold) { held=new P.Job {Id="old-bore-hold",Owner=MinerId,Kind=P.JobKind.Survey,Entry=Vector3D.Forward*1000,Radius=3,Depth=1}; r.Holds.Add(held.Id,held); } else { r.Fleet.Command("mark 6 6 20 GPS:entry:0:0:-1000: GPS:inward:0:0:-1001:"); held=Assert.Single(r.Fleet.GetJobs());r.FleetRig.Planner.SetOwner(held,MinerId); } r.Sent.Clear();r.Program.Now+=6;r.Request(r.Miner()); Assert.Equal(OldAddress,r.Registered.Address);Assert.Equal(MinerId,held.Owner); Assert.DoesNotContain(r.Sent,d=>d.Address==NewAddress || d.Packet.Kind=="JOB" || d.Packet.Kind=="DOCK_GRANT"); if(clearanceHold)Assert.Same(held,r.Holds[held.Id]);else Assert.Same(held,r.FleetRig.Planner.OwnedJobs[MinerId]); } [Theory] [InlineData(P.FlightState.Drilling,"")] [InlineData(P.FlightState.Returning,"replacement-task")] public void ARebuiltShipMustBeReturningWithoutATask(P.FlightState state,string job) { var r=new Rig();r.InitialGrant();r.Program.Now+=6;var replacement=r.Miner();replacement.State=state;replacement.JobId=job; r.Request(replacement);Assert.Equal(OldAddress,r.Registered.Address);Assert.Empty(r.Sent); } [Fact] public void RestoredObsoleteRegistrationAndBerthCanRecoverToTheRebuiltShip() { var before=new Rig();var old=before.InitialGrant();var storage=before.FleetRig.Checkpoint(); var after=new Rig();after.FleetRig.Restore(storage);Assert.Equal(OldAddress,after.Registered.Address); Assert.Empty(P.Data.Text(after.FleetRig.Save(),"fleet","LaneToken"));after.Program.Now+=6; after.Request(after.Miner());var delivery=Assert.Single(after.Sent,d=>d.Packet.Kind=="DOCK_GRANT"); var frame=P.DockFrame.FromIni(delivery.Packet.Body);Assert.Equal(NewAddress,delivery.Address);Assert.Equal(Berth,frame.ConnectorId); Assert.Equal(2,after.Registered.WaitingSlot);Assert.Equal(NewAddress,after.Registered.Address); Assert.NotEqual(P.DockFrame.FromIni(old.Packet.Body).Token,frame.Token);Assert.NotEmpty(frame.Token); var saved=after.FleetRig.Save();Assert.Equal(1,P.Data.ReadLong(saved,"fleet","LaneCount"));Assert.Equal(0,P.Data.ReadLong(saved,"fleet","ProtectedOwnerCount")); } } }