Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions tools/ComplianceRunner/src/Shared/Compliance.Shared/FileFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,55 @@ public static bool IsRelevantFile(string file, string checkType)

/// <summary>
/// Returns true for .json files whose path contains "datasets" (case-insensitive),
/// matching BHoMBot's DatasetCompliance filter exactly:
/// x.ToLower().Contains("datasets") &amp;&amp; x.EndsWith(".json")
/// except versioning upgrade maps, which are never Dataset documents.
/// </summary>
/// <remarks>
/// The path test is BHoMBot's DatasetCompliance filter,
/// <c>x.ToLower().Contains("datasets") &amp;&amp; x.EndsWith(".json")</c>. The upgrade-map
/// exclusion is a deliberate divergence from it, so the two no longer agree.
/// </remarks>
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);
}

/// <summary>
/// True for a versioning upgrade map, named Versioning_&lt;digits&gt;.json.
/// </summary>
/// <remarks>
/// These hold <c>{"Dataset":{"ToNew":..,"ToOld":..}}</c>, carry no <c>_t</c>, 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.
/// </remarks>
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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading