From aceaa98294de0328e3980d538eefb1dab1bf8d1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:53:09 +0000 Subject: [PATCH 01/35] Initial plan From 4f7217fff5978aab9791a01b9ef72e329d936a1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:56:56 +0000 Subject: [PATCH 02/35] Initial setup: Understanding codebase and planning JUnit provider implementation Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- src/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/global.json b/src/global.json index a30898fd0..ac54556e4 100644 --- a/src/global.json +++ b/src/global.json @@ -1,7 +1,7 @@ { "sdk": { "allowPrerelease": true, - "version": "9.0.303", + "version": "8.0.118", "rollForward": "latestFeature" } } \ No newline at end of file From 8fa8ba3ed5d66d6b5cfeb500881c4a94bf6c3fea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:03:44 +0000 Subject: [PATCH 03/35] Implement JUnit issue provider with tests and validation Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../Cake.Issues.JUnit.Tests.csproj | 17 ++ .../JUnitIssuesProviderFixture.cs | 31 ++ .../JUnitIssuesProviderTests.cs | 183 ++++++++++++ .../JUnitIssuesSettingsTests.cs | 129 +++++++++ .../Properties/AssemblyInfo.cs | 3 + .../Testfiles/commitlint.xml | 10 + .../Testfiles/cpplint.xml | 14 + .../Testfiles/htmlhint.xml | 13 + .../Testfiles/kubeconform.xml | 17 ++ .../Cake.Issues.JUnit.csproj | 9 + src/Cake.Issues.JUnit/JUnitIssuesAliases.cs | 147 ++++++++++ src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 265 ++++++++++++++++++ src/Cake.Issues.JUnit/JUnitIssuesSettings.cs | 66 +++++ .../Properties/AssemblyInfo.cs | 3 + 14 files changed, 907 insertions(+) create mode 100644 src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj create mode 100644 src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs create mode 100644 src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs create mode 100644 src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs create mode 100644 src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml create mode 100644 src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.cs create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesProvider.cs create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesSettings.cs create mode 100644 src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs diff --git a/src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj b/src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj new file mode 100644 index 000000000..28a16dbe2 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj @@ -0,0 +1,17 @@ + + + Tests for the Cake.Issues.JUnit addin + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs new file mode 100644 index 000000000..981a47070 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs @@ -0,0 +1,31 @@ +namespace Cake.Issues.JUnit.Tests; + +using System.Text; + +internal class JUnitIssuesProviderFixture + : BaseConfigurableIssueProviderFixture +{ + public JUnitIssuesProviderFixture(string fileResourceName) + : this(fileResourceName, @"c:\Source\Cake.Issues") + { + } + + public JUnitIssuesProviderFixture(string fileResourceName, string repositoryRoot) + : base(fileResourceName) + { + this.ReadIssuesSettings = + new ReadIssuesSettings(repositoryRoot); + } + + protected override string FileResourceNamespace => "Cake.Issues.JUnit.Tests.Testfiles."; + + /// + /// Sets the content of the log file. + /// + /// Content to set. + public void SetFileContent(string content) + { + content.NotNullOrWhiteSpace(); + this.LogFileContent = Encoding.UTF8.GetBytes(content); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs new file mode 100644 index 000000000..123d03c34 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -0,0 +1,183 @@ +namespace Cake.Issues.JUnit.Tests; + +public sealed class JUnitIssuesProviderTests +{ + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new JUnitIssuesProvider( + null, + new JUnitIssuesSettings("Foo".ToByteArray()))); + + // Then + result.IsArgumentNullException("log"); + } + + [Fact] + public void Should_Throw_If_IssueProviderSettings_Are_Null() + { + // Given / When + var result = Record.Exception(() => new JUnitIssuesProvider(new FakeLog(), null)); + + // Then + result.IsArgumentNullException("issueProviderSettings"); + } + } + + public sealed class TheReadIssuesMethod + { + [Fact] + public void Should_Read_Issues_Correct_For_CppLint() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + var issue1 = issues[0]; + issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue1.ProviderName.ShouldBe("JUnit"); + issue1.MessageText.ShouldBe("Lines should be <= 80 characters long\nsrc/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2]"); + issue1.Priority.ShouldBe(IssuePriority.Error); + issue1.Rule.ShouldBe("warning"); + issue1.AffectedFileRelativePath.ShouldBe("src/example.cpp"); + issue1.Line.ShouldBe(15); + + var issue2 = issues[1]; + issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue2.ProviderName.ShouldBe("JUnit"); + issue2.MessageText.ShouldBe("Include order issue\nsrc/example.cpp:5: #includes are not properly sorted [build/include_order] [4]"); + issue2.Priority.ShouldBe(IssuePriority.Error); + issue2.Rule.ShouldBe("warning"); + issue2.AffectedFileRelativePath.ShouldBe("src/example.cpp"); + issue2.Line.ShouldBe(5); + } + + [Fact] + public void Should_Read_Issues_Correct_For_Kubeconform() + { + // Given + var fixture = new JUnitIssuesProviderFixture("kubeconform.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + var issue1 = issues[0]; + issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue1.ProviderName.ShouldBe("JUnit"); + issue1.MessageText.ShouldBe("Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string"); + issue1.Priority.ShouldBe(IssuePriority.Error); + issue1.Rule.ShouldBe("ValidationError"); + issue1.AffectedFileRelativePath.ShouldBe("deployment.yaml"); + issue1.Line.ShouldBe(10); + issue1.Column.ShouldBe(15); + + var issue2 = issues[1]; + issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue2.ProviderName.ShouldBe("JUnit"); + issue2.MessageText.ShouldBe("Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service"); + issue2.Priority.ShouldBe(IssuePriority.Error); + issue2.Rule.ShouldBe("ConfigError"); + issue2.AffectedFileRelativePath.ShouldBe("service.yaml"); + issue2.Line.ShouldBe(8); + issue2.Column.ShouldBe(5); + } + + [Fact] + public void Should_Read_Issues_Correct_For_HtmlHint() + { + // Given + var fixture = new JUnitIssuesProviderFixture("htmlhint.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + var issue1 = issues[0]; + issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue1.ProviderName.ShouldBe("JUnit"); + issue1.MessageText.ShouldBe("Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase"); + issue1.Priority.ShouldBe(IssuePriority.Error); + issue1.Rule.ShouldBe("error"); + issue1.AffectedFileRelativePath.ShouldBe("index.html"); + issue1.Line.ShouldBe(12); + issue1.Column.ShouldBe(5); + + var issue2 = issues[1]; + issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue2.ProviderName.ShouldBe("JUnit"); + issue2.MessageText.ShouldBe("Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes."); + issue2.Priority.ShouldBe(IssuePriority.Error); + issue2.Rule.ShouldBe("warning"); + issue2.AffectedFileRelativePath.ShouldBe("about.html"); + issue2.Line.ShouldBe(8); + } + + [Fact] + public void Should_Read_Issues_Correct_For_CommitLint() + { + // Given + var fixture = new JUnitIssuesProviderFixture("commitlint.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + + var issue = issues[0]; + issue.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); + issue.ProviderName.ShouldBe("JUnit"); + issue.MessageText.ShouldBe("Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]"); + issue.Priority.ShouldBe(IssuePriority.Error); + issue.Rule.ShouldBe("error"); + } + + [Fact] + public void Should_Handle_Empty_TestSuite() + { + // Given + var junitContent = @" + +"; + var fixture = new JUnitIssuesProviderFixture("empty.xml"); + fixture.SetFileContent(junitContent); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(0); + } + + [Fact] + public void Should_Handle_Invalid_XML() + { + // Given + var junitContent = @" + + + + "; + var fixture = new JUnitIssuesProviderFixture("invalid.xml"); + fixture.SetFileContent(junitContent); + + // When / Then + Should.Throw(() => fixture.ReadIssues().ToList()) + .Message.ShouldContain("Failed to parse JUnit XML"); + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs new file mode 100644 index 000000000..76318b4cb --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs @@ -0,0 +1,129 @@ +namespace Cake.Issues.JUnit.Tests; + +public sealed class JUnitIssuesSettingsTests +{ + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_LogFilePath_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new JUnitIssuesSettings((FilePath)null)); + + // Then + result.IsArgumentNullException("logFilePath"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new JUnitIssuesSettings((byte[])null)); + + // Then + result.IsArgumentNullException("logFileContent"); + } + + [Fact] + public void Should_Set_LogFileContent() + { + // Given + var logFileContent = "foo".ToByteArray(); + + // When + var settings = new JUnitIssuesSettings(logFileContent); + + // Then + settings.LogFileContent.ShouldBe(logFileContent); + } + } + + public sealed class TheFromFilePathMethod + { + [Fact] + public void Should_Throw_If_LogFilePath_Is_Null() + { + // Given / When + var result = Record.Exception(() => + JUnitIssuesSettings.FromFilePath(null)); + + // Then + result.IsArgumentNullException("logFilePath"); + } + } + + public sealed class TheFromContentMethod + { + [Fact] + public void Should_Throw_If_LogFileContent_Is_Null() + { + // Given / When + var result = Record.Exception(() => + JUnitIssuesSettings.FromContent((string)null)); + + // Then + result.IsArgumentNullException("logFileContent"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_Empty() + { + // Given / When + var result = Record.Exception(() => + JUnitIssuesSettings.FromContent(string.Empty)); + + // Then + result.IsArgumentNullOrWhiteSpaceException("logFileContent"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_WhiteSpace() + { + // Given / When + var result = Record.Exception(() => + JUnitIssuesSettings.FromContent(" ")); + + // Then + result.IsArgumentNullOrWhiteSpaceException("logFileContent"); + } + + [Fact] + public void Should_Set_LogFileContent() + { + // Given + var logFileContent = "foo"; + + // When + var settings = JUnitIssuesSettings.FromContent(logFileContent); + + // Then + settings.LogFileContent.ShouldBe(logFileContent.ToByteArray()); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Byte_Array_Is_Null() + { + // Given / When + var result = Record.Exception(() => + JUnitIssuesSettings.FromContent((byte[])null)); + + // Then + result.IsArgumentNullException("logFileContent"); + } + + [Fact] + public void Should_Set_LogFileContent_From_Byte_Array() + { + // Given + var logFileContent = "foo".ToByteArray(); + + // When + var settings = JUnitIssuesSettings.FromContent(logFileContent); + + // Then + settings.LogFileContent.ShouldBe(logFileContent); + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs b/src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..cb55e3d17 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml new file mode 100644 index 000000000..7fd192b15 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml @@ -0,0 +1,10 @@ + + + + + +commit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml new file mode 100644 index 000000000..96fe4d4c9 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml @@ -0,0 +1,13 @@ + + + + +index.html(12,5): Tagname 'DIV' must be lowercase + + + + +about.html line 8: The value of attribute 'class' must be in double quotes. + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml new file mode 100644 index 000000000..048fdd11c --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml @@ -0,0 +1,17 @@ + + + + + +deployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: "", expected non-empty string + + + + +service.yaml:8:5: Port 8080 is already in use by another service + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj b/src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj new file mode 100644 index 000000000..2aa6fa1c5 --- /dev/null +++ b/src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj @@ -0,0 +1,9 @@ + + + JUnit support for the Cake.Issues Addin for Cake Build Automation System + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs new file mode 100644 index 000000000..aa0e4a18f --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs @@ -0,0 +1,147 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +/// +/// Contains functionality related to . +/// +[CakeAliasCategory(IssuesAliasConstants.MainCakeAliasCategory)] +public static class JUnitIssuesAliases +{ + /// + /// Gets the name of the JUnit issue provider. + /// This name can be used to identify issues based on the property. + /// + /// The context. + /// Name of the JUnit issue provider. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static string JUnitIssuesProviderTypeName( + this ICakeContext context) + { + context.NotNull(); + + return JUnitIssuesProvider.ProviderTypeName; + } + + /// + /// Gets an instance of a provider for issues reported in JUnit XML format using the log file from disk. + /// + /// The context. + /// Path to the JUnit log file. + /// Instance of a provider for issues reported in JUnit XML format. + /// + /// Report issues reported by cpplint: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromFilePath( + this ICakeContext context, + FilePath logFilePath) + { + context.NotNull(); + logFilePath.NotNull(); + + return context.JUnitIssues(JUnitIssuesSettings.FromFilePath(logFilePath)); + } + + /// + /// Gets an instance of a provider for issues reported in JUnit XML format using log file content. + /// + /// The context. + /// Content of the JUnit log file. + /// Instance of a provider for issues reported in JUnit XML format. + /// + /// Report issues reported by kubeconform: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromContent( + this ICakeContext context, + string logFileContent) + { + context.NotNull(); + logFileContent.NotNullOrWhiteSpace(); + + return context.JUnitIssues(JUnitIssuesSettings.FromContent(logFileContent)); + } + + /// + /// Gets an instance of a provider for issues reported in JUnit XML format using log file content. + /// + /// The context. + /// Content of the JUnit log file. + /// Instance of a provider for issues reported in JUnit XML format. + /// + /// Report issues reported by htmlhint: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromContent( + this ICakeContext context, + byte[] logFileContent) + { + context.NotNull(); + logFileContent.NotNull(); + + return context.JUnitIssues(JUnitIssuesSettings.FromContent(logFileContent)); + } + + /// + /// Gets an instance of a provider for issues reported in JUnit XML format using the specified settings. + /// + /// The context. + /// Settings for reading the JUnit log. + /// Instance of a provider for issues reported in JUnit XML format. + /// + /// Report issues reported by commitlint-format-junit: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssues( + this ICakeContext context, + JUnitIssuesSettings settings) + { + context.NotNull(); + settings.NotNull(); + + return new JUnitIssuesProvider(context.Log, settings); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs new file mode 100644 index 000000000..10bc517a0 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -0,0 +1,265 @@ +namespace Cake.Issues.JUnit; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; +using Cake.Core.IO; + +/// +/// Provider for issues in JUnit XML format. +/// +/// The Cake log context. +/// Settings for the issue provider. +internal class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) : BaseConfigurableIssueProvider(log, issueProviderSettings) +{ + /// + /// Gets the name of the JUnit issue provider. + /// This name can be used to identify issues based on the property. + /// + public static string ProviderTypeName => typeof(JUnitIssuesProvider).FullName; + + /// + public override string ProviderName => "JUnit"; + + /// + protected override IEnumerable InternalReadIssues() + { + var result = new List(); + + var logContent = this.IssueProviderSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var doc = XDocument.Parse(logContent); + + // Handle both single testsuite and testsuites root elements + var testSuites = doc.Root?.Name.LocalName == "testsuites" + ? doc.Root.Elements("testsuite") + : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) continue; + + var suiteName = testSuite.Attribute("name")?.Value ?? string.Empty; + + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + var time = testCase.Attribute("time")?.Value; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = ProcessTestFailure(failure, className, testName, suiteName, IssuePriority.Error); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = ProcessTestFailure(error, className, testName, suiteName, IssuePriority.Error); + if (issue != null) + { + result.Add(issue); + } + } + + // Process system-out for additional context + var systemOut = testCase.Element("system-out")?.Value; + if (!string.IsNullOrEmpty(systemOut) && (testCase.Elements("failure").Any() || testCase.Elements("error").Any())) + { + // Try to extract file path and line number from system-out + var fileInfo = ExtractFileInfoFromOutput(systemOut); + if (fileInfo.HasValue) + { + // Update the last added issue with file information if it doesn't have it + var lastIssue = result.LastOrDefault(); + if (lastIssue != null && string.IsNullOrEmpty(lastIssue.AffectedFileRelativePath)) + { + result[result.Count - 1] = UpdateIssueWithFileInfo(lastIssue, fileInfo.Value); + } + } + } + } + } + } + catch (Exception ex) + { + throw new IssuesException($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + /// + /// Processes a test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The test suite name. + /// The issue priority. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, string suiteName, IssuePriority priority) + { + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var type = failureElement.Attribute("type")?.Value ?? string.Empty; + var content = failureElement.Value ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + // Try to extract file information from the message or content + var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, ProviderTypeName, this.ProviderName) + .WithPriority(priority); + + if (!string.IsNullOrEmpty(type)) + { + issueBuilder = issueBuilder.OfRule(type); + } + else if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + if (fileInfo.HasValue) + { + var (filePath, line, column) = fileInfo.Value; + issueBuilder = issueBuilder.InFile(filePath, line, line, column, column); + } + + return issueBuilder.Create(); + } + + /// + /// Tries to extract file path and line information from output text. + /// + /// The output text to parse. + /// File information if found, null otherwise. + private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOutput(string output) + { + if (string.IsNullOrEmpty(output)) + { + return null; + } + + // Common patterns for file paths and line numbers in linter output: + // file.txt:123:45: message + // file.txt(123,45): message + // file.txt line 123: message + // /path/to/file.txt:123: message + // file.txt:123 message + + var patterns = new[] + { + @"([^\s:]+):(\d+):(\d+)", // file:line:column + @"([^\s:]+):(\d+)", // file:line + @"([^\s\(\)]+)\((\d+),(\d+)\)", // file(line,column) + @"([^\s\(\)]+)\((\d+)\)", // file(line) + @"([^\s]+)\s+line\s+(\d+)", // file line 123 + @"File:\s*([^\s]+)", // File: path + }; + + foreach (var pattern in patterns) + { + var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline); + if (match.Success) + { + var filePath = match.Groups[1].Value.Trim(); + + // Skip if it looks like a URL or doesn't look like a file path + if (filePath.StartsWith("http") || filePath.StartsWith("www.")) + { + continue; + } + + int? line = null; + int? column = null; + + if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var lineNum)) + { + line = lineNum; + } + + if (match.Groups.Count > 3 && int.TryParse(match.Groups[3].Value, out var colNum)) + { + column = colNum; + } + + return (filePath, line, column); + } + } + + return null; + } + + /// + /// Tries to extract file path from a class name. + /// + /// The class name to parse. + /// File information if the class name looks like a file path, null otherwise. + private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromClassName(string className) + { + if (string.IsNullOrEmpty(className)) + { + return null; + } + + // Some tools use file paths as class names + if (className.Contains('/') || className.Contains('\\')) + { + return (className, null, null); + } + + // Convert class names to potential file paths + if (className.Contains('.')) + { + // Java-style package.Class -> package/Class.java (but only if it looks like a real package) + var parts = className.Split('.'); + if (parts.Length > 1 && parts.All(p => !string.IsNullOrEmpty(p))) + { + var potentialPath = string.Join("/", parts) + ".java"; + return (potentialPath, null, null); + } + } + + return null; + } + + /// + /// Updates an existing issue with file information. + /// + /// The original issue. + /// The file information to add. + /// A new issue with the file information. + private static IIssue UpdateIssueWithFileInfo(IIssue issue, (string FilePath, int? Line, int? Column) fileInfo) + { + var (filePath, line, column) = fileInfo; + + return IssueBuilder + .NewIssue(issue.MessageText, issue.ProviderType, issue.ProviderName) + .WithPriority(issue.Priority) + .OfRule(issue.Rule, issue.RuleUrl) + .InFile(filePath, line, line, column, column) + .Create(); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs new file mode 100644 index 000000000..fa49c3f87 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs @@ -0,0 +1,66 @@ +namespace Cake.Issues.JUnit; + +using System.Text; +using Cake.Core.IO; + +/// +/// Settings for . +/// +public class JUnitIssuesSettings : IssueProviderSettings +{ + /// + /// Initializes a new instance of the class + /// for reading a JUnit log file on disk. + /// + /// Path to the JUnit log file. + public JUnitIssuesSettings(FilePath logFilePath) + : base(logFilePath) + { + } + + /// + /// Initializes a new instance of the class + /// for a JUnit log file content in memory. + /// + /// Content of the JUnit log file. + public JUnitIssuesSettings(byte[] logFileContent) + : base(logFileContent) + { + } + + /// + /// Returns a new instance of the class from a log file on disk. + /// + /// Path to the JUnit log file. + /// Instance of the class. + public static JUnitIssuesSettings FromFilePath(FilePath logFilePath) + { + logFilePath.NotNull(); + + return new JUnitIssuesSettings(logFilePath); + } + + /// + /// Returns a new instance of the class from log file content. + /// + /// Content of the JUnit log file. + /// Instance of the class. + public static JUnitIssuesSettings FromContent(string logFileContent) + { + logFileContent.NotNullOrWhiteSpace(); + + return new JUnitIssuesSettings(Encoding.UTF8.GetBytes(logFileContent)); + } + + /// + /// Returns a new instance of the class from log file content. + /// + /// Content of the JUnit log file. + /// Instance of the class. + public static JUnitIssuesSettings FromContent(byte[] logFileContent) + { + logFileContent.NotNull(); + + return new JUnitIssuesSettings(logFileContent); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs b/src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8b50f795b --- /dev/null +++ b/src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Cake.Issues.JUnit.Tests")] \ No newline at end of file From 303cb95ca18a9a498b05ae870b79ffbf5cf6b8c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:07:31 +0000 Subject: [PATCH 04/35] Complete JUnit provider implementation with solution integration and documentation Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 2 +- src/Cake.Issues.JUnit/README.md | 63 ++++++++++++++++++++ src/Cake.Issues.sln | 12 ++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/Cake.Issues.JUnit/README.md diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index 10bc517a0..22f52936d 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -14,7 +14,7 @@ /// /// The Cake log context. /// Settings for the issue provider. -internal class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) : BaseConfigurableIssueProvider(log, issueProviderSettings) +public class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) : BaseConfigurableIssueProvider(log, issueProviderSettings) { /// /// Gets the name of the JUnit issue provider. diff --git a/src/Cake.Issues.JUnit/README.md b/src/Cake.Issues.JUnit/README.md new file mode 100644 index 000000000..63d19ab32 --- /dev/null +++ b/src/Cake.Issues.JUnit/README.md @@ -0,0 +1,63 @@ +# Cake.Issues.JUnit + +This provider supports reading issues from JUnit XML format, which is used by various linters and tools. + +## Supported Tools + +The JUnit issue provider can read issues from any tool that outputs JUnit XML format, including: + +- **cpplint**: C++ linter that can output JUnit format +- **commitlint-format-junit**: Commit message linter with JUnit output +- **kubeconform**: Kubernetes manifest validator with JUnit format +- **htmlhint**: HTML linter with JUnit format support +- Many other tools that support JUnit XML output + +## Features + +- Parses both single `testsuite` and `testsuites` root elements +- Extracts file paths from classname attributes or failure messages +- Supports multiple file path patterns: + - `file:line:column` (e.g., `src/file.cpp:15:5`) + - `file(line,column)` (e.g., `index.html(12,5)`) + - `file line number` (e.g., `about.html line 8`) +- Maps test failures and errors to Cake.Issues format +- Handles system-out sections for additional context + +## Usage + +```csharp +#addin nuget:?package=Cake.Issues&version=x.x.x +#addin nuget:?package=Cake.Issues.JUnit&version=x.x.x + +Task("ReadIssues") + .Does(() => +{ + var issues = ReadIssues( + JUnitIssuesFromFilePath(@"c:\build\junit-results.xml"), + @"c:\repo"); + + Information($"{issues.Count()} issues found"); +}); +``` + +## JUnit XML Format + +The provider expects standard JUnit XML format: + +```xml + + + + + Additional failure details with file:line:column information + + + +``` + +The provider will extract: +- File path from `classname` attribute or failure message +- Line/column numbers from failure message patterns +- Issue description from `message` attribute and failure content +- Rule name from failure `type` attribute or test `name` +- Priority based on failure vs error elements \ No newline at end of file diff --git a/src/Cake.Issues.sln b/src/Cake.Issues.sln index 740ba5c81..106ccb487 100644 --- a/src/Cake.Issues.sln +++ b/src/Cake.Issues.sln @@ -301,6 +301,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet", "NuGet", "{9887889A ..\nuspec\nuget\Cake.Issues.Tap.nuspec = ..\nuspec\nuget\Cake.Issues.Tap.nuspec EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.JUnit", "Cake.Issues.JUnit\Cake.Issues.JUnit.csproj", "{993E2A82-6E24-47EB-BBA7-0E735B0E8172}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.JUnit.Tests", "Cake.Issues.JUnit.Tests\Cake.Issues.JUnit.Tests.csproj", "{9AFF330A-9151-4AF9-BAFE-B79DB6016228}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -455,6 +459,14 @@ Global {58C5D37D-18B1-4F44-9527-F515C319C7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {58C5D37D-18B1-4F44-9527-F515C319C7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {58C5D37D-18B1-4F44-9527-F515C319C7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {993E2A82-6E24-47EB-BBA7-0E735B0E8172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {993E2A82-6E24-47EB-BBA7-0E735B0E8172}.Debug|Any CPU.Build.0 = Debug|Any CPU + {993E2A82-6E24-47EB-BBA7-0E735B0E8172}.Release|Any CPU.ActiveCfg = Release|Any CPU + {993E2A82-6E24-47EB-BBA7-0E735B0E8172}.Release|Any CPU.Build.0 = Release|Any CPU + {9AFF330A-9151-4AF9-BAFE-B79DB6016228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AFF330A-9151-4AF9-BAFE-B79DB6016228}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AFF330A-9151-4AF9-BAFE-B79DB6016228}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AFF330A-9151-4AF9-BAFE-B79DB6016228}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 07738b4bd3569503aa609603b2627689b40d8273 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Fri, 1 Aug 2025 15:49:48 +0200 Subject: [PATCH 05/35] Revert global.json --- src/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/global.json b/src/global.json index ac54556e4..a30898fd0 100644 --- a/src/global.json +++ b/src/global.json @@ -1,7 +1,7 @@ { "sdk": { "allowPrerelease": true, - "version": "8.0.118", + "version": "9.0.303", "rollForward": "latestFeature" } } \ No newline at end of file From 1f49e6c9ef4fd839099224f4d28ee7287a754a1e Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Fri, 1 Aug 2025 15:50:01 +0200 Subject: [PATCH 06/35] Add NuGet lock files --- .../packages.lock.json | 545 ++++++++++++++++++ src/Cake.Issues.JUnit/packages.lock.json | 151 +++++ 2 files changed, 696 insertions(+) create mode 100644 src/Cake.Issues.JUnit.Tests/packages.lock.json create mode 100644 src/Cake.Issues.JUnit/packages.lock.json diff --git a/src/Cake.Issues.JUnit.Tests/packages.lock.json b/src/Cake.Issues.JUnit.Tests/packages.lock.json new file mode 100644 index 000000000..365358108 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/packages.lock.json @@ -0,0 +1,545 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "coverlet.msbuild": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "Qa7Hg+wrOMDKpXVn2dw4Wlun490bIWsFW0fdNJQFJLZnbU27MCP0HJ2mPgS+3EQBQUb0zKlkwiQzP+j38Hc3Iw==" + }, + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.3.0, )", + "resolved": "4.3.0", + "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.3, )", + "resolved": "3.1.3", + "contentHash": "go7e81n/UI3LeNqoJIJ3thkS4JfJtiQnDbAxLh09JkJqoHthnfbLS5p68s4/Bm12B9umkoYSB5SaDr68hZNleg==" + }, + "Xunit.SkippableFact": { + "type": "Direct", + "requested": "[1.5.23, )", + "resolved": "1.5.23", + "contentHash": "JlKobLTlsGcuJ8OtoodxL63bUagHSVBnF+oQ2GgnkwNqK+XYjeYyhQasULi5Ebx1MNDGNbOMplQYr89mR+nItQ==", + "dependencies": { + "Validation": "2.5.51", + "xunit.extensibility.execution": "2.4.0" + } + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "Validation": { + "type": "Transitive", + "resolved": "2.5.51", + "contentHash": "g/Aug7PVWaenlJ0QUyt/mEetngkQNsMCuNeRVXbcJED1nZS7JcK+GTU4kz3jcQ7bFuKfi8PF4ExXH7XSFNuSLQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "cake.issues.junit": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )" + } + }, + "cake.issues.testing": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )", + "Cake.Testing": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Cake.Testing": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "oEERVvRww03yd54aFtbSFYc7w4xou9X1An8za8JVOM8JvOhp8mNqh53a4+ogJ9qVOgTSFzK/MvbVfZQNeJECjg==", + "dependencies": { + "Cake.Core": "5.0.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.1, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + } + }, + "net9.0": { + "coverlet.msbuild": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "Qa7Hg+wrOMDKpXVn2dw4Wlun490bIWsFW0fdNJQFJLZnbU27MCP0HJ2mPgS+3EQBQUb0zKlkwiQzP+j38Hc3Iw==" + }, + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.3.0, )", + "resolved": "4.3.0", + "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.3, )", + "resolved": "3.1.3", + "contentHash": "go7e81n/UI3LeNqoJIJ3thkS4JfJtiQnDbAxLh09JkJqoHthnfbLS5p68s4/Bm12B9umkoYSB5SaDr68hZNleg==" + }, + "Xunit.SkippableFact": { + "type": "Direct", + "requested": "[1.5.23, )", + "resolved": "1.5.23", + "contentHash": "JlKobLTlsGcuJ8OtoodxL63bUagHSVBnF+oQ2GgnkwNqK+XYjeYyhQasULi5Ebx1MNDGNbOMplQYr89mR+nItQ==", + "dependencies": { + "Validation": "2.5.51", + "xunit.extensibility.execution": "2.4.0" + } + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "Validation": { + "type": "Transitive", + "resolved": "2.5.51", + "contentHash": "g/Aug7PVWaenlJ0QUyt/mEetngkQNsMCuNeRVXbcJED1nZS7JcK+GTU4kz3jcQ7bFuKfi8PF4ExXH7XSFNuSLQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "cake.issues.junit": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )" + } + }, + "cake.issues.testing": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )", + "Cake.Testing": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Cake.Testing": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "oEERVvRww03yd54aFtbSFYc7w4xou9X1An8za8JVOM8JvOhp8mNqh53a4+ogJ9qVOgTSFzK/MvbVfZQNeJECjg==", + "dependencies": { + "Cake.Core": "5.0.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.1, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/packages.lock.json b/src/Cake.Issues.JUnit/packages.lock.json new file mode 100644 index 000000000..4bf07773a --- /dev/null +++ b/src/Cake.Issues.JUnit/packages.lock.json @@ -0,0 +1,151 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.2.0-beta.556, )", + "resolved": "1.2.0-beta.556", + "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "StyleCop.Analyzers.Unstable": { + "type": "Transitive", + "resolved": "1.2.0.556", + "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + } + }, + "net9.0": { + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.2.0-beta.556, )", + "resolved": "1.2.0-beta.556", + "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "StyleCop.Analyzers.Unstable": { + "type": "Transitive", + "resolved": "1.2.0.556", + "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + } + } + } +} \ No newline at end of file From 34661eaadc1ab3cd89b1cb81786fc49a8f87d886 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Fri, 1 Aug 2025 15:50:54 +0200 Subject: [PATCH 07/35] Add solution folder --- src/Cake.Issues.sln | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Cake.Issues.sln b/src/Cake.Issues.sln index 106ccb487..9fc56c294 100644 --- a/src/Cake.Issues.sln +++ b/src/Cake.Issues.sln @@ -305,6 +305,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.JUnit", "Cake.I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.JUnit.Tests", "Cake.Issues.JUnit.Tests\Cake.Issues.JUnit.Tests.csproj", "{9AFF330A-9151-4AF9-BAFE-B79DB6016228}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JUnit", "JUnit", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -547,6 +549,9 @@ Global {3B3452CA-3B1A-4390-B5D2-A906E2AD64B4} = {D3CEEB00-3E23-40F3-8BFE-321AC511E725} {58C5D37D-18B1-4F44-9527-F515C319C7BE} = {D3CEEB00-3E23-40F3-8BFE-321AC511E725} {9887889A-9AAC-440F-9D82-5D297A731785} = {D3CEEB00-3E23-40F3-8BFE-321AC511E725} + {993E2A82-6E24-47EB-BBA7-0E735B0E8172} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {9AFF330A-9151-4AF9-BAFE-B79DB6016228} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {D404813F-4EBD-4093-BA1C-B5BFEB781A65} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E9F2EC94-9A1B-4834-A464-E5208B210F11} From 268c5ee9de5d0c7b8aa7368e02d46ba219c1f980 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Fri, 1 Aug 2025 16:14:37 +0200 Subject: [PATCH 08/35] Cleanup --- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 184 ++++++++++--------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index 22f52936d..f24813331 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -2,19 +2,18 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using Cake.Core.Diagnostics; -using Cake.Core.IO; /// /// Provider for issues in JUnit XML format. /// /// The Cake log context. /// Settings for the issue provider. -public class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) : BaseConfigurableIssueProvider(log, issueProviderSettings) +public class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) + : BaseConfigurableIssueProvider(log, issueProviderSettings) { /// /// Gets the name of the JUnit issue provider. @@ -31,22 +30,25 @@ protected override IEnumerable InternalReadIssues() var result = new List(); var logContent = this.IssueProviderSettings.LogFileContent.ToStringUsingEncoding(); - + try { var doc = XDocument.Parse(logContent); - + // Handle both single testsuite and testsuites root elements - var testSuites = doc.Root?.Name.LocalName == "testsuites" + var testSuites = doc.Root?.Name.LocalName == "testsuites" ? doc.Root.Elements("testsuite") : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); foreach (var testSuite in testSuites) { - if (testSuite == null) continue; + if (testSuite == null) + { + continue; + } var suiteName = testSuite.Attribute("name")?.Value ?? string.Empty; - + foreach (var testCase in testSuite.Elements("testcase")) { var className = testCase.Attribute("classname")?.Value ?? string.Empty; @@ -56,7 +58,7 @@ protected override IEnumerable InternalReadIssues() // Process failures foreach (var failure in testCase.Elements("failure")) { - var issue = ProcessTestFailure(failure, className, testName, suiteName, IssuePriority.Error); + var issue = this.ProcessTestFailure(failure, className, testName, suiteName, IssuePriority.Error); if (issue != null) { result.Add(issue); @@ -66,90 +68,40 @@ protected override IEnumerable InternalReadIssues() // Process errors foreach (var error in testCase.Elements("error")) { - var issue = ProcessTestFailure(error, className, testName, suiteName, IssuePriority.Error); + var issue = this.ProcessTestFailure(error, className, testName, suiteName, IssuePriority.Error); if (issue != null) { result.Add(issue); } } - // Process system-out for additional context - var systemOut = testCase.Element("system-out")?.Value; - if (!string.IsNullOrEmpty(systemOut) && (testCase.Elements("failure").Any() || testCase.Elements("error").Any())) - { - // Try to extract file path and line number from system-out - var fileInfo = ExtractFileInfoFromOutput(systemOut); - if (fileInfo.HasValue) - { - // Update the last added issue with file information if it doesn't have it - var lastIssue = result.LastOrDefault(); - if (lastIssue != null && string.IsNullOrEmpty(lastIssue.AffectedFileRelativePath)) - { - result[result.Count - 1] = UpdateIssueWithFileInfo(lastIssue, fileInfo.Value); - } - } - } + //// Process system-out for additional context + //var systemOut = testCase.Element("system-out")?.Value; + //if (!string.IsNullOrEmpty(systemOut) && (testCase.Elements("failure").Any() || testCase.Elements("error").Any())) + //{ + // // Try to extract file path and line number from system-out + // var fileInfo = ExtractFileInfoFromOutput(systemOut); + // if (fileInfo.HasValue) + // { + // // Update the last added issue with file information if it doesn't have it + // var lastIssue = result.LastOrDefault(); + // if (lastIssue != null && lastIssue.AffectedFileRelativePath != null) + // { + // result[^1] = UpdateIssueWithFileInfo(lastIssue, fileInfo.Value); + // } + // } + //} } } } catch (Exception ex) { - throw new IssuesException($"Failed to parse JUnit XML: {ex.Message}", ex); + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); } return result; } - /// - /// Processes a test failure or error element and creates an issue. - /// - /// The failure or error XML element. - /// The test class name. - /// The test name. - /// The test suite name. - /// The issue priority. - /// The created issue or null if the failure should be ignored. - private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, string suiteName, IssuePriority priority) - { - var message = failureElement.Attribute("message")?.Value ?? string.Empty; - var type = failureElement.Attribute("type")?.Value ?? string.Empty; - var content = failureElement.Value ?? string.Empty; - - // Combine message and content for full description - var fullMessage = string.IsNullOrEmpty(message) ? content : - string.IsNullOrEmpty(content) ? message : - $"{message}\n{content}"; - - if (string.IsNullOrEmpty(fullMessage)) - { - return null; - } - - // Try to extract file information from the message or content - var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); - - var issueBuilder = IssueBuilder - .NewIssue(fullMessage, ProviderTypeName, this.ProviderName) - .WithPriority(priority); - - if (!string.IsNullOrEmpty(type)) - { - issueBuilder = issueBuilder.OfRule(type); - } - else if (!string.IsNullOrEmpty(testName)) - { - issueBuilder = issueBuilder.OfRule(testName); - } - - if (fileInfo.HasValue) - { - var (filePath, line, column) = fileInfo.Value; - issueBuilder = issueBuilder.InFile(filePath, line, line, column, column); - } - - return issueBuilder.Create(); - } - /// /// Tries to extract file path and line information from output text. /// @@ -185,7 +137,7 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut if (match.Success) { var filePath = match.Groups[1].Value.Trim(); - + // Skip if it looks like a URL or doesn't look like a file path if (filePath.StartsWith("http") || filePath.StartsWith("www.")) { @@ -245,21 +197,71 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromCla return null; } + ///// + ///// Updates an existing issue with file information. + ///// + ///// The original issue. + ///// The file information to add. + ///// A new issue with the file information. + //private static IIssue UpdateIssueWithFileInfo(IIssue issue, (string FilePath, int? Line, int? Column) fileInfo) + //{ + // var (filePath, line, column) = fileInfo; + + // return IssueBuilder + // .NewIssue(issue.MessageText, issue.ProviderType, issue.ProviderName) + // .WithPriority(issue.Priority) + // .OfRule(issue.Rule, issue.RuleUrl) + // .InFile(filePath, line, line, column, column) + // .Create(); + //} + /// - /// Updates an existing issue with file information. + /// Processes a test failure or error element and creates an issue. /// - /// The original issue. - /// The file information to add. - /// A new issue with the file information. - private static IIssue UpdateIssueWithFileInfo(IIssue issue, (string FilePath, int? Line, int? Column) fileInfo) + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The test suite name. + /// The issue priority. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, string suiteName, IssuePriority priority) { - var (filePath, line, column) = fileInfo; - - return IssueBuilder - .NewIssue(issue.MessageText, issue.ProviderType, issue.ProviderName) - .WithPriority(issue.Priority) - .OfRule(issue.Rule, issue.RuleUrl) - .InFile(filePath, line, line, column, column) - .Create(); + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var type = failureElement.Attribute("type")?.Value ?? string.Empty; + var content = failureElement.Value ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, ProviderTypeName, this.ProviderName) + .WithPriority(priority); + + if (!string.IsNullOrEmpty(type)) + { + issueBuilder = issueBuilder.OfRule(type); + } + else if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + // Try to extract file information from the message or content + var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + + if (fileInfo.HasValue) + { + var (filePath, line, column) = fileInfo.Value; + issueBuilder = issueBuilder.InFile(filePath, line, line, column, column); + } + + return issueBuilder.Create(); } } \ No newline at end of file From f1252f7b84842d5d54038aacfbed434f55d46a7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 Aug 2025 23:42:07 +0000 Subject: [PATCH 09/35] Fix build issues: resolve SLNX migration conflict and compilation errors Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- nuspec/nuget/Cake.Frosting.Issues.JUnit.md | 80 +++++++++++++++++++ .../nuget/Cake.Frosting.Issues.JUnit.nuspec | 58 ++++++++++++++ .../nuget/Cake.Frosting.Issues.JUnit.targets | 5 ++ nuspec/nuget/Cake.Issues.JUnit.md | 68 ++++++++++++++++ nuspec/nuget/Cake.Issues.JUnit.nuspec | 47 +++++++++++ .../JUnitIssuesProviderTests.cs | 42 +++++----- .../JUnitIssuesSettingsTests.cs | 6 +- 7 files changed, 283 insertions(+), 23 deletions(-) create mode 100644 nuspec/nuget/Cake.Frosting.Issues.JUnit.md create mode 100644 nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec create mode 100644 nuspec/nuget/Cake.Frosting.Issues.JUnit.targets create mode 100644 nuspec/nuget/Cake.Issues.JUnit.md create mode 100644 nuspec/nuget/Cake.Issues.JUnit.nuspec diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.md b/nuspec/nuget/Cake.Frosting.Issues.JUnit.md new file mode 100644 index 000000000..c25e4bad9 --- /dev/null +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.md @@ -0,0 +1,80 @@ +# Support for reading JUnit XML files using the Cake.Issues addin for Cake Frosting + +> **NOTE**: +> This is the version of the addin compatible with [Cake Frosting]. +> For addin compatible with [Cake .NET Tool] see [Cake.Issues.JUnit](https://www.nuget.org/packages/Cake.Issues.JUnit). + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +Cake.Issues redefines issue management within the Cake build system by offering a comprehensive, universal, and extensible solution. +The unique capabilities of the addins empower development teams to enforce coding standards, generate insightful reports, +seamlessly incorporate various linting tools, and streamlining the integration with pull requests. +With its [modular architecture] and extensive [set of aliases], Cake.Issues provides a future-proof infrastructure for issue management +in Cake builds, fostering a more efficient and adaptable development process. + +For more information and extensive documentation see the [Cake.Issues website](https://cakeissues.net). +For general information about the Cake build automation system see the [Cake website](http://cakebuild.net). + +## How to use + +Integrating Cake.Issues into your Cake build is straightforward. +With minimal setup, teams can enjoy the benefits of enhanced code quality management seamlessly integrated into their existing build pipeline. + +After adding the addin the JUnit XML file can be parsed: + +```csharp +public class Program : FrostingHost +{ + public static int Main(string[] args) + { + return new CakeHost().UseContext().Run(args); + } +} + +[TaskName("Analyze")] +public sealed class AnalyzeTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var logPath = @"c:\build\junit-results.xml"; + var repoRootPath = @"c:\repo"; + + // Read issues. + var issues = + context.ReadIssues( + context.JUnitIssuesFromFilePath(logPath), + repoRootPath); + + context.Information($"{issues.Count()} issues are found."); + } +} +``` + +## Supported Tools + +The provider works with any tool that outputs standard JUnit XML, including: + +- **cpplint**: C++ linter +- **commitlint-format-junit**: Commit message linter +- **kubeconform**: Kubernetes manifest validator +- **htmlhint**: HTML linter with JUnit format support + +## Support & Discussion + +For questions and to discuss ideas & feature requests, use the [GitHub discussions on the Cake GitHub repository](https://github.com/cake-build/cake/discussions), under the [Extension Q&A](https://github.com/orgs/cake-build/discussions/categories/extension-q-a) category. + +## Contributing + +Contributions are welcome. See [Contribution Guidelines](https://github.com/cake-contrib/Cake.Issues/blob/develop/CONTRIBUTING.md). + +## License + +[MIT License - Copyright © Cake Issues contributors](LICENSE) + +Binary distributions for some addins contain third-party code which is licensed under its own respective license. +See [LICENSE](https://github.com/cake-contrib/Cake.Issues/blob/develop/LICENSE) for details. + +[modular architecture]: https://cakeissues.net/docs/fundamentals/architecture +[set of aliases]: https://cakeissues.net/dsl/ +[Cake Frosting]: https://cakebuild.net/docs/running-builds/runners/cake-frosting +[Cake .NET Tool]: https://cakebuild.net/docs/running-builds/runners/dotnet-tool \ No newline at end of file diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec b/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec new file mode 100644 index 000000000..a74672009 --- /dev/null +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec @@ -0,0 +1,58 @@ + + + + Cake.Frosting.Issues.JUnit + Cake.Frosting.Issues.JUnit + 0.0.0 + Cake Issues contributors + pascalberger, cake-contrib + Support for reading JUnit XML files using the Cake.Issues addin for Cake Frosting + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +This addin provides the aliases for reading issues from JUnit XML files and providing them to the Cake.Issues addin. +It also requires the core Cake.Issues addin. + +The provider supports JUnit XML files from various tools including cpplint, commitlint-format-junit, kubeconform, and htmlhint. + +There are also additional addins for generating reports or posting issues to pull requests. + +See the Project Site for an overview of the whole ecosystem of addins for working with issues in Cake scripts. + +NOTE: +This is the version of the addin compatible with Cake Frosting. +For addin compatible with Cake Script Runners see Cake.Issues.JUnit. + + MIT + https://cakeissues.net + icon.png + false + + Copyright © Cake Issues contributors + cake cake-addin cake-issues cake-issueprovider linting junit xml cpplint commitlint kubeconform htmlhint + docs\README.md + https://github.com/cake-contrib/Cake.Issues/releases/tag/5.6.1 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.targets b/nuspec/nuget/Cake.Frosting.Issues.JUnit.targets new file mode 100644 index 000000000..c527182ae --- /dev/null +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/nuspec/nuget/Cake.Issues.JUnit.md b/nuspec/nuget/Cake.Issues.JUnit.md new file mode 100644 index 000000000..c41267de7 --- /dev/null +++ b/nuspec/nuget/Cake.Issues.JUnit.md @@ -0,0 +1,68 @@ +# Support for reading JUnit XML files using the Cake.Issues addin for Cake Build Automation System + +> **NOTE**: +> This is the version of the addin compatible with [Cake .NET Tool]. +> For addin compatible with [Cake Frosting] see [Cake.Frosting.Issues.JUnit](https://www.nuget.org/packages/Cake.Frosting.Issues.JUnit). + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +Cake.Issues redefines issue management within the Cake build system by offering a comprehensive, universal, and extensible solution. +The unique capabilities of the addins empower development teams to enforce coding standards, generate insightful reports, +seamlessly incorporate various linting tools, and streamlining the integration with pull requests. +With its [modular architecture] and extensive [set of aliases], Cake.Issues provides a future-proof infrastructure for issue management +in Cake builds, fostering a more efficient and adaptable development process. + +For more information and extensive documentation see the [Cake.Issues website](https://cakeissues.net). +For general information about the Cake build automation system see the [Cake website](http://cakebuild.net). + +## How to use + +Integrating Cake.Issues into your Cake build is straightforward. +With minimal setup, teams can enjoy the benefits of enhanced code quality management seamlessly integrated into their existing build pipeline. + +After adding the addin the JUnit XML file can be parsed: + +```csharp +Task("Analyze").Does(() => +{ + var logPath = @"c:\build\junit-results.xml"; + var repoRootPath = @"c:\repo"; + + // Read issues. + var issues = + ReadIssues( + JUnitIssuesFromFilePath(logPath), + repoRootPath); + + Information("{0} issues are found.", issues.Count()); +}); +``` + +## Supported Tools + +The provider works with any tool that outputs standard JUnit XML, including: + +- **cpplint**: C++ linter +- **commitlint-format-junit**: Commit message linter +- **kubeconform**: Kubernetes manifest validator +- **htmlhint**: HTML linter with JUnit format support + +## Support & Discussion + +For questions and to discuss ideas & feature requests, use the [GitHub discussions on the Cake GitHub repository](https://github.com/cake-build/cake/discussions), under the [Extension Q&A](https://github.com/orgs/cake-build/discussions/categories/extension-q-a) category. + +## Contributing + +Contributions are welcome. See [Contribution Guidelines](https://github.com/cake-contrib/Cake.Issues/blob/develop/CONTRIBUTING.md). + +## License + +[MIT License - Copyright © Cake Issues contributors](LICENSE) + +Binary distributions for some addins contain third-party code which is licensed under its own respective license. +See [LICENSE](https://github.com/cake-contrib/Cake.Issues/blob/develop/LICENSE) for details. + +[modular architecture]: https://cakeissues.net/docs/fundamentals/architecture +[set of aliases]: https://cakeissues.net/dsl/ +[Cake Frosting]: https://cakebuild.net/docs/running-builds/runners/cake-frosting +[Cake .NET Tool]: https://cakebuild.net/docs/running-builds/runners/dotnet-tool \ No newline at end of file diff --git a/nuspec/nuget/Cake.Issues.JUnit.nuspec b/nuspec/nuget/Cake.Issues.JUnit.nuspec new file mode 100644 index 000000000..1dc86a053 --- /dev/null +++ b/nuspec/nuget/Cake.Issues.JUnit.nuspec @@ -0,0 +1,47 @@ + + + + Cake.Issues.JUnit + Cake.Issues.JUnit + 0.0.0 + Cake Issues contributors + pascalberger, cake-contrib + Support for reading JUnit XML files using the Cake.Issues addin for Cake Build Automation System + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +This addin provides the aliases for reading issues from JUnit XML files and providing them to the Cake.Issues addin. +It also requires the core Cake.Issues addin. + +The provider supports JUnit XML files from various tools including cpplint, commitlint-format-junit, kubeconform, and htmlhint. + +There are also additional addins for generating reports or posting issues to pull requests. + +See the Project Site for an overview of the whole ecosystem of addins for working with issues in Cake scripts. + +NOTE: +This is the version of the addin compatible with Cake Script Runners. +For addin compatible with Cake Frosting see Cake.Frosting.Issues.JUnit. + + MIT + https://cakeissues.net + icon.png + false + + Copyright © Cake Issues contributors + cake cake-addin cake-issues cake-issueprovider linting junit xml cpplint commitlint kubeconform htmlhint + docs\README.md + https://github.com/cake-contrib/Cake.Issues/releases/tag/5.6.1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 123d03c34..2a09e32b2 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -46,19 +46,19 @@ public void Should_Read_Issues_Correct_For_CppLint() issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue1.ProviderName.ShouldBe("JUnit"); issue1.MessageText.ShouldBe("Lines should be <= 80 characters long\nsrc/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2]"); - issue1.Priority.ShouldBe(IssuePriority.Error); - issue1.Rule.ShouldBe("warning"); + issue1.Priority.ShouldBe((int)IssuePriority.Error); + issue1.Rule().ShouldBe("warning"); issue1.AffectedFileRelativePath.ShouldBe("src/example.cpp"); - issue1.Line.ShouldBe(15); + issue1.Line.ShouldBe((int?)15); var issue2 = issues[1]; issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue2.ProviderName.ShouldBe("JUnit"); issue2.MessageText.ShouldBe("Include order issue\nsrc/example.cpp:5: #includes are not properly sorted [build/include_order] [4]"); - issue2.Priority.ShouldBe(IssuePriority.Error); - issue2.Rule.ShouldBe("warning"); + issue2.Priority.ShouldBe((int)IssuePriority.Error); + issue2.Rule().ShouldBe("warning"); issue2.AffectedFileRelativePath.ShouldBe("src/example.cpp"); - issue2.Line.ShouldBe(5); + issue2.Line.ShouldBe((int?)5); } [Fact] @@ -77,20 +77,20 @@ public void Should_Read_Issues_Correct_For_Kubeconform() issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue1.ProviderName.ShouldBe("JUnit"); issue1.MessageText.ShouldBe("Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string"); - issue1.Priority.ShouldBe(IssuePriority.Error); - issue1.Rule.ShouldBe("ValidationError"); + issue1.Priority.ShouldBe((int)IssuePriority.Error); + issue1.Rule().ShouldBe("ValidationError"); issue1.AffectedFileRelativePath.ShouldBe("deployment.yaml"); - issue1.Line.ShouldBe(10); + issue1.Line.ShouldBe((int?)10); issue1.Column.ShouldBe(15); var issue2 = issues[1]; issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue2.ProviderName.ShouldBe("JUnit"); issue2.MessageText.ShouldBe("Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service"); - issue2.Priority.ShouldBe(IssuePriority.Error); - issue2.Rule.ShouldBe("ConfigError"); + issue2.Priority.ShouldBe((int)IssuePriority.Error); + issue2.Rule().ShouldBe("ConfigError"); issue2.AffectedFileRelativePath.ShouldBe("service.yaml"); - issue2.Line.ShouldBe(8); + issue2.Line.ShouldBe((int?)8); issue2.Column.ShouldBe(5); } @@ -110,20 +110,20 @@ public void Should_Read_Issues_Correct_For_HtmlHint() issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue1.ProviderName.ShouldBe("JUnit"); issue1.MessageText.ShouldBe("Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase"); - issue1.Priority.ShouldBe(IssuePriority.Error); - issue1.Rule.ShouldBe("error"); + issue1.Priority.ShouldBe((int)IssuePriority.Error); + issue1.Rule().ShouldBe("error"); issue1.AffectedFileRelativePath.ShouldBe("index.html"); - issue1.Line.ShouldBe(12); + issue1.Line.ShouldBe((int?)12); issue1.Column.ShouldBe(5); var issue2 = issues[1]; issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue2.ProviderName.ShouldBe("JUnit"); issue2.MessageText.ShouldBe("Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes."); - issue2.Priority.ShouldBe(IssuePriority.Error); - issue2.Rule.ShouldBe("warning"); + issue2.Priority.ShouldBe((int)IssuePriority.Error); + issue2.Rule().ShouldBe("warning"); issue2.AffectedFileRelativePath.ShouldBe("about.html"); - issue2.Line.ShouldBe(8); + issue2.Line.ShouldBe((int?)8); } [Fact] @@ -142,8 +142,8 @@ public void Should_Read_Issues_Correct_For_CommitLint() issue.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue.ProviderName.ShouldBe("JUnit"); issue.MessageText.ShouldBe("Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]"); - issue.Priority.ShouldBe(IssuePriority.Error); - issue.Rule.ShouldBe("error"); + issue.Priority.ShouldBe((int)IssuePriority.Error); + issue.Rule().ShouldBe("error"); } [Fact] @@ -176,7 +176,7 @@ public void Should_Handle_Invalid_XML() fixture.SetFileContent(junitContent); // When / Then - Should.Throw(() => fixture.ReadIssues().ToList()) + Should.Throw(() => fixture.ReadIssues().ToList()) .Message.ShouldContain("Failed to parse JUnit XML"); } } diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs index 76318b4cb..f093b6888 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs @@ -1,5 +1,7 @@ namespace Cake.Issues.JUnit.Tests; +using Cake.Core.IO; + public sealed class JUnitIssuesSettingsTests { public sealed class TheCtor @@ -75,7 +77,7 @@ public void Should_Throw_If_LogFileContent_Is_Empty() JUnitIssuesSettings.FromContent(string.Empty)); // Then - result.IsArgumentNullOrWhiteSpaceException("logFileContent"); + result.IsArgumentException("logFileContent"); } [Fact] @@ -86,7 +88,7 @@ public void Should_Throw_If_LogFileContent_Is_WhiteSpace() JUnitIssuesSettings.FromContent(" ")); // Then - result.IsArgumentNullOrWhiteSpaceException("logFileContent"); + result.IsArgumentException("logFileContent"); } [Fact] From b642361ee9cfc8b198864b7ee9fd8261c51870d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 Aug 2025 21:51:09 +0000 Subject: [PATCH 10/35] Fix unit tests and add JUnit provider documentation Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../documentation/issue-providers/index.md | 2 + .../issue-providers/junit/features.md | 74 +++++++++++++++++++ .../issue-providers/junit/index.md | 14 ++++ .../JUnitIssuesProviderTests.cs | 14 ++-- .../Properties/AssemblyInfo.cs | 3 - .../Testfiles/empty.xml | 2 + .../Testfiles/invalid.xml | 7 ++ src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 49 ++---------- .../Properties/AssemblyInfo.cs | 3 - 9 files changed, 112 insertions(+), 56 deletions(-) create mode 100644 docs/input/documentation/issue-providers/junit/features.md create mode 100644 docs/input/documentation/issue-providers/junit/index.md delete mode 100644 src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml delete mode 100644 src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs diff --git a/docs/input/documentation/issue-providers/index.md b/docs/input/documentation/issue-providers/index.md index 90b886152..bf171cd04 100644 --- a/docs/input/documentation/issue-providers/index.md +++ b/docs/input/documentation/issue-providers/index.md @@ -11,6 +11,7 @@ Issue provider addins are responsible for providing the output of an analyzer or - :material-layers-plus: __[ESLint]__ – Issue provider for reading ESLint issues - :material-layers-plus: __[Git Repository]__ – Issue provider for analyzing Git repositories - :material-layers-plus: __[Inspect Code]__ – Issue provider for reading JetBrains Inspect Code / ReSharper issues +- :material-layers-plus: __[JUnit]__ – Issue provider for reading JUnit XML format - :material-layers-plus: __[Markdownlint]__ – Issue provider for reading issues from markdownlint - :material-layers-plus: __[MsBuild]__ – Issue provider for reading MsBuild errors and warnings - :material-layers-plus: __[Sarif]__ – Issue provider for reading SARIF reports @@ -23,6 +24,7 @@ Issue provider addins are responsible for providing the output of an analyzer or [ESLint]: eslint/index.md [Git Repository]: gitrepository/index.md [Inspect Code]: inspectcode/index.md +[JUnit]: junit/index.md [Markdownlint]: markdownlint/index.md [MsBuild]: msbuild/index.md [Sarif]: sarif/index.md diff --git a/docs/input/documentation/issue-providers/junit/features.md b/docs/input/documentation/issue-providers/junit/features.md new file mode 100644 index 000000000..33fce4433 --- /dev/null +++ b/docs/input/documentation/issue-providers/junit/features.md @@ -0,0 +1,74 @@ +--- +title: Features +description: Features of the Cake.Issues.JUnit addin. +icon: material/creation-outline +--- + +The [Cake.Issues.JUnit addin](https://cakebuild.net/extensions/cake-issues-junit/){target="_blank"} provides the following features. + +??? tip "Supported Tools" + The JUnit issue provider can read JUnit XML output from any tool that generates standard JUnit XML format, including: + + - **cpplint**: C++ linter + - **commitlint-format-junit**: Commit message linter + - **kubeconform**: Kubernetes manifest validator + - **htmlhint**: HTML linter with JUnit format support + - Any other tool that outputs JUnit XML format + +## Basic features + +- [x] Reads issues from JUnit XML files. +- [x] Supports both single `testsuite` and `testsuites` root elements. +- [x] Extracts file paths from `classname` attributes or embedded within failure messages. +- [x] Supports multiple file path patterns commonly used by linters. +- [x] Maps JUnit test failures and errors to Cake.Issues format with appropriate priorities. +- [x] Extracts rule names from failure types or test names. +- [x] Robust error handling for malformed XML. + +## Supported file path patterns + +The provider can extract file information from various formats commonly used in linter output: + +- [x] `file:line:column` (e.g., `src/example.cpp:15:5`) +- [x] `file(line,column)` (e.g., `index.html(12,5)`) +- [x] `file line number` (e.g., `about.html line 8`) +- [x] `file:line` (e.g., `/path/to/file.txt:123`) + +## Supported log file formats + +- [x] [JUnitIssuesFromFilePath](https://cakebuild.net/api/Cake.Issues.JUnit/JUnitIssuesAliases/){target="_blank"} + alias for reading issues from JUnit XML files. +- [x] [JUnitIssuesFromContent](https://cakebuild.net/api/Cake.Issues.JUnit/JUnitIssuesAliases/){target="_blank"} + alias for reading issues from JUnit XML content. + +## Supported IIssue properties + +
+ +- [x] `IIssue.ProviderType` +- [x] `IIssue.ProviderName` +- [ ] `IIssue.Run` (1) +- [ ] `IIssue.Identifier` +- [ ] `IIssue.ProjectName` +- [ ] `IIssue.ProjectFileRelativePath` +- [x] `IIssue.AffectedFileRelativePath` (2) +- [x] `IIssue.Line` (3) +- [ ] `IIssue.EndLine` +- [x] `IIssue.Column` (3) +- [ ] `IIssue.EndColumn` +- [ ] `IIssue.FileLink` (4) +- [x] `IIssue.MessageText` +- [ ] `IIssue.MessageHtml` +- [ ] `IIssue.MessageMarkdown` +- [x] `IIssue.Priority` +- [x] `IIssue.PriorityName` +- [x] `IIssue.RuleId` (5) +- [ ] `IIssue.RuleUrl` + +
+ +1. Can be set while reading issues +2. Extracted from `classname` attribute or parsed from failure message content +3. Extracted from failure message content when available +4. Can be set while reading issues +5. Set to the failure `type` attribute when available \ No newline at end of file diff --git a/docs/input/documentation/issue-providers/junit/index.md b/docs/input/documentation/issue-providers/junit/index.md new file mode 100644 index 000000000..899616a60 --- /dev/null +++ b/docs/input/documentation/issue-providers/junit/index.md @@ -0,0 +1,14 @@ +--- +title: JUnit issue provider +description: Issue provider which allows you to read issues from JUnit XML files. +--- + +Support for reading issues reported by tools that output JUnit XML format +is implemented in the [Cake.Issues.JUnit addin](https://cakebuild.net/extensions/cake-issues-junit/){target="_blank"}. + +
+ +- :material-creation-outline: [Features](features.md) +- :material-api: [API](https://cakebuild.net/extensions/cake-issues-junit){target="_blank"} + +
\ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 2a09e32b2..7728b5326 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -41,7 +41,7 @@ public void Should_Read_Issues_Correct_For_CppLint() // Then issues.Count.ShouldBe(2); - + var issue1 = issues[0]; issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); issue1.ProviderName.ShouldBe("JUnit"); @@ -49,7 +49,7 @@ public void Should_Read_Issues_Correct_For_CppLint() issue1.Priority.ShouldBe((int)IssuePriority.Error); issue1.Rule().ShouldBe("warning"); issue1.AffectedFileRelativePath.ShouldBe("src/example.cpp"); - issue1.Line.ShouldBe((int?)15); + issue1.Line.ShouldBe(15); var issue2 = issues[1]; issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); @@ -58,7 +58,7 @@ public void Should_Read_Issues_Correct_For_CppLint() issue2.Priority.ShouldBe((int)IssuePriority.Error); issue2.Rule().ShouldBe("warning"); issue2.AffectedFileRelativePath.ShouldBe("src/example.cpp"); - issue2.Line.ShouldBe((int?)5); + issue2.Line.ShouldBe(5); } [Fact] @@ -80,7 +80,7 @@ public void Should_Read_Issues_Correct_For_Kubeconform() issue1.Priority.ShouldBe((int)IssuePriority.Error); issue1.Rule().ShouldBe("ValidationError"); issue1.AffectedFileRelativePath.ShouldBe("deployment.yaml"); - issue1.Line.ShouldBe((int?)10); + issue1.Line.ShouldBe(10); issue1.Column.ShouldBe(15); var issue2 = issues[1]; @@ -90,7 +90,7 @@ public void Should_Read_Issues_Correct_For_Kubeconform() issue2.Priority.ShouldBe((int)IssuePriority.Error); issue2.Rule().ShouldBe("ConfigError"); issue2.AffectedFileRelativePath.ShouldBe("service.yaml"); - issue2.Line.ShouldBe((int?)8); + issue2.Line.ShouldBe(8); issue2.Column.ShouldBe(5); } @@ -113,7 +113,7 @@ public void Should_Read_Issues_Correct_For_HtmlHint() issue1.Priority.ShouldBe((int)IssuePriority.Error); issue1.Rule().ShouldBe("error"); issue1.AffectedFileRelativePath.ShouldBe("index.html"); - issue1.Line.ShouldBe((int?)12); + issue1.Line.ShouldBe(12); issue1.Column.ShouldBe(5); var issue2 = issues[1]; @@ -123,7 +123,7 @@ public void Should_Read_Issues_Correct_For_HtmlHint() issue2.Priority.ShouldBe((int)IssuePriority.Error); issue2.Rule().ShouldBe("warning"); issue2.AffectedFileRelativePath.ShouldBe("about.html"); - issue2.Line.ShouldBe((int?)8); + issue2.Line.ShouldBe(8); } [Fact] diff --git a/src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs b/src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index cb55e3d17..000000000 --- a/src/Cake.Issues.JUnit.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml new file mode 100644 index 000000000..4c4449fd3 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml new file mode 100644 index 000000000..2b5e4b035 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml @@ -0,0 +1,7 @@ + + + + +This is not valid XML content + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index f24813331..ccf0bf56a 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -58,7 +58,7 @@ protected override IEnumerable InternalReadIssues() // Process failures foreach (var failure in testCase.Elements("failure")) { - var issue = this.ProcessTestFailure(failure, className, testName, suiteName, IssuePriority.Error); + var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error); if (issue != null) { result.Add(issue); @@ -68,29 +68,12 @@ protected override IEnumerable InternalReadIssues() // Process errors foreach (var error in testCase.Elements("error")) { - var issue = this.ProcessTestFailure(error, className, testName, suiteName, IssuePriority.Error); + var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error); if (issue != null) { result.Add(issue); } } - - //// Process system-out for additional context - //var systemOut = testCase.Element("system-out")?.Value; - //if (!string.IsNullOrEmpty(systemOut) && (testCase.Elements("failure").Any() || testCase.Elements("error").Any())) - //{ - // // Try to extract file path and line number from system-out - // var fileInfo = ExtractFileInfoFromOutput(systemOut); - // if (fileInfo.HasValue) - // { - // // Update the last added issue with file information if it doesn't have it - // var lastIssue = result.LastOrDefault(); - // if (lastIssue != null && lastIssue.AffectedFileRelativePath != null) - // { - // result[^1] = UpdateIssueWithFileInfo(lastIssue, fileInfo.Value); - // } - // } - //} } } } @@ -116,11 +99,10 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut // Common patterns for file paths and line numbers in linter output: // file.txt:123:45: message - // file.txt(123,45): message + // file.txt(123,45): message // file.txt line 123: message // /path/to/file.txt:123: message // file.txt:123 message - var patterns = new[] { @"([^\s:]+):(\d+):(\d+)", // file:line:column @@ -139,7 +121,7 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut var filePath = match.Groups[1].Value.Trim(); // Skip if it looks like a URL or doesn't look like a file path - if (filePath.StartsWith("http") || filePath.StartsWith("www.")) + if (filePath.StartsWith("http", StringComparison.Ordinal) || filePath.StartsWith("www.", StringComparison.Ordinal)) { continue; } @@ -197,38 +179,19 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromCla return null; } - ///// - ///// Updates an existing issue with file information. - ///// - ///// The original issue. - ///// The file information to add. - ///// A new issue with the file information. - //private static IIssue UpdateIssueWithFileInfo(IIssue issue, (string FilePath, int? Line, int? Column) fileInfo) - //{ - // var (filePath, line, column) = fileInfo; - - // return IssueBuilder - // .NewIssue(issue.MessageText, issue.ProviderType, issue.ProviderName) - // .WithPriority(issue.Priority) - // .OfRule(issue.Rule, issue.RuleUrl) - // .InFile(filePath, line, line, column, column) - // .Create(); - //} - /// /// Processes a test failure or error element and creates an issue. /// /// The failure or error XML element. /// The test class name. /// The test name. - /// The test suite name. /// The issue priority. /// The created issue or null if the failure should be ignored. - private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, string suiteName, IssuePriority priority) + private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, IssuePriority priority) { var message = failureElement.Attribute("message")?.Value ?? string.Empty; var type = failureElement.Attribute("type")?.Value ?? string.Empty; - var content = failureElement.Value ?? string.Empty; + var content = failureElement.Value?.Trim() ?? string.Empty; // Combine message and content for full description var fullMessage = string.IsNullOrEmpty(message) ? content : diff --git a/src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs b/src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs deleted file mode 100644 index 8b50f795b..000000000 --- a/src/Cake.Issues.JUnit/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Cake.Issues.JUnit.Tests")] \ No newline at end of file From 3087a85438fd2c1dfe6374ad7e8d9f4fb710a3b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:35:30 +0000 Subject: [PATCH 11/35] Add integration tests for JUnit provider and update CI configuration Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../stages/integration-tests-junit.yml | 58 +++ .github/workflows/integrationtests.yml | 54 +++ CiStatus.md | 17 + azure-pipelines.yml | 1 + .../frosting/net8.0/build.ps1 | 13 + .../frosting/net8.0/build.sh | 11 + .../frosting/net8.0/build/.gitignore | 12 + .../frosting/net8.0/build/Build.csproj | 13 + .../frosting/net8.0/build/Program.cs | 51 +++ .../frosting/net8.0/global.json | 7 + .../frosting/net8.0/nuget.config | 14 + .../frosting/net8.0/testdata/cpplint.xml | 14 + .../net8.0/.config/dotnet-tools.json | 12 + .../script-runner/net8.0/.gitignore | 379 ++++++++++++++++++ .../script-runner/net8.0/build.cake | 57 +++ .../script-runner/net8.0/build.ps1 | 15 + .../script-runner/net8.0/build.sh | 11 + .../script-runner/net8.0/buildData.cake | 29 ++ .../script-runner/net8.0/global.json | 7 + .../script-runner/net8.0/nuget.config | 11 + .../script-runner/net8.0/testdata/cpplint.xml | 14 + .../script-runner/net9.0/build.cake | 57 +++ .../script-runner/net9.0/build.ps1 | 15 + .../script-runner/net9.0/build.sh | 11 + .../script-runner/net9.0/buildData.cake | 29 ++ .../script-runner/net9.0/global.json | 7 + .../script-runner/net9.0/nuget.config | 11 + .../script-runner/net9.0/testdata/cpplint.xml | 14 + 28 files changed, 944 insertions(+) create mode 100644 .azuredevops/pipelines/templates/stages/integration-tests-junit.yml create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 create mode 100755 tests/Cake.Issues.JUnit/frosting/net8.0/build.sh create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/global.json create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config create mode 100644 tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 create mode 100755 tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/global.json create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config create mode 100644 tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 create mode 100755 tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/global.json create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml diff --git a/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml b/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml new file mode 100644 index 000000000..f4cdd7022 --- /dev/null +++ b/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml @@ -0,0 +1,58 @@ +# Runs integration tests for Cake.Issues.JUnit on different platforms + +stages: + - stage: IntegrationTestsJUnitStage + displayName: Integration Tests Cake.Issues.JUnit + dependsOn: IntegrationTestsBuildStage + jobs: + - job: TestCakeScriptingJob + displayName: Test Cake Scripting + strategy: + matrix: + Windows_Server_2022: + imageName: 'windows-2022' + Windows_Server_2025: + imageName: 'windows-2025' + macOS_13: + imageName: 'macOS-13' + macOS_14: + imageName: 'macOS-14' + Ubuntu_22_04: + imageName: 'ubuntu-22.04' + Ubuntu_24_04: + imageName: 'ubuntu-24.04' + pool: + vmImage: $(imageName) + steps: + - template: ../steps/install-net8.yml + - template: ../steps/provide-nuget-packages.yml + - powershell: ./build.ps1 --verbosity=diagnostic + workingDirectory: ./tests/Cake.Issues.JUnit/script-runner/net8.0 + displayName: 'Run integration tests (.NET 8.0)' + - powershell: ./build.ps1 --verbosity=diagnostic + workingDirectory: ./tests/Cake.Issues.JUnit/script-runner/net9.0 + displayName: 'Run integration tests (.NET 9.0)' + - job: TestCakeFrostingJob + displayName: Test Cake Frosting .NET 8 + strategy: + matrix: + Windows_Server_2022: + imageName: 'windows-2022' + Windows_Server_2025: + imageName: 'windows-2025' + macOS_13: + imageName: 'macOS-13' + macOS_14: + imageName: 'macOS-14' + Ubuntu_22_04: + imageName: 'ubuntu-22.04' + Ubuntu_24_04: + imageName: 'ubuntu-24.04' + pool: + vmImage: $(imageName) + steps: + - template: ../steps/install-net8.yml + - template: ../steps/provide-nuget-packages.yml + - powershell: ./build.ps1 --verbosity=diagnostic + workingDirectory: ./tests/Cake.Issues.JUnit/frosting/net8.0 + displayName: 'Run integration tests (Frosting .NET 8.0)' \ No newline at end of file diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index dec4b81ad..3aaa9af0a 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -69,6 +69,60 @@ jobs: run: ./build.sh --verbosity=diagnostic working-directory: ./tests/Cake.Issues.MsBuild/script-runner/${{ env.TFM }} shell: bash + # Integration Tests Cake.Issues.JUnit Cake Scripting + IntegrationTestsJUnitCakeScripting: + name: Integration Tests Cake.Issues.JUnit Cake Scripting + needs: Build + strategy: + fail-fast: false + matrix: + os: [ + windows-2022, windows-2025, + ubuntu-22.04, ubuntu-24.04, + macos-13, macos-14] + dotnet: [8.x, 9.x] + runs-on: ${{ matrix.os }} + steps: + - name: Get the sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Prepare integration tests + uses: ./.github/actions/prepare-integration-test + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Determine TFM + run: | + DOTNET_VERSION="${{ matrix.dotnet }}" + DOTNET_MAJOR_VERSION="${DOTNET_VERSION%%.*}" + echo "TFM=net${DOTNET_MAJOR_VERSION}.0" >> $GITHUB_ENV + shell: bash + - name: Run integration tests + run: ./build.sh --verbosity=diagnostic + working-directory: ./tests/Cake.Issues.JUnit/script-runner/${{ env.TFM }} + shell: bash + # Integration Tests Cake.Issues.JUnit Cake Frosting + IntegrationTestsJUnitCakeFrosting: + name: Integration Tests Cake.Issues.JUnit Cake Frosting + needs: Build + strategy: + fail-fast: false + matrix: + os: [ + windows-2022, windows-2025, + ubuntu-22.04, ubuntu-24.04, + macos-13, macos-14] + dotnet: [8.x] + runs-on: ${{ matrix.os }} + steps: + - name: Get the sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Prepare integration tests + uses: ./.github/actions/prepare-integration-test + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Run integration tests + run: ./build.sh --verbosity=diagnostic + working-directory: ./tests/Cake.Issues.JUnit/frosting/net8.0 + shell: bash # Integration Tests Cake.Issues.PullRequests.GitHubActions Cake Scripting IntegrationTestsPullRequestsGitHubActionsCakeScripting: name: Integration Tests Cake.Issues.PullRequests.GitHubActions Cake Scripting diff --git a/CiStatus.md b/CiStatus.md index f93feddd3..223749161 100644 --- a/CiStatus.md +++ b/CiStatus.md @@ -36,6 +36,23 @@ |Azure Pipelines|Cake Scripting|Ubuntu 22.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| |Azure Pipelines|Cake Scripting|Ubuntu 24.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +### Integration Tests Cake.Issues.JUnit + +| CI Server | Runner | Operating System | Develop | Master | +|:--:|:--:|:--:|:--:|:--:| +|Azure Pipelines|Cake Scripting|Windows Server 2022|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|Windows Server 2025|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|macOS 12|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|macOS 14|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|Ubuntu 22.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|Ubuntu 24.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Windows Server 2022|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Windows Server 2025|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|macOS 12|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|macOS 14|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Ubuntu 22.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Ubuntu 24.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| + ### Integration Tests Cake.Issues.Markdownlint | CI Server | Runner | Operating System | Develop | Master | diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d71eb25bc..34548c08a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,6 +24,7 @@ stages: - template: .azuredevops/pipelines/templates/stages/unit-tests.yml - template: .azuredevops/pipelines/templates/stages/build-for-integration-tests.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-git-repository.yml + - template: .azuredevops/pipelines/templates/stages/integration-tests-junit.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-markdownlint.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-msbuild.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-reporting-console.yml diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 b/tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 new file mode 100644 index 000000000..478356d64 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 @@ -0,0 +1,13 @@ +$RECIPE_PACKAGE_PATH = "packages/cake.issues.reporting.console" +if (Test-Path $RECIPE_PACKAGE_PATH) +{ + Write-Host "Cleaning up cached version of $RECIPE_PACKAGE_PATH..." + Remove-Item $RECIPE_PACKAGE_PATH -Recurse; +} +else +{ + Write-Host "$RECIPE_PACKAGE_PATH not cached..." +} + +dotnet run --project build/Build.csproj -- $args +exit $LASTEXITCODE; \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build.sh b/tests/Cake.Issues.JUnit/frosting/net8.0/build.sh new file mode 100755 index 000000000..e0ec44460 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build.sh @@ -0,0 +1,11 @@ + +$RECIPE_PACKAGE_PATH = "packages/cake.issues.reporting.console" +if [ -d "$RECIPE_PACKAGE_PATH" ] +then + echo "Cleaning up cached version of $RECIPE_PACKAGE_PATH..." + rm -Rf $RECIPE_PACKAGE_PATH +else + echo "$RECIPE_PACKAGE_PATH not cached..." +fi + +dotnet run --project ./build/Build.csproj -- "$@" diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore b/tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore new file mode 100644 index 000000000..d9be55e6d --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore @@ -0,0 +1,12 @@ +BuildArtifacts/ +tools/ +.vs/ +.vscode/ + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Cake +tools +!tools/packages.config \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj new file mode 100644 index 000000000..0b8e80956 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj @@ -0,0 +1,13 @@ + + + Exe + net8.0 + $(MSBuildProjectDirectory) + enable + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs new file mode 100644 index 000000000..7dfaeca2c --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs @@ -0,0 +1,51 @@ +using Cake.Common.Diagnostics; +using Cake.Core; +using Cake.Core.IO; +using Cake.Frosting; + +public static class Program +{ + public static int Main(string[] args) + { + return new CakeHost() + .UseContext() + .Run(args); + } +} + +public class BuildContext : FrostingContext +{ + public BuildContext(ICakeContext context) + : base(context) + { + } +} + +[TaskName("Read-Issues")] +public sealed class ReadIssuesTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var junitLogPath = @"../testdata/cpplint.xml"; + + var issues = context.ReadIssues( + context.JUnitIssuesFromFilePath(junitLogPath), + @"../"); + + context.Information("Found {0} issues", issues.Count()); + + // Validate that we found expected issues + if (issues.Count() != 2) + { + throw new Exception($"Expected 2 issues but found {issues.Count()}"); + } + + context.Information("All validation checks passed!"); + } +} + +[TaskName("Default")] +[IsDependentOn(typeof(ReadIssuesTask))] +public class DefaultTask : FrostingTask +{ +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/global.json b/tests/Cake.Issues.JUnit/frosting/net8.0/global.json new file mode 100644 index 000000000..a7bc3e2bf --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "8.0.412", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config b/tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config new file mode 100644 index 000000000..d4a4e547e --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml b/tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json b/tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json new file mode 100644 index 000000000..43e6f74ce --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "5.0.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore b/tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore new file mode 100644 index 000000000..e7c496290 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore @@ -0,0 +1,379 @@ + +# Created by https://www.gitignore.io/api/cake,windows,visualstudio,visualstudiocode + +### Cake ### +tools/* +!tools/packages.config + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + + +# End of https://www.gitignore.io/api/cake,windows,visualstudio,visualstudiocode + + +# Project specific folders +BuildArtifacts diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake new file mode 100644 index 000000000..3cb3de8bd --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake @@ -0,0 +1,57 @@ +#load "buildData.cake" + +#addin "Cake.Issues&prerelease" +#addin "Cake.Issues.JUnit&prerelease" + +////////////////////////////////////////////////// +// ARGUMENTS +////////////////////////////////////////////////// + +var target = Argument("target", "Default"); + +////////////////////////////////////////////////// +// SETUP / TEARDOWN +////////////////////////////////////////////////// + +Setup(setupContext => +{ + return new BuildData(); +}); + +var repoRootFolder = MakeAbsolute(Directory("./")); + +////////////////////////////////////////////////// +// TARGETS +////////////////////////////////////////////////// + +Task("ReadIssues") + .Does((data) => +{ + var junitLogPath = repoRootFolder.Combine("testdata").CombineWithFilePath("cpplint.xml"); + + data.AddIssues( + ReadIssues( + JUnitIssuesFromFilePath(junitLogPath), + repoRootFolder) + ); + + Information("Found {0} issues", data.Issues.Count()); + + // Validate that we found expected issues + if (data.Issues.Count() != 2) + { + throw new Exception($"Expected 2 issues but found {data.Issues.Count()}"); + } + + Information("All validation checks passed!"); +}); + +// Run ReadIssues task by default. +Task("Default") + .IsDependentOn("ReadIssues"); + +////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////// + +RunTarget(target); \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 new file mode 100644 index 000000000..fe6027689 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 @@ -0,0 +1,15 @@ +$ErrorActionPreference = 'Stop' + +$SCRIPT_NAME = "build.cake" + +Write-Host "Restoring .NET Core tools" +dotnet tool restore +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Running Build" +dotnet cake $SCRIPT_NAME @args +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh new file mode 100755 index 000000000..921a3241b --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +SCRIPT_NAME="build.cake" + +echo "Restoring .NET Core tools" +dotnet tool restore + +echo "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap + +echo "Running Build" +dotnet cake $SCRIPT_NAME "$@" \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake b/tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake new file mode 100644 index 000000000..8a22d4145 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake @@ -0,0 +1,29 @@ +public class BuildData +{ + private readonly List issues = new List(); + + /// + /// Gets issues determined during building. + /// + public IEnumerable Issues + { + get + { + return issues.AsReadOnly(); + } + } + + /// + /// Add issues to . + /// + /// List of issues which should be added. + public void AddIssues(IEnumerable issues) + { + if (issues == null) + { + throw new NullReferenceException(nameof(issues)); + } + + this.issues.AddRange(issues); + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/global.json b/tests/Cake.Issues.JUnit/script-runner/net8.0/global.json new file mode 100644 index 000000000..a7bc3e2bf --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "8.0.412", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config b/tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config new file mode 100644 index 000000000..0daba1151 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml b/tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake new file mode 100644 index 000000000..3cb3de8bd --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake @@ -0,0 +1,57 @@ +#load "buildData.cake" + +#addin "Cake.Issues&prerelease" +#addin "Cake.Issues.JUnit&prerelease" + +////////////////////////////////////////////////// +// ARGUMENTS +////////////////////////////////////////////////// + +var target = Argument("target", "Default"); + +////////////////////////////////////////////////// +// SETUP / TEARDOWN +////////////////////////////////////////////////// + +Setup(setupContext => +{ + return new BuildData(); +}); + +var repoRootFolder = MakeAbsolute(Directory("./")); + +////////////////////////////////////////////////// +// TARGETS +////////////////////////////////////////////////// + +Task("ReadIssues") + .Does((data) => +{ + var junitLogPath = repoRootFolder.Combine("testdata").CombineWithFilePath("cpplint.xml"); + + data.AddIssues( + ReadIssues( + JUnitIssuesFromFilePath(junitLogPath), + repoRootFolder) + ); + + Information("Found {0} issues", data.Issues.Count()); + + // Validate that we found expected issues + if (data.Issues.Count() != 2) + { + throw new Exception($"Expected 2 issues but found {data.Issues.Count()}"); + } + + Information("All validation checks passed!"); +}); + +// Run ReadIssues task by default. +Task("Default") + .IsDependentOn("ReadIssues"); + +////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////// + +RunTarget(target); \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 new file mode 100644 index 000000000..fe6027689 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 @@ -0,0 +1,15 @@ +$ErrorActionPreference = 'Stop' + +$SCRIPT_NAME = "build.cake" + +Write-Host "Restoring .NET Core tools" +dotnet tool restore +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Running Build" +dotnet cake $SCRIPT_NAME @args +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh new file mode 100755 index 000000000..921a3241b --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +SCRIPT_NAME="build.cake" + +echo "Restoring .NET Core tools" +dotnet tool restore + +echo "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap + +echo "Running Build" +dotnet cake $SCRIPT_NAME "$@" \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake b/tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake new file mode 100644 index 000000000..8a22d4145 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake @@ -0,0 +1,29 @@ +public class BuildData +{ + private readonly List issues = new List(); + + /// + /// Gets issues determined during building. + /// + public IEnumerable Issues + { + get + { + return issues.AsReadOnly(); + } + } + + /// + /// Add issues to . + /// + /// List of issues which should be added. + public void AddIssues(IEnumerable issues) + { + if (issues == null) + { + throw new NullReferenceException(nameof(issues)); + } + + this.issues.AddRange(issues); + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json b/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json new file mode 100644 index 000000000..a7bc3e2bf --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "8.0.412", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config b/tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config new file mode 100644 index 000000000..0daba1151 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml b/tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file From 484824fc505477da32f559225877b3deceea605b Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 00:58:59 +0200 Subject: [PATCH 12/35] Fix tests --- .github/workflows/integrationtests.yml | 8 +++++++- tests/Cake.Issues.JUnit/script-runner/net9.0/global.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index 3aaa9af0a..b73737d61 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -119,9 +119,15 @@ jobs: uses: ./.github/actions/prepare-integration-test with: dotnet-version: ${{ matrix.dotnet }} + - name: Determine TFM + run: | + DOTNET_VERSION="${{ matrix.dotnet }}" + DOTNET_MAJOR_VERSION="${DOTNET_VERSION%%.*}" + echo "TFM=net${DOTNET_MAJOR_VERSION}.0" >> $GITHUB_ENV + shell: bash - name: Run integration tests run: ./build.sh --verbosity=diagnostic - working-directory: ./tests/Cake.Issues.JUnit/frosting/net8.0 + working-directory: ./tests/Cake.Issues.JUnit/frosting/${{ env.TFM }} shell: bash # Integration Tests Cake.Issues.PullRequests.GitHubActions Cake Scripting IntegrationTestsPullRequestsGitHubActionsCakeScripting: diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json b/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json index a7bc3e2bf..a30898fd0 100644 --- a/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json @@ -1,7 +1,7 @@ { "sdk": { "allowPrerelease": true, - "version": "8.0.412", + "version": "9.0.303", "rollForward": "latestFeature" } } \ No newline at end of file From 84016bb9f63ecbadab1a1e8f70248f6292a1a0f5 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 01:08:58 +0200 Subject: [PATCH 13/35] Fix documentation --- docs/mkdocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b5db682dc..ecfbdf28c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -220,6 +220,10 @@ nav: - Features: documentation/issue-providers/inspectcode/features.md - Examples: documentation/issue-providers/inspectcode/examples.md - API: https://cakebuild.net/extensions/cake-issues-inspectcode" target="_blank + - JUnit: + - documentation/issue-providers/junit/index.md + - Features: documentation/issue-providers/junit/features.md + - API: https://cakebuild.net/extensions/cake-issues-junit" target="_blank - markdownlint: - documentation/issue-providers/markdownlint/index.md - Features: documentation/issue-providers/markdownlint/features.md From 48fa45f8e93dde25880e5784a58887f6dd41bbb5 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 01:25:41 +0200 Subject: [PATCH 14/35] Fix test --- .../templates/stages/integration-tests-junit.yml | 9 +++------ .../script-runner/net9.0/.config/dotnet-tools.json | 12 ++++++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json diff --git a/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml b/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml index f4cdd7022..92ccc2c21 100644 --- a/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml +++ b/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml @@ -6,7 +6,7 @@ stages: dependsOn: IntegrationTestsBuildStage jobs: - job: TestCakeScriptingJob - displayName: Test Cake Scripting + displayName: Test Cake Scripting .NET 8 strategy: matrix: Windows_Server_2022: @@ -28,10 +28,7 @@ stages: - template: ../steps/provide-nuget-packages.yml - powershell: ./build.ps1 --verbosity=diagnostic workingDirectory: ./tests/Cake.Issues.JUnit/script-runner/net8.0 - displayName: 'Run integration tests (.NET 8.0)' - - powershell: ./build.ps1 --verbosity=diagnostic - workingDirectory: ./tests/Cake.Issues.JUnit/script-runner/net9.0 - displayName: 'Run integration tests (.NET 9.0)' + displayName: 'Run integration tests' - job: TestCakeFrostingJob displayName: Test Cake Frosting .NET 8 strategy: @@ -55,4 +52,4 @@ stages: - template: ../steps/provide-nuget-packages.yml - powershell: ./build.ps1 --verbosity=diagnostic workingDirectory: ./tests/Cake.Issues.JUnit/frosting/net8.0 - displayName: 'Run integration tests (Frosting .NET 8.0)' \ No newline at end of file + displayName: 'Run integration tests' \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json b/tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json new file mode 100644 index 000000000..43e6f74ce --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "5.0.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file From 691bf412fe736c3b08882e35a9a449a43206c890 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 02:52:52 +0200 Subject: [PATCH 15/35] Mark issue provider as new in documentation --- docs/input/documentation/issue-providers/junit/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/input/documentation/issue-providers/junit/index.md b/docs/input/documentation/issue-providers/junit/index.md index 899616a60..4622a06b8 100644 --- a/docs/input/documentation/issue-providers/junit/index.md +++ b/docs/input/documentation/issue-providers/junit/index.md @@ -1,6 +1,7 @@ --- title: JUnit issue provider description: Issue provider which allows you to read issues from JUnit XML files. +status: new --- Support for reading issues reported by tools that output JUnit XML format From ff7ad27ee0ef10177438e69265064de58399d1c1 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 02:35:23 +0200 Subject: [PATCH 16/35] Cleanup --- .../nuget/Cake.Frosting.Issues.JUnit.nuspec | 9 +- nuspec/nuget/Cake.Issues.JUnit.nuspec | 2 +- .../JUnitIssuesSettingsTests.cs | 87 ------------------- src/Cake.Issues.JUnit/JUnitIssuesAliases.cs | 43 ++------- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 10 +-- src/Cake.Issues.JUnit/JUnitIssuesSettings.cs | 39 +-------- 6 files changed, 13 insertions(+), 177 deletions(-) diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec b/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec index a74672009..b5caa73df 100644 --- a/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec @@ -29,7 +29,7 @@ For addin compatible with Cake Script Runners see Cake.Issues.JUnit. false Copyright © Cake Issues contributors - cake cake-addin cake-issues cake-issueprovider linting junit xml cpplint commitlint kubeconform htmlhint + cake cake-addin cake-issues cake-issueprovider linting junit xml docs\README.md https://github.com/cake-contrib/Cake.Issues/releases/tag/5.6.1 @@ -48,11 +48,6 @@ For addin compatible with Cake Script Runners see Cake.Issues.JUnit. - - - - - - + \ No newline at end of file diff --git a/nuspec/nuget/Cake.Issues.JUnit.nuspec b/nuspec/nuget/Cake.Issues.JUnit.nuspec index 1dc86a053..33d5086ac 100644 --- a/nuspec/nuget/Cake.Issues.JUnit.nuspec +++ b/nuspec/nuget/Cake.Issues.JUnit.nuspec @@ -29,7 +29,7 @@ For addin compatible with Cake Frosting see Cake.Frosting.Issues.JUnit. false Copyright © Cake Issues contributors - cake cake-addin cake-issues cake-issueprovider linting junit xml cpplint commitlint kubeconform htmlhint + cake cake-addin cake-issues cake-issueprovider linting junit xml docs\README.md https://github.com/cake-contrib/Cake.Issues/releases/tag/5.6.1 diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs index f093b6888..6c63780f1 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs @@ -41,91 +41,4 @@ public void Should_Set_LogFileContent() settings.LogFileContent.ShouldBe(logFileContent); } } - - public sealed class TheFromFilePathMethod - { - [Fact] - public void Should_Throw_If_LogFilePath_Is_Null() - { - // Given / When - var result = Record.Exception(() => - JUnitIssuesSettings.FromFilePath(null)); - - // Then - result.IsArgumentNullException("logFilePath"); - } - } - - public sealed class TheFromContentMethod - { - [Fact] - public void Should_Throw_If_LogFileContent_Is_Null() - { - // Given / When - var result = Record.Exception(() => - JUnitIssuesSettings.FromContent((string)null)); - - // Then - result.IsArgumentNullException("logFileContent"); - } - - [Fact] - public void Should_Throw_If_LogFileContent_Is_Empty() - { - // Given / When - var result = Record.Exception(() => - JUnitIssuesSettings.FromContent(string.Empty)); - - // Then - result.IsArgumentException("logFileContent"); - } - - [Fact] - public void Should_Throw_If_LogFileContent_Is_WhiteSpace() - { - // Given / When - var result = Record.Exception(() => - JUnitIssuesSettings.FromContent(" ")); - - // Then - result.IsArgumentException("logFileContent"); - } - - [Fact] - public void Should_Set_LogFileContent() - { - // Given - var logFileContent = "foo"; - - // When - var settings = JUnitIssuesSettings.FromContent(logFileContent); - - // Then - settings.LogFileContent.ShouldBe(logFileContent.ToByteArray()); - } - - [Fact] - public void Should_Throw_If_LogFileContent_Byte_Array_Is_Null() - { - // Given / When - var result = Record.Exception(() => - JUnitIssuesSettings.FromContent((byte[])null)); - - // Then - result.IsArgumentNullException("logFileContent"); - } - - [Fact] - public void Should_Set_LogFileContent_From_Byte_Array() - { - // Given - var logFileContent = "foo".ToByteArray(); - - // When - var settings = JUnitIssuesSettings.FromContent(logFileContent); - - // Then - settings.LogFileContent.ShouldBe(logFileContent); - } - } } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs index aa0e4a18f..554689117 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs @@ -5,7 +5,7 @@ using Cake.Core.IO; /// -/// Contains functionality related to . +/// Contains functionality for reading issues from JUnit XML files. /// [CakeAliasCategory(IssuesAliasConstants.MainCakeAliasCategory)] public static class JUnitIssuesAliases @@ -23,7 +23,7 @@ public static string JUnitIssuesProviderTypeName( { context.NotNull(); - return JUnitIssuesProvider.ProviderTypeName; + return typeof(JUnitIssuesProvider).FullName; } /// @@ -33,7 +33,7 @@ public static string JUnitIssuesProviderTypeName( /// Path to the JUnit log file. /// Instance of a provider for issues reported in JUnit XML format. /// - /// Report issues reported by cpplint: + /// Read issues from a JUnit XML file: /// /// @@ -62,7 +62,7 @@ public static IIssueProvider JUnitIssuesFromFilePath( /// Content of the JUnit log file. /// Instance of a provider for issues reported in JUnit XML format. /// - /// Report issues reported by kubeconform: + /// Read issues the content of JUnit XML file: /// /// - /// Gets an instance of a provider for issues reported in JUnit XML format using log file content. - /// - /// The context. - /// Content of the JUnit log file. - /// Instance of a provider for issues reported in JUnit XML format. - /// - /// Report issues reported by htmlhint: - /// - /// - /// - /// - [CakeMethodAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static IIssueProvider JUnitIssuesFromContent( - this ICakeContext context, - byte[] logFileContent) - { - context.NotNull(); - logFileContent.NotNull(); - - return context.JUnitIssues(JUnitIssuesSettings.FromContent(logFileContent)); + return context.JUnitIssues(new JUnitIssuesSettings(logFileContent.ToByteArray())); } /// @@ -120,7 +91,7 @@ public static IIssueProvider JUnitIssuesFromContent( /// Settings for reading the JUnit log. /// Instance of a provider for issues reported in JUnit XML format. /// - /// Report issues reported by commitlint-format-junit: + /// Read issues from a JUnit XML file: /// /// /// The Cake log context. /// Settings for the issue provider. -public class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) +internal class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) : BaseConfigurableIssueProvider(log, issueProviderSettings) { - /// - /// Gets the name of the JUnit issue provider. - /// This name can be used to identify issues based on the property. - /// - public static string ProviderTypeName => typeof(JUnitIssuesProvider).FullName; - /// public override string ProviderName => "JUnit"; @@ -204,7 +198,7 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str } var issueBuilder = IssueBuilder - .NewIssue(fullMessage, ProviderTypeName, this.ProviderName) + .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, this.ProviderName) .WithPriority(priority); if (!string.IsNullOrEmpty(type)) diff --git a/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs index fa49c3f87..baa1d7635 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs @@ -1,10 +1,9 @@ namespace Cake.Issues.JUnit; -using System.Text; using Cake.Core.IO; /// -/// Settings for . +/// Settings for . /// public class JUnitIssuesSettings : IssueProviderSettings { @@ -27,40 +26,4 @@ public JUnitIssuesSettings(byte[] logFileContent) : base(logFileContent) { } - - /// - /// Returns a new instance of the class from a log file on disk. - /// - /// Path to the JUnit log file. - /// Instance of the class. - public static JUnitIssuesSettings FromFilePath(FilePath logFilePath) - { - logFilePath.NotNull(); - - return new JUnitIssuesSettings(logFilePath); - } - - /// - /// Returns a new instance of the class from log file content. - /// - /// Content of the JUnit log file. - /// Instance of the class. - public static JUnitIssuesSettings FromContent(string logFileContent) - { - logFileContent.NotNullOrWhiteSpace(); - - return new JUnitIssuesSettings(Encoding.UTF8.GetBytes(logFileContent)); - } - - /// - /// Returns a new instance of the class from log file content. - /// - /// Content of the JUnit log file. - /// Instance of the class. - public static JUnitIssuesSettings FromContent(byte[] logFileContent) - { - logFileContent.NotNull(); - - return new JUnitIssuesSettings(logFileContent); - } } \ No newline at end of file From bf5f899afc81ef2bb165b64c4268cb3dadf897e0 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 10:54:14 +0200 Subject: [PATCH 17/35] Fix line and column reporting --- .../JUnitIssuesProviderTests.cs | 40 +++++++++++-------- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 2 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 7728b5326..8a0405c55 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -42,23 +42,29 @@ public void Should_Read_Issues_Correct_For_CppLint() // Then issues.Count.ShouldBe(2); - var issue1 = issues[0]; - issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue1.ProviderName.ShouldBe("JUnit"); - issue1.MessageText.ShouldBe("Lines should be <= 80 characters long\nsrc/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2]"); - issue1.Priority.ShouldBe((int)IssuePriority.Error); - issue1.Rule().ShouldBe("warning"); - issue1.AffectedFileRelativePath.ShouldBe("src/example.cpp"); - issue1.Line.ShouldBe(15); - - var issue2 = issues[1]; - issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue2.ProviderName.ShouldBe("JUnit"); - issue2.MessageText.ShouldBe("Include order issue\nsrc/example.cpp:5: #includes are not properly sorted [build/include_order] [4]"); - issue2.Priority.ShouldBe((int)IssuePriority.Error); - issue2.Rule().ShouldBe("warning"); - issue2.AffectedFileRelativePath.ShouldBe("src/example.cpp"); - issue2.Line.ShouldBe(5); + var issue = issues[0]; + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "Lines should be <= 80 characters long\nsrc/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"src/example.cpp", 15) + .OfRule("warning") + .WithPriority(IssuePriority.Error) + .Create()); + + issue = issues[1]; + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "Include order issue\nsrc/example.cpp:5: #includes are not properly sorted [build/include_order] [4]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"src/example.cpp", 5) + .OfRule("warning") + .WithPriority(IssuePriority.Error) + .Create()); } [Fact] diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index e8064fc8d..51415f50d 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -216,7 +216,7 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str if (fileInfo.HasValue) { var (filePath, line, column) = fileInfo.Value; - issueBuilder = issueBuilder.InFile(filePath, line, line, column, column); + issueBuilder = issueBuilder.InFile(filePath, line, column); } return issueBuilder.Create(); From 0357b07192f485aa0bea8766db1c2ab17916c112 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 09:12:35 +0000 Subject: [PATCH 18/35] Add comprehensive cpplint-style JUnit test cases and improve file path extraction Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../JUnitIssuesProviderTests.cs | 256 +++++++++++++++--- .../Testfiles/cpplint-mixed-error-failure.xml | 9 + .../Testfiles/cpplint-multiple-errors.xml | 7 + .../Testfiles/cpplint-multiple-failures.xml | 10 + .../Testfiles/cpplint-passed.xml | 5 + .../Testfiles/cpplint-single-error.xml | 6 + .../Testfiles/cpplint-xml-escaping.xml | 9 + src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 36 ++- 8 files changed, 294 insertions(+), 44 deletions(-) create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 8a0405c55..81fc00e4d 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -79,25 +79,27 @@ public void Should_Read_Issues_Correct_For_Kubeconform() // Then issues.Count.ShouldBe(2); - var issue1 = issues[0]; - issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue1.ProviderName.ShouldBe("JUnit"); - issue1.MessageText.ShouldBe("Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string"); - issue1.Priority.ShouldBe((int)IssuePriority.Error); - issue1.Rule().ShouldBe("ValidationError"); - issue1.AffectedFileRelativePath.ShouldBe("deployment.yaml"); - issue1.Line.ShouldBe(10); - issue1.Column.ShouldBe(15); - - var issue2 = issues[1]; - issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue2.ProviderName.ShouldBe("JUnit"); - issue2.MessageText.ShouldBe("Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service"); - issue2.Priority.ShouldBe((int)IssuePriority.Error); - issue2.Rule().ShouldBe("ConfigError"); - issue2.AffectedFileRelativePath.ShouldBe("service.yaml"); - issue2.Line.ShouldBe(8); - issue2.Column.ShouldBe(5); + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"deployment.yaml", 10, 15) + .OfRule("ValidationError") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"service.yaml", 8, 5) + .OfRule("ConfigError") + .WithPriority(IssuePriority.Error) + .Create()); } [Fact] @@ -112,24 +114,27 @@ public void Should_Read_Issues_Correct_For_HtmlHint() // Then issues.Count.ShouldBe(2); - var issue1 = issues[0]; - issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue1.ProviderName.ShouldBe("JUnit"); - issue1.MessageText.ShouldBe("Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase"); - issue1.Priority.ShouldBe((int)IssuePriority.Error); - issue1.Rule().ShouldBe("error"); - issue1.AffectedFileRelativePath.ShouldBe("index.html"); - issue1.Line.ShouldBe(12); - issue1.Column.ShouldBe(5); - - var issue2 = issues[1]; - issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue2.ProviderName.ShouldBe("JUnit"); - issue2.MessageText.ShouldBe("Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes."); - issue2.Priority.ShouldBe((int)IssuePriority.Error); - issue2.Rule().ShouldBe("warning"); - issue2.AffectedFileRelativePath.ShouldBe("about.html"); - issue2.Line.ShouldBe(8); + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"index.html", 12, 5) + .OfRule("error") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes.", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"about.html", 8) + .OfRule("warning") + .WithPriority(IssuePriority.Error) + .Create()); } [Fact] @@ -144,12 +149,15 @@ public void Should_Read_Issues_Correct_For_CommitLint() // Then issues.Count.ShouldBe(1); - var issue = issues[0]; - issue.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider"); - issue.ProviderName.ShouldBe("JUnit"); - issue.MessageText.ShouldBe("Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]"); - issue.Priority.ShouldBe((int)IssuePriority.Error); - issue.Rule().ShouldBe("error"); + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("error") + .WithPriority(IssuePriority.Error) + .Create()); } [Fact] @@ -185,5 +193,167 @@ public void Should_Handle_Invalid_XML() Should.Throw(() => fixture.ReadIssues().ToList()) .Message.ShouldContain("Failed to parse JUnit XML"); } + + [Fact] + public void Should_Handle_CppLint_Passed_Test() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(0); + } + + [Fact] + public void Should_Handle_CppLint_Single_Error() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "ErrMsg1", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Multiple_Errors() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "ErrMsg1\nErrMsg2", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Mixed_Error_And_Failure() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "ErrMsg", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "5: FailMsg [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File", 5) + .OfRule("File") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "5: FailMsg1 [category/subcategory] [3]\n19: FailMsg3 [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File1", 5) + .OfRule("File1") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "99: FailMsg2 [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File2", 99) + .OfRule("File2") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_XML_Escaping() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "&", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "5: & [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File1", 5) + .OfRule("File1") + .WithPriority(IssuePriority.Error) + .Create()); + } } } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml new file mode 100644 index 000000000..bcd7fbc62 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml @@ -0,0 +1,9 @@ + + + + ErrMsg + + + 5: FailMsg [category/subcategory] [3] + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml new file mode 100644 index 000000000..a7bdca34e --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml @@ -0,0 +1,7 @@ + + + + ErrMsg1 +ErrMsg2 + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml new file mode 100644 index 000000000..718a45ae5 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml @@ -0,0 +1,10 @@ + + + + 5: FailMsg1 [category/subcategory] [3] +19: FailMsg3 [category/subcategory] [3] + + + 99: FailMsg2 [category/subcategory] [3] + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml new file mode 100644 index 000000000..d45c77584 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml new file mode 100644 index 000000000..21d95e7df --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml @@ -0,0 +1,6 @@ + + + + ErrMsg1 + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml new file mode 100644 index 000000000..49769bc8c --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml @@ -0,0 +1,9 @@ + + + + &</error> + + + 5: &</failure> [category/subcategory] [3] + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index 51415f50d..b971ee72b 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -210,9 +210,43 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str issueBuilder = issueBuilder.OfRule(testName); } - // Try to extract file information from the message or content + // Try to extract file information from the message or content first var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + // For cpplint-style output, if we don't have file info and the test name looks like a file name, + // use the test name as the file name and try to extract line info from the message + if (!fileInfo.HasValue && !string.IsNullOrEmpty(testName) && testName != "errors") + { + // Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]" + // This is a strong indicator that it's a cpplint-style failure where the test name is the file name + var lineMatch = Regex.Match( + fullMessage, + @"^(\d+):\s*.*\[.*\].*\[.*\]", + RegexOptions.Multiline); + if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) + { + fileInfo = (testName, lineNum, null); + } + + // Also check for simple line number pattern without the category/subcategory format + else + { + var simpleLineMatch = Regex.Match( + fullMessage, + @"^(\d+):", + RegexOptions.Multiline); + if (simpleLineMatch.Success && + int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum)) + { + // Only treat as file if the test name doesn't contain hyphens (which are common in rule names) + if (!testName.Contains('-') && !testName.Contains('_')) + { + fileInfo = (testName, simpleLineNum, null); + } + } + } + } + if (fileInfo.HasValue) { var (filePath, line, column) = fileInfo.Value; From 746694ef0c2eae7e86b6fcd7b56c184fcad7c612 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 11:16:58 +0200 Subject: [PATCH 19/35] Cleanup tests --- .../JUnitIssuesProviderTests.cs | 240 +++++++++--------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 81fc00e4d..d8284a975 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -68,45 +68,69 @@ public void Should_Read_Issues_Correct_For_CppLint() } [Fact] - public void Should_Read_Issues_Correct_For_Kubeconform() + public void Should_Handle_CppLint_Passed_Test() { // Given - var fixture = new JUnitIssuesProviderFixture("kubeconform.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml"); // When var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(2); + issues.ShouldBeEmpty(); + } + + [Fact] + public void Should_Handle_CppLint_Single_Error() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + var issue = issues.ShouldHaveSingleItem(); IssueChecker.Check( - issues[0], + issue, IssueBuilder.NewIssue( - "Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string", + "ErrMsg1", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile(@"deployment.yaml", 10, 15) - .OfRule("ValidationError") + .OfRule("errors") .WithPriority(IssuePriority.Error) .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Multiple_Errors() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + var issue = issues.ShouldHaveSingleItem(); IssueChecker.Check( - issues[1], + issue, IssueBuilder.NewIssue( - "Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service", + "ErrMsg1\nErrMsg2", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile(@"service.yaml", 8, 5) - .OfRule("ConfigError") + .OfRule("errors") .WithPriority(IssuePriority.Error) .Create()); } [Fact] - public void Should_Read_Issues_Correct_For_HtmlHint() + public void Should_Handle_CppLint_Mixed_Error_And_Failure() { // Given - var fixture = new JUnitIssuesProviderFixture("htmlhint.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -117,147 +141,99 @@ public void Should_Read_Issues_Correct_For_HtmlHint() IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase", + "ErrMsg", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile(@"index.html", 12, 5) - .OfRule("error") + .OfRule("errors") .WithPriority(IssuePriority.Error) .Create()); IssueChecker.Check( issues[1], IssueBuilder.NewIssue( - "Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes.", + "5: FailMsg [category/subcategory] [3]", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile(@"about.html", 8) - .OfRule("warning") + .InFile("File", 5) + .OfRule("File") .WithPriority(IssuePriority.Error) .Create()); } [Fact] - public void Should_Read_Issues_Correct_For_CommitLint() + public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() { // Given - var fixture = new JUnitIssuesProviderFixture("commitlint.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml"); // When var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(1); + issues.Count.ShouldBe(2); IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]", + "5: FailMsg1 [category/subcategory] [3]\n19: FailMsg3 [category/subcategory] [3]", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .OfRule("error") + .InFile("File1", 5) + .OfRule("File1") .WithPriority(IssuePriority.Error) .Create()); - } - - [Fact] - public void Should_Handle_Empty_TestSuite() - { - // Given - var junitContent = @" - -"; - var fixture = new JUnitIssuesProviderFixture("empty.xml"); - fixture.SetFileContent(junitContent); - - // When - var issues = fixture.ReadIssues().ToList(); - // Then - issues.Count.ShouldBe(0); - } - - [Fact] - public void Should_Handle_Invalid_XML() - { - // Given - var junitContent = @" - - - - "; - var fixture = new JUnitIssuesProviderFixture("invalid.xml"); - fixture.SetFileContent(junitContent); - - // When / Then - Should.Throw(() => fixture.ReadIssues().ToList()) - .Message.ShouldContain("Failed to parse JUnit XML"); - } - - [Fact] - public void Should_Handle_CppLint_Passed_Test() - { - // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml"); - - // When - var issues = fixture.ReadIssues().ToList(); - - // Then - issues.Count.ShouldBe(0); + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "99: FailMsg2 [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File2", 99) + .OfRule("File2") + .WithPriority(IssuePriority.Error) + .Create()); } [Fact] - public void Should_Handle_CppLint_Single_Error() + public void Should_Handle_CppLint_XML_Escaping() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml"); // When var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(1); + issues.Count.ShouldBe(2); IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "ErrMsg1", + "&", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") .OfRule("errors") .WithPriority(IssuePriority.Error) .Create()); - } - - [Fact] - public void Should_Handle_CppLint_Multiple_Errors() - { - // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml"); - - // When - var issues = fixture.ReadIssues().ToList(); - - // Then - issues.Count.ShouldBe(1); IssueChecker.Check( - issues[0], + issues[1], IssueBuilder.NewIssue( - "ErrMsg1\nErrMsg2", + "5: & [category/subcategory] [3]", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .OfRule("errors") + .InFile("File1", 5) + .OfRule("File1") .WithPriority(IssuePriority.Error) .Create()); } [Fact] - public void Should_Handle_CppLint_Mixed_Error_And_Failure() + public void Should_Read_Issues_Correct_For_Kubeconform() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml"); + var fixture = new JUnitIssuesProviderFixture("kubeconform.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -268,30 +244,31 @@ public void Should_Handle_CppLint_Mixed_Error_And_Failure() IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "ErrMsg", + "Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .OfRule("errors") + .InFile(@"deployment.yaml", 10, 15) + .OfRule("ValidationError") .WithPriority(IssuePriority.Error) .Create()); IssueChecker.Check( issues[1], IssueBuilder.NewIssue( - "5: FailMsg [category/subcategory] [3]", + "Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile("File", 5) - .OfRule("File") + .InFile(@"service.yaml", 8, 5) + .OfRule("ConfigError") .WithPriority(IssuePriority.Error) .Create()); } [Fact] - public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() + public void Should_Read_Issues_Correct_For_HtmlHint() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml"); + var fixture = new JUnitIssuesProviderFixture("htmlhint.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -302,58 +279,81 @@ public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "5: FailMsg1 [category/subcategory] [3]\n19: FailMsg3 [category/subcategory] [3]", + "Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile("File1", 5) - .OfRule("File1") + .InFile(@"index.html", 12, 5) + .OfRule("error") .WithPriority(IssuePriority.Error) .Create()); IssueChecker.Check( issues[1], IssueBuilder.NewIssue( - "99: FailMsg2 [category/subcategory] [3]", + "Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes.", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile("File2", 99) - .OfRule("File2") + .InFile(@"about.html", 8) + .OfRule("warning") .WithPriority(IssuePriority.Error) .Create()); } [Fact] - public void Should_Handle_CppLint_XML_Escaping() + public void Should_Read_Issues_Correct_For_CommitLint() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml"); + var fixture = new JUnitIssuesProviderFixture("commitlint.xml"); // When var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(2); + issues.Count.ShouldBe(1); IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "&", + "Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .OfRule("errors") + .OfRule("error") .WithPriority(IssuePriority.Error) .Create()); + } - IssueChecker.Check( - issues[1], - IssueBuilder.NewIssue( - "5: & [category/subcategory] [3]", - "Cake.Issues.JUnit.JUnitIssuesProvider", - "JUnit") - .InFile("File1", 5) - .OfRule("File1") - .WithPriority(IssuePriority.Error) - .Create()); + [Fact] + public void Should_Handle_Empty_TestSuite() + { + // Given + var junitContent = @" + +"; + var fixture = new JUnitIssuesProviderFixture("empty.xml"); + fixture.SetFileContent(junitContent); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(0); + } + + [Fact] + public void Should_Handle_Invalid_XML() + { + // Given + var junitContent = @" + + + + "; + var fixture = new JUnitIssuesProviderFixture("invalid.xml"); + fixture.SetFileContent(junitContent); + + // When / Then + Should.Throw(() => fixture.ReadIssues().ToList()) + .Message.ShouldContain("Failed to parse JUnit XML"); } } } \ No newline at end of file From dbdd83205b3ee55943325bc09ee0db7f5e75eba4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 09:28:21 +0000 Subject: [PATCH 20/35] Replace commitlint test XML with official documentation format and enhance nested testsuite support Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../JUnitIssuesProviderTests.cs | 2 +- .../Testfiles/commitlint.xml | 16 ++-- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 78 ++++++++++++------- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index d8284a975..7a5523241 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -314,7 +314,7 @@ public void Should_Read_Issues_Correct_For_CommitLint() IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]", + "type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum)\n\nfoo: bar", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") .OfRule("error") diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml index 7fd192b15..781d11850 100644 --- a/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml @@ -1,10 +1,10 @@ - - - - -commit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] - - - + + + + type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum) + +foo: bar + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index b971ee72b..02f99d955 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -41,34 +41,7 @@ protected override IEnumerable InternalReadIssues() continue; } - var suiteName = testSuite.Attribute("name")?.Value ?? string.Empty; - - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - var time = testCase.Attribute("time")?.Value; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error); - if (issue != null) - { - result.Add(issue); - } - } - - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error); - if (issue != null) - { - result.Add(issue); - } - } - } + this.ProcessTestSuite(testSuite, result); } } catch (Exception ex) @@ -79,6 +52,55 @@ protected override IEnumerable InternalReadIssues() return result; } + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + private void ProcessTestSuite(XElement testSuite, List result) + { + if (testSuite == null) + { + return; + } + + var suiteName = testSuite.Attribute("name")?.Value ?? string.Empty; + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + var time = testCase.Attribute("time")?.Value; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result); + } + } + /// /// Tries to extract file path and line information from output text. /// From 40511a6115a26e1e1cb962b7030f1952219aa37a Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 12:33:21 +0200 Subject: [PATCH 21/35] Test cleanup --- .../JUnitIssuesProviderFixture.cs | 12 ------------ .../JUnitIssuesProviderTests.cs | 16 +++------------- src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml | 3 ++- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs index 981a47070..c26a916f2 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs @@ -1,7 +1,5 @@ namespace Cake.Issues.JUnit.Tests; -using System.Text; - internal class JUnitIssuesProviderFixture : BaseConfigurableIssueProviderFixture { @@ -18,14 +16,4 @@ public JUnitIssuesProviderFixture(string fileResourceName, string repositoryRoot } protected override string FileResourceNamespace => "Cake.Issues.JUnit.Tests.Testfiles."; - - /// - /// Sets the content of the log file. - /// - /// Content to set. - public void SetFileContent(string content) - { - content.NotNullOrWhiteSpace(); - this.LogFileContent = Encoding.UTF8.GetBytes(content); - } } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 7a5523241..9443f613e 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -309,10 +309,10 @@ public void Should_Read_Issues_Correct_For_CommitLint() var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(1); + var issue = issues.ShouldHaveSingleItem(); IssueChecker.Check( - issues[0], + issue, IssueBuilder.NewIssue( "type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum)\n\nfoo: bar", "Cake.Issues.JUnit.JUnitIssuesProvider", @@ -326,30 +326,20 @@ public void Should_Read_Issues_Correct_For_CommitLint() public void Should_Handle_Empty_TestSuite() { // Given - var junitContent = @" - -"; var fixture = new JUnitIssuesProviderFixture("empty.xml"); - fixture.SetFileContent(junitContent); // When var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(0); + issues.ShouldBeEmpty(); } [Fact] public void Should_Handle_Invalid_XML() { // Given - var junitContent = @" - - - - "; var fixture = new JUnitIssuesProviderFixture("invalid.xml"); - fixture.SetFileContent(junitContent); // When / Then Should.Throw(() => fixture.ReadIssues().ToList()) diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml index 4c4449fd3..e96bc4a25 100644 --- a/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml @@ -1,2 +1,3 @@ - \ No newline at end of file + + \ No newline at end of file From 949019642de93f0064d6e71981815b8d18b54393 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 12:41:49 +0200 Subject: [PATCH 22/35] Update list of supported tools --- .../input/documentation/assets/tables/supported-tools-cpp.csv | 2 ++ docs/input/documentation/supported-tools.md | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 docs/input/documentation/assets/tables/supported-tools-cpp.csv diff --git a/docs/input/documentation/assets/tables/supported-tools-cpp.csv b/docs/input/documentation/assets/tables/supported-tools-cpp.csv new file mode 100644 index 000000000..fdf09d6e7 --- /dev/null +++ b/docs/input/documentation/assets/tables/supported-tools-cpp.csv @@ -0,0 +1,2 @@ +"Tool","Tool Version","Format","Issue Provider","Supported Since" +"[cpplint](https://github.com/cpplint/cpplint){target='_blank'} :material-alert-decagram:",,"[junit](https://github.com/cpplint/cpplint){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.7.0 diff --git a/docs/input/documentation/supported-tools.md b/docs/input/documentation/supported-tools.md index b32ea204f..29bacd316 100644 --- a/docs/input/documentation/supported-tools.md +++ b/docs/input/documentation/supported-tools.md @@ -37,6 +37,10 @@ This pages lists tools known to be working with Cake Issues (1) {{ read_csv('assets/tables/supported-tools-copypaste.csv',keep_default_na=False) }} +## C++ + +{{ read_csv('assets/tables/supported-tools-cpp.csv',keep_default_na=False) }} + ## CSS, SCSS, Sass {{ read_csv('assets/tables/supported-tools-css.csv',keep_default_na=False) }} From e0a62560b9c8c7d15249024ccc4c321f3cde66f8 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 3 Aug 2025 12:48:30 +0200 Subject: [PATCH 23/35] Fix linting issues --- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 96 ++++++++++---------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index 02f99d955..d8ef5833a 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -52,55 +52,6 @@ protected override IEnumerable InternalReadIssues() return result; } - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - private void ProcessTestSuite(XElement testSuite, List result) - { - if (testSuite == null) - { - return; - } - - var suiteName = testSuite.Attribute("name")?.Value ?? string.Empty; - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - var time = testCase.Attribute("time")?.Value; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error); - if (issue != null) - { - result.Add(issue); - } - } - - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result); - } - } - /// /// Tries to extract file path and line information from output text. /// @@ -195,6 +146,53 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromCla return null; } + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + private void ProcessTestSuite(XElement testSuite, List result) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + var time = testCase.Attribute("time")?.Value; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result); + } + } + /// /// Processes a test failure or error element and creates an issue. /// From e3f49a07e08efd0f522522da86e50eaf880e4077 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:45:03 +0000 Subject: [PATCH 24/35] Add markdownlint-cli2 JUnit support and update supported tools list Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../tables/supported-tools-markdown.csv | 3 +- .../JUnitIssuesProviderTests.cs | 68 +++++++++++++++++ .../Testfiles/markdownlint-cli2.xml | 28 +++++++ src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 73 ++++++++++++++++++- 4 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml diff --git a/docs/input/documentation/assets/tables/supported-tools-markdown.csv b/docs/input/documentation/assets/tables/supported-tools-markdown.csv index 9f64c7f04..e72b5a27c 100644 --- a/docs/input/documentation/assets/tables/supported-tools-markdown.csv +++ b/docs/input/documentation/assets/tables/supported-tools-markdown.csv @@ -5,4 +5,5 @@ "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.9.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.3.0 "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.19.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.8.1 "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.22.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.8.2 -"[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",,"[json](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",1.1.0 \ No newline at end of file +"[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",,"[json](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",1.1.0 +"[markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2){target='_blank'}",,"[JUnit](https://github.com/DavidAnson/markdownlint-cli2#markdownlint-cli2-formatter-junit){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 9443f613e..1488a138d 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -322,6 +322,74 @@ public void Should_Read_Issues_Correct_For_CommitLint() .Create()); } + [Fact] + public void Should_Read_Issues_Correct_For_MarkdownlintCli2() + { + // Given + var fixture = new JUnitIssuesProviderFixture("markdownlint-cli2.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(5); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Trailing spaces\nLine 3, Column 10, Expected: 0 or 2; Actual: 1", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 3, 10) + .OfRule("MD009/no-trailing-spaces") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "Multiple consecutive blank lines\nLine 5, Expected: 1; Actual: 2", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 5) + .OfRule("MD012/no-multiple-blanks") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[2], + IssueBuilder.NewIssue( + "Multiple top-level headings in the same document\nLine 6, Context: \"# Description\"", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 6) + .OfRule("MD025/single-title/single-h1") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[3], + IssueBuilder.NewIssue( + "Multiple spaces after hash on atx style heading\nLine 12, Column 1, Context: \"## Summary\"", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 12, 1) + .OfRule("MD019/no-multiple-space-atx") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[4], + IssueBuilder.NewIssue( + "Files should end with a single newline character\nLine 14, Column 14", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 14, 14) + .OfRule("MD047/single-trailing-newline") + .WithPriority(IssuePriority.Error) + .Create()); + } + [Fact] public void Should_Handle_Empty_TestSuite() { diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml new file mode 100644 index 000000000..4559d7e50 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index d8ef5833a..2b4dbd27c 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -70,13 +70,14 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut // file.txt line 123: message // /path/to/file.txt:123: message // file.txt:123 message + // Line 3, Column 10 (markdownlint-cli2 format) var patterns = new[] { @"([^\s:]+):(\d+):(\d+)", // file:line:column @"([^\s:]+):(\d+)", // file:line @"([^\s\(\)]+)\((\d+),(\d+)\)", // file(line,column) @"([^\s\(\)]+)\((\d+)\)", // file(line) - @"([^\s]+)\s+line\s+(\d+)", // file line 123 + @"^([^\s]+)\s+line\s+(\d+)", // file line 123 (must start at beginning of line) @"File:\s*([^\s]+)", // File: path }; @@ -113,6 +114,53 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut return null; } + /// + /// Tries to extract line and column information from markdownlint-cli2 format text. + /// + /// The output text to parse. + /// Line and column information if found, null otherwise. + private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(string output) + { + if (string.IsNullOrEmpty(output)) + { + return null; + } + + // Patterns for markdownlint-cli2 format: + // Line 3, Column 10, Expected: 0 or 2; Actual: 1 + // Line 5, Expected: 1; Actual: 2 + // Line 6, Context: "# Description" + var patterns = new[] + { + @"Line\s+(\d+),\s+Column\s+(\d+)", // Line 3, Column 10 + @"Line\s+(\d+)", // Line 5 + }; + + foreach (var pattern in patterns) + { + var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase); + if (match.Success) + { + int? line = null; + int? column = null; + + if (int.TryParse(match.Groups[1].Value, out var lineNum)) + { + line = lineNum; + } + + if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var colNum)) + { + column = colNum; + } + + return (line, column); + } + } + + return null; + } + /// /// Tries to extract file path from a class name. /// @@ -230,8 +278,27 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str issueBuilder = issueBuilder.OfRule(testName); } - // Try to extract file information from the message or content first - var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + // Initialize fileInfo variable + (string FilePath, int? Line, int? Column)? fileInfo = null; + + // For markdownlint-cli2 style output, check if the content contains the specific format + // and use the class name as file path in that case + if (!string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(content)) + { + var lineColumnInfo = ExtractLineColumnFromMarkdownlintCli2(content); + if (lineColumnInfo.HasValue) + { + // This looks like markdownlint-cli2 format, use class name as file path + var (line, column) = lineColumnInfo.Value; + fileInfo = (className, line, column); + } + } + + // If we don't have file info yet, try to extract from the message or content + if (!fileInfo.HasValue) + { + fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + } // For cpplint-style output, if we don't have file info and the test name looks like a file name, // use the test name as the file name and try to extract line info from the message From 90176c52aafdbee133f98684dd6ef0302e7bdea9 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Mon, 4 Aug 2025 21:04:44 +0200 Subject: [PATCH 25/35] Add more tools to list --- .../documentation/assets/tables/supported-tools-cpp.csv | 2 +- .../documentation/assets/tables/supported-tools-git.csv | 2 ++ .../documentation/assets/tables/supported-tools-html.csv | 2 ++ .../assets/tables/supported-tools-kubernetes.csv | 1 + .../assets/tables/supported-tools-markdown.csv | 2 +- docs/input/documentation/supported-tools.md | 8 ++++++++ 6 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 docs/input/documentation/assets/tables/supported-tools-git.csv create mode 100644 docs/input/documentation/assets/tables/supported-tools-html.csv diff --git a/docs/input/documentation/assets/tables/supported-tools-cpp.csv b/docs/input/documentation/assets/tables/supported-tools-cpp.csv index fdf09d6e7..3c033fb7b 100644 --- a/docs/input/documentation/assets/tables/supported-tools-cpp.csv +++ b/docs/input/documentation/assets/tables/supported-tools-cpp.csv @@ -1,2 +1,2 @@ "Tool","Tool Version","Format","Issue Provider","Supported Since" -"[cpplint](https://github.com/cpplint/cpplint){target='_blank'} :material-alert-decagram:",,"[junit](https://github.com/cpplint/cpplint){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.7.0 +"[cpplint](https://github.com/cpplint/cpplint){target='_blank'} :material-alert-decagram:",,"[junit](https://github.com/cpplint/cpplint){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 diff --git a/docs/input/documentation/assets/tables/supported-tools-git.csv b/docs/input/documentation/assets/tables/supported-tools-git.csv new file mode 100644 index 000000000..93e396cac --- /dev/null +++ b/docs/input/documentation/assets/tables/supported-tools-git.csv @@ -0,0 +1,2 @@ +"Tool","Tool Version","Format","Issue Provider","Supported Since" +"[commitlint](https://commitlint.js.org/){target='_blank'} :material-alert-decagram:",,"[junit](https://www.npmjs.com/package/commitlint-format-junit){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/docs/input/documentation/assets/tables/supported-tools-html.csv b/docs/input/documentation/assets/tables/supported-tools-html.csv new file mode 100644 index 000000000..50e555f3e --- /dev/null +++ b/docs/input/documentation/assets/tables/supported-tools-html.csv @@ -0,0 +1,2 @@ +"Tool","Tool Version","Format","Issue Provider","Supported Since" +"[HtmlHint](https://htmlhint.com/){target='_blank'} :material-alert-decagram:",,"[junit](https://htmlhint.com/usage/options/#format){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv b/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv index bcbfbbaa3..d6f24e4cd 100644 --- a/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv +++ b/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv @@ -2,4 +2,5 @@ "[checkov](https://www.checkov.io/){target='_blank'}",,"[SARIF](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html){target='_blank'}","[Cake.Issues.Sarif](issue-providers/sarif/index.md)",4.2.0 "[kics](https://kics.io/){target='_blank'}",,"[sarif](https://github.com/Checkmarx/kics/blob/master/docs/results.md#sarif){target='_blank'}","[Cake.Issues.Sarif](issue-providers/sarif/index.md)",4.2.0 "[Kubeconform](https://github.com/yannh/kubeconform){target='_blank'} :material-alert-decagram:",,"[tap](https://github.com/yannh/kubeconform?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Tap](issue-providers/tap/index.md)",4.2.0 +"[Kubeconform](https://github.com/yannh/kubeconform){target='_blank'} :material-alert-decagram:",,"[junit](https://github.com/yannh/kubeconform?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 "[Trivy](https://trivy.dev/){target='_blank'}",,"[SARIF](https://trivy.dev/v0.58/docs/configuration/reporting/#sarif){target='_blank'}","[Cake.Issues.Sarif](issue-providers/sarif/index.md)",4.2.0 \ No newline at end of file diff --git a/docs/input/documentation/assets/tables/supported-tools-markdown.csv b/docs/input/documentation/assets/tables/supported-tools-markdown.csv index e72b5a27c..ac03f4434 100644 --- a/docs/input/documentation/assets/tables/supported-tools-markdown.csv +++ b/docs/input/documentation/assets/tables/supported-tools-markdown.csv @@ -6,4 +6,4 @@ "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.19.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.8.1 "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.22.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.8.2 "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",,"[json](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",1.1.0 -"[markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2){target='_blank'}",,"[JUnit](https://github.com/DavidAnson/markdownlint-cli2#markdownlint-cli2-formatter-junit){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file +"[markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2){target='_blank'} :material-alert-decagram:",,"[JUnit](https://github.com/DavidAnson/markdownlint-cli2#markdownlint-cli2-formatter-junit){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/docs/input/documentation/supported-tools.md b/docs/input/documentation/supported-tools.md index 29bacd316..6f3b1950f 100644 --- a/docs/input/documentation/supported-tools.md +++ b/docs/input/documentation/supported-tools.md @@ -49,6 +49,10 @@ This pages lists tools known to be working with Cake Issues (1) {{ read_csv('assets/tables/supported-tools-docker.csv',keep_default_na=False) }} +## Git + +{{ read_csv('assets/tables/supported-tools-git.csv',keep_default_na=False) }} + ## GitHub Actions {{ read_csv('assets/tables/supported-tools-github-actions.csv',keep_default_na=False) }} @@ -65,6 +69,10 @@ This pages lists tools known to be working with Cake Issues (1) {{ read_csv('assets/tables/supported-tools-helm.csv',keep_default_na=False) }} +## HTML + +{{ read_csv('assets/tables/supported-tools-html.csv',keep_default_na=False) }} + ## Java {{ read_csv('assets/tables/supported-tools-java.csv',keep_default_na=False) }} From 13a8f706e0f9bd53164e2a20217bcd2f652e04f1 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Mon, 4 Aug 2025 21:10:58 +0200 Subject: [PATCH 26/35] Update commitlint testfile to match example from website --- .../Testfiles/commitlint.xml | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml index 781d11850..eace4439c 100644 --- a/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml @@ -1,10 +1,14 @@ - - - - - type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum) + + + + + + + type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum) -foo: bar - - - \ No newline at end of file + foo: bar + + + + + \ No newline at end of file From 232aca2b999745a2eaf8d5b2653d03e8cf5c1388 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:24:31 +0000 Subject: [PATCH 27/35] Fix XML whitespace handling in JUnit parser for commitlint test with tabs Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 37 +++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index 2b4dbd27c..02d8a33d6 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -241,6 +241,41 @@ private void ProcessTestSuite(XElement testSuite, List result) } } + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + private string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = Regex.Replace(result, @"\n{3,}", "\n\n"); + + return result; + } + /// /// Processes a test failure or error element and creates an issue. /// @@ -253,7 +288,7 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str { var message = failureElement.Attribute("message")?.Value ?? string.Empty; var type = failureElement.Attribute("type")?.Value ?? string.Empty; - var content = failureElement.Value?.Trim() ?? string.Empty; + var content = this.NormalizeXmlContent(failureElement.Value) ?? string.Empty; // Combine message and content for full description var fullMessage = string.IsNullOrEmpty(message) ? content : From 6d0dc5db6ca15ccfb5543ac5e41e42bf100f96a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:55:08 +0000 Subject: [PATCH 28/35] Refactor JUnit provider to BaseMultiFormatIssueProvider pattern with optimized formats Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../JUnitIssuesProviderFixture.cs | 7 +- .../JUnitIssuesProviderTests.cs | 48 +-- .../JUnitIssuesSettingsTests.cs | 7 +- .../Testfiles/htmlhint.xml | 26 +- .../BaseJUnitLogFileFormat.cs | 38 ++ .../JUnitIssuesAliases.IssueProvider.cs | 131 ++++++ .../JUnitIssuesAliases.LogFileFormats.cs | 59 +++ src/Cake.Issues.JUnit/JUnitIssuesAliases.cs | 109 +---- src/Cake.Issues.JUnit/JUnitIssuesProvider.cs | 373 +----------------- src/Cake.Issues.JUnit/JUnitIssuesSettings.cs | 18 +- .../LogFileFormat/CppLintLogFileFormat.cs | 222 +++++++++++ .../GenericJUnitLogFileFormat.cs | 288 ++++++++++++++ .../MarkdownlintCli2LogFileFormat.cs | 246 ++++++++++++ 13 files changed, 1046 insertions(+), 526 deletions(-) create mode 100644 src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs create mode 100644 src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs create mode 100644 src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs create mode 100644 src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs index c26a916f2..d8578d523 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs @@ -1,7 +1,10 @@ namespace Cake.Issues.JUnit.Tests; -internal class JUnitIssuesProviderFixture - : BaseConfigurableIssueProviderFixture +using Cake.Issues.Testing; + +internal class JUnitIssuesProviderFixture + : BaseMultiFormatIssueProviderFixture + where T : BaseJUnitLogFileFormat { public JUnitIssuesProviderFixture(string fileResourceName) : this(fileResourceName, @"c:\Source\Cake.Issues") diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs index 1488a138d..80ebe1eb1 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -1,5 +1,7 @@ namespace Cake.Issues.JUnit.Tests; +using Cake.Issues.JUnit.LogFileFormat; + public sealed class JUnitIssuesProviderTests { public sealed class TheCtor @@ -11,7 +13,7 @@ public void Should_Throw_If_Log_Is_Null() var result = Record.Exception(() => new JUnitIssuesProvider( null, - new JUnitIssuesSettings("Foo".ToByteArray()))); + new JUnitIssuesSettings("Foo".ToByteArray(), new GenericJUnitLogFileFormat(new FakeLog())))); // Then result.IsArgumentNullException("log"); @@ -34,7 +36,7 @@ public sealed class TheReadIssuesMethod public void Should_Read_Issues_Correct_For_CppLint() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -71,7 +73,7 @@ public void Should_Read_Issues_Correct_For_CppLint() public void Should_Handle_CppLint_Passed_Test() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -84,7 +86,7 @@ public void Should_Handle_CppLint_Passed_Test() public void Should_Handle_CppLint_Single_Error() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -107,7 +109,7 @@ public void Should_Handle_CppLint_Single_Error() public void Should_Handle_CppLint_Multiple_Errors() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -130,7 +132,7 @@ public void Should_Handle_CppLint_Multiple_Errors() public void Should_Handle_CppLint_Mixed_Error_And_Failure() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -164,7 +166,7 @@ public void Should_Handle_CppLint_Mixed_Error_And_Failure() public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -199,7 +201,7 @@ public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() public void Should_Handle_CppLint_XML_Escaping() { // Given - var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml"); + var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -233,7 +235,7 @@ public void Should_Handle_CppLint_XML_Escaping() public void Should_Read_Issues_Correct_For_Kubeconform() { // Given - var fixture = new JUnitIssuesProviderFixture("kubeconform.xml"); + var fixture = new JUnitIssuesProviderFixture("kubeconform.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -268,33 +270,21 @@ public void Should_Read_Issues_Correct_For_Kubeconform() public void Should_Read_Issues_Correct_For_HtmlHint() { // Given - var fixture = new JUnitIssuesProviderFixture("htmlhint.xml"); + var fixture = new JUnitIssuesProviderFixture("htmlhint.xml"); // When var issues = fixture.ReadIssues().ToList(); // Then - issues.Count.ShouldBe(2); + issues.Count.ShouldBe(1); IssueChecker.Check( issues[0], IssueBuilder.NewIssue( - "Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase", - "Cake.Issues.JUnit.JUnitIssuesProvider", - "JUnit") - .InFile(@"index.html", 12, 5) - .OfRule("error") - .WithPriority(IssuePriority.Error) - .Create()); - - IssueChecker.Check( - issues[1], - IssueBuilder.NewIssue( - "Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes.", + "Found 6 errors\nL2 |\n^ An lang attribute must be present on elements. (html-lang-require)\nL16 | \n^ An alt attribute must be present on elements. (alt-require)\nL14 |\n^
must be present in tag. (main-require)\nL3 |\n^ must be present in tag. (meta-charset-require)\nL3 |\n^ must be present in tag. (meta-description-require)\nL3 |\n^ must be present in tag. (meta-viewport-require)", "Cake.Issues.JUnit.JUnitIssuesProvider", "JUnit") - .InFile(@"about.html", 8) - .OfRule("warning") + .OfRule(@"C:\temp\htmlhint\test.html") .WithPriority(IssuePriority.Error) .Create()); } @@ -303,7 +293,7 @@ public void Should_Read_Issues_Correct_For_HtmlHint() public void Should_Read_Issues_Correct_For_CommitLint() { // Given - var fixture = new JUnitIssuesProviderFixture("commitlint.xml"); + var fixture = new JUnitIssuesProviderFixture("commitlint.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -326,7 +316,7 @@ public void Should_Read_Issues_Correct_For_CommitLint() public void Should_Read_Issues_Correct_For_MarkdownlintCli2() { // Given - var fixture = new JUnitIssuesProviderFixture("markdownlint-cli2.xml"); + var fixture = new JUnitIssuesProviderFixture("markdownlint-cli2.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -394,7 +384,7 @@ public void Should_Read_Issues_Correct_For_MarkdownlintCli2() public void Should_Handle_Empty_TestSuite() { // Given - var fixture = new JUnitIssuesProviderFixture("empty.xml"); + var fixture = new JUnitIssuesProviderFixture("empty.xml"); // When var issues = fixture.ReadIssues().ToList(); @@ -407,7 +397,7 @@ public void Should_Handle_Empty_TestSuite() public void Should_Handle_Invalid_XML() { // Given - var fixture = new JUnitIssuesProviderFixture("invalid.xml"); + var fixture = new JUnitIssuesProviderFixture("invalid.xml"); // When / Then Should.Throw(() => fixture.ReadIssues().ToList()) diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs index 6c63780f1..5fe0e9eb8 100644 --- a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs @@ -1,6 +1,7 @@ namespace Cake.Issues.JUnit.Tests; using Cake.Core.IO; +using Cake.Issues.JUnit.LogFileFormat; public sealed class JUnitIssuesSettingsTests { @@ -11,7 +12,7 @@ public void Should_Throw_If_LogFilePath_Is_Null() { // Given / When var result = Record.Exception(() => - new JUnitIssuesSettings((FilePath)null)); + new JUnitIssuesSettings((FilePath)null, new GenericJUnitLogFileFormat(new FakeLog()))); // Then result.IsArgumentNullException("logFilePath"); @@ -22,7 +23,7 @@ public void Should_Throw_If_LogFileContent_Is_Null() { // Given / When var result = Record.Exception(() => - new JUnitIssuesSettings((byte[])null)); + new JUnitIssuesSettings((byte[])null, new GenericJUnitLogFileFormat(new FakeLog()))); // Then result.IsArgumentNullException("logFileContent"); @@ -35,7 +36,7 @@ public void Should_Set_LogFileContent() var logFileContent = "foo".ToByteArray(); // When - var settings = new JUnitIssuesSettings(logFileContent); + var settings = new JUnitIssuesSettings(logFileContent, new GenericJUnitLogFileFormat(new FakeLog())); // Then settings.LogFileContent.ShouldBe(logFileContent); diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml index 96fe4d4c9..c6dca1bc9 100644 --- a/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml @@ -1,13 +1,17 @@ - - - -index.html(12,5): Tagname 'DIV' must be lowercase - - - - -about.html line 8: The value of attribute 'class' must be in double quotes. - - + + + + ^ An lang attribute must be present on elements. (html-lang-require) +L16 | + ^ An alt attribute must be present on elements. (alt-require) +L14 | + ^
must be present in tag. (main-require) +L3 | + ^ must be present in tag. (meta-charset-require) +L3 | + ^ must be present in tag. (meta-description-require) +L3 | + ^ must be present in tag. (meta-viewport-require)]]> + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs new file mode 100644 index 000000000..44975f750 --- /dev/null +++ b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs @@ -0,0 +1,38 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core.Diagnostics; +using Cake.Core.IO; + +/// +/// Base class for all log file formats supported by the JUnit issue provider. +/// +/// The Cake log instance. +public abstract class BaseJUnitLogFileFormat(ICakeLog log) + : BaseLogFileFormat(log) +{ + /// + /// Validates a file path. + /// + /// Full file path. + /// Repository settings. + /// Tuple containing a value if validation was successful, and file path relative to repository root. + protected static (bool Valid, string FilePath) ValidateFilePath(string filePath, IRepositorySettings repositorySettings) + { + filePath.NotNullOrWhiteSpace(); + repositorySettings.NotNull(); + + if (!new FilePath(filePath).IsRelative) + { + // Ignore files from outside the repository. + if (!filePath.IsInRepository(repositorySettings)) + { + return (false, string.Empty); + } + + // Make path relative to repository root. + filePath = filePath.NormalizePath().MakeFilePathRelativeToRepositoryRoot(repositorySettings); + } + + return (true, filePath); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs new file mode 100644 index 000000000..0b5e51bb5 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs @@ -0,0 +1,131 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +/// +/// Aliases to read issues from JUnit XML files. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets the name of the JUnit issue provider. + /// This name can be used to identify issues based on the property. + /// + /// The context. + /// Name of the JUnit issue provider. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static string JUnitIssuesProviderTypeName( + this ICakeContext context) + { + context.NotNull(); + + return JUnitIssuesProvider.ProviderTypeName; + } + + /// + /// Gets an instance of a provider for issues in a JUnit XML file from disk. + /// + /// The context. + /// Path to the JUnit XML file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit XML file. + /// Instance of a provider for issues in JUnit XML format. + /// + /// Read issues from a JUnit XML file: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromFilePath( + this ICakeContext context, + FilePath logFilePath, + BaseJUnitLogFileFormat format) + { + context.NotNull(); + logFilePath.NotNull(); + format.NotNull(); + + return context.JUnitIssues(new JUnitIssuesSettings(logFilePath, format)); + } + + /// + /// Gets an instance of a provider for issues in a JUnit XML file from memory. + /// + /// The context. + /// Content of the JUnit XML file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit XML file. + /// Instance of a provider for issues in JUnit XML format. + /// + /// Read issues from a JUnit XML file: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromContent( + this ICakeContext context, + string logFileContent, + BaseJUnitLogFileFormat format) + { + context.NotNull(); + logFileContent.NotNullOrWhiteSpace(); + format.NotNull(); + + return context.JUnitIssues(new JUnitIssuesSettings(logFileContent.ToByteArray(), format)); + } + + /// + /// Gets an instance of a provider for issues in a JUnit XML file using specified settings. + /// + /// The context. + /// Settings for reading the JUnit XML file. + /// Instance of a provider for issues in JUnit XML format. + /// + /// Read issues from a JUnit XML file: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssues( + this ICakeContext context, + JUnitIssuesSettings settings) + { + context.NotNull(); + settings.NotNull(); + + return new JUnitIssuesProvider(context.Log, settings); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs new file mode 100644 index 000000000..1476fc309 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs @@ -0,0 +1,59 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for JUnit log file formats. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for any file compatible with JUnit XML format. + /// Does best effort parsing for any JUnit XML format. + /// + /// The context. + /// Instance for the generic JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat GenericJUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new GenericJUnitLogFileFormat(context.Log); + } + + /// + /// Gets an instance for the log format for cpplint JUnit XML output. + /// Optimized for cpplint's specific JUnit format where test case names represent file names. + /// + /// The context. + /// Instance for the cpplint JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat CppLintJUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new CppLintLogFileFormat(context.Log); + } + + /// + /// Gets an instance for the log format for markdownlint-cli2 JUnit XML output. + /// Optimized for markdownlint-cli2's specific JUnit format where file paths are in classname attributes. + /// + /// The context. + /// Instance for the markdownlint-cli2 JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat MarkdownlintCli2JUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new MarkdownlintCli2LogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs index 554689117..a39aa1040 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs @@ -1,118 +1,11 @@ namespace Cake.Issues.JUnit; -using Cake.Core; using Cake.Core.Annotations; -using Cake.Core.IO; /// /// Contains functionality for reading issues from JUnit XML files. /// [CakeAliasCategory(IssuesAliasConstants.MainCakeAliasCategory)] -public static class JUnitIssuesAliases +public static partial class JUnitIssuesAliases { - /// - /// Gets the name of the JUnit issue provider. - /// This name can be used to identify issues based on the property. - /// - /// The context. - /// Name of the JUnit issue provider. - [CakePropertyAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static string JUnitIssuesProviderTypeName( - this ICakeContext context) - { - context.NotNull(); - - return typeof(JUnitIssuesProvider).FullName; - } - - /// - /// Gets an instance of a provider for issues reported in JUnit XML format using the log file from disk. - /// - /// The context. - /// Path to the JUnit log file. - /// Instance of a provider for issues reported in JUnit XML format. - /// - /// Read issues from a JUnit XML file: - /// - /// - /// - /// - [CakeMethodAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static IIssueProvider JUnitIssuesFromFilePath( - this ICakeContext context, - FilePath logFilePath) - { - context.NotNull(); - logFilePath.NotNull(); - - return context.JUnitIssues(new JUnitIssuesSettings(logFilePath)); - } - - /// - /// Gets an instance of a provider for issues reported in JUnit XML format using log file content. - /// - /// The context. - /// Content of the JUnit log file. - /// Instance of a provider for issues reported in JUnit XML format. - /// - /// Read issues the content of JUnit XML file: - /// - /// - /// - /// - [CakeMethodAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static IIssueProvider JUnitIssuesFromContent( - this ICakeContext context, - string logFileContent) - { - context.NotNull(); - logFileContent.NotNullOrWhiteSpace(); - - return context.JUnitIssues(new JUnitIssuesSettings(logFileContent.ToByteArray())); - } - - /// - /// Gets an instance of a provider for issues reported in JUnit XML format using the specified settings. - /// - /// The context. - /// Settings for reading the JUnit log. - /// Instance of a provider for issues reported in JUnit XML format. - /// - /// Read issues from a JUnit XML file: - /// - /// - /// - /// - [CakeMethodAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static IIssueProvider JUnitIssues( - this ICakeContext context, - JUnitIssuesSettings settings) - { - context.NotNull(); - settings.NotNull(); - - return new JUnitIssuesProvider(context.Log, settings); - } } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs index 02d8a33d6..a559edba5 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -1,10 +1,5 @@ namespace Cake.Issues.JUnit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; using Cake.Core.Diagnostics; /// @@ -12,369 +7,15 @@ /// /// The Cake log context. /// Settings for the issue provider. -internal class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) - : BaseConfigurableIssueProvider(log, issueProviderSettings) +public class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) + : BaseMultiFormatIssueProvider(log, issueProviderSettings) { - /// - public override string ProviderName => "JUnit"; - - /// - protected override IEnumerable InternalReadIssues() - { - var result = new List(); - - var logContent = this.IssueProviderSettings.LogFileContent.ToStringUsingEncoding(); - - try - { - var doc = XDocument.Parse(logContent); - - // Handle both single testsuite and testsuites root elements - var testSuites = doc.Root?.Name.LocalName == "testsuites" - ? doc.Root.Elements("testsuite") - : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); - - foreach (var testSuite in testSuites) - { - if (testSuite == null) - { - continue; - } - - this.ProcessTestSuite(testSuite, result); - } - } - catch (Exception ex) - { - throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); - } - - return result; - } - - /// - /// Tries to extract file path and line information from output text. - /// - /// The output text to parse. - /// File information if found, null otherwise. - private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOutput(string output) - { - if (string.IsNullOrEmpty(output)) - { - return null; - } - - // Common patterns for file paths and line numbers in linter output: - // file.txt:123:45: message - // file.txt(123,45): message - // file.txt line 123: message - // /path/to/file.txt:123: message - // file.txt:123 message - // Line 3, Column 10 (markdownlint-cli2 format) - var patterns = new[] - { - @"([^\s:]+):(\d+):(\d+)", // file:line:column - @"([^\s:]+):(\d+)", // file:line - @"([^\s\(\)]+)\((\d+),(\d+)\)", // file(line,column) - @"([^\s\(\)]+)\((\d+)\)", // file(line) - @"^([^\s]+)\s+line\s+(\d+)", // file line 123 (must start at beginning of line) - @"File:\s*([^\s]+)", // File: path - }; - - foreach (var pattern in patterns) - { - var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline); - if (match.Success) - { - var filePath = match.Groups[1].Value.Trim(); - - // Skip if it looks like a URL or doesn't look like a file path - if (filePath.StartsWith("http", StringComparison.Ordinal) || filePath.StartsWith("www.", StringComparison.Ordinal)) - { - continue; - } - - int? line = null; - int? column = null; - - if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var lineNum)) - { - line = lineNum; - } - - if (match.Groups.Count > 3 && int.TryParse(match.Groups[3].Value, out var colNum)) - { - column = colNum; - } - - return (filePath, line, column); - } - } - - return null; - } - - /// - /// Tries to extract line and column information from markdownlint-cli2 format text. - /// - /// The output text to parse. - /// Line and column information if found, null otherwise. - private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(string output) - { - if (string.IsNullOrEmpty(output)) - { - return null; - } - - // Patterns for markdownlint-cli2 format: - // Line 3, Column 10, Expected: 0 or 2; Actual: 1 - // Line 5, Expected: 1; Actual: 2 - // Line 6, Context: "# Description" - var patterns = new[] - { - @"Line\s+(\d+),\s+Column\s+(\d+)", // Line 3, Column 10 - @"Line\s+(\d+)", // Line 5 - }; - - foreach (var pattern in patterns) - { - var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase); - if (match.Success) - { - int? line = null; - int? column = null; - - if (int.TryParse(match.Groups[1].Value, out var lineNum)) - { - line = lineNum; - } - - if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var colNum)) - { - column = colNum; - } - - return (line, column); - } - } - - return null; - } - /// - /// Tries to extract file path from a class name. + /// Gets the name of the JUnit issue provider. + /// This name can be used to identify issues based on the property. /// - /// The class name to parse. - /// File information if the class name looks like a file path, null otherwise. - private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromClassName(string className) - { - if (string.IsNullOrEmpty(className)) - { - return null; - } + public static string ProviderTypeName => typeof(JUnitIssuesProvider).FullName; - // Some tools use file paths as class names - if (className.Contains('/') || className.Contains('\\')) - { - return (className, null, null); - } - - // Convert class names to potential file paths - if (className.Contains('.')) - { - // Java-style package.Class -> package/Class.java (but only if it looks like a real package) - var parts = className.Split('.'); - if (parts.Length > 1 && parts.All(p => !string.IsNullOrEmpty(p))) - { - var potentialPath = string.Join("/", parts) + ".java"; - return (potentialPath, null, null); - } - } - - return null; - } - - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - private void ProcessTestSuite(XElement testSuite, List result) - { - if (testSuite == null) - { - return; - } - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - var time = testCase.Attribute("time")?.Value; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error); - if (issue != null) - { - result.Add(issue); - } - } - - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result); - } - } - - /// - /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. - /// - /// The XML content to normalize. - /// The normalized content. - private string NormalizeXmlContent(string content) - { - if (string.IsNullOrEmpty(content)) - { - return string.Empty; - } - - // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); - var normalizedLines = new List(); - - foreach (var line in lines) - { - // Trim leading and trailing whitespace (including tabs) from each line - var trimmedLine = line.Trim(); - normalizedLines.Add(trimmedLine); - } - - // Join lines back together and clean up multiple consecutive empty lines - var result = string.Join("\n", normalizedLines); - - // Remove leading and trailing empty lines - result = result.Trim('\n'); - - // Normalize multiple consecutive newlines to double newlines maximum - result = Regex.Replace(result, @"\n{3,}", "\n\n"); - - return result; - } - - /// - /// Processes a test failure or error element and creates an issue. - /// - /// The failure or error XML element. - /// The test class name. - /// The test name. - /// The issue priority. - /// The created issue or null if the failure should be ignored. - private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, IssuePriority priority) - { - var message = failureElement.Attribute("message")?.Value ?? string.Empty; - var type = failureElement.Attribute("type")?.Value ?? string.Empty; - var content = this.NormalizeXmlContent(failureElement.Value) ?? string.Empty; - - // Combine message and content for full description - var fullMessage = string.IsNullOrEmpty(message) ? content : - string.IsNullOrEmpty(content) ? message : - $"{message}\n{content}"; - - if (string.IsNullOrEmpty(fullMessage)) - { - return null; - } - - var issueBuilder = IssueBuilder - .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, this.ProviderName) - .WithPriority(priority); - - if (!string.IsNullOrEmpty(type)) - { - issueBuilder = issueBuilder.OfRule(type); - } - else if (!string.IsNullOrEmpty(testName)) - { - issueBuilder = issueBuilder.OfRule(testName); - } - - // Initialize fileInfo variable - (string FilePath, int? Line, int? Column)? fileInfo = null; - - // For markdownlint-cli2 style output, check if the content contains the specific format - // and use the class name as file path in that case - if (!string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(content)) - { - var lineColumnInfo = ExtractLineColumnFromMarkdownlintCli2(content); - if (lineColumnInfo.HasValue) - { - // This looks like markdownlint-cli2 format, use class name as file path - var (line, column) = lineColumnInfo.Value; - fileInfo = (className, line, column); - } - } - - // If we don't have file info yet, try to extract from the message or content - if (!fileInfo.HasValue) - { - fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); - } - - // For cpplint-style output, if we don't have file info and the test name looks like a file name, - // use the test name as the file name and try to extract line info from the message - if (!fileInfo.HasValue && !string.IsNullOrEmpty(testName) && testName != "errors") - { - // Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]" - // This is a strong indicator that it's a cpplint-style failure where the test name is the file name - var lineMatch = Regex.Match( - fullMessage, - @"^(\d+):\s*.*\[.*\].*\[.*\]", - RegexOptions.Multiline); - if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) - { - fileInfo = (testName, lineNum, null); - } - - // Also check for simple line number pattern without the category/subcategory format - else - { - var simpleLineMatch = Regex.Match( - fullMessage, - @"^(\d+):", - RegexOptions.Multiline); - if (simpleLineMatch.Success && - int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum)) - { - // Only treat as file if the test name doesn't contain hyphens (which are common in rule names) - if (!testName.Contains('-') && !testName.Contains('_')) - { - fileInfo = (testName, simpleLineNum, null); - } - } - } - } - - if (fileInfo.HasValue) - { - var (filePath, line, column) = fileInfo.Value; - issueBuilder = issueBuilder.InFile(filePath, line, column); - } - - return issueBuilder.Create(); - } + /// + public override string ProviderName => "JUnit"; } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs index baa1d7635..e56c8440d 100644 --- a/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs +++ b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs @@ -5,15 +5,17 @@ /// /// Settings for . /// -public class JUnitIssuesSettings : IssueProviderSettings +public class JUnitIssuesSettings : BaseMultiFormatIssueProviderSettings { /// /// Initializes a new instance of the class /// for reading a JUnit log file on disk. /// - /// Path to the JUnit log file. - public JUnitIssuesSettings(FilePath logFilePath) - : base(logFilePath) + /// Path to the JUnit log file. + /// The JUnit file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit file. + public JUnitIssuesSettings(FilePath logFilePath, BaseJUnitLogFileFormat format) + : base(logFilePath, format) { } @@ -21,9 +23,11 @@ public JUnitIssuesSettings(FilePath logFilePath) /// Initializes a new instance of the class /// for a JUnit log file content in memory. ///
- /// Content of the JUnit log file. - public JUnitIssuesSettings(byte[] logFileContent) - : base(logFileContent) + /// Content of the JUnit log file. + /// The JUnit file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit file. + public JUnitIssuesSettings(byte[] logFileContent, BaseJUnitLogFileFormat format) + : base(logFileContent, format) { } } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs new file mode 100644 index 000000000..10f0cf2c6 --- /dev/null +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -0,0 +1,222 @@ +namespace Cake.Issues.JUnit.LogFileFormat; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; + +/// +/// CppLint log file format for parsing JUnit XML files specifically from cpplint. +/// Optimized for cpplint's specific JUnit format where test case names represent file names. +/// +/// The Cake log instance. +internal class CppLintLogFileFormat(ICakeLog log) + : BaseJUnitLogFileFormat(log) +{ + /// + public override IEnumerable ReadIssues( + JUnitIssuesProvider issueProvider, + IRepositorySettings repositorySettings, + JUnitIssuesSettings junitIssuesSettings) + { + issueProvider.NotNull(); + repositorySettings.NotNull(); + junitIssuesSettings.NotNull(); + + var result = new List(); + + var logContent = junitIssuesSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var doc = XDocument.Parse(logContent); + + // Handle both single testsuite and testsuites root elements + var testSuites = doc.Root?.Name.LocalName == "testsuites" + ? doc.Root.Elements("testsuite") + : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) + { + continue; + } + + this.ProcessTestSuite(testSuite, result, repositorySettings); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessCppLintFailure(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessCppLintFailure(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); + } + } + + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + private static string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = Regex.Replace(result, @"\n{3,}", "\n\n"); + + return result; + } + + /// + /// Processes a cpplint test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The issue priority. + /// Repository settings. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessCppLintFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + { + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var type = failureElement.Attribute("type")?.Value ?? string.Empty; + var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, "JUnit") + .WithPriority(priority); + + if (!string.IsNullOrEmpty(type)) + { + issueBuilder = issueBuilder.OfRule(type); + } + else if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + // For cpplint-style output, if the test name looks like a file name, + // use the test name as the file name and try to extract line info from the message + if (!string.IsNullOrEmpty(testName) && testName != "errors") + { + // Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]" + // This is a strong indicator that it's a cpplint-style failure where the test name is the file name + var lineMatch = Regex.Match( + fullMessage, + @"^(\d+):\s*.*\[.*\].*\[.*\]", + RegexOptions.Multiline); + if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) + { + var pathValidation = ValidateFilePath(testName, repositorySettings); + if (pathValidation.Valid) + { + issueBuilder = issueBuilder.InFile(pathValidation.FilePath, lineNum, null); + } + } + // Also check for simple line number pattern without the category/subcategory format + else + { + var simpleLineMatch = Regex.Match( + fullMessage, + @"^(\d+):", + RegexOptions.Multiline); + if (simpleLineMatch.Success && + int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum)) + { + // Only treat as file if the test name doesn't contain hyphens (which are common in rule names) + if (!testName.Contains('-') && !testName.Contains('_')) + { + var pathValidation = ValidateFilePath(testName, repositorySettings); + if (pathValidation.Valid) + { + issueBuilder = issueBuilder.InFile(pathValidation.FilePath, simpleLineNum, null); + } + } + } + } + } + + return issueBuilder.Create(); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs new file mode 100644 index 000000000..a4be5de2b --- /dev/null +++ b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs @@ -0,0 +1,288 @@ +namespace Cake.Issues.JUnit.LogFileFormat; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; + +/// +/// Generic log file format for parsing JUnit XML files. +/// Does best effort parsing for any JUnit XML format. +/// +/// The Cake log instance. +internal class GenericJUnitLogFileFormat(ICakeLog log) + : BaseJUnitLogFileFormat(log) +{ + /// + public override IEnumerable ReadIssues( + JUnitIssuesProvider issueProvider, + IRepositorySettings repositorySettings, + JUnitIssuesSettings junitIssuesSettings) + { + issueProvider.NotNull(); + repositorySettings.NotNull(); + junitIssuesSettings.NotNull(); + + var result = new List(); + + var logContent = junitIssuesSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var doc = XDocument.Parse(logContent); + + // Handle both single testsuite and testsuites root elements + var testSuites = doc.Root?.Name.LocalName == "testsuites" + ? doc.Root.Elements("testsuite") + : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) + { + continue; + } + + this.ProcessTestSuite(testSuite, result, repositorySettings); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); + } + } + + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + private static string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = Regex.Replace(result, @"\n{3,}", "\n\n"); + + return result; + } + + /// + /// Tries to extract file path from a class name. + /// + /// The class name to parse. + /// File information if the class name looks like a file path, null otherwise. + private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromClassName(string className) + { + if (string.IsNullOrEmpty(className)) + { + return null; + } + + // Some tools use file paths as class names + if (className.Contains('/') || className.Contains('\\')) + { + return (className, null, null); + } + + // Convert class names to potential file paths + if (className.Contains('.')) + { + // Java-style package.Class -> package/Class.java (but only if it looks like a real package) + var parts = className.Split('.'); + if (parts.Length > 1 && parts.All(p => !string.IsNullOrEmpty(p))) + { + var potentialPath = string.Join("/", parts) + ".java"; + return (potentialPath, null, null); + } + } + + return null; + } + + /// + /// Tries to extract file path and line information from output text. + /// + /// The output text to parse. + /// File information if found, null otherwise. + private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOutput(string output) + { + if (string.IsNullOrEmpty(output)) + { + return null; + } + + // Common patterns for file paths and line numbers in linter output: + // file.txt:123:45: message + // file.txt(123,45): message + // file.txt line 123: message + // /path/to/file.txt:123: message + // file.txt:123 message + var patterns = new[] + { + @"([^\s:]+):(\d+):(\d+)", // file:line:column + @"([^\s:]+):(\d+)", // file:line + @"([^\s\(\)]+)\((\d+),(\d+)\)", // file(line,column) + @"([^\s\(\)]+)\((\d+)\)", // file(line) + @"^([^\s]+)\s+line\s+(\d+)", // file line 123 (must start at beginning of line) + @"File:\s*([^\s]+)", // File: path + }; + + foreach (var pattern in patterns) + { + var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline); + if (match.Success) + { + var filePath = match.Groups[1].Value.Trim(); + + // Skip if it looks like a URL or doesn't look like a file path + if (filePath.StartsWith("http", StringComparison.Ordinal) || filePath.StartsWith("www.", StringComparison.Ordinal)) + { + continue; + } + + int? line = null; + int? column = null; + + if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var lineNum)) + { + line = lineNum; + } + + if (match.Groups.Count > 3 && int.TryParse(match.Groups[3].Value, out var colNum)) + { + column = colNum; + } + + return (filePath, line, column); + } + } + + return null; + } + + /// + /// Processes a test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The issue priority. + /// Repository settings. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + { + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var type = failureElement.Attribute("type")?.Value ?? string.Empty; + var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, "JUnit") + .WithPriority(priority); + + if (!string.IsNullOrEmpty(type)) + { + issueBuilder = issueBuilder.OfRule(type); + } + else if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + // Try to extract file information + var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + + if (fileInfo.HasValue) + { + var (filePath, line, column) = fileInfo.Value; + var pathValidation = ValidateFilePath(filePath, repositorySettings); + if (pathValidation.Valid) + { + issueBuilder = issueBuilder.InFile(pathValidation.FilePath, line, column); + } + } + + return issueBuilder.Create(); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs new file mode 100644 index 000000000..c688ae14a --- /dev/null +++ b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs @@ -0,0 +1,246 @@ +namespace Cake.Issues.JUnit.LogFileFormat; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; + +/// +/// Markdownlint-cli2 log file format for parsing JUnit XML files specifically from markdownlint-cli2. +/// Optimized for markdownlint-cli2's specific JUnit format where file paths are in classname attributes. +/// +/// The Cake log instance. +internal class MarkdownlintCli2LogFileFormat(ICakeLog log) + : BaseJUnitLogFileFormat(log) +{ + /// + public override IEnumerable ReadIssues( + JUnitIssuesProvider issueProvider, + IRepositorySettings repositorySettings, + JUnitIssuesSettings junitIssuesSettings) + { + issueProvider.NotNull(); + repositorySettings.NotNull(); + junitIssuesSettings.NotNull(); + + var result = new List(); + + var logContent = junitIssuesSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var doc = XDocument.Parse(logContent); + + // Handle both single testsuite and testsuites root elements + var testSuites = doc.Root?.Name.LocalName == "testsuites" + ? doc.Root.Elements("testsuite") + : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) + { + continue; + } + + this.ProcessTestSuite(testSuite, result, repositorySettings); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessMarkdownlintCli2Failure(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessMarkdownlintCli2Failure(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); + } + } + + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + private static string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = Regex.Replace(result, @"\n{3,}", "\n\n"); + + return result; + } + + /// + /// Tries to extract line and column information from markdownlint-cli2 format text. + /// + /// The output text to parse. + /// Line and column information if found, null otherwise. + private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(string output) + { + if (string.IsNullOrEmpty(output)) + { + return null; + } + + // Patterns for markdownlint-cli2 format: + // Line 3, Column 10, Expected: 0 or 2; Actual: 1 + // Line 5, Expected: 1; Actual: 2 + // Line 6, Context: "# Description" + var patterns = new[] + { + @"Line\s+(\d+),\s+Column\s+(\d+)", // Line 3, Column 10 + @"Line\s+(\d+)", // Line 5 + }; + + foreach (var pattern in patterns) + { + var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase); + if (match.Success) + { + int? line = null; + int? column = null; + + if (int.TryParse(match.Groups[1].Value, out var lineNum)) + { + line = lineNum; + } + + if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var colNum)) + { + column = colNum; + } + + return (line, column); + } + } + + return null; + } + + /// + /// Processes a markdownlint-cli2 test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The issue priority. + /// Repository settings. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessMarkdownlintCli2Failure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + { + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var type = failureElement.Attribute("type")?.Value ?? string.Empty; + var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, "JUnit") + .WithPriority(priority); + + // Use test name as rule (markdownlint-cli2 puts rule IDs like "MD009/no-trailing-spaces" in test names) + if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + else if (!string.IsNullOrEmpty(type)) + { + issueBuilder = issueBuilder.OfRule(type); + } + + // For markdownlint-cli2 style output, check if the content contains the specific format + // and use the class name as file path in that case + if (!string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(content)) + { + var lineColumnInfo = ExtractLineColumnFromMarkdownlintCli2(content); + if (lineColumnInfo.HasValue) + { + // This looks like markdownlint-cli2 format, use class name as file path + var (line, column) = lineColumnInfo.Value; + var pathValidation = ValidateFilePath(className, repositorySettings); + if (pathValidation.Valid) + { + issueBuilder = issueBuilder.InFile(pathValidation.FilePath, line, column); + } + } + } + + return issueBuilder.Create(); + } +} \ No newline at end of file From f77ce13a479a14435b33979a91517118ddd18b2f Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Mon, 4 Aug 2025 23:15:45 +0200 Subject: [PATCH 29/35] Fix integration tests for multi-format JUnit provider API --- .../LogFileFormat/CppLintLogFileFormat.cs | 90 +++++++------- .../GenericJUnitLogFileFormat.cs | 114 +++++++++--------- .../MarkdownlintCli2LogFileFormat.cs | 114 +++++++++--------- .../frosting/net8.0/build/Program.cs | 2 +- .../script-runner/net8.0/build.cake | 2 +- .../script-runner/net9.0/build.cake | 2 +- 6 files changed, 162 insertions(+), 162 deletions(-) diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs index 10f0cf2c6..dcdc347c9 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -56,6 +56,41 @@ public override IEnumerable ReadIssues( return result; } + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + private static string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(['\r', '\n'], StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = Regex.Replace(result, @"\n{3,}", "\n\n"); + + return result; + } + /// /// Recursively processes a testsuite element and its nested testsuites and testcases. /// @@ -78,7 +113,7 @@ private void ProcessTestSuite(XElement testSuite, List result, IReposito // Process failures foreach (var failure in testCase.Elements("failure")) { - var issue = this.ProcessCppLintFailure(failure, className, testName, IssuePriority.Error, repositorySettings); + var issue = this.ProcessCppLintFailure(failure, testName, IssuePriority.Error, repositorySettings); if (issue != null) { result.Add(issue); @@ -88,7 +123,7 @@ private void ProcessTestSuite(XElement testSuite, List result, IReposito // Process errors foreach (var error in testCase.Elements("error")) { - var issue = this.ProcessCppLintFailure(error, className, testName, IssuePriority.Error, repositorySettings); + var issue = this.ProcessCppLintFailure(error, testName, IssuePriority.Error, repositorySettings); if (issue != null) { result.Add(issue); @@ -103,51 +138,15 @@ private void ProcessTestSuite(XElement testSuite, List result, IReposito } } - /// - /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. - /// - /// The XML content to normalize. - /// The normalized content. - private static string NormalizeXmlContent(string content) - { - if (string.IsNullOrEmpty(content)) - { - return string.Empty; - } - - // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); - var normalizedLines = new List(); - - foreach (var line in lines) - { - // Trim leading and trailing whitespace (including tabs) from each line - var trimmedLine = line.Trim(); - normalizedLines.Add(trimmedLine); - } - - // Join lines back together and clean up multiple consecutive empty lines - var result = string.Join("\n", normalizedLines); - - // Remove leading and trailing empty lines - result = result.Trim('\n'); - - // Normalize multiple consecutive newlines to double newlines maximum - result = Regex.Replace(result, @"\n{3,}", "\n\n"); - - return result; - } - /// /// Processes a cpplint test failure or error element and creates an issue. /// /// The failure or error XML element. - /// The test class name. /// The test name. /// The issue priority. /// Repository settings. /// The created issue or null if the failure should be ignored. - private IIssue ProcessCppLintFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + private IIssue ProcessCppLintFailure(XElement failureElement, string testName, IssuePriority priority, IRepositorySettings repositorySettings) { var message = failureElement.Attribute("message")?.Value ?? string.Empty; var type = failureElement.Attribute("type")?.Value ?? string.Empty; @@ -188,12 +187,13 @@ private IIssue ProcessCppLintFailure(XElement failureElement, string className, RegexOptions.Multiline); if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) { - var pathValidation = ValidateFilePath(testName, repositorySettings); - if (pathValidation.Valid) + var (valid, filePath) = ValidateFilePath(testName, repositorySettings); + if (valid) { - issueBuilder = issueBuilder.InFile(pathValidation.FilePath, lineNum, null); + issueBuilder = issueBuilder.InFile(filePath, lineNum, null); } } + // Also check for simple line number pattern without the category/subcategory format else { @@ -207,10 +207,10 @@ private IIssue ProcessCppLintFailure(XElement failureElement, string className, // Only treat as file if the test name doesn't contain hyphens (which are common in rule names) if (!testName.Contains('-') && !testName.Contains('_')) { - var pathValidation = ValidateFilePath(testName, repositorySettings); - if (pathValidation.Valid) + var (valid, filePath) = ValidateFilePath(testName, repositorySettings); + if (valid) { - issueBuilder = issueBuilder.InFile(pathValidation.FilePath, simpleLineNum, null); + issueBuilder = issueBuilder.InFile(filePath, simpleLineNum, null); } } } diff --git a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs index a4be5de2b..fb7e1cc6d 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs @@ -56,53 +56,6 @@ public override IEnumerable ReadIssues( return result; } - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - /// Repository settings. - private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) - { - if (testSuite == null) - { - return; - } - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); - } - } - /// /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. /// @@ -116,7 +69,7 @@ private static string NormalizeXmlContent(string content) } // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + var lines = content.Split(['\r', '\n'], StringSplitOptions.None); var normalizedLines = new List(); foreach (var line in lines) @@ -128,13 +81,13 @@ private static string NormalizeXmlContent(string content) // Join lines back together and clean up multiple consecutive empty lines var result = string.Join("\n", normalizedLines); - + // Remove leading and trailing empty lines result = result.Trim('\n'); - + // Normalize multiple consecutive newlines to double newlines maximum result = Regex.Replace(result, @"\n{3,}", "\n\n"); - + return result; } @@ -189,15 +142,15 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut // file.txt line 123: message // /path/to/file.txt:123: message // file.txt:123 message - var patterns = new[] - { + string[] patterns = + [ @"([^\s:]+):(\d+):(\d+)", // file:line:column @"([^\s:]+):(\d+)", // file:line @"([^\s\(\)]+)\((\d+),(\d+)\)", // file(line,column) @"([^\s\(\)]+)\((\d+)\)", // file(line) @"^([^\s]+)\s+line\s+(\d+)", // file line 123 (must start at beginning of line) @"File:\s*([^\s]+)", // File: path - }; + ]; foreach (var pattern in patterns) { @@ -232,6 +185,53 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut return null; } + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); + } + } + /// /// Processes a test failure or error element and creates an issue. /// @@ -276,10 +276,10 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str if (fileInfo.HasValue) { var (filePath, line, column) = fileInfo.Value; - var pathValidation = ValidateFilePath(filePath, repositorySettings); - if (pathValidation.Valid) + var (valid, validatedPath) = ValidateFilePath(filePath, repositorySettings); + if (valid) { - issueBuilder = issueBuilder.InFile(pathValidation.FilePath, line, column); + issueBuilder = issueBuilder.InFile(validatedPath, line, column); } } diff --git a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs index c688ae14a..96c046314 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs @@ -56,53 +56,6 @@ public override IEnumerable ReadIssues( return result; } - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - /// Repository settings. - private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) - { - if (testSuite == null) - { - return; - } - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessMarkdownlintCli2Failure(failure, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessMarkdownlintCli2Failure(error, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); - } - } - /// /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. /// @@ -116,7 +69,7 @@ private static string NormalizeXmlContent(string content) } // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.None); + var lines = content.Split(['\r', '\n'], StringSplitOptions.None); var normalizedLines = new List(); foreach (var line in lines) @@ -128,13 +81,13 @@ private static string NormalizeXmlContent(string content) // Join lines back together and clean up multiple consecutive empty lines var result = string.Join("\n", normalizedLines); - + // Remove leading and trailing empty lines result = result.Trim('\n'); - + // Normalize multiple consecutive newlines to double newlines maximum result = Regex.Replace(result, @"\n{3,}", "\n\n"); - + return result; } @@ -154,11 +107,11 @@ private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(s // Line 3, Column 10, Expected: 0 or 2; Actual: 1 // Line 5, Expected: 1; Actual: 2 // Line 6, Context: "# Description" - var patterns = new[] - { + string[] patterns = + [ @"Line\s+(\d+),\s+Column\s+(\d+)", // Line 3, Column 10 @"Line\s+(\d+)", // Line 5 - }; + ]; foreach (var pattern in patterns) { @@ -185,6 +138,53 @@ private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(s return null; } + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = this.ProcessMarkdownlintCli2Failure(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = this.ProcessMarkdownlintCli2Failure(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); + } + } + /// /// Processes a markdownlint-cli2 test failure or error element and creates an issue. /// @@ -233,10 +233,10 @@ private IIssue ProcessMarkdownlintCli2Failure(XElement failureElement, string cl { // This looks like markdownlint-cli2 format, use class name as file path var (line, column) = lineColumnInfo.Value; - var pathValidation = ValidateFilePath(className, repositorySettings); - if (pathValidation.Valid) + var (valid, filePath) = ValidateFilePath(className, repositorySettings); + if (valid) { - issueBuilder = issueBuilder.InFile(pathValidation.FilePath, line, column); + issueBuilder = issueBuilder.InFile(filePath, line, column); } } } diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs index 7dfaeca2c..52c0d4089 100644 --- a/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs @@ -29,7 +29,7 @@ public override void Run(BuildContext context) var junitLogPath = @"../testdata/cpplint.xml"; var issues = context.ReadIssues( - context.JUnitIssuesFromFilePath(junitLogPath), + context.JUnitIssuesFromFilePath(junitLogPath, context.CppLintJUnitLogFileFormat()), @"../"); context.Information("Found {0} issues", issues.Count()); diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake index 3cb3de8bd..259e1e154 100644 --- a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake @@ -31,7 +31,7 @@ Task("ReadIssues") data.AddIssues( ReadIssues( - JUnitIssuesFromFilePath(junitLogPath), + JUnitIssuesFromFilePath(junitLogPath, CppLintJUnitLogFileFormat), repoRootFolder) ); diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake index 3cb3de8bd..259e1e154 100644 --- a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake @@ -31,7 +31,7 @@ Task("ReadIssues") data.AddIssues( ReadIssues( - JUnitIssuesFromFilePath(junitLogPath), + JUnitIssuesFromFilePath(junitLogPath, CppLintJUnitLogFileFormat), repoRootFolder) ); From 9d689a9e07e9030d5bf7adcab1f54697985b39d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:45:43 +0000 Subject: [PATCH 30/35] Refactor: Move duplicated code to BaseJUnitLogFileFormat class Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- .../BaseJUnitLogFileFormat.cs | 109 ++++++++++++++++++ .../LogFileFormat/CppLintLogFileFormat.cs | 92 +-------------- .../GenericJUnitLogFileFormat.cs | 87 +------------- .../MarkdownlintCli2LogFileFormat.cs | 87 +------------- 4 files changed, 117 insertions(+), 258 deletions(-) diff --git a/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs index 44975f750..fa389cd9a 100644 --- a/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs @@ -1,5 +1,10 @@ namespace Cake.Issues.JUnit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; using Cake.Core.Diagnostics; using Cake.Core.IO; @@ -35,4 +40,108 @@ protected static (bool Valid, string FilePath) ValidateFilePath(string filePath, return (true, filePath); } + + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + protected static string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(['\r', '\n'], StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = Regex.Replace(result, @"\n{3,}", "\n\n"); + + return result; + } + + /// + /// Parses the JUnit XML document and extracts test suites. + /// + /// The XML log content to parse. + /// Collection of test suite elements. + protected static IEnumerable ParseJUnitXml(string logContent) + { + var doc = XDocument.Parse(logContent); + + // Handle both single testsuite and testsuites root elements + return doc.Root?.Name.LocalName == "testsuites" + ? doc.Root.Elements("testsuite") + : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + } + + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + /// Function to process failure elements. + /// Function to process error elements. + protected static void ProcessTestSuite( + XElement testSuite, + List result, + IRepositorySettings repositorySettings, + Func failureProcessor, + Func errorProcessor) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = failureProcessor(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = errorProcessor(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + ProcessTestSuite(nestedTestSuite, result, repositorySettings, failureProcessor, errorProcessor); + } + } } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs index dcdc347c9..f8d0d1ff4 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -31,12 +31,7 @@ public override IEnumerable ReadIssues( try { - var doc = XDocument.Parse(logContent); - - // Handle both single testsuite and testsuites root elements - var testSuites = doc.Root?.Name.LocalName == "testsuites" - ? doc.Root.Elements("testsuite") - : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + var testSuites = ParseJUnitXml(logContent); foreach (var testSuite in testSuites) { @@ -45,7 +40,7 @@ public override IEnumerable ReadIssues( continue; } - this.ProcessTestSuite(testSuite, result, repositorySettings); + ProcessTestSuite(testSuite, result, repositorySettings, this.ProcessCppLintFailure, this.ProcessCppLintFailure); } } catch (Exception ex) @@ -56,97 +51,18 @@ public override IEnumerable ReadIssues( return result; } - /// - /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. - /// - /// The XML content to normalize. - /// The normalized content. - private static string NormalizeXmlContent(string content) - { - if (string.IsNullOrEmpty(content)) - { - return string.Empty; - } - - // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(['\r', '\n'], StringSplitOptions.None); - var normalizedLines = new List(); - - foreach (var line in lines) - { - // Trim leading and trailing whitespace (including tabs) from each line - var trimmedLine = line.Trim(); - normalizedLines.Add(trimmedLine); - } - - // Join lines back together and clean up multiple consecutive empty lines - var result = string.Join("\n", normalizedLines); - - // Remove leading and trailing empty lines - result = result.Trim('\n'); - - // Normalize multiple consecutive newlines to double newlines maximum - result = Regex.Replace(result, @"\n{3,}", "\n\n"); - - return result; - } - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - /// Repository settings. - private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) - { - if (testSuite == null) - { - return; - } - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessCppLintFailure(failure, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessCppLintFailure(error, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); - } - } /// /// Processes a cpplint test failure or error element and creates an issue. /// /// The failure or error XML element. + /// The test class name. /// The test name. /// The issue priority. /// Repository settings. /// The created issue or null if the failure should be ignored. - private IIssue ProcessCppLintFailure(XElement failureElement, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + private IIssue ProcessCppLintFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) { var message = failureElement.Attribute("message")?.Value ?? string.Empty; var type = failureElement.Attribute("type")?.Value ?? string.Empty; diff --git a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs index fb7e1cc6d..cacc59ab3 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs @@ -31,12 +31,7 @@ public override IEnumerable ReadIssues( try { - var doc = XDocument.Parse(logContent); - - // Handle both single testsuite and testsuites root elements - var testSuites = doc.Root?.Name.LocalName == "testsuites" - ? doc.Root.Elements("testsuite") - : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + var testSuites = ParseJUnitXml(logContent); foreach (var testSuite in testSuites) { @@ -45,7 +40,7 @@ public override IEnumerable ReadIssues( continue; } - this.ProcessTestSuite(testSuite, result, repositorySettings); + ProcessTestSuite(testSuite, result, repositorySettings, this.ProcessTestFailure, this.ProcessTestFailure); } } catch (Exception ex) @@ -56,40 +51,7 @@ public override IEnumerable ReadIssues( return result; } - /// - /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. - /// - /// The XML content to normalize. - /// The normalized content. - private static string NormalizeXmlContent(string content) - { - if (string.IsNullOrEmpty(content)) - { - return string.Empty; - } - - // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(['\r', '\n'], StringSplitOptions.None); - var normalizedLines = new List(); - - foreach (var line in lines) - { - // Trim leading and trailing whitespace (including tabs) from each line - var trimmedLine = line.Trim(); - normalizedLines.Add(trimmedLine); - } - // Join lines back together and clean up multiple consecutive empty lines - var result = string.Join("\n", normalizedLines); - - // Remove leading and trailing empty lines - result = result.Trim('\n'); - - // Normalize multiple consecutive newlines to double newlines maximum - result = Regex.Replace(result, @"\n{3,}", "\n\n"); - - return result; - } /// /// Tries to extract file path from a class name. @@ -185,52 +147,7 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut return null; } - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - /// Repository settings. - private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) - { - if (testSuite == null) - { - return; - } - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessTestFailure(failure, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessTestFailure(error, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); - } - } /// /// Processes a test failure or error element and creates an issue. diff --git a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs index 96c046314..04a379da5 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs @@ -31,12 +31,7 @@ public override IEnumerable ReadIssues( try { - var doc = XDocument.Parse(logContent); - - // Handle both single testsuite and testsuites root elements - var testSuites = doc.Root?.Name.LocalName == "testsuites" - ? doc.Root.Elements("testsuite") - : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + var testSuites = ParseJUnitXml(logContent); foreach (var testSuite in testSuites) { @@ -45,7 +40,7 @@ public override IEnumerable ReadIssues( continue; } - this.ProcessTestSuite(testSuite, result, repositorySettings); + ProcessTestSuite(testSuite, result, repositorySettings, this.ProcessMarkdownlintCli2Failure, this.ProcessMarkdownlintCli2Failure); } } catch (Exception ex) @@ -56,40 +51,7 @@ public override IEnumerable ReadIssues( return result; } - /// - /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. - /// - /// The XML content to normalize. - /// The normalized content. - private static string NormalizeXmlContent(string content) - { - if (string.IsNullOrEmpty(content)) - { - return string.Empty; - } - - // Split by lines, trim each line to remove XML indentation, then rejoin - var lines = content.Split(['\r', '\n'], StringSplitOptions.None); - var normalizedLines = new List(); - - foreach (var line in lines) - { - // Trim leading and trailing whitespace (including tabs) from each line - var trimmedLine = line.Trim(); - normalizedLines.Add(trimmedLine); - } - // Join lines back together and clean up multiple consecutive empty lines - var result = string.Join("\n", normalizedLines); - - // Remove leading and trailing empty lines - result = result.Trim('\n'); - - // Normalize multiple consecutive newlines to double newlines maximum - result = Regex.Replace(result, @"\n{3,}", "\n\n"); - - return result; - } /// /// Tries to extract line and column information from markdownlint-cli2 format text. @@ -138,52 +100,7 @@ private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(s return null; } - /// - /// Recursively processes a testsuite element and its nested testsuites and testcases. - /// - /// The testsuite element to process. - /// The list to add found issues to. - /// Repository settings. - private void ProcessTestSuite(XElement testSuite, List result, IRepositorySettings repositorySettings) - { - if (testSuite == null) - { - return; - } - - // Process direct testcase children - foreach (var testCase in testSuite.Elements("testcase")) - { - var className = testCase.Attribute("classname")?.Value ?? string.Empty; - var testName = testCase.Attribute("name")?.Value ?? string.Empty; - - // Process failures - foreach (var failure in testCase.Elements("failure")) - { - var issue = this.ProcessMarkdownlintCli2Failure(failure, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - // Process errors - foreach (var error in testCase.Elements("error")) - { - var issue = this.ProcessMarkdownlintCli2Failure(error, className, testName, IssuePriority.Error, repositorySettings); - if (issue != null) - { - result.Add(issue); - } - } - } - - // Recursively process nested testsuite elements - foreach (var nestedTestSuite in testSuite.Elements("testsuite")) - { - this.ProcessTestSuite(nestedTestSuite, result, repositorySettings); - } - } /// /// Processes a markdownlint-cli2 test failure or error element and creates an issue. From 56b513392ceda018b37326c1b32dabfe4470e8d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:31:08 +0000 Subject: [PATCH 31/35] Fix StyleCop warnings: remove duplicate blank lines and unused using directives Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com> --- src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs | 3 --- .../LogFileFormat/GenericJUnitLogFileFormat.cs | 4 ---- .../LogFileFormat/MarkdownlintCli2LogFileFormat.cs | 5 ----- 3 files changed, 12 deletions(-) diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs index f8d0d1ff4..5477bac6b 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -2,7 +2,6 @@ namespace Cake.Issues.JUnit.LogFileFormat; using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using Cake.Core.Diagnostics; @@ -51,8 +50,6 @@ public override IEnumerable ReadIssues( return result; } - - /// /// Processes a cpplint test failure or error element and creates an issue. /// diff --git a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs index cacc59ab3..782a54188 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs @@ -51,8 +51,6 @@ public override IEnumerable ReadIssues( return result; } - - /// /// Tries to extract file path from a class name. /// @@ -147,8 +145,6 @@ private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOut return null; } - - /// /// Processes a test failure or error element and creates an issue. /// diff --git a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs index 04a379da5..c79bdd7fe 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs @@ -2,7 +2,6 @@ namespace Cake.Issues.JUnit.LogFileFormat; using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using Cake.Core.Diagnostics; @@ -51,8 +50,6 @@ public override IEnumerable ReadIssues( return result; } - - /// /// Tries to extract line and column information from markdownlint-cli2 format text. /// @@ -100,8 +97,6 @@ private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(s return null; } - - /// /// Processes a markdownlint-cli2 test failure or error element and creates an issue. /// From dc6bd5432002c5945dd1d08e05c09e44929ae008 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Fri, 8 Aug 2025 00:26:31 +0200 Subject: [PATCH 32/35] Cleanup --- .../BaseJUnitLogFileFormat.cs | 7 ++- ...UnitIssuesAliases.CppLintLogFileFormats.cs | 27 +++++++++ ...UnitIssuesAliases.GenericLogFileFormats.cs | 27 +++++++++ .../JUnitIssuesAliases.LogFileFormats.cs | 59 ------------------- ...ssuesAliases.MarkdownlintLogFileFormats.cs | 27 +++++++++ .../LogFileFormat/CppLintLogFileFormat.cs | 18 +++--- 6 files changed, 95 insertions(+), 70 deletions(-) create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs delete mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs create mode 100644 src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs diff --git a/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs index fa389cd9a..f5a80d540 100644 --- a/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs @@ -12,7 +12,7 @@ namespace Cake.Issues.JUnit; /// Base class for all log file formats supported by the JUnit issue provider. /// /// The Cake log instance. -public abstract class BaseJUnitLogFileFormat(ICakeLog log) +public abstract partial class BaseJUnitLogFileFormat(ICakeLog log) : BaseLogFileFormat(log) { /// @@ -71,7 +71,7 @@ protected static string NormalizeXmlContent(string content) result = result.Trim('\n'); // Normalize multiple consecutive newlines to double newlines maximum - result = Regex.Replace(result, @"\n{3,}", "\n\n"); + result = DoubleNewlinesRegex().Replace(result, "\n\n"); return result; } @@ -144,4 +144,7 @@ protected static void ProcessTestSuite( ProcessTestSuite(nestedTestSuite, result, repositorySettings, failureProcessor, errorProcessor); } } + + [GeneratedRegex(@"\n{3,}")] + private static partial Regex DoubleNewlinesRegex(); } \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs new file mode 100644 index 000000000..ce55f7bad --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs @@ -0,0 +1,27 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for provider to read issues in JUnit file format generated by CppLint. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for cpplint JUnit XML output. + /// Optimized for cpplint's specific JUnit format where test case names represent file names. + /// + /// The context. + /// Instance for the cpplint JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat CppLintJUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new CppLintLogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs new file mode 100644 index 000000000..8e4ddb52e --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs @@ -0,0 +1,27 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for provider to read issues in JUnit file format. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for any file compatible with JUnit XML format. + /// Does best effort parsing for any JUnit XML format. + /// + /// The context. + /// Instance for the generic JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat GenericJUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new GenericJUnitLogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs deleted file mode 100644 index 1476fc309..000000000 --- a/src/Cake.Issues.JUnit/JUnitIssuesAliases.LogFileFormats.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Cake.Issues.JUnit; - -using Cake.Core; -using Cake.Core.Annotations; -using Cake.Issues.JUnit.LogFileFormat; - -/// -/// Aliases for JUnit log file formats. -/// -public static partial class JUnitIssuesAliases -{ - /// - /// Gets an instance for the log format for any file compatible with JUnit XML format. - /// Does best effort parsing for any JUnit XML format. - /// - /// The context. - /// Instance for the generic JUnit XML format. - [CakePropertyAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static BaseJUnitLogFileFormat GenericJUnitLogFileFormat( - this ICakeContext context) - { - context.NotNull(); - - return new GenericJUnitLogFileFormat(context.Log); - } - - /// - /// Gets an instance for the log format for cpplint JUnit XML output. - /// Optimized for cpplint's specific JUnit format where test case names represent file names. - /// - /// The context. - /// Instance for the cpplint JUnit XML format. - [CakePropertyAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static BaseJUnitLogFileFormat CppLintJUnitLogFileFormat( - this ICakeContext context) - { - context.NotNull(); - - return new CppLintLogFileFormat(context.Log); - } - - /// - /// Gets an instance for the log format for markdownlint-cli2 JUnit XML output. - /// Optimized for markdownlint-cli2's specific JUnit format where file paths are in classname attributes. - /// - /// The context. - /// Instance for the markdownlint-cli2 JUnit XML format. - [CakePropertyAlias] - [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] - public static BaseJUnitLogFileFormat MarkdownlintCli2JUnitLogFileFormat( - this ICakeContext context) - { - context.NotNull(); - - return new MarkdownlintCli2LogFileFormat(context.Log); - } -} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs new file mode 100644 index 000000000..a1a574138 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs @@ -0,0 +1,27 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for provider to read issues in JUnit file format generated by markdownlint-cli2. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for markdownlint-cli2 JUnit XML output. + /// Optimized for markdownlint-cli2's specific JUnit format where file paths are in classname attributes. + /// + /// The context. + /// Instance for the markdownlint-cli2 JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat MarkdownlintCli2JUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new MarkdownlintCli2LogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs index 5477bac6b..fa8e6cb6d 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -11,7 +11,7 @@ namespace Cake.Issues.JUnit.LogFileFormat; /// Optimized for cpplint's specific JUnit format where test case names represent file names. /// /// The Cake log instance. -internal class CppLintLogFileFormat(ICakeLog log) +internal partial class CppLintLogFileFormat(ICakeLog log) : BaseJUnitLogFileFormat(log) { /// @@ -50,6 +50,12 @@ public override IEnumerable ReadIssues( return result; } + [GeneratedRegex(@"^(\d+):\s*.*\[.*\].*\[.*\]", RegexOptions.Multiline)] + private static partial Regex LineRegex(); + + [GeneratedRegex(@"^(\d+):", RegexOptions.Multiline)] + private static partial Regex SimpleLineRegex(); + /// /// Processes a cpplint test failure or error element and creates an issue. /// @@ -94,10 +100,7 @@ private IIssue ProcessCppLintFailure(XElement failureElement, string className, { // Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]" // This is a strong indicator that it's a cpplint-style failure where the test name is the file name - var lineMatch = Regex.Match( - fullMessage, - @"^(\d+):\s*.*\[.*\].*\[.*\]", - RegexOptions.Multiline); + var lineMatch = LineRegex().Match(fullMessage); if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) { var (valid, filePath) = ValidateFilePath(testName, repositorySettings); @@ -110,10 +113,7 @@ private IIssue ProcessCppLintFailure(XElement failureElement, string className, // Also check for simple line number pattern without the category/subcategory format else { - var simpleLineMatch = Regex.Match( - fullMessage, - @"^(\d+):", - RegexOptions.Multiline); + var simpleLineMatch = SimpleLineRegex().Match(fullMessage); if (simpleLineMatch.Success && int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum)) { From 413e9f1938a7de0a94b363770175539642bf5f69 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 10 Aug 2025 22:07:26 +0200 Subject: [PATCH 33/35] Update documentation --- .../issue-providers/junit/features.md | 177 ++++++++++++------ 1 file changed, 121 insertions(+), 56 deletions(-) diff --git a/docs/input/documentation/issue-providers/junit/features.md b/docs/input/documentation/issue-providers/junit/features.md index 33fce4433..0139b57cc 100644 --- a/docs/input/documentation/issue-providers/junit/features.md +++ b/docs/input/documentation/issue-providers/junit/features.md @@ -6,69 +6,134 @@ icon: material/creation-outline The [Cake.Issues.JUnit addin](https://cakebuild.net/extensions/cake-issues-junit/){target="_blank"} provides the following features. -??? tip "Supported Tools" - The JUnit issue provider can read JUnit XML output from any tool that generates standard JUnit XML format, including: - - - **cpplint**: C++ linter - - **commitlint-format-junit**: Commit message linter - - **kubeconform**: Kubernetes manifest validator - - **htmlhint**: HTML linter with JUnit format support - - Any other tool that outputs JUnit XML format - ## Basic features -- [x] Reads issues from JUnit XML files. +- [x] Reads issues from [JUnit]{target="_blank"} XML files. - [x] Supports both single `testsuite` and `testsuites` root elements. -- [x] Extracts file paths from `classname` attributes or embedded within failure messages. -- [x] Supports multiple file path patterns commonly used by linters. -- [x] Maps JUnit test failures and errors to Cake.Issues format with appropriate priorities. -- [x] Extracts rule names from failure types or test names. -- [x] Robust error handling for malformed XML. -## Supported file path patterns +## Supported log file formats -The provider can extract file information from various formats commonly used in linter output: +!!! note + There is no official specification for the [JUnit]{target="_blank"} XML file format and various tools generate different flavors of this format. + The `GenericLogFileFormat` supports different patterns to try to extract details, like file, line / column or rule information. + Tool specific log file formats needs to be used to parse specific formats are additional data provided by the tool. -- [x] `file:line:column` (e.g., `src/example.cpp:15:5`) -- [x] `file(line,column)` (e.g., `index.html(12,5)`) -- [x] `file line number` (e.g., `about.html line 8`) -- [x] `file:line` (e.g., `/path/to/file.txt:123`) +- [x] `GenericJUnitLogFileFormat alias for reading issues from any file in [JUnit]{target="_blank"} XML format + - [x] Combines `message` and content for issue description. + - [x] Extracts rule name from `type` or `name` attribute. + - [x] Extracts file paths from `classname` attributes or embedded within failure messages. + Supported file paths: + - `::` (e.g., `src/example.cpp:15:5`) + - `:` (e.g., `/path/to/file.txt:123`) + - `(,)` (e.g., `index.html(12,5)`) + - `()` (e.g., `index.html(12)`) + - ` ` (e.g., `about.html line 8`) + - `File: ` (e.g., `File: about.html`) +- [x] `CppLintLogFileFormat` alias for reading JUnit XML files generated by [CppLint](https://github.com/cpplint/cpplint){target="_blank"}. +- [x] `MarkdownlintCli2LogFileFormat` alias for reading JUnit XML files generated by [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2){target="_blank"}. -## Supported log file formats +## Supported IIssue properties -- [x] [JUnitIssuesFromFilePath](https://cakebuild.net/api/Cake.Issues.JUnit/JUnitIssuesAliases/){target="_blank"} - alias for reading issues from JUnit XML files. -- [x] [JUnitIssuesFromContent](https://cakebuild.net/api/Cake.Issues.JUnit/JUnitIssuesAliases/){target="_blank"} - alias for reading issues from JUnit XML content. +=== "GenericJUnitLogFileFormat" -## Supported IIssue properties +
+ + - [x] `IIssue.ProviderType` + - [x] `IIssue.ProviderName` + - [ ] `IIssue.Run` (1) + - [x] `IIssue.Identifier` (2) + - [ ] `IIssue.ProjectName` + - [ ] `IIssue.ProjectFileRelativePath` + - [x] `IIssue.AffectedFileRelativePath` (3) + - [x] `IIssue.Line` (4) + - [ ] `IIssue.EndLine` + - [x] `IIssue.Column` (5) + - [ ] `IIssue.EndColumn` + - [ ] `IIssue.FileLink` (6) + - [x] `IIssue.MessageText` + - [ ] `IIssue.MessageHtml` + - [ ] `IIssue.MessageMarkdown` + - [x] `IIssue.Priority` + - [x] `IIssue.PriorityName` + - [x] `IIssue.RuleId` (7) + - [ ] `IIssue.RuleUrl` + +
+ + 1. Can be set while reading issues + 2. Set to `IIssue.MessageText` + 3. Extracted from `classname` attribute or parsed from failure message content + 4. Extracted from failure message and content when available + 5. Extracted from failure message and content when available + 6. Can be set while reading issues + 7. Set to the failure `type` or `name` attribute when available + +=== "CppLintLogFileFormat" + +
+ + - [x] `IIssue.ProviderType` + - [x] `IIssue.ProviderName` + - [ ] `IIssue.Run` (1) + - [x] `IIssue.Identifier` (2) + - [ ] `IIssue.ProjectName` + - [ ] `IIssue.ProjectFileRelativePath` + - [x] `IIssue.AffectedFileRelativePath` (3) + - [x] `IIssue.Line` (4) + - [ ] `IIssue.EndLine` + - [x] `IIssue.Column` (5) + - [ ] `IIssue.EndColumn` + - [ ] `IIssue.FileLink` (6) + - [x] `IIssue.MessageText` + - [ ] `IIssue.MessageHtml` + - [ ] `IIssue.MessageMarkdown` + - [x] `IIssue.Priority` + - [x] `IIssue.PriorityName` + - [x] `IIssue.RuleId` (7) + - [ ] `IIssue.RuleUrl` + +
+ + 1. Can be set while reading issues + 2. Set to `IIssue.MessageText` + 3. Parsed from failure message content + 4. Extracted from failure content when available + 5. Extracted from failure content when available + 6. Can be set while reading issues + 7. Set to the failure `name` attribute + +=== "MarkdownlintCli2LogFileFormat" + +
+ + - [x] `IIssue.ProviderType` + - [x] `IIssue.ProviderName` + - [ ] `IIssue.Run` (1) + - [x] `IIssue.Identifier` (2) + - [ ] `IIssue.ProjectName` + - [ ] `IIssue.ProjectFileRelativePath` + - [x] `IIssue.AffectedFileRelativePath` (3) + - [x] `IIssue.Line` (4) + - [ ] `IIssue.EndLine` + - [x] `IIssue.Column` (5) + - [ ] `IIssue.EndColumn` + - [ ] `IIssue.FileLink` (6) + - [x] `IIssue.MessageText` + - [ ] `IIssue.MessageHtml` + - [ ] `IIssue.MessageMarkdown` + - [x] `IIssue.Priority` + - [x] `IIssue.PriorityName` + - [x] `IIssue.RuleId` (7) + - [ ] `IIssue.RuleUrl` + +
+ + 1. Can be set while reading issues + 2. Set to `IIssue.MessageText` + 3. Extracted from `classname` attribute + 4. Extracted from failure message and content when available + 5. Extracted from failure message and content when available + 6. Can be set while reading issues + 7. Set to the failure `name` attribute when available -
- -- [x] `IIssue.ProviderType` -- [x] `IIssue.ProviderName` -- [ ] `IIssue.Run` (1) -- [ ] `IIssue.Identifier` -- [ ] `IIssue.ProjectName` -- [ ] `IIssue.ProjectFileRelativePath` -- [x] `IIssue.AffectedFileRelativePath` (2) -- [x] `IIssue.Line` (3) -- [ ] `IIssue.EndLine` -- [x] `IIssue.Column` (3) -- [ ] `IIssue.EndColumn` -- [ ] `IIssue.FileLink` (4) -- [x] `IIssue.MessageText` -- [ ] `IIssue.MessageHtml` -- [ ] `IIssue.MessageMarkdown` -- [x] `IIssue.Priority` -- [x] `IIssue.PriorityName` -- [x] `IIssue.RuleId` (5) -- [ ] `IIssue.RuleUrl` - -
- -1. Can be set while reading issues -2. Extracted from `classname` attribute or parsed from failure message content -3. Extracted from failure message content when available -4. Can be set while reading issues -5. Set to the failure `type` attribute when available \ No newline at end of file +[JUnit]: https://github.com/testmoapp/junitxml From 52519da9ff8b0979d2837c0f355db02dc19462f0 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 10 Aug 2025 22:29:45 +0200 Subject: [PATCH 34/35] Simplifiy log file formats --- .../LogFileFormat/CppLintLogFileFormat.cs | 23 +++++-------------- .../MarkdownlintCli2LogFileFormat.cs | 5 ---- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs index fa8e6cb6d..3470bbdee 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -67,40 +67,29 @@ public override IEnumerable ReadIssues( /// The created issue or null if the failure should be ignored. private IIssue ProcessCppLintFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) { - var message = failureElement.Attribute("message")?.Value ?? string.Empty; - var type = failureElement.Attribute("type")?.Value ?? string.Empty; var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; - // Combine message and content for full description - var fullMessage = string.IsNullOrEmpty(message) ? content : - string.IsNullOrEmpty(content) ? message : - $"{message}\n{content}"; - - if (string.IsNullOrEmpty(fullMessage)) + if (string.IsNullOrEmpty(content)) { return null; } var issueBuilder = IssueBuilder - .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, "JUnit") + .NewIssue(content, typeof(JUnitIssuesProvider).FullName, "JUnit") .WithPriority(priority); - if (!string.IsNullOrEmpty(type)) - { - issueBuilder = issueBuilder.OfRule(type); - } - else if (!string.IsNullOrEmpty(testName)) + if (!string.IsNullOrEmpty(testName)) { issueBuilder = issueBuilder.OfRule(testName); } // For cpplint-style output, if the test name looks like a file name, // use the test name as the file name and try to extract line info from the message - if (!string.IsNullOrEmpty(testName) && testName != "errors") + if (!string.IsNullOrEmpty(testName)) { // Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]" // This is a strong indicator that it's a cpplint-style failure where the test name is the file name - var lineMatch = LineRegex().Match(fullMessage); + var lineMatch = LineRegex().Match(content); if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) { var (valid, filePath) = ValidateFilePath(testName, repositorySettings); @@ -113,7 +102,7 @@ private IIssue ProcessCppLintFailure(XElement failureElement, string className, // Also check for simple line number pattern without the category/subcategory format else { - var simpleLineMatch = SimpleLineRegex().Match(fullMessage); + var simpleLineMatch = SimpleLineRegex().Match(content); if (simpleLineMatch.Success && int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum)) { diff --git a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs index c79bdd7fe..01fc1da54 100644 --- a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs +++ b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs @@ -109,7 +109,6 @@ private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(s private IIssue ProcessMarkdownlintCli2Failure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) { var message = failureElement.Attribute("message")?.Value ?? string.Empty; - var type = failureElement.Attribute("type")?.Value ?? string.Empty; var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; // Combine message and content for full description @@ -131,10 +130,6 @@ private IIssue ProcessMarkdownlintCli2Failure(XElement failureElement, string cl { issueBuilder = issueBuilder.OfRule(testName); } - else if (!string.IsNullOrEmpty(type)) - { - issueBuilder = issueBuilder.OfRule(type); - } // For markdownlint-cli2 style output, check if the content contains the specific format // and use the class name as file path in that case From 43d7f21af00c9475caf5fe3e0b8cfa70720f8d59 Mon Sep 17 00:00:00 2001 From: Pascal Berger Date: Sun, 17 Aug 2025 22:11:23 +0200 Subject: [PATCH 35/35] Fix package lock --- .../packages.lock.json | 110 +++++++++++++++++- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/src/Cake.Issues.JUnit.Tests/packages.lock.json b/src/Cake.Issues.JUnit.Tests/packages.lock.json index 9ba4d55aa..56fc71200 100644 --- a/src/Cake.Issues.JUnit.Tests/packages.lock.json +++ b/src/Cake.Issues.JUnit.Tests/packages.lock.json @@ -108,7 +108,10 @@ "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.14.1", - "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==" + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", @@ -119,11 +122,25 @@ "Newtonsoft.Json": "13.0.3" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.CodeDom": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, "System.Management": { "type": "Transitive", "resolved": "6.0.1", @@ -132,6 +149,28 @@ "System.CodeDom": "6.0.0" } }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, "Validation": { "type": "Transitive", "resolved": "2.5.51", @@ -202,7 +241,9 @@ "resolved": "5.0.0", "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", "dependencies": { - "Microsoft.NETCore.Platforms": "7.0.4" + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" } }, "Cake.Testing": { @@ -212,9 +253,17 @@ "contentHash": "oEERVvRww03yd54aFtbSFYc7w4xou9X1An8za8JVOM8JvOhp8mNqh53a4+ogJ9qVOgTSFzK/MvbVfZQNeJECjg==", "dependencies": { "Cake.Core": "5.0.0", - "Microsoft.NETCore.Platforms": "7.0.4" + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" } }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, "Newtonsoft.Json": { "type": "CentralTransitive", "requested": "[13.0.1, )", @@ -329,7 +378,10 @@ "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.14.1", - "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==" + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", @@ -340,11 +392,25 @@ "Newtonsoft.Json": "13.0.3" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.CodeDom": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, "System.Management": { "type": "Transitive", "resolved": "6.0.1", @@ -353,6 +419,28 @@ "System.CodeDom": "6.0.0" } }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, "Validation": { "type": "Transitive", "resolved": "2.5.51", @@ -423,7 +511,9 @@ "resolved": "5.0.0", "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", "dependencies": { - "Microsoft.NETCore.Platforms": "7.0.4" + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" } }, "Cake.Testing": { @@ -433,9 +523,17 @@ "contentHash": "oEERVvRww03yd54aFtbSFYc7w4xou9X1An8za8JVOM8JvOhp8mNqh53a4+ogJ9qVOgTSFzK/MvbVfZQNeJECjg==", "dependencies": { "Cake.Core": "5.0.0", - "Microsoft.NETCore.Platforms": "7.0.4" + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" } }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, "Newtonsoft.Json": { "type": "CentralTransitive", "requested": "[13.0.1, )",