diff --git a/.github/workflows/actions-audit.yaml b/.github/workflows/actions-audit.yaml index 08f2bda1..085900ea 100644 --- a/.github/workflows/actions-audit.yaml +++ b/.github/workflows/actions-audit.yaml @@ -45,7 +45,7 @@ jobs: go-version: 'stable' - name: Install actionlint - run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 + run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 - name: Run actionlint run: actionlint -color diff --git a/tests/Wolfgang.TryPattern.Tests.Unit/CompileFailureMessage.cs b/tests/Wolfgang.TryPattern.Tests.Unit/CompileFailureMessage.cs new file mode 100644 index 00000000..5dc47cdb --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests.Unit/CompileFailureMessage.cs @@ -0,0 +1,34 @@ +#if NET8_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Wolfgang.TryPattern.Tests.Unit; + +/// +/// Builds the assertion message the snippet-compilation tests +/// (, ) +/// fail with: the compiler errors, the offending snippet indented for +/// copy-paste reproduction, and a repo-specific hint on where to fix it. +/// +internal static class CompileFailureMessage +{ + internal static string Format + ( + string header, + IEnumerable errors, + string snippet, + string hint + ) + { + string errorList = string.Join(Environment.NewLine, errors.Select(e => $" {e}")); + string reproHint = string.Join(Environment.NewLine, snippet.Split('\n').Select(l => $" {l.TrimEnd()}")); + return + $"{header}{Environment.NewLine}" + + $"Errors:{Environment.NewLine}{errorList}{Environment.NewLine}" + + $"Snippet:{Environment.NewLine}{reproHint}{Environment.NewLine}" + + hint; + } +} +#endif diff --git a/tests/Wolfgang.TryPattern.Tests.Unit/CompileFailureMessageTests.cs b/tests/Wolfgang.TryPattern.Tests.Unit/CompileFailureMessageTests.cs new file mode 100644 index 00000000..d9230b83 --- /dev/null +++ b/tests/Wolfgang.TryPattern.Tests.Unit/CompileFailureMessageTests.cs @@ -0,0 +1,33 @@ +#if NET8_0_OR_GREATER +using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace Wolfgang.TryPattern.Tests.Unit; + +public class CompileFailureMessageTests +{ + [Fact] + public void Format_lists_every_error_then_the_indented_snippet_then_the_hint() + { + const string snippet = "class C\n{\n void M() { int x = ; } \n}"; + var errors = CSharpSyntaxTree.ParseText(snippet) + .GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + Assert.NotEmpty(errors); + + string message = CompileFailureMessage.Format("Fence #3 failed to compile.", errors, snippet, "Fix the fence."); + + string[] lines = message.Split(Environment.NewLine); + Assert.Equal("Fence #3 failed to compile.", lines[0]); + Assert.Equal("Errors:", lines[1]); + Assert.All(errors, e => Assert.Contains($" {e}", lines)); + Assert.Contains("Snippet:", lines); + Assert.Contains(" void M() { int x = ; }", lines); // 4-space indent added, trailing whitespace trimmed + Assert.Equal("Fix the fence.", lines[^1]); + } +} +#endif diff --git a/tests/Wolfgang.TryPattern.Tests.Unit/DocExampleCompilationTests.cs b/tests/Wolfgang.TryPattern.Tests.Unit/DocExampleCompilationTests.cs index 8be05a95..5e3529c6 100644 --- a/tests/Wolfgang.TryPattern.Tests.Unit/DocExampleCompilationTests.cs +++ b/tests/Wolfgang.TryPattern.Tests.Unit/DocExampleCompilationTests.cs @@ -150,13 +150,11 @@ private static void Process(int item) { } if (errors.Count > 0) { - string errorList = string.Join(Environment.NewLine, errors.Select(e => $" {e}")); - string reproHint = string.Join(Environment.NewLine, snippet.Split('\n').Select(l => $" {l.TrimEnd()}")); - Assert.Fail( - $"XML doc example on `{memberName}` (example index {index}) failed to compile.{Environment.NewLine}" + - $"Errors:{Environment.NewLine}{errorList}{Environment.NewLine}" + - $"Snippet:{Environment.NewLine}{reproHint}{Environment.NewLine}" + - $"Fix the snippet in the source XML doc, or if it needs an additional using, add it inline in the `` block."); + Assert.Fail(CompileFailureMessage.Format( + $"XML doc example on `{memberName}` (example index {index}) failed to compile.", + errors, + snippet, + "Fix the snippet in the source XML doc, or if it needs an additional using, add it inline in the `` block.")); } } } diff --git a/tests/Wolfgang.TryPattern.Tests.Unit/FuzzTests.cs b/tests/Wolfgang.TryPattern.Tests.Unit/FuzzTests.cs index 18dc5d5d..4464ac2f 100644 --- a/tests/Wolfgang.TryPattern.Tests.Unit/FuzzTests.cs +++ b/tests/Wolfgang.TryPattern.Tests.Unit/FuzzTests.cs @@ -13,12 +13,17 @@ // pulled from the test output. using System; +using System.Diagnostics.CodeAnalysis; using FsCheck; using FsCheck.Xunit; using Xunit; namespace Wolfgang.TryPattern.Tests.Unit; +// Never executed by the PR gate (`--filter "Category!=Fuzz"`), only by the +// scheduled fuzz.yaml run, so the instrumented test assembly would score it +// at 0% on every PR. The same properties ARE measured through PropertyTests. +[ExcludeFromCodeCoverage] [Trait("Category", "Fuzz")] public class FuzzTests { diff --git a/tests/Wolfgang.TryPattern.Tests.Unit/ReadmeExampleCompilationTests.cs b/tests/Wolfgang.TryPattern.Tests.Unit/ReadmeExampleCompilationTests.cs index f1a536b3..97cb2a80 100644 --- a/tests/Wolfgang.TryPattern.Tests.Unit/ReadmeExampleCompilationTests.cs +++ b/tests/Wolfgang.TryPattern.Tests.Unit/ReadmeExampleCompilationTests.cs @@ -111,10 +111,7 @@ public static IEnumerable AllReadmeFences() [MemberData(nameof(AllReadmeFences))] public void Readme_fence_compiles(int index, string snippet) { - if (snippet is null) - { - throw new ArgumentNullException(nameof(snippet)); - } + _ = snippet ?? throw new ArgumentNullException(nameof(snippet)); // Wrap in an async method body so `await` / free-standing // statements both compile. Async is superset — synchronous @@ -165,13 +162,11 @@ private static void Process(int item) { } if (errors.Count > 0) { - string errorList = string.Join(Environment.NewLine, errors.Select(e => $" {e}")); - string reproHint = string.Join(Environment.NewLine, snippet.Split('\n').Select(l => $" {l.TrimEnd()}")); - Assert.Fail( - $"README fence #{index} failed to compile.{Environment.NewLine}" + - $"Errors:{Environment.NewLine}{errorList}{Environment.NewLine}" + - $"Snippet:{Environment.NewLine}{reproHint}{Environment.NewLine}" + - $"Fix the fence in README.md, or if it uses framework types the test project doesn't reference (SqlConnection, ASP.NET Core, EF dbContext), add a marker string to ReadmeExampleCompilationTests.SkipMarkers."); + Assert.Fail(CompileFailureMessage.Format( + $"README fence #{index} failed to compile.", + errors, + snippet, + "Fix the fence in README.md, or if it uses framework types the test project doesn't reference (SqlConnection, ASP.NET Core, EF dbContext), add a marker string to ReadmeExampleCompilationTests.SkipMarkers.")); } }