using System;
using System.Linq;
using System.Reflection;
using Xunit;
using VRageMath;
using P = AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class DiscoveryDispatchTests
{
const BindingFlags Private = BindingFlags.Instance | BindingFlags.NonPublic;
static void Set(object o, string field, object value) => o.GetType().GetField(field, Private).SetValue(o, value);
static void Assign(FleetStateTests.Rig r) => r.Fleet.GetType().GetMethod("Assign", Private).Invoke(r.Fleet, null);
static FleetStateTests.Rig ScanningFleet()
{
var r = new FleetStateTests.Rig(3, true);
r.RegisterAll(); r.Fleet.Command("survey 300");
while (r.Planner.Generating) r.Planner.GenerateStep();
var entries = r.Planner.Jobs.GroupBy(j => j.Entry).Take(3).Select(g => g.First()).ToArray();
for (int n = 0; n < 3; n++)
{
r.SetConnected(n, false);
var t = r.Miners[n]; var j = entries[n];
r.Planner.SetOwner(j, t.Id); t.JobId = j.Id; t.Position = j.Entry; t.State = P.FlightState.Survey;
r.Receive("TELEMETRY", t);
}
r.Fleet.Command("start"); r.Sent.Clear(); Set(r.Fleet, "assignMinerCursor", 0);
return r;
}
static P.JobReport Detection(FleetStateTests.Rig r)
{
var t = r.Miners[2];
return new P.JobReport { JobId = t.JobId, Outcome = "SurveyHit", Position = Vector3D.Forward * 150, Direction = Vector3D.Forward, Up = Vector3D.Up, EntityId = 4242 };
}
static void Report(FleetStateTests.Rig r, P.JobReport hit)
=> r.Fleet.Receive(new P.Packet { Kind = "RESULT", From = r.Miners[2].Id, Source = 5002, Body = hit.ToIni() });
static void Ready(FleetStateTests.Rig r, P.JobReport hit)
{
var t = r.Miners[2]; t.JobId = ""; t.NextAfter = hit.JobId; r.Receive("TELEMETRY", t);
}
static P.Packet NextAssignment(FleetStateTests.Rig r)
{
// A 294-ray search is intentionally processed in bounded chunks. The
// next completed pass must concern the discoverer, not a fleet lap.
for (int n = 0; n < 12 && !r.Sent.Any(p => p.Kind == "JOB"); n++) Assign(r);
return Assert.Single(r.Sent, p => p.Kind == "JOB");
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void NewAsteroidDispatchesOneProbeBeforeAnotherFleetLapWithoutCancellingOtherScans(bool telemetryFirst)
{
var r = ScanningFleet(); var held = r.Miners.Take(2).Select(t => r.Planner.Find(t.JobId)).ToArray();
var hit = Detection(r);
if (telemetryFirst) { Ready(r, hit); Report(r, hit); }
else { Report(r, hit); Ready(r, hit); }
var packet = NextAssignment(r); var probe = P.Job.FromIni(packet.Body);
Assert.Equal(r.Miners[2].Id, packet.Target); Assert.Equal(P.JobKind.Probe, probe.Kind);
Assert.Equal(hit.Position, probe.Entry); Assert.Equal(hit.EntityId, probe.EntityId);
Assert.Equal(hit.JobId, P.Data.Text(packet.Body, "next", "After"));
Assert.Equal(3, r.Planner.OwnedJobs.Count);
for (int n = 0; n < 2; n++) { Assert.Same(held[n], r.Planner.OwnedJobs[r.Miners[n].Id]); Assert.Equal("", held[n].Outcome); }
Assert.DoesNotContain(r.Sent, p => p.Kind == "COMMAND");
}
[Fact]
public void NewAsteroidDoesNotPreemptAnOccupiedBore()
{
var r = ScanningFleet(); var bore = r.Planner.Find(r.Miners[0].JobId);
bore.Kind = P.JobKind.Probe; bore.Entry = Vector3D.Right * 240; bore.Direction = Vector3D.Forward; bore.Depth = 40;
r.Miners[0].State = P.FlightState.Drilling; r.Miners[0].Position = bore.Entry + bore.Direction * 8;
r.Receive("TELEMETRY", r.Miners[0]);
var hit = Detection(r); Report(r, hit); Ready(r, hit);
Assert.Equal(P.JobKind.Probe, P.Job.FromIni(NextAssignment(r).Body).Kind);
Assert.Same(bore, r.Planner.OwnedJobs[r.Miners[0].Id]); Assert.Equal(P.FlightState.Drilling, r.Fleet.GetTelemetry().Single(t => t.Id == r.Miners[0].Id).State);
Assert.DoesNotContain(r.Sent, p => p.Kind == "COMMAND");
}
[Fact]
public void UnfitDiscovererLeavesTheProbeForAnotherAvailableMiner()
{
var r = ScanningFleet(); var hit = Detection(r); Report(r, hit);
r.Miners[2].Battery = .1; Ready(r, hit);
// Miner 0 becomes free at its dock; miner 1 retains its active ray.
var previous = r.Planner.Find(r.Miners[0].JobId); r.Planner.Report(previous, new P.JobReport { Outcome = "SurveyEmpty" });
r.SetConnected(0, true); r.Miners[0].JobId = ""; r.Miners[0].State = P.FlightState.Servicing; r.Receive("TELEMETRY", r.Miners[0]);
var active = r.Planner.Find(r.Miners[1].JobId); var packet = NextAssignment(r);
Assert.Equal(r.Miners[0].Id, packet.Target); Assert.Equal(P.JobKind.Probe, P.Job.FromIni(packet.Body).Kind);
Assert.Empty(r.Planner.Jobs.Single(j => j.Id == hit.JobId).Owner);
Assert.Same(active, r.Planner.OwnedJobs[r.Miners[1].Id]);
}
[Fact]
public void DuplicateDetectionDoesNotCreateASecondProbeOrResetAnActiveScanPass()
{
var r = ScanningFleet(); var hit = Detection(r); Report(r, hit); Ready(r, hit);
var assigned = P.Job.FromIni(NextAssignment(r).Body);
Set(r.Fleet, "assignMinerCursor", 1); r.Receive("TELEMETRY", r.Miners[2]); Report(r, hit);
Assert.Equal(1, (int)r.Fleet.GetType().GetField("assignMinerCursor", Private).GetValue(r.Fleet));
Assert.Single(r.Planner.Jobs, j => j.Kind == P.JobKind.Probe);
Assert.Equal(assigned.Id, r.Planner.OwnedJobs[r.Miners[2].Id].Id);
}
}
}
using System;
using System.Linq;
using System.Reflection;
using Xunit;
using VRageMath;
using P = AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class DiscoveryDispatchTests
{
const BindingFlags Private = BindingFlags.Instance | BindingFlags.NonPublic;
static void Set(object o, string field, object value) => o.GetType().GetField(field, Private).SetValue(o, value);
static void Assign(FleetStateTests.Rig r) => r.Fleet.GetType().GetMethod("Assign", Private).Invoke(r.Fleet, null);
static FleetStateTests.Rig ScanningFleet()
{
var r = new FleetStateTests.Rig(3, true);
r.RegisterAll(); r.Fleet.Command("survey 300");
while (r.Planner.Generating) r.Planner.GenerateStep();
var entries = r.Planner.Jobs.GroupBy(j => j.Entry).Take(3).Select(g => g.First()).ToArray();
for (int n = 0; n < 3; n++)
{
r.SetConnected(n, false);
var t = r.Miners[n]; var j = entries[n];
r.Planner.SetOwner(j, t.Id); t.JobId = j.Id; t.Position = j.Entry; t.State = P.FlightState.Survey;
r.Receive("TELEMETRY", t);
}
r.Fleet.Command("start"); r.Sent.Clear(); Set(r.Fleet, "assignMinerCursor", 0);
return r;
}
static P.JobReport Detection(FleetStateTests.Rig r)
{
var t = r.Miners[2];
return new P.JobReport { JobId = t.JobId, Outcome = "SurveyHit", Position = Vector3D.Forward * 150, Direction = Vector3D.Forward, Up = Vector3D.Up, EntityId = 4242 };
}
static void Report(FleetStateTests.Rig r, P.JobReport hit)
=> r.Fleet.Receive(new P.Packet { Kind = "RESULT", From = r.Miners[2].Id, Source = 5002, Body = hit.ToIni() });
static void Ready(FleetStateTests.Rig r, P.JobReport hit)
{
var t = r.Miners[2]; t.JobId = ""; t.NextAfter = hit.JobId; r.Receive("TELEMETRY", t);
}
static P.Packet NextAssignment(FleetStateTests.Rig r)
{
// A 294-ray search is intentionally processed in bounded chunks. The
// next completed pass must concern the discoverer, not a fleet lap.
for (int n = 0; n < 12 && !r.Sent.Any(p => p.Kind == "JOB"); n++) Assign(r);
return Assert.Single(r.Sent, p => p.Kind == "JOB");
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void NewAsteroidDispatchesOneProbeBeforeAnotherFleetLapWithoutCancellingOtherScans(bool telemetryFirst)
{
var r = ScanningFleet(); var held = r.Miners.Take(2).Select(t => r.Planner.Find(t.JobId)).ToArray();
var hit = Detection(r);
if (telemetryFirst) { Ready(r, hit); Report(r, hit); }
else { Report(r, hit); Ready(r, hit); }
var packet = NextAssignment(r); var probe = P.Job.FromIni(packet.Body);
Assert.Equal(r.Miners[2].Id, packet.Target); Assert.Equal(P.JobKind.Probe, probe.Kind);
Assert.Equal(hit.Position, probe.Entry); Assert.Equal(hit.EntityId, probe.EntityId);
Assert.Equal(hit.JobId, P.Data.Text(packet.Body, "next", "After"));
Assert.Equal(3, r.Planner.OwnedJobs.Count);
for (int n = 0; n < 2; n++) { Assert.Same(held[n], r.Planner.OwnedJobs[r.Miners[n].Id]); Assert.Equal("", held[n].Outcome); }
Assert.DoesNotContain(r.Sent, p => p.Kind == "COMMAND");
}
[Fact]
public void NewAsteroidDoesNotPreemptAnOccupiedBore()
{
var r = ScanningFleet(); var bore = r.Planner.Find(r.Miners[0].JobId);
bore.Kind = P.JobKind.Probe; bore.Entry = Vector3D.Right * 240; bore.Direction = Vector3D.Forward; bore.Depth = 40;
r.Miners[0].State = P.FlightState.Drilling; r.Miners[0].Position = bore.Entry + bore.Direction * 8;
r.Receive("TELEMETRY", r.Miners[0]);
var hit = Detection(r); Report(r, hit); Ready(r, hit);
Assert.Equal(P.JobKind.Probe, P.Job.FromIni(NextAssignment(r).Body).Kind);
Assert.Same(bore, r.Planner.OwnedJobs[r.Miners[0].Id]); Assert.Equal(P.FlightState.Drilling, r.Fleet.GetTelemetry().Single(t => t.Id == r.Miners[0].Id).State);
Assert.DoesNotContain(r.Sent, p => p.Kind == "COMMAND");
}
[Fact]
public void UnfitDiscovererLeavesTheProbeForAnotherAvailableMiner()
{
var r = ScanningFleet(); var hit = Detection(r); Report(r, hit);
r.Miners[2].Battery = .1; Ready(r, hit);
// Miner 0 becomes free at its dock; miner 1 retains its active ray.
var previous = r.Planner.Find(r.Miners[0].JobId); r.Planner.Report(previous, new P.JobReport { Outcome = "SurveyEmpty" });
r.SetConnected(0, true); r.Miners[0].JobId = ""; r.Miners[0].State = P.FlightState.Servicing; r.Receive("TELEMETRY", r.Miners[0]);
var active = r.Planner.Find(r.Miners[1].JobId); var packet = NextAssignment(r);
Assert.Equal(r.Miners[0].Id, packet.Target); Assert.Equal(P.JobKind.Probe, P.Job.FromIni(packet.Body).Kind);
Assert.Empty(r.Planner.Jobs.Single(j => j.Id == hit.JobId).Owner);
Assert.Same(active, r.Planner.OwnedJobs[r.Miners[1].Id]);
}
[Fact]
public void DuplicateDetectionDoesNotCreateASecondProbeOrResetAnActiveScanPass()
{
var r = ScanningFleet(); var hit = Detection(r); Report(r, hit); Ready(r, hit);
var assigned = P.Job.FromIni(NextAssignment(r).Body);
Set(r.Fleet, "assignMinerCursor", 1); r.Receive("TELEMETRY", r.Miners[2]); Report(r, hit);
Assert.Equal(1, (int)r.Fleet.GetType().GetField("assignMinerCursor", Private).GetValue(r.Fleet));
Assert.Single(r.Planner.Jobs, j => j.Kind == P.JobKind.Probe);
Assert.Equal(assigned.Id, r.Planner.OwnedJobs[r.Miners[2].Id].Id);
}
}
}