From 68359350fc0a8f2cc8ee67a34d353c75358c2d02 Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Mon, 11 May 2026 04:42:45 +0300 Subject: [PATCH] [AZ-372] Add .editorconfig, Directory.Build.props, format/coverage wiring Wires the C19 tooling baseline so dotnet format and Coverlet gate the test script and a small NetAnalyzers ruleset (CA1001, CA1051, CA1816, CA2227) at warning severity is visible from the next build. - .editorconfig (new, root=true): whitespace rules, per-extension indent sizes, C# style preferences as suggestions, initial CA rules. - Directory.Build.props (new): EnableNETAnalyzers=true, AnalysisLevel=latest, AnalysisMode=None so only rules explicitly enabled in .editorconfig fire; EnforceCodeStyleInBuild=false to keep build clean from style. - scripts/run-tests.sh: Step 0 runs dotnet format whitespace --verify-no-changes via Docker SDK; unit/integration test calls now collect XPlat Code Coverage into TestResults/. New --skip-format escape hatch. - .gitignore: TestResults/, coverage.cobertura.xml, *.coverage. - SatelliteProvider.Tests/ToolingConfigurationTests.cs (new, 6 tests): runtime assertions that the config files, script wiring, and coverlet.collector reference are all in place; mirrors the AcceptanceCriteriaRT2Tests pattern. Whitespace cleanup that the new format gate uncovers is staged for the next commit (per AZ-372 spec: "commit cleanup as a separate batch"). Co-authored-by: Cursor --- .editorconfig | 42 ++++++++ .gitignore | 4 + Directory.Build.props | 10 ++ .../ToolingConfigurationTests.cs | 100 ++++++++++++++++++ scripts/run-tests.sh | 44 ++++++-- 5 files changed, 189 insertions(+), 11 deletions(-) create mode 100644 .editorconfig create mode 100644 Directory.Build.props create mode 100644 SatelliteProvider.Tests/ToolingConfigurationTests.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ed63bb2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,42 @@ +root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.cs] +indent_size = 4 + +[*.{csproj,props,targets,nuspec,resx}] +indent_size = 2 + +[*.{json,yml,yaml}] +indent_size = 2 + +[*.{md,sql}] +trim_trailing_whitespace = false + +[*.cs] +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_using_directive_placement = outside_namespace:suggestion + +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +dotnet_diagnostic.CA1001.severity = warning +dotnet_diagnostic.CA1051.severity = warning +dotnet_diagnostic.CA1816.severity = warning +dotnet_diagnostic.CA2227.severity = warning diff --git a/.gitignore b/.gitignore index af2a9f3..1e10283 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ Content/ tiles/ ready/ .DS_Store +TestResults/ +coverage.cobertura.xml +coverage.opencover.xml +*.coverage diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..c428384 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + true + latest + None + false + + + diff --git a/SatelliteProvider.Tests/ToolingConfigurationTests.cs b/SatelliteProvider.Tests/ToolingConfigurationTests.cs new file mode 100644 index 0000000..a371aba --- /dev/null +++ b/SatelliteProvider.Tests/ToolingConfigurationTests.cs @@ -0,0 +1,100 @@ +using FluentAssertions; + +namespace SatelliteProvider.Tests; + +public class ToolingConfigurationTests +{ + [Fact] + public void EditorConfig_ExistsAtRoot_AZ372_AC1() + { + var path = LocateRepoFile(".editorconfig"); + + path.Should().NotBeNull(".editorconfig must exist at the workspace root for `dotnet format` to honor formatting rules"); + + var content = File.ReadAllText(path!); + content.Should().Contain("root = true"); + content.Should().Contain("indent_style = space"); + content.Should().Contain("end_of_line = lf"); + content.Should().Contain("insert_final_newline = true"); + content.Should().Contain("trim_trailing_whitespace = true"); + } + + [Fact] + public void EditorConfig_DefinesInitialAnalyzerRuleset_AZ372_AC3() + { + var path = LocateRepoFile(".editorconfig"); + path.Should().NotBeNull(); + + var content = File.ReadAllText(path!); + + content.Should().Contain("dotnet_diagnostic.CA1001.severity = warning"); + content.Should().Contain("dotnet_diagnostic.CA1051.severity = warning"); + content.Should().Contain("dotnet_diagnostic.CA1816.severity = warning"); + content.Should().Contain("dotnet_diagnostic.CA2227.severity = warning"); + } + + [Fact] + public void DirectoryBuildProps_ExistsAtRoot_AZ372_AC3() + { + var path = LocateRepoFile("Directory.Build.props"); + path.Should().NotBeNull("Directory.Build.props centralizes analyzer settings for every csproj"); + + var content = File.ReadAllText(path!); + + content.Should().Contain("true"); + content.Should().Contain("latest"); + content.Should().Contain("None", + "AnalysisMode=None ensures only rules explicitly enabled in .editorconfig fire; protects against analyzer flood"); + } + + [Fact] + public void RunTestsScript_WiresFormatVerify_AZ372_AC1() + { + var path = LocateRepoFile(Path.Combine("scripts", "run-tests.sh")); + path.Should().NotBeNull(); + + var content = File.ReadAllText(path!); + + content.Should().Contain("dotnet format whitespace"); + content.Should().Contain("--verify-no-changes", + "AC-1 requires the script to gate on whitespace conformance via dotnet format"); + } + + [Fact] + public void RunTestsScript_CollectsCoverage_AZ372_AC2() + { + var path = LocateRepoFile(Path.Combine("scripts", "run-tests.sh")); + path.Should().NotBeNull(); + + var content = File.ReadAllText(path!); + + content.Should().Contain("XPlat Code Coverage"); + } + + [Fact] + public void TestProject_ReferencesCoverletCollector_AZ372_AC2() + { + var path = LocateRepoFile(Path.Combine("SatelliteProvider.Tests", "SatelliteProvider.Tests.csproj")); + path.Should().NotBeNull(); + + var content = File.ReadAllText(path!); + + content.Should().Contain("coverlet.collector", + "Coverlet collector is the data collector that produces XPlat Code Coverage reports"); + } + + private static string? LocateRepoFile(string relativePath) + { + var dir = new DirectoryInfo(Directory.GetCurrentDirectory()); + while (dir is not null) + { + var candidate = Path.Combine(dir.FullName, relativePath); + if (File.Exists(candidate)) + { + return candidate; + } + dir = dir.Parent; + } + return null; + } +} diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index 4a9cade..61628b8 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -11,40 +11,62 @@ trap cleanup EXIT usage() { cat <