Skip to content

Commit

Permalink
Sort test results by name, improve duration calculation
Browse files Browse the repository at this point in the history
By sorting tests by name we make it easier to find visually those that belong logically together.

While doing this change, we switch to adding the duration of each individual test counted test run, which is more precise than adding the overal test results file duration which can include a full run from a previous trx (i.e. a later one run partially only failed tests, for example).
  • Loading branch information
kzu committed Jul 4, 2024
1 parent c166255 commit a7101fc
Showing 1 changed file with 88 additions and 74 deletions.
162 changes: 88 additions & 74 deletions src/dotnet-trx/TrxCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using Devlooped.Web;
using Humanizer;
Expand All @@ -15,7 +16,7 @@

namespace Devlooped;

public partial class TrxCommand : Command<TrxCommand.TrxSettings>
public partial class TrxCommand : AsyncCommand<TrxCommand.TrxSettings>
{
public class TrxSettings : CommandSettings
{
Expand Down Expand Up @@ -46,14 +47,14 @@ public class TrxSettings : CommandSettings
public bool? Version { get; init; }
}

public override int Execute(CommandContext context, TrxSettings settings)
public override async Task<int> ExecuteAsync(CommandContext context, TrxSettings settings)
{
var path = settings.Path ?? Directory.GetCurrentDirectory();
if (!Path.IsPathFullyQualified(path))
path = Path.Combine(Directory.GetCurrentDirectory(), path);

if (File.Exists(path))
path = new FileInfo(path).DirectoryName;
path = new FileInfo(path).DirectoryName!;
else
path = Path.GetFullPath(path);

Expand All @@ -74,99 +75,112 @@ public override int Execute(CommandContext context, TrxSettings settings)
""");

// Process from newest files to oldest
foreach (var trx in Directory.EnumerateFiles(path, "*.trx", search).OrderByDescending(File.GetLastWriteTime))
{
using var file = File.OpenRead(trx);
// Clears namespaces
var doc = HtmlDocument.Load(file, new HtmlReaderSettings { CaseFolding = Sgml.CaseFolding.None });
var results = new List<XElement>();

foreach (var result in doc.CssSelectElements("UnitTestResult").OrderBy(x => x.Attribute("testName")?.Value))
Status().Start("Discovering test results...", ctx =>
{
// Process from newest files to oldest so that newest result we find (by test id) is the one we keep
foreach (var trx in Directory.EnumerateFiles(path, "*.trx", search).OrderByDescending(File.GetLastWriteTime))
{
var id = result.Attribute("testId")!.Value;
// Process only once per test id, this avoids duplicates when multiple trx files are processed
if (!testIds.Add(id))
continue;
ctx.Status($"Discovering test results in {Path.GetFileName(trx)}...");
using var file = File.OpenRead(trx);
// Clears namespaces
var doc = HtmlDocument.Load(file, new HtmlReaderSettings { CaseFolding = Sgml.CaseFolding.None });
foreach (var result in doc.CssSelectElements("UnitTestResult"))
{
var id = result.Attribute("testId")!.Value;
// Process only once per test id, this avoids duplicates when multiple trx files are processed
if (!testIds.Add(id))
continue;

var test = result.Attribute("testName")!.Value;
string? output = settings.Output ? result.CssSelectElement("StdOut")?.Value : default;
results.Add(result);
}
}

switch (result.Attribute("outcome")?.Value)
{
case "Passed":
passed++;
MarkupLine($":check_mark_button: {test}");
if (output == null)
details.AppendLine($":white_check_mark: {test}");
else
details.AppendLine(
$"""
ctx.Status("Sorting tests by name...");
results.Sort(new Comparison<XElement>((x, y) => x.Attribute("testName")!.Value.CompareTo(y.Attribute("testName")!.Value)));
});

foreach (var result in results)
{
var id = result.Attribute("testId")!.Value;
// Process only once per test id, this avoids duplicates when multiple trx files are processed
if (!testIds.Add(id))
continue;

var test = result.Attribute("testName")!.Value;
var elapsed = TimeSpan.Parse(result.Attribute("duration")!.Value);
var output = settings.Output ? result.CssSelectElement("StdOut")?.Value : default;

switch (result.Attribute("outcome")?.Value)
{
case "Passed":
passed++;
duration += elapsed;
MarkupLine($":check_mark_button: {test}");
if (output == null)
details.AppendLine($":white_check_mark: {test}");
else
details.AppendLine(
$"""
<details>
<summary>:white_check_mark: {test}</summary>
""")
.AppendLineIndented(output, "> &gt; ")
.AppendLine(
"""
.AppendLineIndented(output, "> &gt; ")
.AppendLine(
"""
</details>
""");
break;
case "Failed":
failed++;
MarkupLine($":cross_mark: {test}");
details.AppendLine(
$"""
break;
case "Failed":
failed++;
duration += elapsed;
MarkupLine($":cross_mark: {test}");
details.AppendLine(
$"""
<details>
<summary>:x: {test}</summary>
""");
WriteError(path, failures, result, details);
if (output != null)
details.AppendLineIndented(output, "> &gt; ");
details.AppendLine().AppendLine("</details>").AppendLine();
break;
case "NotExecuted":
if (!settings.Skipped)
break;

skipped++;
var reason = result.CssSelectElement("Output > ErrorInfo > Message")?.Value;
Markup($"[dim]:white_question_mark: {test}[/]");
details.Append($":grey_question: {test}");

if (reason != null)
{
Markup($"[dim] => {reason}[/]");
details.Append($" => {reason}");
}

WriteLine();
details.AppendLine();
WriteError(path, failures, result, details);
if (output != null)
details.AppendLineIndented(output, "> &gt; ");
details.AppendLine().AppendLine("</details>").AppendLine();
break;
case "NotExecuted":
if (!settings.Skipped)
break;
default:
break;
}

if (output != null)
{
Write(new Panel($"[dim]{output.ReplaceLineEndings()}[/]")
skipped++;
var reason = result.CssSelectElement("Output > ErrorInfo > Message")?.Value;
Markup($"[dim]:white_question_mark: {test}[/]");
details.Append($":grey_question: {test}");

if (reason != null)
{
Border = BoxBorder.None,
Padding = new Padding(5, 0, 0, 0),
});
}
Markup($"[dim] => {reason}[/]");
details.Append($" => {reason}");
}

WriteLine();
details.AppendLine();
break;
default:
break;
}

var times = doc.CssSelectElement("Times");
if (times == null)
continue;

var start = DateTime.Parse(times.Attribute("start")!.Value);
var finish = DateTime.Parse(times.Attribute("finish")!.Value);
duration += finish - start;
if (output != null)
{
Write(new Panel($"[dim]{output.ReplaceLineEndings()}[/]")
{
Border = BoxBorder.None,
Padding = new Padding(5, 0, 0, 0),
});
}
}

details.AppendLine().AppendLine("</details>");
Expand Down

0 comments on commit a7101fc

Please sign in to comment.