Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ And view results in an automatic pull request comment like:

> NOTE: this behavior is triggered by the presence of the `GITHUB_REF_NAME` and `CI` environment variables.

--only-latest - Only use the most recently modified TRX file:

trx --only-latest


--only-files - Specify specific TRX files to include (reads all files until the next -- flag):

trx --only-files test1.trx test2.trx

--only-tests - Specify one or more tests (until next -- flag) that are the only tests included in the output report rather than all the tests:

trx --only-tests Test1 Test2
The --only-files option supports both absolute and relative paths. Relative paths are first tried against the trx directory, then fall back to the current working directory.

<!-- include src/dotnet-trx/help.md -->
```shell
USAGE:
Expand Down
8 changes: 8 additions & 0 deletions src/dotnet-trx/ConsoleMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Devlooped;

static class ConsoleMode
{
public static bool BatchMode { get; set; }
public static bool NoColor { get; set; }
public static bool NoUpdates { get; set; }
}
39 changes: 34 additions & 5 deletions src/dotnet-trx/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// See https://aka.ms/new-console-template for more information
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
Expand All @@ -11,26 +12,54 @@
using Spectre.Console;
using Spectre.Console.Cli;

ConsoleMode.BatchMode = args.Contains("--batch");
ConsoleMode.NoColor = Environment.GetEnvironmentVariables().Contains("NO_COLOR") || ConsoleMode.BatchMode;
ConsoleMode.NoUpdates = args.Contains("--no-updates") || args.Contains("-u") || ConsoleMode.BatchMode;


if (ConsoleMode.BatchMode)
{
// Ensure Spectre does not emit any VT/ANSI/OSC sequences (colors, cursor movement, hyperlinks, etc.)
AnsiConsole.Console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.No,
ColorSystem = ColorSystemSupport.NoColors,
Interactive = InteractionSupport.No,
});
AnsiConsole.Profile.Width = 999999;

}

var app = new CommandApp<TrxCommand>();

// Alias -? to -h for help
if (args.Contains("-?"))
args = args.Select(x => x == "-?" ? "-h" : x).ToArray();
Dictionary<string,string> MigratedCLI = new() { {"-?","-h"}, {"--unattended","-u" } };
foreach (var toMigrate in MigratedCLI)
{
var pos = Array.IndexOf(args,toMigrate.Key);
if (pos == -1)
continue;
args[pos] = toMigrate.Value;
}

if (args.Contains("--debug"))
Debugger.Launch();

app.Configure(config =>
{
config.SetApplicationName(ThisAssembly.Project.ToolCommandName);
if (Environment.GetEnvironmentVariables().Contains("NO_COLOR"))
if (ConsoleMode.NoColor)
config.Settings.HelpProviderStyles = null;
});

if (args.Contains("--version"))
{
AnsiConsole.MarkupLine($"{ThisAssembly.Project.ToolCommandName} version [lime]{ThisAssembly.Project.Version}[/] ({ThisAssembly.Project.BuildDate})");
AnsiConsole.MarkupLine($"[link]{ThisAssembly.Git.Url}/releases/tag/{ThisAssembly.Project.BuildRef}[/]");

if (ConsoleMode.BatchMode)
AnsiConsole.WriteLine($"{ThisAssembly.Git.Url}/releases/tag/{ThisAssembly.Project.BuildRef}");
else
AnsiConsole.MarkupLine($"[link]{ThisAssembly.Git.Url}/releases/tag/{ThisAssembly.Project.BuildRef}[/]");

foreach (var message in await CheckUpdates(args))
AnsiConsole.MarkupLine(message);
Expand All @@ -51,7 +80,7 @@

static async Task<string[]> CheckUpdates(string[] args)
{
if (args.Contains("-u") || args.Contains("--unattended"))
if (ConsoleMode.NoUpdates)
return [];

var providers = Repository.Provider.GetCoreV3();
Expand Down
112 changes: 91 additions & 21 deletions src/dotnet-trx/TrxCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public class TrxSettings : CommandSettings
[CommandOption("--version")]
public bool Version { get; init; }

[Description("Do not output VT/ANSI formatting codes or progress status messages (emojis are still shown). No update check.")]
[CommandOption("--batch")]
public bool BatchMode { get; set; }

[Description("No update check")]
[CommandOption("-u|--no-updates")]
public bool NoUpdates { get; set; }

[Description("Optional base directory for *.trx files discovery. Defaults to current directory.")]
[CommandOption("-p|--path")]
public string? Path { get; set; }
Expand Down Expand Up @@ -109,6 +117,18 @@ public bool Skipped
[DefaultValue(true)]
public bool GitHubSummary { get; set; } = true;

[Description("Only use the most recently modified TRX file")]
[CommandOption("--only-latest")]
public bool OnlyLatest { get; set; }

[Description("Specify specific TRX files to include (reads all files until the next -- flag)")]
[CommandOption("--only-files")]
public string[]? OnlyFiles { get; set; }

[Description("Specify one or more tests (until next -- flag) that are the only tests included in the output report rather than all the tests")]
[CommandOption("--only-tests")]
public string[]? OnlyTests { get; set; }

public override ValidationResult Validate()
{
// Validate, normalize and default path.
Expand All @@ -124,13 +144,16 @@ public override ValidationResult Validate()

public override int Execute(CommandContext context, TrxSettings settings)
{
// these should have already been handled but just incase
ConsoleMode.BatchMode |= settings.BatchMode;
ConsoleMode.NoUpdates |= settings.NoUpdates;

if (Environment.GetEnvironmentVariable("RUNNER_DEBUG") == "1")
WriteLine(JsonSerializer.Serialize(new { settings }, indentedJson));

// We get this validated by the settings, so it's always non-null.
var path = settings.Path!;
var search = settings.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var testIds = new HashSet<string>();
var passed = 0;
var failed = 0;
var skipped = 0;
Expand All @@ -148,27 +171,17 @@ public override int Execute(CommandContext context, TrxSettings settings)

var results = new List<XElement>();

Status().Start("Discovering test results...", ctx =>
if (ConsoleMode.BatchMode)
{
results = DiscoverResults(settings, path, search);
}
else
{
// 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))
Status().Start("Discovering test results...", ctx =>
{
ctx.Status($"Discovering test results in {Path.GetFileName(trx).EscapeMarkup()}...");
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))
results.Add(result);
}
}

ctx.Status("Sorting tests by name...");
results.Sort(new Comparison<XElement>((x, y) => x.Attribute("testName")!.Value.CompareTo(y.Attribute("testName")!.Value)));
});
results = DiscoverResults(settings, path, search, s => ctx.Status = s);
});
}

foreach (var result in results)
{
Expand Down Expand Up @@ -488,7 +501,10 @@ void WriteError(string baseDir, List<Failed> failures, XElement result, StringBu
stackTrace.ReplaceLineEndings(),
relative, int.Parse(pos));

cli.AppendLine(line.Replace(file, $"[link={file}][steelblue1_1]{relative}[/][/]"));
if (ConsoleMode.BatchMode)
cli.AppendLine(line.Replace(file, relative.EscapeMarkup()));
else
cli.AppendLine(line.Replace(file, $"[link={file}][steelblue1_1]{relative.EscapeMarkup()}[/][/]"));
// TODO: can we render a useful link in comment details?
details.AppendLineIndented(line.Replace(file, relative), "> ");
}
Expand Down Expand Up @@ -546,4 +562,58 @@ record Summary(int Passed, int Failed, int Skipped, TimeSpan Duration)
}

record Failed(string Test, string Title, string Message, string File, int Line);

static List<XElement> DiscoverResults(TrxSettings settings, string path, SearchOption search, Action<string>? status = null)
{
var testIds = new HashSet<string>();
var results = new List<XElement>();

IEnumerable<string> files;

if (settings.OnlyFiles is { Length: > 0 } onlyFiles)
{
files = onlyFiles.Select(f =>
{
var p1 = System.IO.Path.Combine(path, f);
if (File.Exists(p1)) return p1;
var p2 = System.IO.Path.Combine(Directory.GetCurrentDirectory(), f);
if (File.Exists(p2)) return p2;
return f;
});
}
else
{
files = Directory.EnumerateFiles(path, "*.trx", search);
}

files = files.OrderByDescending(File.GetLastWriteTime);

if (settings.OnlyLatest)
files = files.Take(1);

// Process from newest files to oldest so that newest result we find (by test id) is the one we keep
foreach (var trx in files)
{
status?.Invoke($"Discovering test results in {Path.GetFileName(trx).EscapeMarkup()}...");
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"))
{
if (settings.OnlyTests is { Length: > 0 } onlyTests &&
result.Attribute("testName")?.Value is string name &&
!onlyTests.Contains(name))
continue;

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))
results.Add(result);
}
}

status?.Invoke("Sorting tests by name...");
results.Sort(new Comparison<XElement>((x, y) => x.Attribute("testName")!.Value.CompareTo(y.Attribute("testName")!.Value)));
return results;
}
}
2 changes: 1 addition & 1 deletion src/dotnet-trx/dotnet-trx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

<Target Name="RenderHelp" AfterTargets="Build" Condition="$(NoHelp) != 'true' and $(DesignTimeBuild) != 'true'">
<WriteLinesToFile Lines="```shell" Overwrite="true" Encoding="UTF-8" File="help.md" />
<Exec Command="dotnet run --no-build --no-launch-profile -- --help &gt;&gt; help.md" StdOutEncoding="UTF-8" EnvironmentVariables="NO_COLOR=true" />
<Exec Command="dotnet run --no-build --no-launch-profile -- --batch --help &gt;&gt; help.md" StdOutEncoding="UTF-8" />
<WriteLinesToFile Lines="```" Overwrite="false" Encoding="UTF-8" File="help.md" />
</Target>

