diff --git a/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs b/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs
index 8df6ee2..3043935 100644
--- a/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs
+++ b/tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs
@@ -30,14 +30,55 @@ public static bool IsRelevantFile(string file, string checkType)
///
/// Returns true for .json files whose path contains "datasets" (case-insensitive),
- /// matching BHoMBot's DatasetCompliance filter exactly:
- /// x.ToLower().Contains("datasets") && x.EndsWith(".json")
+ /// except versioning upgrade maps, which are never Dataset documents.
///
+ ///
+ /// The path test is BHoMBot's DatasetCompliance filter,
+ /// x.ToLower().Contains("datasets") && x.EndsWith(".json"). The upgrade-map
+ /// exclusion is a deliberate divergence from it, so the two no longer agree.
+ ///
public static bool IsDatasetFile(string file)
{
if (!file.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
return false;
- return file.Contains("datasets", StringComparison.OrdinalIgnoreCase);
+ if (!file.Contains("datasets", StringComparison.OrdinalIgnoreCase))
+ return false;
+
+ return !IsVersioningUpgradeMap(file);
+ }
+
+ ///
+ /// True for a versioning upgrade map, named Versioning_<digits>.json.
+ ///
+ ///
+ /// These hold {"Dataset":{"ToNew":..,"ToOld":..}}, carry no _t, and can never
+ /// deserialise into a BH.oM.Data.Library.Dataset. They are caught by the path test above
+ /// only when some directory supplies the "datasets" substring, which in practice is a
+ /// project directory named *_Datasets. The rule lives here rather than in a per-repository
+ /// pathspec because a repository carrying no pathspec must still get the right answer.
+ ///
+ /// Anchored on the file name, not the path, so a directory named Versioning_93 does not
+ /// take every file under it out of scope.
+ ///
+ /// The digit is what separates a generated upgrade map from a hand-authored dataset that
+ /// happens to start with the same word: Versioning_Rules.json stays in scope. Discriminating
+ /// on content instead, by requiring a top-level _t, was rejected: it would also skip a real
+ /// Dataset document that had lost its _t, which is a defect this check exists to catch.
+ ///
+ public static bool IsVersioningUpgradeMap(string file)
+ {
+ // Both separators are split by hand rather than through Path.GetFileName, which treats
+ // '\' as one only on Windows. Paths arrive from git with '/' and from a caller on
+ // Windows with '\', and the answer must not depend on which platform is asking.
+ var normalized = file.Replace("\\", "/");
+ int lastSlash = normalized.LastIndexOf('/');
+ var name = lastSlash >= 0 ? normalized[(lastSlash + 1)..] : normalized;
+
+ const string prefix = "Versioning_";
+ if (!name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ return false;
+
+ return name.Length > prefix.Length && char.IsAsciiDigit(name[prefix.Length]);
}
}
diff --git a/tools/ComplianceRunner/tests/Compliance.Tests/Integration/DatasetComplianceRunnerE2ETests.cs b/tools/ComplianceRunner/tests/Compliance.Tests/Integration/DatasetComplianceRunnerE2ETests.cs
index 9646503..dfacac7 100644
--- a/tools/ComplianceRunner/tests/Compliance.Tests/Integration/DatasetComplianceRunnerE2ETests.cs
+++ b/tools/ComplianceRunner/tests/Compliance.Tests/Integration/DatasetComplianceRunnerE2ETests.cs
@@ -49,6 +49,40 @@ public void NonJsonFile_ExitsWithCode0()
Assert.That(exitCode, Is.EqualTo(0));
}
+ [Test]
+ [Description("A versioning upgrade map under a *_Datasets project directory is filtered before BHoM is called, and counts as not relevant rather than as an examined file.")]
+ public void VersioningUpgradeMap_UnderADatasetsDirectory_IsNotExamined()
+ {
+ // The shape that fails today: the file name is the only signal, and the "datasets"
+ // substring comes from the project directory rather than from a data directory.
+ var (exitCode, stdout) = RunnerFixture.Run("DatasetComplianceRunner",
+ "--output", "json", "BHoM_Datasets/Versioning_93.json");
+
+ var root = JsonDocument.Parse(stdout).RootElement;
+ Assert.Multiple(() =>
+ {
+ Assert.That(exitCode, Is.EqualTo(0));
+ Assert.That(root.GetProperty("status").GetString(), Is.EqualTo("Pass"));
+ Assert.That(root.GetProperty("annotationCount").GetInt32(), Is.EqualTo(0));
+ });
+ }
+
+ [Test]
+ [Description("The digit gate holds end to end: Versioning_Rules.json is still selected, so it reaches the BHoM call and is reported as missing on disk rather than filtered.")]
+ public void VersioningRulesJson_IsStillInScope()
+ {
+ // Distinguishes "filtered out" from "examined": a filtered file never reaches the
+ // File.Exists branch, so the [SKIP] line is what proves this one was not filtered.
+ var (exitCode, stdout) = RunnerFixture.Run("DatasetComplianceRunner",
+ "--output", "json", "BHoM_Datasets/Versioning_Rules.json");
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(exitCode, Is.EqualTo(0));
+ Assert.That(stdout, Does.Contain("[SKIP] File not found"));
+ });
+ }
+
// ── JSON output structure ─────────────────────────────────────────────────
[Test]
diff --git a/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs b/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs
index 69ab273..9e47289 100644
--- a/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs
+++ b/tools/ComplianceRunner/tests/Compliance.Unit.Tests/Unit/FileFilterTests.cs
@@ -40,7 +40,48 @@ public class IsDatasetFileTests
[TestCase("datasets/foo.json", ExpectedResult = true)] // root-level
[TestCase("DataSets/foo.json", ExpectedResult = true)] // root-level, mixed case
[TestCase("DataSets/LCA/deep/x.json", ExpectedResult = true)] // root-level, nested
+
+ // Versioning upgrade maps. The live case is a project directory supplying the
+ // "datasets" substring, which is how BuroHappold_Datasets/Versioning_93.json and
+ // BHoM_Datasets/Versioning_100.json get selected at all.
+ [TestCase("BHoM_Datasets/Versioning_93.json", ExpectedResult = false)]
+ [TestCase("BHoM_Datasets/Versioning_100.json", ExpectedResult = false)]
+ [TestCase(@"BHoM_Datasets\Versioning_93.json", ExpectedResult = false)] // backslash separators
+ [TestCase("Datasets/Versioning_9.json", ExpectedResult = false)] // single digit
+ [TestCase("a/Datasets/deep/Versioning_93.json", ExpectedResult = false)] // at depth
+ [TestCase("BHoM_Datasets/versioning_93.json", ExpectedResult = false)] // name case-insensitive
+ [TestCase("BHoM_Datasets/Versioning_93.JSON", ExpectedResult = false)] // extension case-insensitive
+
+ // The digit gate. A hand-authored dataset starting with the same word stays in scope,
+ // which is the whole reason the rule is not a bare Versioning_* match.
+ [TestCase("Datasets/Versioning_Rules.json", ExpectedResult = true)]
+ [TestCase("BHoM_Datasets/Versioning_.json", ExpectedResult = true)]
+ [TestCase("BHoM_Datasets/Versioning.json", ExpectedResult = true)]
+
+ // Anchored on the file name, so a directory named for a version does not take the real
+ // datasets under it out of scope.
+ [TestCase("Datasets/Versioning_93/RealDataset.json", ExpectedResult = true)]
+
+ // The prefix has to start the name. A dataset merely containing the word is unaffected.
+ [TestCase("Datasets/MyVersioning_93.json", ExpectedResult = true)]
+
+ // Pins the anchoring itself rather than the digit test. The name is contrived on
+ // purpose: the prefix has to appear at an offset AND a digit has to sit at the index
+ // the digit test reads, which is the only way the two can disagree. Without it, a
+ // relaxed prefix match still passes every realistic fixture above, because reading a
+ // fixed index lands inside the prefix whenever the prefix is not at the start.
+ [TestCase("Datasets/012345678901Versioning_5.json", ExpectedResult = true)]
public bool IsDatasetFile(string file)
=> FileFilter.IsDatasetFile(file);
+
+ // The upgrade-map rule on its own, so a failure says which of the two predicates moved.
+ // These paths carry no "datasets" substring, so IsDatasetFile rejects them anyway and
+ // could not distinguish the two.
+ [TestCase("Tagging_oM/Versioning_93.json", ExpectedResult = true)]
+ [TestCase("Tagging_oM/Versioning_Rules.json", ExpectedResult = false)]
+ [TestCase("Versioning_93.json", ExpectedResult = true)] // no directory at all
+ [TestCase("Structure_oM/Versioning_93.txt", ExpectedResult = true)] // extension is IsDatasetFile's job
+ public bool IsVersioningUpgradeMap(string file)
+ => FileFilter.IsVersioningUpgradeMap(file);
}
}