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<MyIGCMessage> Incoming=new Queue<MyIGCMessage>();
public readonly List<Delivery> Sent=new List<Delivery>();
public P Program=>FleetRig.Program;
public P.FleetController Fleet=>FleetRig.Fleet;
public Rig()
{
var ports=(List<Stub<IMyShipConnector>>)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<IMyUnicastListener>().Method("get_HasPendingMessage",a=>Incoming.Count>0).Method("AcceptMessage",a=>Incoming.Dequeue());
var igc=new Stub<IMyIntergridCommunicationSystem>().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<string,P.Job> Holds=>(Dictionary<string,P.Job>)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"));
}
}
}
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<MyIGCMessage> Incoming=new Queue<MyIGCMessage>();
public readonly List<Delivery> Sent=new List<Delivery>();
public P Program=>FleetRig.Program;
public P.FleetController Fleet=>FleetRig.Fleet;
public Rig()
{
var ports=(List<Stub<IMyShipConnector>>)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<IMyUnicastListener>().Method("get_HasPendingMessage",a=>Incoming.Count>0).Method("AcceptMessage",a=>Incoming.Dequeue());
var igc=new Stub<IMyIntergridCommunicationSystem>().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<string,P.Job> Holds=>(Dictionary<string,P.Job>)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"));
}
}
}