Expand Down
35 changes: 18 additions & 17 deletions src/dotnet-trx/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ USAGE:
trx [OPTIONS]

OPTIONS:
DEFAULT
-h, --help Prints help information
--version Prints version information
-p, --path Optional base directory for *.trx files
discovery. Defaults to current directory
-o, --output Include test output
-r, --recursive True Recursively search for *.trx files
-v, --verbosity Quiet Output display verbosity:
- quiet: only failed tests are displayed
- normal: failed and skipped tests are
displayed
- verbose: failed, skipped and passed tests
are displayed
--no-exit-code Do not return a -1 exit code on test
failures
--gh-comment True Report as GitHub PR comment
--gh-summary True Report as GitHub step summary
DEFAULT
-h, --help Prints help information
--version Prints version information
--batch Do not output VT/ANSI formatting codes or progress status messages (emojis are still shown). No update check
-u, --no-updates No update check
-p, --path Optional base directory for *.trx files discovery. Defaults to current directory
-o, --output Include test output
-r, --recursive True Recursively search for *.trx files
-v, --verbosity Quiet Output display verbosity:
- quiet: only failed tests are displayed
- normal: failed and skipped tests are displayed
- verbose: failed, skipped and passed tests are displayed
--no-exit-code Do not return a -1 exit code on test failures
--gh-comment True Report as GitHub PR comment
--gh-summary True Report as GitHub step summary
--only-latest Only use the most recently modified TRX file
--only-files Specify specific TRX files to include (reads all files until the next -- flag)
--only-tests Specify one or more tests (until next -- flag) that are the only tests included in the output report rather than all the tests
```