From afb2e837e11904f81ca39cf3cdc272219de6a458 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 29 Jan 2021 17:11:55 +0000 Subject: [PATCH 1/4] feat(validation): check that active runways reference an airport --- .../AllActiveRunwaysMustReferenceAnAirport.cs | 33 ++++++++++++++++ src/Compiler/Validate/OutputValidator.cs | 3 +- ...ActiveRunwaysMustReferenceAnAirportTest.cs | 39 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs create mode 100644 tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceAnAirportTest.cs diff --git a/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs b/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs new file mode 100644 index 00000000..c06e4920 --- /dev/null +++ b/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Compiler.Argument; +using Compiler.Error; +using Compiler.Event; +using Compiler.Model; + +namespace Compiler.Validate +{ + public class AllActiveRunwaysMustReferenceAnAirport: IValidationRule + { + public void Validate(SectorElementCollection sectorElements, CompilerArguments args, IEventLogger events) + { + var airports = sectorElements.Airports.Select(airport => airport.Icao).ToList(); + var missingAirports = sectorElements.ActiveRunways + .Where(activeRunway => !airports.Contains(activeRunway.Airfield)); + + if (missingAirports.Count() == 0) + { + return; + } + + foreach (ActiveRunway runway in missingAirports) + { + events.AddEvent( + new ValidationRuleFailure( + $"Airport {runway.Airfield} specified in active runway does not exist", + runway + ) + ); + } + } + } +} \ No newline at end of file diff --git a/src/Compiler/Validate/OutputValidator.cs b/src/Compiler/Validate/OutputValidator.cs index fc02497d..29054408 100644 --- a/src/Compiler/Validate/OutputValidator.cs +++ b/src/Compiler/Validate/OutputValidator.cs @@ -49,7 +49,8 @@ public class OutputValidator new AllRunwaysMustReferenceAnAirport(), new AllCoordinationPointsMustHaveValidDepartureRunways(), new AllCoordinationPointsMustHaveValidArrivalRunways(), - new CentrelineColourIsDefined() + new CentrelineColourIsDefined(), + new AllActiveRunwaysMustReferenceAnAirport() }; public static void Validate(SectorElementCollection sectorElements, CompilerArguments args, IEventLogger events) diff --git a/tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceAnAirportTest.cs b/tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceAnAirportTest.cs new file mode 100644 index 00000000..8344877f --- /dev/null +++ b/tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceAnAirportTest.cs @@ -0,0 +1,39 @@ +using Compiler.Validate; +using CompilerTest.Bogus.Factory; +using Xunit; + +namespace CompilerTest.Validate +{ + public class AllActiveRunwaysMustReferenceAnAirportTest: AbstractValidatorTestCase + { + public AllActiveRunwaysMustReferenceAnAirportTest() + { + sectorElements.Add(AirportFactory.Make("EGLL")); + sectorElements.Add(AirportFactory.Make("EGKK")); + sectorElements.Add(AirportFactory.Make("EGCC")); + } + + [Fact] + public void TestItFailsIfAirportDoesntExist() + { + sectorElements.Add(ActiveRunwayFactory.Make("EGKB")); + sectorElements.Add(ActiveRunwayFactory.Make("EGLC")); + sectorElements.Add(ActiveRunwayFactory.Make("EGGD")); + AssertValidationErrors(3); + } + + [Fact] + public void TestItPassesIfAirportExists() + { + sectorElements.Add(ActiveRunwayFactory.Make("EGLL")); + sectorElements.Add(ActiveRunwayFactory.Make("EGKK")); + sectorElements.Add(ActiveRunwayFactory.Make("EGCC")); + AssertNoValidationErrors(); + } + + protected override IValidationRule GetValidationRule() + { + return new AllActiveRunwaysMustReferenceAnAirport(); + } + } +} \ No newline at end of file From 34ac130f94985688e68ec30374f9d9cf136b965c Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 29 Jan 2021 17:22:42 +0000 Subject: [PATCH 2/4] feat(validation): check that active runways reference an actual runway --- .../AllActiveRunwaysMustReferenceARunway.cs | 41 ++++++++++++++++++ src/Compiler/Validate/OutputValidator.cs | 3 +- ...llActiveRunwaysMustReferenceARunwayTest.cs | 42 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs create mode 100644 tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceARunwayTest.cs diff --git a/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs b/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs new file mode 100644 index 00000000..415d9518 --- /dev/null +++ b/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Compiler.Argument; +using Compiler.Error; +using Compiler.Event; +using Compiler.Model; + +namespace Compiler.Validate +{ + public class AllActiveRunwaysMustReferenceARunway: IValidationRule + { + public void Validate(SectorElementCollection sectorElements, CompilerArguments args, IEventLogger events) + { + var missingRunways = sectorElements.ActiveRunways + .Where( + activeRunway => !sectorElements.Runways.Exists(runway => IsSameRunway(activeRunway, runway)) + ); + + if (missingRunways.Count() == 0) + { + return; + } + + foreach (ActiveRunway runway in missingRunways) + { + events.AddEvent( + new ValidationRuleFailure( + $"Ruunway {runway.Airfield}/{runway.Identifier} specified in active runway does not exist", + runway + ) + ); + } + } + + private bool IsSameRunway(ActiveRunway activeRunway, Runway runway) + { + return runway.AirfieldIcao == activeRunway.Airfield && + (runway.FirstIdentifier == activeRunway.Identifier || + runway.ReverseIdentifier == activeRunway.Identifier); + } + } +} \ No newline at end of file diff --git a/src/Compiler/Validate/OutputValidator.cs b/src/Compiler/Validate/OutputValidator.cs index 29054408..4a0b3227 100644 --- a/src/Compiler/Validate/OutputValidator.cs +++ b/src/Compiler/Validate/OutputValidator.cs @@ -50,7 +50,8 @@ public class OutputValidator new AllCoordinationPointsMustHaveValidDepartureRunways(), new AllCoordinationPointsMustHaveValidArrivalRunways(), new CentrelineColourIsDefined(), - new AllActiveRunwaysMustReferenceAnAirport() + new AllActiveRunwaysMustReferenceAnAirport(), + new AllActiveRunwaysMustReferenceARunway(), }; public static void Validate(SectorElementCollection sectorElements, CompilerArguments args, IEventLogger events) diff --git a/tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceARunwayTest.cs b/tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceARunwayTest.cs new file mode 100644 index 00000000..ab107dc1 --- /dev/null +++ b/tests/CompilerTest/Validate/AllActiveRunwaysMustReferenceARunwayTest.cs @@ -0,0 +1,42 @@ +using Compiler.Validate; +using CompilerTest.Bogus.Factory; +using Xunit; + +namespace CompilerTest.Validate +{ + public class AllActiveRunwaysMustReferenceARunwayTest: AbstractValidatorTestCase + { + public AllActiveRunwaysMustReferenceARunwayTest() + { + sectorElements.Add(RunwayFactory.Make("EGLL", "09R", "27L")); + sectorElements.Add(RunwayFactory.Make("EGKK", "26L", "08R")); + } + + [Theory] + [InlineData("EGLL", "27R")] + [InlineData("EGKK", "26R")] + [InlineData("EGLL", "abc")] + [InlineData("EGGD", "09")] + public void TestItFailsIfRunwayDoesntExist(string icao, string identifier) + { + sectorElements.Add(ActiveRunwayFactory.Make(icao, identifier)); + AssertValidationErrors(); + } + + [Theory] + [InlineData("EGLL", "09R")] // First identifier match + [InlineData("EGKK", "26L")] // First identifier match + [InlineData("EGLL", "27L")] // Second identifier match + [InlineData("EGKK", "08R")] // Second identifier match + public void TestItPassesIfRunwayExists(string icao, string identifier) + { + sectorElements.Add(ActiveRunwayFactory.Make(icao, identifier)); + AssertNoValidationErrors(); + } + + protected override IValidationRule GetValidationRule() + { + return new AllActiveRunwaysMustReferenceARunway(); + } + } +} \ No newline at end of file From c46d211c632486958ce4634af69b2c79137f3098 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 29 Jan 2021 17:32:19 +0000 Subject: [PATCH 3/4] feat(validation): validate that active runway rows are unique --- src/Compiler/Model/ActiveRunway.cs | 20 +++++++++- .../Validate/AllActiveRunwaysMustBeUnique.cs | 36 ++++++++++++++++++ .../AllActiveRunwaysMustReferenceARunway.cs | 5 ++- .../AllActiveRunwaysMustReferenceAnAirport.cs | 5 ++- src/Compiler/Validate/OutputValidator.cs | 1 + .../AllActiveRunwaysMustBeUniqueTest.cs | 37 +++++++++++++++++++ 6 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/Compiler/Validate/AllActiveRunwaysMustBeUnique.cs create mode 100644 tests/CompilerTest/Validate/AllActiveRunwaysMustBeUniqueTest.cs diff --git a/src/Compiler/Model/ActiveRunway.cs b/src/Compiler/Model/ActiveRunway.cs index e81b2e8c..28d5340f 100644 --- a/src/Compiler/Model/ActiveRunway.cs +++ b/src/Compiler/Model/ActiveRunway.cs @@ -1,4 +1,6 @@ -namespace Compiler.Model +using System; + +namespace Compiler.Model { public class ActiveRunway : AbstractCompilableElement { @@ -25,6 +27,22 @@ Comment inlineComment */ public int Mode { get; } + public override bool Equals(object obj) + { + return obj is ActiveRunway && + Equals((ActiveRunway) obj); + } + + protected bool Equals(ActiveRunway other) + { + return Identifier == other.Identifier && Airfield == other.Airfield && Mode == other.Mode; + } + + public override int GetHashCode() + { + return HashCode.Combine(Identifier, Airfield, Mode); + } + public override string GetCompileData(SectorElementCollection elements) { return $"ACTIVE_RUNWAY:{this.Airfield}:{this.Identifier}:{this.Mode}"; diff --git a/src/Compiler/Validate/AllActiveRunwaysMustBeUnique.cs b/src/Compiler/Validate/AllActiveRunwaysMustBeUnique.cs new file mode 100644 index 00000000..522f0d48 --- /dev/null +++ b/src/Compiler/Validate/AllActiveRunwaysMustBeUnique.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Compiler.Argument; +using Compiler.Error; +using Compiler.Event; +using Compiler.Model; + +namespace Compiler.Validate +{ + public class AllActiveRunwaysMustBeUnique: IValidationRule + { + public void Validate(SectorElementCollection sectorElements, CompilerArguments args, IEventLogger events) + { + var duplicates = sectorElements.ActiveRunways.GroupBy( + runway => runway + ) + .Where(g => g.Count() > 1) + .ToList(); + + if (!duplicates.Any()) + { + return; + } + + foreach (var duplicate in duplicates) + { + ActiveRunway runway = duplicate.First(); + events.AddEvent( + new ValidationRuleFailure( + $"Duplicate ACTIVE_RUNWAY {runway.Airfield}:{runway.Identifier}:{runway.Mode}", + runway + ) + ); + } + } + } +} \ No newline at end of file diff --git a/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs b/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs index 415d9518..6e6a8743 100644 --- a/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs +++ b/src/Compiler/Validate/AllActiveRunwaysMustReferenceARunway.cs @@ -13,9 +13,10 @@ public void Validate(SectorElementCollection sectorElements, CompilerArguments a var missingRunways = sectorElements.ActiveRunways .Where( activeRunway => !sectorElements.Runways.Exists(runway => IsSameRunway(activeRunway, runway)) - ); + ) + .ToList(); - if (missingRunways.Count() == 0) + if (!missingRunways.Any()) { return; } diff --git a/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs b/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs index c06e4920..314d973b 100644 --- a/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs +++ b/src/Compiler/Validate/AllActiveRunwaysMustReferenceAnAirport.cs @@ -12,9 +12,10 @@ public void Validate(SectorElementCollection sectorElements, CompilerArguments a { var airports = sectorElements.Airports.Select(airport => airport.Icao).ToList(); var missingAirports = sectorElements.ActiveRunways - .Where(activeRunway => !airports.Contains(activeRunway.Airfield)); + .Where(activeRunway => !airports.Contains(activeRunway.Airfield)) + .ToList(); - if (missingAirports.Count() == 0) + if (!missingAirports.Any()) { return; } diff --git a/src/Compiler/Validate/OutputValidator.cs b/src/Compiler/Validate/OutputValidator.cs index 4a0b3227..48398d30 100644 --- a/src/Compiler/Validate/OutputValidator.cs +++ b/src/Compiler/Validate/OutputValidator.cs @@ -52,6 +52,7 @@ public class OutputValidator new CentrelineColourIsDefined(), new AllActiveRunwaysMustReferenceAnAirport(), new AllActiveRunwaysMustReferenceARunway(), + new AllActiveRunwaysMustBeUnique(), }; public static void Validate(SectorElementCollection sectorElements, CompilerArguments args, IEventLogger events) diff --git a/tests/CompilerTest/Validate/AllActiveRunwaysMustBeUniqueTest.cs b/tests/CompilerTest/Validate/AllActiveRunwaysMustBeUniqueTest.cs new file mode 100644 index 00000000..32505390 --- /dev/null +++ b/tests/CompilerTest/Validate/AllActiveRunwaysMustBeUniqueTest.cs @@ -0,0 +1,37 @@ +using Compiler.Validate; +using CompilerTest.Bogus.Factory; +using Xunit; + +namespace CompilerTest.Validate +{ + public class AllActiveRunwaysMustBeUniqueTest: AbstractValidatorTestCase + { + [Fact] + public void TestItFailsIfDuplicatesExist() + { + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27R", 1)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27R", 1)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27R", 0)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27L", 1)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27L", 1)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27L", 0)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27L", 0)); + AssertValidationErrors(3); + } + + [Fact] + public void TestItPassesOnNoDuplicates() + { + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27R", 1)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27R", 0)); + sectorElements.Add(ActiveRunwayFactory.Make("EGLL", "27L", 0)); + sectorElements.Add(ActiveRunwayFactory.Make("EGXY", "27L", 0)); + AssertNoValidationErrors(); + } + + protected override IValidationRule GetValidationRule() + { + return new AllActiveRunwaysMustBeUnique(); + } + } +} \ No newline at end of file From 30afdb7d98ce35d84745edfc1c356817c049b1c4 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 29 Jan 2021 17:38:37 +0000 Subject: [PATCH 4/4] test(validation): add missing tests for ActiveRunway equality --- tests/CompilerTest/Model/ActiveRunwayTest.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/CompilerTest/Model/ActiveRunwayTest.cs b/tests/CompilerTest/Model/ActiveRunwayTest.cs index 3d02cf14..03b5d525 100644 --- a/tests/CompilerTest/Model/ActiveRunwayTest.cs +++ b/tests/CompilerTest/Model/ActiveRunwayTest.cs @@ -1,4 +1,5 @@ -using Xunit; +using System; +using Xunit; using Compiler.Model; using CompilerTest.Bogus.Factory; @@ -46,5 +47,22 @@ public void TestItCompiles() this.activeRunway.GetCompileData(new SectorElementCollection()) ); } + + [Theory] + [InlineData("ABC", "DEF", 0, false)] // All different + [InlineData("EGBB", "34", 1, false)] // Different identifier + [InlineData("EGCC", "33", 1, false)] // Different airport + [InlineData("EGBB", "33", 0, false)] // Different mode + [InlineData("EGBB", "33", 1, true)] // All the same + public void TestEquality(string icao, string identifier, int mode, bool expected) + { + Assert.Equal(expected, activeRunway.Equals(ActiveRunwayFactory.Make(icao, identifier, mode))); + } + + [Fact] + public void TestHash() + { + Assert.Equal(HashCode.Combine("33", "EGBB", 1), activeRunway.GetHashCode()); + } } }