XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
UTF-8
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 FleetResetTests
    {
        const BindingFlags Private = BindingFlags.Instance | BindingFlags.NonPublic;
        const string Configuration = "[System]\nId=base\nFleetId=mining\nBaseId=base\n[Fleet]\nDockGroup=AMS Docks\n; keep my configuration\n";
        static T Read<T>(object o, string name) { return (T)o.GetType().GetField(name, Private).GetValue(o); }
        static void Write(object o, string name, object value) { o.GetType().GetField(name, Private).SetValue(o, value); }
        static MyIni Save(P.RoleLogic role) { var data = new MyIni(); role.Save(data); return data; }
        static P.FleetController Current(P program) { return Assert.IsType<P.FleetController>(Read<P.RoleLogic>(program, "role")); }
        static FleetStateTests.Rig ActiveFleet()
        {
            var r = new FleetStateTests.Rig(2, false);
            r.Program.Me.CustomData = Configuration; Write(r.Program, "role", r.Fleet);
            r.RegisterAll(); r.Tick(30); r.Receive("DOCK_REQUEST", r.Miners[0]); r.Tick(12);
            r.Fleet.Command("mark 24 6 20 GPS:entry:0:0:-200: GPS:inside:0:0:-201:"); r.Tick(10);
            r.Fleet.Command("start"); r.Checkpoint();
            Assert.NotEmpty(r.Fleet.GetJobs()); Assert.NotEmpty(r.Fleet.GetTelemetry());
            Assert.True(P.Data.ReadLong(r.Save(), "fleet", "LaneCount") > 0);
            return r;
        }
        static void AssertEmpty(P.FleetController fleet)
        {
            Assert.False(fleet.Restoring); Assert.Empty(fleet.GetJobs()); Assert.Empty(fleet.GetTelemetry());
            var data = Save(fleet); Assert.False(P.Data.Flag(data, "fleet", "Running"));
            foreach (var key in new[] { "MinerCount", "LaneCount", "CancellationCount", "ClearanceCount", "ProtectedOwnerCount", "MarkerCount", "OperatorStopCount", "NextWaitingSlot", "TokenSerial" })
                Assert.Equal(0, P.Data.ReadLong(data, "fleet", key));
            Assert.Equal(0, Read<P.TaskPlanner>(fleet, "planner").TaskSerial);
        }

        [Theory]
        [InlineData("init")]
        [InlineData("INIT")]
        [InlineData("  init  ")]
        [InlineData("初始化")]
        public void MainInitDropsAllSavedAndInMemoryWorkWithoutChangingConfiguration(string command)
        {
            if (command == "初始化" && P.L.Language != "zh-CN") return;
            var r = ActiveFleet(); var previousStorage = r.Program.Storage; var previousSession = r.Program.Bus.Session;
            r.Program.Events.Enqueue("old event"); r.Sent.Clear();
            r.Program.Main(command, UpdateType.Trigger);
            var reset = Current(r.Program); Assert.NotSame(r.Fleet, reset); AssertEmpty(reset);
            Assert.Equal(Configuration, r.Program.Me.CustomData); Assert.Equal("base", r.Program.Config.Id);
            Assert.Equal("fleet", r.Program.Config.Role); Assert.NotEqual(previousStorage, r.Program.Storage);
            Assert.Equal(P.FleetController.ResetStorage(r.Program.Bus.Epoch), r.Program.Storage);
            Assert.True(r.Program.Bus.Epoch > 0); Assert.NotEqual(previousSession, r.Program.Bus.Session);
            Assert.Equal(0, r.Program.Bus.PendingCount); Assert.DoesNotContain("old event", r.Program.Events);
            var recall = Assert.Single(r.Sent); Assert.Equal("COMMAND", recall.Kind);
            Assert.Equal("", recall.Target); Assert.Equal("return", P.Data.Text(recall.Body, "command", "Text"));
            Assert.Equal(r.Program.Bus.Epoch, recall.Epoch);
        }

        [Theory]
        [InlineData("legacy")]
        [InlineData("huge legacy")]
        [InlineData("corrupt current")]
        [InlineData("half restored")]
        [InlineData("initialization failed")]
        public void InitPreemptsUnreadableOrPartiallyRestoredStorage(string scenario)
        {
            var r = ActiveFleet();
            var old = scenario == "huge legacy" ? "[planner]\nJobs=" + new string('x', 1600000) :
                scenario == "legacy" ? "[storage]\nVersion=2\nRole=fleet\nId=base\n" : "XFEAMS-FLEET-3\n999999:broken";
            if (scenario == "half restored")
            {
                old = r.Program.Storage; var restoring = new P.FleetController(r.Program);
                restoring.BeginRestoreStorage(old); Write(r.Program, "role", restoring); Assert.True(restoring.Restoring);
            }
            if (scenario == "initialization failed") { Write(r.Program, "role", null); Write(r.Program, "storageFailure", true); }
            TestRig.SetBase(r.Program, "Storage", old); r.Program.Main("init", UpdateType.Trigger);
            AssertEmpty(Current(r.Program)); Assert.DoesNotContain(old, r.Program.Storage);
            Assert.Equal(P.FleetController.ResetStorage(r.Program.Bus.Epoch), r.Program.Storage);
        }

        [Fact]
        public void ResetMarkerIsDurableEvenWhenUserConfigurationIsInvalid()
        {
            var r = ActiveFleet(); r.Program.Me.CustomData = "[broken";
            r.Program.Main("init", UpdateType.Trigger);
            Assert.Null(Read<P.RoleLogic>(r.Program, "role")); Assert.Equal("[broken", r.Program.Me.CustomData);
            Assert.Equal(0, r.Program.Bus.PendingCount); var marker = r.Program.Storage;
            Assert.StartsWith("XFEAMS-RESET-1\n", marker);
            var restart = new FleetStateTests.Rig(2, false); restart.Restore(marker); AssertEmpty(restart.Fleet);
            r.Program.Me.CustomData = Configuration; r.Program.Main("init", UpdateType.Trigger); AssertEmpty(Current(r.Program));
        }

        [Theory]
        [InlineData(false)]
        [InlineData(true)]
        public void LegacyIniIsNeverMigratedOrReplacedDuringOrdinaryRestore(bool large)
        {
            var r = new FleetStateTests.Rig(0, false);
            var text = "[storage]\nVersion=2\nRole=fleet\nId=base\n[planner]\nJobs=0\n" + (large ? ";" + new string('x', 1000000) : "");
            TestRig.SetBase(r.Program, "Storage", text);
            Assert.Equal(P.L.FleetLegacyResetRequired, Assert.Throws<ArgumentException>(() => r.Fleet.BeginRestoreStorage(text)).Message);
            Assert.Equal(text, r.Program.Storage); Assert.False(r.Fleet.Restoring);
        }

        [Fact]
        public void EmptyResetSurvivesImmediateRestartAndCurrentCheckpointRestore()
        {
            var r = ActiveFleet(); r.Program.Main("init", UpdateType.Trigger); var epoch = r.Program.Bus.Epoch;
            var immediate = new FleetStateTests.Rig(2, false); immediate.Restore(r.Program.Storage);
            AssertEmpty(immediate.Fleet); Assert.Equal(epoch, immediate.Program.Bus.Epoch);
            var checkpoint = immediate.Checkpoint(); Assert.StartsWith("XFEAMS-FLEET-3\n", checkpoint);
            var restarted = new FleetStateTests.Rig(2, false); restarted.Restore(checkpoint);
            AssertEmpty(restarted.Fleet); Assert.Equal(epoch, restarted.Program.Bus.Epoch);
            immediate.Program.Me.CustomData = Configuration; Write(immediate.Program, "role", immediate.Fleet);
            immediate.Program.Main("init", UpdateType.Trigger); Assert.True(immediate.Program.Bus.Epoch > epoch);
        }

        [Fact]
        public void NormalCurrentFormatRestoreStillPreservesJobsAndOccupancy()
        {
            var r = ActiveFleet(); r.Program.Bus.Epoch = 42; var checkpoint = r.Checkpoint();
            var restored = new FleetStateTests.Rig(2, false); restored.Restore(checkpoint);
            Assert.Equal(42, restored.Program.Bus.Epoch); Assert.Equal(r.Fleet.GetJobs().Select(j => j.Id), restored.Fleet.GetJobs().Select(j => j.Id));
            Assert.Equal(2, restored.Fleet.GetTelemetry().Count); Assert.Equal(1, P.Data.ReadLong(restored.Save(), "fleet", "LaneCount"));
            Assert.All(restored.Fleet.GetTelemetry(), t => Assert.True(t.ReceivedAt < 0));
        }

        [Theory]
        [InlineData("init miner-0")]
        [InlineData("rescan")]
        public void TargetedInitializationAndRescanKeepFleetStorage(string command)
        {
            var r = ActiveFleet(); var storage = r.Program.Storage; var count = r.Fleet.GetJobs().Count;
            r.Sent.Clear(); r.Fleet.Command(command);
            Assert.Same(r.Fleet, Current(r.Program)); Assert.Equal(storage, r.Program.Storage); Assert.Equal(count, r.Fleet.GetJobs().Count);
            Assert.Contains(r.Sent, p => p.Kind == "COMMAND" && P.Data.Text(p.Body, "command", "Text") == "rescan");
        }

        [Fact]
        public void DirectFleetCommandUsesTheSameResetEntryPoint()
        {
            var r = ActiveFleet(); r.Fleet.Command("init"); AssertEmpty(Current(r.Program));
        }

        [Theory]
        [InlineData("MARK")]
        [InlineData("SURVEY_REQUEST")]
        [InlineData("RESULT")]
        [InlineData("DEPART_REQUEST")]
        [InlineData("HELLO")]
        [InlineData("TELEMETRY")]
        public void OldMessagesCannotRecreateWorkAfterReset(string kind)
        {
            var r = ActiveFleet(); r.Program.Main("init", UpdateType.Trigger); var reset = Current(r.Program); r.Sent.Clear();
            var body = kind == "MARK" ? P.Marking.Parse(r.Program, "mark 6 6 20 GPS:entry:0:0:-200: GPS:inside:0:0:-201:") : new MyIni();
            reset.Receive(new P.Packet { Epoch = 0, Kind = kind, From = "miner-0", Source = 5000, Body = body });
            AssertEmpty(reset); Assert.Contains(r.Sent, p => p.Kind == "COMMAND" && p.Epoch == r.Program.Bus.Epoch);
        }

        [Fact]
        public void ReturningMinerMustAdoptCurrentGenerationBeforeNewAssignments()
        {
            var r = ActiveFleet(); r.Program.Main("init", UpdateType.Trigger); var reset = Current(r.Program);
            r.SetConnected(0, true);
            var old = new P.Packet { Kind = "HELLO", From = "miner-0", Source = 5000, Session = "old", Body = r.Miners[0].ToIni() };
            reset.Receive(old); Assert.Empty(reset.GetTelemetry());
            reset.Command("mark 6 6 20 GPS:entry:0:0:-200: GPS:inside:0:0:-201:"); reset.Command("start");
            for (int i = 0; i < 20; i++) { r.Program.Now += .1; reset.Tick(.1); }
            Assert.All(reset.GetJobs(), j => Assert.Equal("", j.Owner));
            var current = new P.Packet { Kind = "HELLO", Epoch = r.Program.Bus.Epoch, From = "miner-0", Source = 5000, Session = "fresh", Body = r.Miners[0].ToIni() };
            reset.Receive(current); reset.Receive(old);
            Assert.Equal(r.Program.Bus.Epoch, Assert.Single(reset.GetTelemetry()).Epoch);
            for (int i = 0; i < 20; i++) { r.Program.Now += .1; reset.Tick(.1); }
            Assert.Contains(reset.GetJobs(), j => j.Owner == "miner-0");
        }

        static P.Packet ResetPacket(P program, long epoch = 100)
        {
            var body = new MyIni(); body.Set("command", "Text", "return"); body.Set("command", "Reset", true);
            return new P.Packet { From = program.Config.BaseId, Epoch = epoch, Kind = "COMMAND", Body = body };
        }

        [Theory]
        [InlineData(P.FlightState.Transit)]
        [InlineData(P.FlightState.Survey)]
        [InlineData(P.FlightState.DockAlign)]
        [InlineData(P.FlightState.DockApproach)]
        [InlineData(P.FlightState.Holding)]
        public void AirborneResetCancelsOldWorkAndReacquiresItsReturnRoute(P.FlightState state)
        {
            var r = new MinerStateTests.MinerRig(); r.Task(); r.State(state);
            Write(r.Miner, "lane", "old-permit"); Write(r.Miner, "dock", new P.DockFrame());
            r.Program.Bus.Send("RESULT", r.Program.Config.BaseId, new MyIni(), true); r.Drill.Set("Enabled", true);
            r.Miner.Receive(ResetPacket(r.Program));
            Assert.Equal(P.FlightState.Returning, r.StateNow); Assert.Equal("", Read<string>(r.Miner, "lane")); Assert.Null(Read<P.DockFrame>(r.Miner, "dock"));
            Assert.True(Read<bool>(r.Miner, "canceling")); Assert.False(Read<bool>(r.Miner, "leaseConfirmed")); Assert.False(r.Drill.Value.Enabled);
            Assert.Equal(0, r.Program.Bus.PendingCount); Assert.Equal(100, r.Program.Bus.Epoch);
            Assert.Equal(100, P.Data.ReadLong(Save(r.Miner), "miner", "FleetEpoch"));
        }

        [Fact]
        public void BoreResetRetainsTheOriginalExitAxisUntilRetreatIsComplete()
        {
            var r = new MinerStateTests.MinerRig(); r.Task(12); r.State(P.FlightState.Drilling);
            var job = Assert.Single(r.Miner.GetJobs()); var direction = job.Direction;
            r.Drill.Set("Enabled", true); r.Miner.Receive(ResetPacket(r.Program));
            Assert.Equal(P.FlightState.Retreat, r.StateNow); Assert.Same(job, Assert.Single(r.Miner.GetJobs()));
            Assert.Equal(direction, job.Direction); Assert.Equal(12, job.Progress); Assert.True(Read<bool>(r.Miner, "insideBore")); Assert.False(r.Drill.Value.Enabled);
            var saved = Save(r.Miner); var restarted = new MinerStateTests.MinerRig(); restarted.Miner.Load(saved);
            Assert.Equal(100, restarted.Program.Bus.Epoch); Assert.True(Read<bool>(restarted.Miner, "insideBore"));
            Assert.Equal(direction, Assert.Single(restarted.Miner.GetJobs()).Direction);
        }

        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public void ResetDoesNotTakeOverManualControlOrBrokenHardware(bool manual)
        {
            var r = new MinerStateTests.MinerRig(); r.Task();
            r.State(manual ? P.FlightState.Manual : P.FlightState.Fault);
            if (manual) r.Controller.Set("IsUnderControl", true); else Write(r.Miner, "healthy", false);
            r.Miner.Receive(ResetPacket(r.Program));
            r.Miner.Receive(ResetPacket(r.Program));
            Assert.Equal(manual ? P.FlightState.Manual : P.FlightState.Fault, r.StateNow);
            Assert.True(Read<bool>(r.Miner, "canceling")); Assert.False(Read<bool>(r.Miner, "running"));
        }

        [Fact]
        public void DockedMinerDiscardsCancelledSerialWatermarkButRejectsEveryOldGenerationPacket()
        {
            var r = new MinerStateTests.MinerRig(); r.PrepareDeparture("old"); Write(r.Miner, "cancelledThrough", 500L);
            r.Miner.Receive(ResetPacket(r.Program)); Assert.Empty(r.Miner.GetJobs()); Assert.False(Read<bool>(r.Miner, "canceling"));
            Assert.Equal(-1, Read<long>(r.Miner, "cancelledThrough")); Assert.True(r.Connector.Value.Status == MyShipConnectorStatus.Connected);
            var job = new P.Job { Id = r.Program.Config.BaseId + "-job-1", Owner = r.Program.Config.Id, Depth = 10, Direction = Vector3D.Forward, Up = Vector3D.Up };
            var packet = new P.Packet { Kind = "JOB", From = r.Program.Config.BaseId, Body = job.ToIni() };
            r.Miner.Receive(packet); Assert.Empty(r.Miner.GetJobs());
            packet.Epoch = 100; r.Miner.Receive(packet); Assert.Equal(job.Id, Assert.Single(r.Miner.GetJobs()).Id);
            var staleCancel = ResetPacket(r.Program, 99); staleCancel.Body.Set("command", "Text", "cancel-through 999");
            staleCancel.Body.Set("command", "Reset", false);
            r.Miner.Receive(staleCancel); Assert.Equal(job.Id, Assert.Single(r.Miner.GetJobs()).Id);
            r.Miner.Receive(new P.Packet { From = r.Program.Config.BaseId, Epoch = 99, Kind = "DOCK_GRANT", Body = new P.DockFrame { Token = "old", BaseId = r.Program.Config.BaseId }.ToIni() });
            Assert.Equal("", Read<string>(r.Miner, "lane"));
        }

        sealed class Transport
        {
            readonly P program;
            public int Registrations, Disabled;
            public readonly Queue<MyIGCMessage> Incoming = new Queue<MyIGCMessage>();
            public readonly List<P.Packet> Sent = new List<P.Packet>();
            public Transport(P p)
            {
                program = p;
                var listener = new Stub<IMyUnicastListener>().Method("get_HasPendingMessage", a => Incoming.Count > 0).Method("AcceptMessage", a => Incoming.Dequeue());
                TestRig.SetBase(p, "IGC", new Stub<IMyIntergridCommunicationSystem>().Set("UnicastListener", listener.Value)
                    .Method("RegisterBroadcastListener", a => { Registrations++; return new Stub<IMyBroadcastListener>().Value; })
                    .Method("DisableBroadcastListener", a => { Disabled++; return null; })
                    .Method("SendBroadcastMessage", a => Capture((string)a[1]))
                    .Method("SendUnicastMessage", a => { Capture((string)a[2]); return true; }).Value);
                p.Bus = new P.Wire(p);
            }
            object Capture(string text) { P.Packet packet; Assert.True(P.Packet.TryDecode(text, program.Config.FleetId, out packet)); Sent.Add(packet); return null; }
            public void Add(P.Packet packet, long source = 5000)
            { Incoming.Enqueue(new MyIGCMessage(packet.Encode(program.Config.FleetId), "AMS2/" + program.Config.FleetId, source)); }
        }

        [Fact]
        public void RepeatedInitClearsRetriesWithoutRegisteringDuplicateListeners()
        {
            var r = ActiveFleet(); var transport = new Transport(r.Program);
            for (int n = 0; n < 4; n++)
            {
                r.Program.Bus.Send("JOB", "miner-0", new MyIni(), true); r.Program.Main("init", UpdateType.Trigger);
                Assert.Equal(0, r.Program.Bus.PendingCount); AssertEmpty(Current(r.Program));
            }
            Assert.Equal(1, transport.Registrations); Assert.Equal(0, transport.Disabled);
            r.Program.Me.CustomData = Configuration.Replace("FleetId=mining", "FleetId=another"); r.Program.Main("init", UpdateType.Trigger);
            Assert.Equal(2, transport.Registrations); Assert.Equal(1, transport.Disabled);
        }

        [Fact]
        public void OldGenerationCannotPoisonTheWireSessionOfAnAlreadyReconnectedMiner()
        {
            var r = ActiveFleet(); var transport = new Transport(r.Program); r.Program.Main("init", UpdateType.Trigger); var fleet = Current(r.Program);
            var current = new P.Packet { Kind = "HELLO", From = "miner-0", Epoch = r.Program.Bus.Epoch, Seq = 1, Session = "fresh", Id = "fresh1", Body = r.Miners[0].ToIni() };
            transport.Add(current); r.Program.Bus.Drain(fleet.Receive);
            var old = new P.Packet { Kind = "HELLO", From = "miner-0", Epoch = 0, Seq = 999, Session = "old", Id = "old999", Body = r.Miners[0].ToIni() };
            transport.Add(old); r.Program.Bus.Drain(fleet.Receive);
            r.Miners[0].Battery = .7; current.Body = r.Miners[0].ToIni(); current.Seq++; current.Id = "fresh2";
            transport.Add(current); r.Program.Bus.Drain(fleet.Receive);
            var actual = Assert.Single(fleet.GetTelemetry()); Assert.Equal(.7, actual.Battery, 8); Assert.Equal(current.Epoch, actual.Epoch);
        }

        [Fact]
        public void MarkerDropsPendingOldMarksWhenBaseGenerationChangesAndPersistsIt()
        {
            var p = TestRig.Program(); TestRig.SetBase(p, "GridTerminalSystem", new Stub<IMyGridTerminalSystem>().Value);
            var transport = new Transport(p); var marker = new P.MarkerController(p);
            marker.Command("mark 6 6 20 GPS:entry:0:0:-200: GPS:inside:0:0:-201:"); Assert.Equal(1, p.Bus.PendingCount);
            marker.Receive(ResetPacket(p)); Assert.Equal(0, p.Bus.PendingCount); Assert.Equal(100, p.Bus.Epoch);
            var saved = Save(marker); p.Bus.Epoch = 0; marker.Load(saved); Assert.Equal(100, p.Bus.Epoch);
            marker.Receive(ResetPacket(p, 99)); Assert.Equal(100, p.Bus.Epoch);
            marker.Command("survey 300"); Assert.Equal(100, transport.Sent.Last().Epoch);
        }

        [Theory]
        [InlineData("-1")]
        [InlineData("3155378976000000000")]
        [InlineData("NaN")]
        public void InvalidResetGenerationIsRejectedByStorageAndProtocol(string value)
        {
            var r = new FleetStateTests.Rig(0, false);
            Assert.Throws<ArgumentException>(() => r.Fleet.BeginRestoreStorage("XFEAMS-RESET-1\n" + value));
            var packet = new P.Packet { Kind = "BASE", From = "base", Session = "s", Seq = 1 };
            var ini = new MyIni(); ini.TryParse(packet.Encode("mining")); ini.Set("wire", "Epoch", value);
            P.Packet decoded; Assert.False(P.Packet.TryDecode(ini.ToString(), "mining", out decoded));
        }
    }
}