From 90cf2410a407c45f2747c97496f6b0a0af5ad3d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 10:26:50 +0000 Subject: [PATCH 1/4] build: derive Containers.Benchmarks identity from ktsu.Sdk [patch] Containers.Benchmarks declared raw Microsoft.NET.Sdk with no ktsu.Sdk import, so AssemblyName and RootNamespace were both hand-written and had drifted apart: the assembly was Containers.Benchmarks while the namespace was ktsu.Containers.Benchmarks. Import ktsu.Sdk alongside Microsoft.NET.Sdk and drop both overrides, so each derives from the solution-relative folder path as ktsu.Containers.Benchmarks. Set IsPackable=false explicitly rather than relying on OutputType=Exe; it evaluated to true before this change. Pulling in ktsu.Sdk applies its analyzers to the benchmark harness, which needs three adjustments: - KTSU0006: reference BenchmarkDotNet.Annotations directly, since the benchmark attributes come from it rather than from BenchmarkDotNet itself. - KTSU0001: reference Polyfill, as the library does. - CA5394 and KTSU0002: opt out at project scope with a justification. Benchmarks seed Random with a fixed value on purpose so every run measures the same input sequence, and nothing tests the harness. The existing SonarQubeExclude property and its justification are unchanged. Fixes #41 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CdAs5Q7rxgumCR8KraGer1 --- .../Containers.Benchmarks.csproj | 23 +++++++++++++++---- Directory.Packages.props | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Containers.Benchmarks/Containers.Benchmarks.csproj b/Containers.Benchmarks/Containers.Benchmarks.csproj index 68b7a11..a2c6d3e 100644 --- a/Containers.Benchmarks/Containers.Benchmarks.csproj +++ b/Containers.Benchmarks/Containers.Benchmarks.csproj @@ -1,11 +1,12 @@ - + + + + Exe net10.0 - enable - enable - Containers.Benchmarks - ktsu.Containers.Benchmarks + + false true + + $(NoWarn);CA5394;KTSU0002 + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 03037d1..d30e9de 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,6 +4,7 @@ + From 694853d48893f1fedbb618cfb5e3a22c20cdc260 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 10:26:58 +0000 Subject: [PATCH 2/4] test: guard that every project imports ktsu.Sdk and derives its identity [patch] Two conventions tests over the repository's own project files, so the drift fixed in the previous commit cannot come back silently: - every .csproj imports ktsu.Sdk - no .csproj hand-writes AssemblyName or RootNamespace Both fail against the previous Containers.Benchmarks.csproj, naming the offending project in the assertion message. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CdAs5Q7rxgumCR8KraGer1 --- Containers.Test/ProjectConventionTests.cs | 112 ++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 Containers.Test/ProjectConventionTests.cs diff --git a/Containers.Test/ProjectConventionTests.cs b/Containers.Test/ProjectConventionTests.cs new file mode 100644 index 0000000..4d75c48 --- /dev/null +++ b/Containers.Test/ProjectConventionTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.Containers.Tests; + +using System.Xml.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Guards the project conventions behind the estate-wide naming audit: every project in the +/// repository imports ktsu.Sdk and lets it derive the assembly name and root namespace from the +/// solution-relative folder path, instead of hand-writing either value. +/// +/// +/// Containers.Benchmarks drifted off ktsu.Sdk and hand-wrote both, which left its assembly named +/// Containers.Benchmarks while its namespace was ktsu.Containers.Benchmarks. +/// +[TestClass] +public class ProjectConventionTests +{ + /// + /// Walks up from the test binary until the directory holding the solution file is found. + /// + private static DirectoryInfo FindRepositoryRoot() + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "Containers.sln"))) + { + directory = directory.Parent; + } + + Assert.IsNotNull(directory, $"Could not locate Containers.sln above '{AppContext.BaseDirectory}'."); + return directory; + } + + /// + /// Every project file in the repository, excluding build output. + /// + private static List EnumerateProjects() + { + string binSegment = $"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"; + string objSegment = $"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"; + + List projects = + [ + .. FindRepositoryRoot() + .EnumerateFiles("*.csproj", SearchOption.AllDirectories) + .Where(file => !file.FullName.Contains(binSegment, StringComparison.Ordinal)) + .Where(file => !file.FullName.Contains(objSegment, StringComparison.Ordinal)) + .OrderBy(file => file.FullName, StringComparer.Ordinal) + ]; + + Assert.IsGreaterThan(0, projects.Count, "Expected at least one project file in the repository."); + return projects; + } + + private static bool ImportsKtsuSdk(XDocument project) + { + string? sdkAttribute = project.Root?.Attribute("Sdk")?.Value; + if (sdkAttribute is not null && sdkAttribute.Contains("ktsu.Sdk", StringComparison.Ordinal)) + { + return true; + } + + return project.Root? + .Elements() + .Where(element => element.Name.LocalName == "Sdk") + .Select(element => element.Attribute("Name")?.Value) + .Any(name => name is not null && name.StartsWith("ktsu.Sdk", StringComparison.Ordinal)) == true; + } + + [TestMethod] + public void EveryProject_ImportsKtsuSdk() + { + List violations = []; + + foreach (FileInfo project in EnumerateProjects()) + { + if (!ImportsKtsuSdk(XDocument.Load(project.FullName))) + { + violations.Add(project.Name); + } + } + + Assert.IsEmpty(violations, $"These projects do not import ktsu.Sdk, so their identity is not derived: {string.Join(", ", violations)}"); + } + + [TestMethod] + public void NoProject_HandWritesAssemblyNameOrRootNamespace() + { + List violations = []; + + foreach (FileInfo project in EnumerateProjects()) + { + XDocument document = XDocument.Load(project.FullName); + List overrides = + [ + .. document.Descendants() + .Select(element => element.Name.LocalName) + .Where(name => name is "AssemblyName" or "RootNamespace") + .Distinct(StringComparer.Ordinal) + .OrderBy(name => name, StringComparer.Ordinal) + ]; + + if (overrides.Count > 0) + { + violations.Add($"{project.Name} ({string.Join(" and ", overrides)})"); + } + } + + Assert.IsEmpty(violations, $"ktsu.Sdk derives both values from the folder path; these projects override them: {string.Join(", ", violations)}"); + } +} From df397d95f004f7d164bc8610a69c3d53f2378b63 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 10:26:58 +0000 Subject: [PATCH 3/4] fix: unblock the test build by replacing the range indexer CA1832 rejects [patch] Containers.Test has not compiled since the analyzers began flagging array[0..4] in CopyTo_ValidParameters_CopiesElements as CA1832, which fails the .NET workflow on main across all three platforms. AsSpan, which the rule recommends, is not an option here: Assert.AreSequenceEqual takes an IEnumerable. Take(4) expresses the same intent and allocates no copy. This is pre-existing on main and unrelated to the rest of this branch, but the new conventions test cannot build or run in CI without it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CdAs5Q7rxgumCR8KraGer1 --- Containers.Test/OrderedSetTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Containers.Test/OrderedSetTests.cs b/Containers.Test/OrderedSetTests.cs index f43d5f0..dad6539 100644 --- a/Containers.Test/OrderedSetTests.cs +++ b/Containers.Test/OrderedSetTests.cs @@ -176,7 +176,7 @@ public void CopyTo_ValidParameters_CopiesElements() set.CopyTo(array, 0); int[] expected = [1, 3, 4, 5]; // Note: only 4 unique elements - Assert.AreSequenceEqual(expected, array[0..4]); + Assert.AreSequenceEqual(expected, array.Take(4)); } [TestMethod] From 929713ca8aeac99aac2541df5b35956f848bcca5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 10:30:12 +0000 Subject: [PATCH 4/4] test: simplify the nullable-bool return in ImportsKtsuSdk [patch] Replace `== true` with `?? false`, which keeps the same null semantics and drops the redundant comparison CodeQL flagged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CdAs5Q7rxgumCR8KraGer1 --- Containers.Test/ProjectConventionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Containers.Test/ProjectConventionTests.cs b/Containers.Test/ProjectConventionTests.cs index 4d75c48..cba3273 100644 --- a/Containers.Test/ProjectConventionTests.cs +++ b/Containers.Test/ProjectConventionTests.cs @@ -65,7 +65,7 @@ private static bool ImportsKtsuSdk(XDocument project) .Elements() .Where(element => element.Name.LocalName == "Sdk") .Select(element => element.Attribute("Name")?.Value) - .Any(name => name is not null && name.StartsWith("ktsu.Sdk", StringComparison.Ordinal)) == true; + .Any(name => name is not null && name.StartsWith("ktsu.Sdk", StringComparison.Ordinal)) ?? false; } [TestMethod]