From edfca6e1afed5466e7d5913578976e63863e9d30 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Mon, 12 May 2025 00:45:37 -0400 Subject: [PATCH 1/4] [CI] Add a tools/GenerateTestSummary.csproj for use with github actions --- .github/workflows/run-tests.yml | 11 ++ .github/workflows/tests.yml | 21 ++- Aspire.sln | 15 ++ .../GenerateTestSummary.csproj | 14 ++ tools/GenerateTestSummary/Program.cs | 74 ++++++++ .../TestSummaryGenerator.cs | 169 ++++++++++++++++++ tools/GenerateTestSummary/TrxReader.cs | 149 +++++++++++++++ 7 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 tools/GenerateTestSummary/GenerateTestSummary.csproj create mode 100644 tools/GenerateTestSummary/Program.cs create mode 100644 tools/GenerateTestSummary/TestSummaryGenerator.cs create mode 100644 tools/GenerateTestSummary/TrxReader.cs diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8eeba91a4c0..715d08ae506 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -257,6 +257,17 @@ jobs: --timeout ${{ inputs.testSessionTimeout }} ${{ inputs.extraTestArgs }} + - name: Generate test results summary + if: always() + env: + CI: false + run: > + ${{ github.workspace }}/dotnet.sh + run + --project ${{ github.workspace }}/tools/GenerateTestSummary/GenerateTestSummary.csproj + -- + ${{ github.workspace }}/testresults + # Save the result of the previous steps - success or failure # in the form of a file result-success/result-failure -{name}.rst - name: Store result - success diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f14c896e2f..e14bdfba4ce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -148,6 +148,9 @@ jobs: name: Final Results needs: [ integrations_test_lin, integrations_test_win, templates_test_lin, templates_test_win, endtoend_tests ] steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # get all the test-job-result* artifacts into a single directory - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: @@ -159,20 +162,32 @@ jobs: with: pattern: logs-*-ubuntu-latest merge-multiple: true - path: testresults/ubuntu-latest + path: ${{ github.workspace }}/testresults/ubuntu-latest - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 with: pattern: logs-*-windows-latest merge-multiple: true - path: testresults/windows-latest + path: ${{ github.workspace }}/testresults/windows-latest - name: Upload test results if: always() uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: All-TestResults - path: testresults/**/*.trx + path: ${{ github.workspace }}/testresults/**/*.trx + + - name: Generate test results summary + if: always() + env: + CI: false + run: > + ${{ github.workspace }}/dotnet.sh + run + --project ${{ github.workspace }}/tools/GenerateTestSummary/GenerateTestSummary.csproj + -- + ${{ github.workspace }}/testresults + --combined # return success if zero result-failed-* files are found - name: Compute result diff --git a/Aspire.sln b/Aspire.sln index 1a8387b15d3..efe28447dac 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -678,6 +678,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication1", "playgrou EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication2", "playground\SqlServerScript\WebApplication2\WebApplication2.csproj", "{554D72B3-F0B0-FB9A-67ED-BBDF55A6DE81}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateTestSummary", "tools\GenerateTestSummary\GenerateTestSummary.csproj", "{29950A00-A83A-48D3-8739-EE3D667B5229}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -3976,6 +3978,18 @@ Global {554D72B3-F0B0-FB9A-67ED-BBDF55A6DE81}.Release|x64.Build.0 = Release|Any CPU {554D72B3-F0B0-FB9A-67ED-BBDF55A6DE81}.Release|x86.ActiveCfg = Release|Any CPU {554D72B3-F0B0-FB9A-67ED-BBDF55A6DE81}.Release|x86.Build.0 = Release|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Debug|x64.ActiveCfg = Debug|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Debug|x64.Build.0 = Debug|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Debug|x86.ActiveCfg = Debug|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Debug|x86.Build.0 = Debug|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Release|Any CPU.Build.0 = Release|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x64.ActiveCfg = Release|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x64.Build.0 = Release|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x86.ActiveCfg = Release|Any CPU + {29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -4302,6 +4316,7 @@ Global {3928CF69-B803-43A2-8AE5-5E29CB3E8D24} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {E79A95EA-08D9-9947-377D-6F2213B36E1B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {554D72B3-F0B0-FB9A-67ED-BBDF55A6DE81} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {29950A00-A83A-48D3-8739-EE3D667B5229} = {2136E31D-2CBB-41BB-8618-716FF8E46E9E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {47DCFECF-5631-4BDE-A1EC-BE41E90F60C4} diff --git a/tools/GenerateTestSummary/GenerateTestSummary.csproj b/tools/GenerateTestSummary/GenerateTestSummary.csproj new file mode 100644 index 00000000000..ef372971aa6 --- /dev/null +++ b/tools/GenerateTestSummary/GenerateTestSummary.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/tools/GenerateTestSummary/Program.cs b/tools/GenerateTestSummary/Program.cs new file mode 100644 index 00000000000..5aeaca834cb --- /dev/null +++ b/tools/GenerateTestSummary/Program.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using System.CommandLine; +using Aspire.TestTools; + +// Usage: dotnet tools run GenerateTestSummary --dirPathOrTrxFilePath [--output ] [--combined] +// Generate a summary report from trx files. +// And write to $GITHUB_STEP_SUMMARY if running in GitHub Actions. + +var dirPathOrTrxFilePathArgument = new Argument("dirPathOrTrxFilePath"); +var outputOption = new Option("--output", "-o"); +var combinedSummaryOption = new Option("--combined", "-c"); + +var rootCommand = new RootCommand +{ + dirPathOrTrxFilePathArgument, + outputOption, + combinedSummaryOption +}; + +rootCommand.SetAction(result => +{ + var dirPathOrTrxFilePath = result.GetValue(dirPathOrTrxFilePathArgument); + if (string.IsNullOrEmpty(dirPathOrTrxFilePath)) + { + Console.WriteLine("Please provide a directory path with trx files or a trx file path."); + return; + } + + var combinedSummary = result.GetValue(combinedSummaryOption); + + string report; + if (combinedSummary) + { + report = TestSummaryGenerator.CreateCombinedTestSummaryReport(dirPathOrTrxFilePath); + } + else + { + var reportBuilder = new StringBuilder(); + if (Directory.Exists(dirPathOrTrxFilePath)) + { + var trxFiles = Directory.EnumerateFiles(dirPathOrTrxFilePath, "*.trx", SearchOption.AllDirectories); + foreach (var trxFile in trxFiles) + { + TestSummaryGenerator.CreateSingleTestSummaryReport(trxFile, reportBuilder); + } + } + else + { + TestSummaryGenerator.CreateSingleTestSummaryReport(dirPathOrTrxFilePath, reportBuilder); + } + + report = reportBuilder.ToString(); + } + + var outputFilePath = result.GetValue(outputOption); + if (outputFilePath is not null) + { + File.WriteAllText(outputFilePath, report); + Console.WriteLine($"Report written to {outputFilePath}"); + } + + if (report.Length > 0 + && Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true" + && Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY") is string summaryPath + && !string.IsNullOrEmpty(summaryPath)) + { + File.WriteAllText(summaryPath, report); + } +}); + +return rootCommand.Parse(args).Invoke(); diff --git a/tools/GenerateTestSummary/TestSummaryGenerator.cs b/tools/GenerateTestSummary/TestSummaryGenerator.cs new file mode 100644 index 00000000000..0fa5541bbe1 --- /dev/null +++ b/tools/GenerateTestSummary/TestSummaryGenerator.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Aspire.TestTools; + +sealed partial class TestSummaryGenerator +{ + public static string CreateCombinedTestSummaryReport(string basePath) + { + if (!Directory.Exists(basePath)) + { + throw new DirectoryNotFoundException($"The directory '{basePath}' does not exist."); + } + + var trxFiles = System.IO.Directory.EnumerateFiles(basePath, "*.trx", System.IO.SearchOption.AllDirectories); + + int overallTotalTestCount = 0; + int overallPassedTestCount = 0; + int overallFailedTestCount = 0; + int overallSkippedTestCount = 0; + + // Update to use markdown tables instead of HTML + var tableBuilder = new StringBuilder(); + tableBuilder.AppendLine("| Name | Passed | Failed | Skipped | Total |"); + tableBuilder.AppendLine("|------|--------|--------|---------|-------|"); + + foreach (var file in trxFiles.OrderBy(f => Path.GetFileName(f))) + { + TestRun? testRun; + try + { + testRun = TrxReader.DeserializeTrxFile(file); + if (testRun == null || testRun.ResultSummary?.Counters == null) + { + Console.WriteLine($"Failed to deserialize or find results in file: {file}, tr: {testRun}"); + continue; + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to deserialize file: {file}, exception: {ex}"); + continue; + } + + // emit row for each trx file + var counters = testRun.ResultSummary.Counters; + int total = counters.Total; + int passed = counters.Passed; + int failed = counters.Failed; + int skipped = counters.NotExecuted; + + overallTotalTestCount += total; + overallPassedTestCount += passed; + overallFailedTestCount += failed; + overallSkippedTestCount += skipped; + + tableBuilder.AppendLine(CultureInfo.InvariantCulture, $"| {(failed > 0 ? "❌" : "✅")} {GetTestTitle(file)} | {passed} | {failed} | {skipped} | {total} |"); + } + + var overallTableBuilder = new StringBuilder(); + overallTableBuilder.AppendLine("## Overall Summary"); + + overallTableBuilder.AppendLine("| Passed | Failed | Skipped | Total |"); + overallTableBuilder.AppendLine("|--------|--------|---------|-------|"); + overallTableBuilder.AppendLine(CultureInfo.InvariantCulture, $"| {overallPassedTestCount} | {overallFailedTestCount} | {overallSkippedTestCount} | {overallTotalTestCount} |"); + + overallTableBuilder.AppendLine(); + overallTableBuilder.Append(tableBuilder); + + return overallTableBuilder.ToString(); + } + + public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuilder reportBuilder) + { + if (!File.Exists(trxFilePath)) + { + throw new FileNotFoundException($"The file '{trxFilePath}' does not exist."); + } + + TestRun? testRun; + try + { + testRun = TrxReader.DeserializeTrxFile(trxFilePath); + if (testRun == null || testRun.ResultSummary?.Counters == null) + { + throw new InvalidOperationException($"Failed to deserialize or find results in file: {trxFilePath}"); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to process file: {trxFilePath}", ex); + } + + var counters = testRun.ResultSummary.Counters; + var failed = counters.Failed; + if (failed == 0) + { + return; + } + + var total = counters.Total; + var passed = counters.Passed; + var skipped = counters.NotExecuted; + + reportBuilder.AppendLine(CultureInfo.InvariantCulture, $"### {GetTestTitle(trxFilePath)}"); + reportBuilder.AppendLine("| Passed | Failed | Skipped | Total |"); + reportBuilder.AppendLine("|--------|--------|---------|-------|"); + reportBuilder.AppendLine(CultureInfo.InvariantCulture, $"| {passed} | {failed} | {skipped} | {total} |"); + + reportBuilder.AppendLine(); + if (testRun.Results?.UnitTestResults is null) + { + return; + } + + var failedTests = testRun.Results.UnitTestResults.Where(r => r.Outcome == "Failed"); + if (failedTests.Any()) + { + foreach (var test in failedTests) + { + reportBuilder.AppendLine("
"); + reportBuilder.AppendLine(CultureInfo.InvariantCulture, $""" +
+ 🔴 {test.TestName} + """); + + var errorMsgBuilder = new StringBuilder(); + errorMsgBuilder.AppendLine(test.Output?.ErrorInfo?.InnerText ?? string.Empty); + errorMsgBuilder.AppendLine(test.Output?.StdOut ?? string.Empty); + + var errorMsgTruncated = TruncateTheStart(errorMsgBuilder.ToString(), 50_000); // Truncate long error messages for readability + + // Truncate long error messages for readability + + reportBuilder.AppendLine(); + reportBuilder.AppendLine("```yml"); + reportBuilder.AppendLine(errorMsgTruncated); + reportBuilder.AppendLine("```"); + reportBuilder.AppendLine(); + reportBuilder.AppendLine("
"); + } + } + reportBuilder.AppendLine(); + } + + public static string GetTestTitle(string trxFileName) + { + var filename = Path.GetFileNameWithoutExtension(trxFileName); + var match = TestNameFromTrxFileNameRegex().Match(filename); + if (match.Success) + { + return $"{match.Groups["testName"].Value} ({match.Groups["tfm"].Value})"; + } + + return filename; + } + + [GeneratedRegex(@"(?.*)_(?net\d+\.0)_.*")] + private static partial Regex TestNameFromTrxFileNameRegex(); + + private static string? TruncateTheStart(string? s, int maxLength) + => s is null || s.Length <= maxLength + ? s + : "... (truncated) " + s[^maxLength..]; +} diff --git a/tools/GenerateTestSummary/TrxReader.cs b/tools/GenerateTestSummary/TrxReader.cs new file mode 100644 index 00000000000..4ccc2e389ff --- /dev/null +++ b/tools/GenerateTestSummary/TrxReader.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Xml; +using System.Xml.Serialization; + +namespace Aspire.TestTools; + +public class TrxReader +{ + public static IList GetTestResultsFromTrx(string filepath, Func? testFilter = null) + { + XmlSerializer serializer = new(typeof(TestRun)); + using FileStream fileStream = new(filepath, FileMode.Open); + + if (serializer.Deserialize(fileStream) is not TestRun testRun || testRun.Results?.UnitTestResults is null) + { + return Array.Empty(); + } + + var testResults = new List(); + + foreach (var unitTestResult in testRun.Results.UnitTestResults) + { + if (string.IsNullOrEmpty(unitTestResult.TestName) || string.IsNullOrEmpty(unitTestResult.Outcome)) + { + continue; + } + + if (testFilter is not null && !testFilter(unitTestResult.TestName, unitTestResult.Outcome)) + { + continue; + } + + var startTime = unitTestResult.StartTime; + var endTime = unitTestResult.EndTime; + + testResults.Add(new TestResult( + Name: unitTestResult.TestName, + Outcome: unitTestResult.Outcome, + StartTime: startTime is null ? TimeSpan.MinValue : TimeSpan.Parse(startTime, CultureInfo.InvariantCulture), + EndTime: endTime is null ? TimeSpan.MinValue : TimeSpan.Parse(endTime, CultureInfo.InvariantCulture), + ErrorMessage: unitTestResult.Output?.ErrorInfoString, + Stdout: unitTestResult.Output?.StdOut + )); + } + + return testResults; + } + + public static TestRun? DeserializeTrxFile(string filePath) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentException($"{nameof(filePath)} cannot be null or empty.", nameof(filePath)); + } + + XmlSerializer serializer = new(typeof(TestRun)); + + using FileStream fileStream = new(filePath, FileMode.Open, FileAccess.Read); + return serializer.Deserialize(fileStream) as TestRun; + } +} + +[XmlRoot("TestRun", Namespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")] +public class TestRun +{ + public Results? Results { get; set; } + + public ResultSummary? ResultSummary { get; set; } +} + +public class Results +{ + [XmlElement("UnitTestResult")] + public List? UnitTestResults { get; set; } +} + +public class UnitTestResult +{ + [XmlAttribute("testName")] + public string? TestName { get; set; } + + [XmlAttribute("outcome")] + public string? Outcome { get; set; } + + [XmlAttribute("startTime")] + public string? StartTime { get; set; } + + [XmlAttribute("endTime")] + public string? EndTime { get; set; } + + public Output? Output { get; set; } +} + +public class Output +{ + [XmlAnyElement] + public XmlElement? ErrorInfo { get; set; } + public string? StdOut { get; set; } + + [XmlIgnore] + public string ErrorInfoString => ErrorInfo?.InnerText ?? string.Empty; +} + +public class ResultSummary +{ + public string? Outcome { get; set; } + public Counters? Counters { get; set; } +} + +public class Counters +{ + [XmlAttribute("total")] + public int Total { get; set; } + + [XmlAttribute("executed")] + public int Executed { get; set; } + + [XmlAttribute("passed")] + public int Passed { get; set; } + + [XmlAttribute("failed")] + public int Failed { get; set; } + + [XmlAttribute("error")] + public int Error { get; set; } + + [XmlAttribute("timeout")] + public int Timeout { get; set; } + + [XmlAttribute("aborted")] + public int Aborted { get; set; } + + [XmlAttribute("inconclusive")] + public int Inconclusive { get; set; } + + [XmlAttribute("passedButRunAborted")] + public int PassedButRunAborted { get; set; } + + [XmlAttribute("notRunnable")] + public int NotRunnable { get; set; } + + [XmlAttribute("notExecuted")] + public int NotExecuted { get; set; } +} + +public record TestResult(string Name, string Outcome, TimeSpan StartTime, TimeSpan EndTime, string? ErrorMessage = null, string? Stdout = null); From ef704aa040c7458267ade33a7b53ec490b8f4797 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Mon, 12 May 2025 01:17:40 -0400 Subject: [PATCH 2/4] Address feedback from @ davidfowler and add link to the logs --- .github/workflows/run-tests.yml | 24 ++++++++++--------- tools/GenerateTestSummary/Program.cs | 10 ++++---- .../TestSummaryGenerator.cs | 15 +++++++++--- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 715d08ae506..9ff92b739bc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -257,17 +257,6 @@ jobs: --timeout ${{ inputs.testSessionTimeout }} ${{ inputs.extraTestArgs }} - - name: Generate test results summary - if: always() - env: - CI: false - run: > - ${{ github.workspace }}/dotnet.sh - run - --project ${{ github.workspace }}/tools/GenerateTestSummary/GenerateTestSummary.csproj - -- - ${{ github.workspace }}/testresults - # Save the result of the previous steps - success or failure # in the form of a file result-success/result-failure -{name}.rst - name: Store result - success @@ -294,6 +283,7 @@ jobs: docker network ls - name: Upload logs, and test results + id: upload-logs if: always() uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: @@ -303,3 +293,15 @@ jobs: testresults/** artifacts/bin/Aspire.Templates.Tests/Debug/net8.0/logs/** artifacts/log/test-logs/** + + - name: Generate test results summary + if: always() + env: + CI: false + run: > + ${{ github.workspace }}/dotnet.sh + run + --project ${{ github.workspace }}/tools/GenerateTestSummary/GenerateTestSummary.csproj + -- + ${{ github.workspace }}/testresults + -u ${{ steps.upload-logs.outputs.artifact-url }} diff --git a/tools/GenerateTestSummary/Program.cs b/tools/GenerateTestSummary/Program.cs index 5aeaca834cb..7f1adc98e63 100644 --- a/tools/GenerateTestSummary/Program.cs +++ b/tools/GenerateTestSummary/Program.cs @@ -10,14 +10,16 @@ // And write to $GITHUB_STEP_SUMMARY if running in GitHub Actions. var dirPathOrTrxFilePathArgument = new Argument("dirPathOrTrxFilePath"); -var outputOption = new Option("--output", "-o"); -var combinedSummaryOption = new Option("--combined", "-c"); +var outputOption = new Option("--output", "-o") { Description = "Output file path" }; +var combinedSummaryOption = new Option("--combined", "-c") { Description = "Generate combined summary report" }; +var urlOption = new Option("--url", "-u") { Description = "URL for test links" }; var rootCommand = new RootCommand { dirPathOrTrxFilePathArgument, outputOption, - combinedSummaryOption + combinedSummaryOption, + urlOption }; rootCommand.SetAction(result => @@ -49,7 +51,7 @@ } else { - TestSummaryGenerator.CreateSingleTestSummaryReport(dirPathOrTrxFilePath, reportBuilder); + TestSummaryGenerator.CreateSingleTestSummaryReport(dirPathOrTrxFilePath, reportBuilder, result.GetValue(urlOption)); } report = reportBuilder.ToString(); diff --git a/tools/GenerateTestSummary/TestSummaryGenerator.cs b/tools/GenerateTestSummary/TestSummaryGenerator.cs index 0fa5541bbe1..036bc5c0ee6 100644 --- a/tools/GenerateTestSummary/TestSummaryGenerator.cs +++ b/tools/GenerateTestSummary/TestSummaryGenerator.cs @@ -74,7 +74,7 @@ public static string CreateCombinedTestSummaryReport(string basePath) return overallTableBuilder.ToString(); } - public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuilder reportBuilder) + public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuilder reportBuilder, string? url = null) { if (!File.Exists(trxFilePath)) { @@ -122,10 +122,19 @@ public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuild { foreach (var test in failedTests) { + string title; + if (string.IsNullOrEmpty(url)) + { + title = $"🔴 {test.TestName}"; + } + else + { + + title = $"🔴 {test.TestName}"; + } reportBuilder.AppendLine("
"); reportBuilder.AppendLine(CultureInfo.InvariantCulture, $""" -
- 🔴 {test.TestName} +
{title} """); var errorMsgBuilder = new StringBuilder(); From 827200d34c915e925075de41ff69f3e67708d583 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Mon, 12 May 2025 01:24:16 -0400 Subject: [PATCH 3/4] Update tools/GenerateTestSummary/TestSummaryGenerator.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tools/GenerateTestSummary/TestSummaryGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/GenerateTestSummary/TestSummaryGenerator.cs b/tools/GenerateTestSummary/TestSummaryGenerator.cs index 036bc5c0ee6..8fed502db77 100644 --- a/tools/GenerateTestSummary/TestSummaryGenerator.cs +++ b/tools/GenerateTestSummary/TestSummaryGenerator.cs @@ -143,7 +143,6 @@ public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuild var errorMsgTruncated = TruncateTheStart(errorMsgBuilder.ToString(), 50_000); // Truncate long error messages for readability - // Truncate long error messages for readability reportBuilder.AppendLine(); reportBuilder.AppendLine("```yml"); From 524dd08727e533f67857691e79e22e3cd648fb04 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Mon, 12 May 2025 01:28:54 -0400 Subject: [PATCH 4/4] cleanup --- .../GenerateTestSummary/TestSummaryGenerator.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tools/GenerateTestSummary/TestSummaryGenerator.cs b/tools/GenerateTestSummary/TestSummaryGenerator.cs index 8fed502db77..22ae9c377e1 100644 --- a/tools/GenerateTestSummary/TestSummaryGenerator.cs +++ b/tools/GenerateTestSummary/TestSummaryGenerator.cs @@ -122,16 +122,10 @@ public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuild { foreach (var test in failedTests) { - string title; - if (string.IsNullOrEmpty(url)) - { - title = $"🔴 {test.TestName}"; - } - else - { + var title = string.IsNullOrEmpty(url) + ? $"🔴 {test.TestName}" + : $"🔴 {test.TestName}"; - title = $"🔴 {test.TestName}"; - } reportBuilder.AppendLine("
"); reportBuilder.AppendLine(CultureInfo.InvariantCulture, $"""
{title} @@ -141,8 +135,8 @@ public static void CreateSingleTestSummaryReport(string trxFilePath, StringBuild errorMsgBuilder.AppendLine(test.Output?.ErrorInfo?.InnerText ?? string.Empty); errorMsgBuilder.AppendLine(test.Output?.StdOut ?? string.Empty); - var errorMsgTruncated = TruncateTheStart(errorMsgBuilder.ToString(), 50_000); // Truncate long error messages for readability - + // Truncate long error messages for readability + var errorMsgTruncated = TruncateTheStart(errorMsgBuilder.ToString(), 50_000); reportBuilder.AppendLine(); reportBuilder.AppendLine("```yml");