using System; using System.Collections.Generic; using System.Linq; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; using Xunit; using P = AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class PlannerTests { [Fact] public void ClearTasksDropsGenerationAndAllCheckpointWorkWithoutReusingIds() { var p=Planner("[Survey]\nMaxJobs=4096\n");p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,1000,100,20); Assert.True(p.Generating);Assert.True(p.PendingGeneration>0);int serial=p.TaskSerial; var partial=new MyIni();Assert.False(p.Checkpoint(partial));List records;Assert.False(p.CheckpointRecords(out records)); p.ClearTasks();Assert.Empty(p.Jobs);Assert.Empty(p.Regions);Assert.Empty(p.OwnedJobs);Assert.Equal(0,p.PendingCount);Assert.Equal(0,p.PendingGeneration);Assert.False(p.Generating); Assert.Equal(serial,p.TaskSerial);for(int n=0;n<100;n++)p.Advance();Assert.Empty(p.Jobs); Assert.True(p.Checkpoint(partial));Assert.False(partial.ContainsKey("job0","Id"));Assert.True(p.CheckpointRecords(out records));Assert.Single(records); var persisted=new MyIni();p.Save(persisted);var restored=Planner();restored.Load(persisted);Assert.Empty(restored.Jobs);Assert.Equal(serial,restored.TaskSerial); restored.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,6,6,10);Assert.True(restored.TaskSerial>serial); } static P.TaskPlanner Planner(string config="") {return new P.TaskPlanner(TestRig.Program(config));} static void Drain(P.TaskPlanner planner) {for(int n=0;n<1024;n++)planner.Advance();} static P.Job SeedProbe(P.TaskPlanner planner) { planner.Survey(Vector3D.Zero,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20); P.Job survey=planner.Jobs[0]; planner.Report(survey,new P.JobReport {JobId=survey.Id,Outcome="SurveyHit",Position=new Vector3D(0,-10,0),Direction=Vector3D.Down,Up=Vector3D.Forward,EntityId=42}); return planner.Jobs.Single(j=>j.Kind==P.JobKind.Probe); } [Fact] public void ManualGridCoversRequestedBoundsAndUsesSerpentineOrder() { var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,12,12,20);Drain(p); Assert.Equal(9,p.Jobs.Count); Assert.Equal(-6,p.Jobs.Min(j=>j.Entry.X)-3,8);Assert.Equal(6,p.Jobs.Max(j=>j.Entry.X)+3,8); Assert.Equal(-6,p.Jobs.Min(j=>j.Entry.Y)-3,8);Assert.Equal(6,p.Jobs.Max(j=>j.Entry.Y)+3,8); Assert.True(p.Jobs[0].Entry.Xp.Jobs[4].Entry.X); Assert.Equal(p.Jobs[2].Entry.X,p.Jobs[3].Entry.X,8); for(int n=1;nAssert.Equal(20,j.Depth)); } [Fact] public void WorldOriginIsAValidEntranceAndGps() { Vector3D position;Assert.True(P.Data.TryGps("GPS:Origin:0:0:0:",out position));Assert.Equal(Vector3D.Zero,position); var p=Planner();p.Manual(position,Vector3D.Forward,Vector3D.Up,6,6,10);Assert.Single(p.Jobs);Assert.Equal(Vector3D.Zero,p.Jobs[0].Entry); } [Theory] [InlineData(0,6,10)] [InlineData(6,-1,10)] [InlineData(6,6,1001)] [InlineData(double.NaN,6,10)] [InlineData(6,double.PositiveInfinity,10)] public void ManualRejectsInvalidDimensions(double width,double height,double depth) { Assert.Throws(()=>Planner().Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,width,height,depth)); } [Fact] public void ManualRejectsZeroDirectionAndOversizedJobBudgetWithoutPartialJobs() { var p=Planner("[Survey]\nMaxJobs=32\n"); Assert.Throws(()=>p.Manual(Vector3D.Zero,Vector3D.Zero,Vector3D.Up,6,6,10)); Assert.Throws(()=>p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,1000,1000,10)); Assert.Empty(p.Jobs);Assert.Empty(p.Regions); } [Fact] public void PlanetSurveyIsFiniteOnFrozenHorizontalPlane() { var p=Planner();Vector3D origin=new Vector3D(123,456,789);p.Survey(origin,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,120);Drain(p); Assert.Equal(13,p.Jobs.Count); Assert.All(p.Jobs,j=>{Assert.Equal(origin.Y+80,j.Entry.Y,8);Assert.InRange(new Vector3D(j.Entry.X-origin.X,0,j.Entry.Z-origin.Z).Length(),0,120.00001);Assert.True(Vector3D.Dot(j.Direction,Vector3D.Down)>0.999);}); Vector3D saved=p.Jobs[0].Entry;p.Survey(origin+new Vector3D(1000,0,0),Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20); Assert.Equal(saved,p.Jobs[0].Entry);Assert.Equal(origin,p.Regions[0].Center); } [Fact] public void SpaceSurveyHasFiniteObservationPointsAndRaysEndingAtSphere() { var p=Planner();Vector3D origin=new Vector3D(10,20,30);p.Survey(origin,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);Drain(p); Assert.Equal(294,p.Jobs.Count); Assert.All(p.Jobs,j=>{Assert.InRange(Vector3D.Distance(origin,j.Entry),0,700.0001);Assert.Equal(2000,Vector3D.Distance(origin,j.Entry+j.Direction*j.Depth),6);}); Assert.Equal(7,p.Jobs.Select(j=>j.Entry).Distinct().Count()); } [Fact] public void LargeSearchKeepsEachMessageWithinJobScanBounds() { var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,20000);Drain(p); Assert.All(p.Jobs,j=>{Assert.InRange(j.Depth,1,10000);Assert.NotNull(P.Job.FromIni(j.ToIni()));}); } [Fact] public void RepeatedSurfaceHitsDeduplicateEntityAndPosition() { var p=Planner();P.Job probe=SeedProbe(p); p.Survey(Vector3D.Zero,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20); P.Job survey=p.Jobs.Last(); p.Report(survey,new P.JobReport {Outcome="SurveyHit",Position=probe.Entry+new Vector3D(0.1,0,0),Direction=Vector3D.Down,Up=Vector3D.Forward,EntityId=42}); Assert.Single(p.Jobs,j=>j.Kind==P.JobKind.Probe); p.Survey(Vector3D.Zero,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20);survey=p.Jobs.Last(); p.Report(survey,new P.JobReport {Outcome="SurveyHit",Position=probe.Entry,Direction=Vector3D.Down,Up=Vector3D.Forward,EntityId=43}); Assert.Equal(2,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe)); } [Fact] public void SurfaceOutsideFrozenSearchDoesNotGenerateProbe() { var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,100); p.Report(p.Jobs[0],new P.JobReport {Outcome="SurveyHit",Position=new Vector3D(101,0,0),Direction=Vector3D.Left,EntityId=42}); Assert.DoesNotContain(p.Jobs,j=>j.Kind==P.JobKind.Probe); } [Fact] public void DiscoveryWaitsForRetreatCompletionBeforeSchedulingNeighborRing() { var p=Planner();P.Job probe=SeedProbe(p);probe.Owner="miner-1"; p.Report(probe,new P.JobReport {Outcome="Discovery",Ore="Cobalt,Iron",Progress=5});Drain(p); Assert.Single(p.Jobs,j=>j.Kind==P.JobKind.Probe);Assert.Equal("miner-1",probe.Owner); Assert.Equal("Iron",p.Regions.Single(r=>r.Patch).Ore); p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p); Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));Assert.Equal("",probe.Owner); Assert.All(p.Jobs.Where(j=>j.Kind==P.JobKind.Probe),j=>Assert.Equal(42,j.EntityId)); } [Fact] public void TerminalOreReportRecoversMissingOrReorderedDiscoveryOnce() { var p=Planner();P.Job probe=SeedProbe(p); p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p); Assert.Single(p.Regions,r=>r.Patch);Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe)); p.Report(probe,new P.JobReport {Outcome="Discovery",Ore="Iron",Progress=5}); p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p); Assert.Single(p.Regions,r=>r.Patch);Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe)); } [Fact] public void TwoCompletedEmptyRingsStopExpansion() { var p=Planner("[Survey]\nExpansionWidth=60\nExpansionHeight=60\n");P.Job probe=SeedProbe(p); // Widen the frozen search so both complete expansion rings fit. p.Regions[0].Radius=100; p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p); Assert.Equal(8,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j))); foreach(P.Job j in p.Jobs.Where(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)).ToList()) p.Report(j,new P.JobReport {Outcome="Complete",Progress=j.Depth});Drain(p); Assert.Equal(16,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j))); foreach(P.Job j in p.Jobs.Where(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)).ToList()) p.Report(j,new P.JobReport {Outcome="Complete",Progress=j.Depth});Drain(p); Assert.Equal(25,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));Assert.DoesNotContain(p.Jobs,j=>!P.TaskPlanner.Finished(j)); Assert.Contains("two empty rings",p.Regions.Single(r=>r.Patch).Status); } [Fact] public void InvalidSamplesNeverCountAsConfirmedEmptyRings() { var p=Planner();P.Job probe=SeedProbe(p);p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p); foreach(P.Job j in p.Jobs.Where(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)).ToList()) p.Report(j,new P.JobReport {Outcome="InvalidSample",Progress=j.Depth});Drain(p); var patch=p.Regions.Single(r=>r.Patch);Assert.Equal(0,patch.EmptyRings);Assert.Contains("incomplete",patch.Status); } [Fact] public void PausedReportPreservesOwnershipAndMonotonicProgress() { var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,6,6,30);P.Job j=p.Jobs[0];j.Owner="miner-1";j.Progress=12; p.Report(j,new P.JobReport {Outcome="Paused",Progress=8});Drain(p); Assert.False(P.TaskPlanner.Finished(j));Assert.Equal("miner-1",j.Owner);Assert.Equal(12,j.Progress);Assert.Equal("Pending",p.Regions[0].Status); } [Fact] public void PlannerStoragePreservesFrozenGeometryOccupancyAndProgress() { var p=Planner();p.Manual(new Vector3D(1,2,3),Vector3D.Forward,Vector3D.Up,12,12,30);p.Jobs[0].Owner="miner-1";p.Jobs[0].Progress=12; var saved=new MyIni();p.Save(saved);var restored=Planner();restored.Load(saved); Assert.Equal(p.Jobs.Count,restored.Jobs.Count);Assert.Equal(p.Regions[0].Center,restored.Regions[0].Center); Assert.Equal("miner-1",restored.Jobs[0].Owner);Assert.Equal(12,restored.Jobs[0].Progress); Assert.Equal(p.Jobs.Select(j=>j.Entry),restored.Jobs.Select(j=>j.Entry)); } [Fact] public void StorageRejectsInvalidBoundsDuplicateIdentifiersAndSerial() { var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,12,6,30);var saved=new MyIni();p.Save(saved); saved.Set("job0","Depth",-1);Assert.Throws(()=>Planner().Load(saved)); saved.Clear();p.Save(saved);saved.Set("job1","Id",p.Jobs[0].Id);Assert.Throws(()=>Planner().Load(saved)); saved.Clear();p.Save(saved);saved.Set("planner","Serial",-1);Assert.Throws(()=>Planner().Load(saved)); } [Fact] public void IncrementalCheckpointPatchesEarlierCompletionsAndAppendedTasks() { var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,300,6,20);Drain(p);p.Jobs[0].Owner="miner-1"; var saved=new MyIni();Assert.False(p.Checkpoint(saved)); p.Report(p.Jobs[0],new P.JobReport {Outcome="Complete",Progress=20});p.Jobs[30].Owner="miner-2"; p.Manual(new Vector3D(1000,0,0),Vector3D.Forward,Vector3D.Up,6,6,20); int frames=0;while(!p.Checkpoint(saved)&&frames++<100){}Assert.True(frames<100); var restored=Planner();restored.Load(saved);Assert.Equal(p.Jobs.Count,restored.Jobs.Count); Assert.Equal("Complete",restored.Jobs[0].Outcome);Assert.Equal("",restored.Jobs[0].Owner);Assert.Equal("miner-2",restored.Jobs[30].Owner); } [Fact] public void SurveyCommandQueuesBoundedWorkAndPreservesAllRays() { var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000); Assert.InRange(p.Jobs.Count,1,8);Assert.True(p.Generating); int count=p.Jobs.Count;p.Advance();Assert.InRange(p.Jobs.Count-count,0,8); Drain(p);Assert.False(p.Generating);Assert.Equal(294,p.Jobs.Count);Assert.Equal(294,p.Jobs.Select(j=>j.Id).Distinct().Count()); } [Fact] public void PendingGenerationReservesCapacityAcrossCommands() { var p=Planner("[Survey]\nMaxJobs=300\n");p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000); Assert.Throws(()=>p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,12,12,20)); Drain(p);Assert.Equal(294,p.Jobs.Count); } [Fact] public void GenerationYieldsWhenInstructionBudgetIsSpent() { var program=TestRig.Program();var runtime=new Stub().Set("MaxInstructionCount",50000).Set("CurrentInstructionCount",30000);TestRig.SetBase(program,"Runtime",runtime.Value); var p=new P.TaskPlanner(program);p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);p.Advance();Assert.Empty(p.Jobs);Assert.True(p.Generating); runtime.Set("CurrentInstructionCount",0);Drain(p);Assert.Equal(294,p.Jobs.Count); } [Fact] public void RestartResumesPartialGenerationWithoutMissingOrRepeatingHoles() { var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);var save=new MyIni();p.Save(save); var restored=Planner();restored.Load(save);Assert.True(restored.Generating);Drain(p);Drain(restored); Assert.Equal(p.Jobs.Select(j=>j.Id),restored.Jobs.Select(j=>j.Id));Assert.Equal(p.Jobs.Select(j=>j.Entry),restored.Jobs.Select(j=>j.Entry)); } [Fact] public void RecordWriterRejectsAnyRecordTheReaderCannotRestore() { var ini=new MyIni();ini.Set("payload","Value",new string('x',32768));Assert.Throws(()=>P.TaskPlanner.StorageRecord(ini,"Fleet")); } [Fact] public void RegionSummaryRestartsWhenJobsAreAppendedBetweenFrames() { var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Down,Vector3D.Forward,Vector3D.Up,20); p.Report(p.Jobs[0],new P.JobReport {Outcome="SurveyEmpty"}); for(int n=0;n<100;n++)p.Manual(new Vector3D(1000+n*20,0,0),Vector3D.Forward,Vector3D.Up,6,6,10); p.Advance();p.Advance(); // Append a new pending hole in the region after its earlier jobs were scanned. p.Manual(new Vector3D(5000,0,0),Vector3D.Forward,Vector3D.Up,6,6,10);p.Jobs.Last().RegionId=p.Regions[0].Id; Drain(p);Assert.Equal("Pending",p.Regions[0].Status); } [Fact] public void UnknownOreDoesNotCreateTargetDeposit() { var p=Planner();P.Job probe=SeedProbe(p);p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Stone,Ice",Progress=probe.Depth});Drain(p); Assert.DoesNotContain(p.Regions,r=>r.Patch);Assert.Equal(0,p.OrePriority("Stone"));Assert.True(p.OrePriority("Iron")>p.OrePriority("Cobalt")); } } }