using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame;
using VRageMath;
using Xunit;
using P = AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class InstantBerthTests
{
static void Returning(FleetStateTests.Rig rig, int index = 0, double distance = 100)
{
var miner = rig.Miners[index];
miner.State = P.FlightState.Returning;
miner.Connected = false;
miner.Position += Vector3D.Forward * distance;
}
static P.DockFrame Grant(FleetStateTests.Rig rig, string miner)
{
var packet = Assert.Single(rig.Sent, p => p.Kind == "DOCK_GRANT" && p.Target == miner);
var pose = P.DockFrame.FromIni(packet.Body);
Assert.Equal(miner, pose.Owner);
Assert.Equal("base", pose.BaseId);
Assert.NotEmpty(pose.Token);
Assert.Equal("", pose.Problem);
return pose;
}
static void Near(Vector3D expected, Vector3D actual)
{ Assert.True(Vector3D.Distance(expected, actual) < .000001, expected + " vs " + actual); }
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(true, true)]
public void ReturningRequestReceivesItsBerthBeforeAnyTickWhetherRegisteredOrBasePaused(bool registered, bool paused)
{
var rig = new FleetStateTests.Rig(1, false, "[Dock]\nStableSeconds=30\n");
Returning(rig);
if (registered) rig.RegisterAll();
if (paused) { rig.Fleet.Command("start"); rig.Fleet.Command("pause"); }
rig.Sent.Clear();
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
Assert.Equal(0, rig.Program.Now);
var pose = Grant(rig, rig.Miners[0].Id);
Assert.Equal(100, pose.ConnectorId);
Assert.Equal(30, pose.ApproachDistance);
Assert.Equal(5000, Assert.Single(rig.Fleet.GetTelemetry()).Address);
// The first motion sample remains unqualified for an actual docking
// maneuver. That must not prevent choosing the free connector.
pose.ReceivedAt = rig.Program.Now;
Assert.False(pose.IsWindow(rig.Program.Config, rig.Program.Now));
}
[Fact]
public void SeveralFirstRequestsReceiveDifferentAvailableBerthsWithoutAdvancingTheSchedulerClock()
{
var rig = new FleetStateTests.Rig(3, false);
for (int i = 0; i < rig.Miners.Count; i++)
{
Returning(rig, i);
rig.Receive("DOCK_REQUEST", rig.Miners[i]);
Assert.Equal(i + 1, rig.Sent.Count(p => p.Kind == "DOCK_GRANT"));
}
var grants = rig.Miners.Select(t => Grant(rig, t.Id)).ToList();
Assert.Equal(0, rig.Program.Now);
Assert.Equal(3, grants.Select(p => p.ConnectorId).Distinct().Count());
Assert.Equal(new[] { 30d, 40d, 50d }, grants.Select(p => p.ApproachDistance).OrderBy(d => d));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void RequestSamplesCurrentRotatingBerthEvenBeforeTheNextFrameUpdate(bool hadEarlierFrame)
{
var rig = new FleetStateTests.Rig(1, false);
Returning(rig);
if (hadEarlierFrame) rig.Tick(1);
var now = rig.Program.Now;
var position = new Vector3D(7, -3, 11);
var forward = Vector3D.Normalize(new Vector3D(1, 2, -1));
var center = new Vector3D(-12, 4, 6);
var velocity = new Vector3D(2, 1, -3);
var angular = new Vector3D(0, .04, 0);
rig.SetPort(0, position, forward);
var controller = (IMyShipController)typeof(P.FleetController).GetField("controller", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rig.Fleet);
var stub = (Stub<IMyShipController>)RemotingServices.GetRealProxy(controller);
stub.Set("CenterOfMass", center).Method("GetShipVelocities", a => new MyShipVelocities(velocity, angular));
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
var pose = Grant(rig, rig.Miners[0].Id);
Assert.Equal(now, rig.Program.Now);
Near(position, pose.Position); Near(forward, pose.Forward);
Near(center, pose.Center); Near(velocity, pose.Velocity); Near(angular, pose.AngularVelocity);
Near(position + forward * pose.ApproachDistance, pose.Position + pose.Forward * pose.ApproachDistance);
pose.ReceivedAt = now;
Assert.False(pose.IsWindow(rig.Program.Config, now));
}
[Theory]
[InlineData(15)]
[InlineData(100)]
public void RestoredInboundOwnerReceivesANewGrantOnItsFreshRequestWithoutAStabilityWait(double distance)
{
var before = new FleetStateTests.Rig(1, false);
Returning(before, distance: distance);
before.Receive("DOCK_REQUEST", before.Miners[0]);
var original = Grant(before, before.Miners[0].Id);
var saved = before.Checkpoint();
var after = new FleetStateTests.Rig(1, false, "[Dock]\nStableSeconds=30\n");
after.Restore(saved);
Returning(after, distance: distance);
after.RegisterAll();
Assert.DoesNotContain(after.Sent, p => p.Kind == "DOCK_GRANT");
after.Receive("DOCK_REQUEST", after.Miners[0]);
var renewed = Grant(after, after.Miners[0].Id);
Assert.Equal(0, after.Program.Now);
Assert.NotEqual(original.Token, renewed.Token);
Assert.Equal(original.ConnectorId, renewed.ConnectorId);
Assert.Equal(original.ApproachDistance, renewed.ApproachDistance);
}
[Fact]
public void FirstRequestCannotClaimAConnectorThatIsPhysicallyOccupied()
{
var rig = new FleetStateTests.Rig(1, true);
Returning(rig);
rig.Miners[0].ConnectorId = 9999;
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
Assert.Single(rig.Fleet.GetTelemetry());
Assert.DoesNotContain(rig.Sent, p => p.Kind == "DOCK_GRANT");
Assert.Equal(0, P.Data.ReadLong(rig.Save(), "fleet", "LaneCount"));
}
static void CloseCachedBerth(FleetStateTests.Rig rig,long connector)
{
var ports=(System.Collections.Generic.List<IMyShipConnector>)typeof(P.FleetController).GetField("docks",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(rig.Fleet);
var port=ports.Single(p=>p.EntityId==connector);
((Stub<IMyShipConnector>)RemotingServices.GetRealProxy(port)).Set("Closed",true).Set("IsFunctional",true);
Assert.True(port.Closed);Assert.True(port.IsFunctional);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void RemovedCachedBerthCannotBlockTheRemainingFreeBerthBeforeHardwareRescan(bool restoredAssignment)
{
var rig=new FleetStateTests.Rig(2,false);Returning(rig);
if(restoredAssignment)
{
rig.Receive("DOCK_REQUEST",rig.Miners[0]);Assert.Equal(100,Grant(rig,rig.Miners[0].Id).ConnectorId);
var saved=rig.Checkpoint();rig=new FleetStateTests.Rig(2,false);rig.Restore(saved);Returning(rig);
}
CloseCachedBerth(rig,100);rig.Sent.Clear();
rig.Receive("DOCK_REQUEST",rig.Miners[0]);
Assert.Equal(0,rig.Program.Now);Assert.Equal(101,Grant(rig,rig.Miners[0].Id).ConnectorId);
Assert.Equal(1,P.Data.ReadLong(rig.Save(),"fleet","LaneCount"));
}
[Fact]
public void ARemovedBerthCannotRenewAStillNearbyRestoredAssignment()
{
var before=new FleetStateTests.Rig(2,false);Returning(before,distance:15);before.Receive("DOCK_REQUEST",before.Miners[0]);
Assert.Equal(100,Grant(before,before.Miners[0].Id).ConnectorId);
var after=new FleetStateTests.Rig(2,false);after.Restore(before.Checkpoint());Returning(after,distance:15);
CloseCachedBerth(after,100);after.Receive("DOCK_REQUEST",after.Miners[0]);
Assert.DoesNotContain(after.Sent,p=>p.Kind=="DOCK_GRANT");
Assert.Empty(P.Data.Text(after.Save(),"fleet","LaneToken"));
Assert.Equal(100,P.Data.ReadLong(after.Save(),"fleet","LaneDock"));
}
[Fact]
public void AFirstRequestOverTheWireRegistersAndReceivesTheGrantInTheSameDrain()
{
var rig = new FleetStateTests.Rig(1, false);
Returning(rig);
var incoming = new System.Collections.Generic.Queue<MyIGCMessage>();
var listener = (Stub<IMyUnicastListener>)RemotingServices.GetRealProxy(rig.Program.IGC.UnicastListener);
listener.Method("get_HasPendingMessage", a => incoming.Count > 0).Method("AcceptMessage", a => incoming.Dequeue());
var packet = new P.Packet { From = rig.Miners[0].Id, Target = "base", Kind = "DOCK_REQUEST", Session = "fresh-miner", Id = "fresh-miner-1", Seq = 1, Reliable = true, Body = rig.Miners[0].ToIni() };
incoming.Enqueue(new MyIGCMessage(packet.Encode("mining"), "AMS2/mining", 5000));
rig.Program.Bus.Drain(rig.Fleet.Receive);
Assert.Empty(incoming);
Assert.Equal(0, rig.Program.Now);
Grant(rig, rig.Miners[0].Id);
Assert.Equal(5000, Assert.Single(rig.Fleet.GetTelemetry()).Address);
}
[Fact]
public void FirstRequestStillHonorsTheRegistryCapacityLimit()
{
var rig = new FleetStateTests.Rig(2, false, "[Fleet]\nMaxMiners=1\n");
Returning(rig, 0); Returning(rig, 1);
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
Grant(rig, rig.Miners[0].Id);
rig.Receive("DOCK_REQUEST", rig.Miners[1]);
Assert.Equal(rig.Miners[0].Id, Assert.Single(rig.Fleet.GetTelemetry()).Id);
Assert.DoesNotContain(rig.Sent, p => p.Kind == "DOCK_GRANT" && p.Target == rig.Miners[1].Id);
Assert.Equal(1, P.Data.ReadLong(rig.Save(), "fleet", "LaneCount"));
}
}
}
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame;
using VRageMath;
using Xunit;
using P = AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class InstantBerthTests
{
static void Returning(FleetStateTests.Rig rig, int index = 0, double distance = 100)
{
var miner = rig.Miners[index];
miner.State = P.FlightState.Returning;
miner.Connected = false;
miner.Position += Vector3D.Forward * distance;
}
static P.DockFrame Grant(FleetStateTests.Rig rig, string miner)
{
var packet = Assert.Single(rig.Sent, p => p.Kind == "DOCK_GRANT" && p.Target == miner);
var pose = P.DockFrame.FromIni(packet.Body);
Assert.Equal(miner, pose.Owner);
Assert.Equal("base", pose.BaseId);
Assert.NotEmpty(pose.Token);
Assert.Equal("", pose.Problem);
return pose;
}
static void Near(Vector3D expected, Vector3D actual)
{ Assert.True(Vector3D.Distance(expected, actual) < .000001, expected + " vs " + actual); }
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(true, true)]
public void ReturningRequestReceivesItsBerthBeforeAnyTickWhetherRegisteredOrBasePaused(bool registered, bool paused)
{
var rig = new FleetStateTests.Rig(1, false, "[Dock]\nStableSeconds=30\n");
Returning(rig);
if (registered) rig.RegisterAll();
if (paused) { rig.Fleet.Command("start"); rig.Fleet.Command("pause"); }
rig.Sent.Clear();
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
Assert.Equal(0, rig.Program.Now);
var pose = Grant(rig, rig.Miners[0].Id);
Assert.Equal(100, pose.ConnectorId);
Assert.Equal(30, pose.ApproachDistance);
Assert.Equal(5000, Assert.Single(rig.Fleet.GetTelemetry()).Address);
// The first motion sample remains unqualified for an actual docking
// maneuver. That must not prevent choosing the free connector.
pose.ReceivedAt = rig.Program.Now;
Assert.False(pose.IsWindow(rig.Program.Config, rig.Program.Now));
}
[Fact]
public void SeveralFirstRequestsReceiveDifferentAvailableBerthsWithoutAdvancingTheSchedulerClock()
{
var rig = new FleetStateTests.Rig(3, false);
for (int i = 0; i < rig.Miners.Count; i++)
{
Returning(rig, i);
rig.Receive("DOCK_REQUEST", rig.Miners[i]);
Assert.Equal(i + 1, rig.Sent.Count(p => p.Kind == "DOCK_GRANT"));
}
var grants = rig.Miners.Select(t => Grant(rig, t.Id)).ToList();
Assert.Equal(0, rig.Program.Now);
Assert.Equal(3, grants.Select(p => p.ConnectorId).Distinct().Count());
Assert.Equal(new[] { 30d, 40d, 50d }, grants.Select(p => p.ApproachDistance).OrderBy(d => d));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void RequestSamplesCurrentRotatingBerthEvenBeforeTheNextFrameUpdate(bool hadEarlierFrame)
{
var rig = new FleetStateTests.Rig(1, false);
Returning(rig);
if (hadEarlierFrame) rig.Tick(1);
var now = rig.Program.Now;
var position = new Vector3D(7, -3, 11);
var forward = Vector3D.Normalize(new Vector3D(1, 2, -1));
var center = new Vector3D(-12, 4, 6);
var velocity = new Vector3D(2, 1, -3);
var angular = new Vector3D(0, .04, 0);
rig.SetPort(0, position, forward);
var controller = (IMyShipController)typeof(P.FleetController).GetField("controller", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rig.Fleet);
var stub = (Stub<IMyShipController>)RemotingServices.GetRealProxy(controller);
stub.Set("CenterOfMass", center).Method("GetShipVelocities", a => new MyShipVelocities(velocity, angular));
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
var pose = Grant(rig, rig.Miners[0].Id);
Assert.Equal(now, rig.Program.Now);
Near(position, pose.Position); Near(forward, pose.Forward);
Near(center, pose.Center); Near(velocity, pose.Velocity); Near(angular, pose.AngularVelocity);
Near(position + forward * pose.ApproachDistance, pose.Position + pose.Forward * pose.ApproachDistance);
pose.ReceivedAt = now;
Assert.False(pose.IsWindow(rig.Program.Config, now));
}
[Theory]
[InlineData(15)]
[InlineData(100)]
public void RestoredInboundOwnerReceivesANewGrantOnItsFreshRequestWithoutAStabilityWait(double distance)
{
var before = new FleetStateTests.Rig(1, false);
Returning(before, distance: distance);
before.Receive("DOCK_REQUEST", before.Miners[0]);
var original = Grant(before, before.Miners[0].Id);
var saved = before.Checkpoint();
var after = new FleetStateTests.Rig(1, false, "[Dock]\nStableSeconds=30\n");
after.Restore(saved);
Returning(after, distance: distance);
after.RegisterAll();
Assert.DoesNotContain(after.Sent, p => p.Kind == "DOCK_GRANT");
after.Receive("DOCK_REQUEST", after.Miners[0]);
var renewed = Grant(after, after.Miners[0].Id);
Assert.Equal(0, after.Program.Now);
Assert.NotEqual(original.Token, renewed.Token);
Assert.Equal(original.ConnectorId, renewed.ConnectorId);
Assert.Equal(original.ApproachDistance, renewed.ApproachDistance);
}
[Fact]
public void FirstRequestCannotClaimAConnectorThatIsPhysicallyOccupied()
{
var rig = new FleetStateTests.Rig(1, true);
Returning(rig);
rig.Miners[0].ConnectorId = 9999;
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
Assert.Single(rig.Fleet.GetTelemetry());
Assert.DoesNotContain(rig.Sent, p => p.Kind == "DOCK_GRANT");
Assert.Equal(0, P.Data.ReadLong(rig.Save(), "fleet", "LaneCount"));
}
static void CloseCachedBerth(FleetStateTests.Rig rig,long connector)
{
var ports=(System.Collections.Generic.List<IMyShipConnector>)typeof(P.FleetController).GetField("docks",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(rig.Fleet);
var port=ports.Single(p=>p.EntityId==connector);
((Stub<IMyShipConnector>)RemotingServices.GetRealProxy(port)).Set("Closed",true).Set("IsFunctional",true);
Assert.True(port.Closed);Assert.True(port.IsFunctional);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void RemovedCachedBerthCannotBlockTheRemainingFreeBerthBeforeHardwareRescan(bool restoredAssignment)
{
var rig=new FleetStateTests.Rig(2,false);Returning(rig);
if(restoredAssignment)
{
rig.Receive("DOCK_REQUEST",rig.Miners[0]);Assert.Equal(100,Grant(rig,rig.Miners[0].Id).ConnectorId);
var saved=rig.Checkpoint();rig=new FleetStateTests.Rig(2,false);rig.Restore(saved);Returning(rig);
}
CloseCachedBerth(rig,100);rig.Sent.Clear();
rig.Receive("DOCK_REQUEST",rig.Miners[0]);
Assert.Equal(0,rig.Program.Now);Assert.Equal(101,Grant(rig,rig.Miners[0].Id).ConnectorId);
Assert.Equal(1,P.Data.ReadLong(rig.Save(),"fleet","LaneCount"));
}
[Fact]
public void ARemovedBerthCannotRenewAStillNearbyRestoredAssignment()
{
var before=new FleetStateTests.Rig(2,false);Returning(before,distance:15);before.Receive("DOCK_REQUEST",before.Miners[0]);
Assert.Equal(100,Grant(before,before.Miners[0].Id).ConnectorId);
var after=new FleetStateTests.Rig(2,false);after.Restore(before.Checkpoint());Returning(after,distance:15);
CloseCachedBerth(after,100);after.Receive("DOCK_REQUEST",after.Miners[0]);
Assert.DoesNotContain(after.Sent,p=>p.Kind=="DOCK_GRANT");
Assert.Empty(P.Data.Text(after.Save(),"fleet","LaneToken"));
Assert.Equal(100,P.Data.ReadLong(after.Save(),"fleet","LaneDock"));
}
[Fact]
public void AFirstRequestOverTheWireRegistersAndReceivesTheGrantInTheSameDrain()
{
var rig = new FleetStateTests.Rig(1, false);
Returning(rig);
var incoming = new System.Collections.Generic.Queue<MyIGCMessage>();
var listener = (Stub<IMyUnicastListener>)RemotingServices.GetRealProxy(rig.Program.IGC.UnicastListener);
listener.Method("get_HasPendingMessage", a => incoming.Count > 0).Method("AcceptMessage", a => incoming.Dequeue());
var packet = new P.Packet { From = rig.Miners[0].Id, Target = "base", Kind = "DOCK_REQUEST", Session = "fresh-miner", Id = "fresh-miner-1", Seq = 1, Reliable = true, Body = rig.Miners[0].ToIni() };
incoming.Enqueue(new MyIGCMessage(packet.Encode("mining"), "AMS2/mining", 5000));
rig.Program.Bus.Drain(rig.Fleet.Receive);
Assert.Empty(incoming);
Assert.Equal(0, rig.Program.Now);
Grant(rig, rig.Miners[0].Id);
Assert.Equal(5000, Assert.Single(rig.Fleet.GetTelemetry()).Address);
}
[Fact]
public void FirstRequestStillHonorsTheRegistryCapacityLimit()
{
var rig = new FleetStateTests.Rig(2, false, "[Fleet]\nMaxMiners=1\n");
Returning(rig, 0); Returning(rig, 1);
rig.Receive("DOCK_REQUEST", rig.Miners[0]);
Grant(rig, rig.Miners[0].Id);
rig.Receive("DOCK_REQUEST", rig.Miners[1]);
Assert.Equal(rig.Miners[0].Id, Assert.Single(rig.Fleet.GetTelemetry()).Id);
Assert.DoesNotContain(rig.Sent, p => p.Kind == "DOCK_GRANT" && p.Target == rig.Miners[1].Id);
Assert.Equal(1, P.Data.ReadLong(rig.Save(), "fleet", "LaneCount"));
}
}
}