Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
38febc9
Migrate from `vstest` to `Microsoft.Testing.Platform` (#43)
Evangelink Oct 11, 2025
3fe7b21
Format
Tyrrrz Oct 11, 2025
a2b5bd6
wip
Tyrrrz Oct 11, 2025
3cd65bd
Format
Tyrrrz Oct 11, 2025
e42c6a6
asd
Tyrrrz Oct 11, 2025
ad398b2
asd
Tyrrrz Oct 11, 2025
d1c23fa
asd
Tyrrrz Oct 11, 2025
09cb57f
asd
Tyrrrz Oct 12, 2025
4e8b5df
Retrofit support for VSTest via an abstraction layer
Tyrrrz Oct 14, 2025
0fa368d
asd
Tyrrrz Oct 14, 2025
ee603f1
asd
Tyrrrz Oct 14, 2025
0ff54ee
asd
Tyrrrz Oct 14, 2025
b0959a1
asd
Tyrrrz Oct 14, 2025
2a04ac6
asd
Tyrrrz Oct 14, 2025
b375377
asd
Tyrrrz Oct 14, 2025
c3baa5e
asd
Tyrrrz Oct 14, 2025
7e73608
asd
Tyrrrz Oct 14, 2025
1ec70f8
asd
Tyrrrz Oct 14, 2025
baa088e
asd
Tyrrrz Oct 14, 2025
e202765
asd
Tyrrrz Oct 14, 2025
05fc4e5
asd
Tyrrrz Oct 15, 2025
3896f9a
asd
Tyrrrz Oct 15, 2025
4555bd6
asd
Tyrrrz Oct 15, 2025
d34bd30
asd
Tyrrrz Oct 15, 2025
86232b4
asd
Tyrrrz Oct 17, 2025
d418098
asd
Tyrrrz Oct 17, 2025
d680c00
asd
Tyrrrz Oct 17, 2025
1084ae2
MTP demo works
Tyrrrz Oct 22, 2025
af06bef
asd
Tyrrrz Oct 25, 2025
23601ff
asd
Tyrrrz Nov 1, 2025
ed6a032
asd
Tyrrrz Nov 1, 2025
201dcae
asd
Tyrrrz Nov 3, 2025
460ef7b
Upgrade to MTP v2
Tyrrrz Nov 11, 2025
77f8f51
asd
Tyrrrz Nov 11, 2025
12e4e65
Merge
Tyrrrz Nov 11, 2025
ab562cc
asd
Tyrrrz Nov 11, 2025
c367f57
Use peer deps
Tyrrrz Nov 12, 2025
f4582b9
tests
Tyrrrz Nov 14, 2025
80ff683
Async github interactions
Tyrrrz Nov 14, 2025
65a9c48
Add MTP tests
Tyrrrz Nov 17, 2025
3c4f5d5
asd
Tyrrrz Nov 17, 2025
29a8325
asd
Tyrrrz Nov 17, 2025
b6a8c73
Migrate demo projects to MSTest
Tyrrrz Nov 17, 2025
b25749c
asd
Tyrrrz Nov 17, 2025
423a9d9
asd
Tyrrrz Nov 17, 2025
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
Prev Previous commit
Next Next commit
asd
  • Loading branch information
Tyrrrz committed Oct 17, 2025
commit 86232b48277b0cf47edcaa7a1682fef16e271cf9
9 changes: 1 addition & 8 deletions GitHubActionsTestLogger.Demo.Mtp/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@
To run the demo tests, use the following command:

```console
$ dotnet test -p:IsTestProject=true
```

In order for the reporter to work, it needs to think it's running within a GitHub Actions environment.
You can simulate this locally by setting the `GITHUB_ACTIONS` environment variable to `true`:

```powershell
$env:GITHUB_ACTIONS="true"
$ dotnet test -p:IsTestProject=true --github-actions
```

To produce a test summary, provide the output file path by setting the `GITHUB_STEP_SUMMARY` environment variable:
Expand Down
8 changes: 4 additions & 4 deletions GitHubActionsTestLogger.Tests/VsTestInitializationSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ public void I_can_use_the_logger_with_a_custom_configuration()
var events = new FakeTestLoggerEvents();
var parameters = new Dictionary<string, string?>
{
["annotations-title-format"] = "TitleFormat",
["annotations-message-format"] = "MessageFormat",
["annotations-title"] = "TitleFormat",
["annotations-message"] = "MessageFormat",
["summary-allow-empty"] = "true",
["summary-include-passed-tests"] = "true",
["summary-include-skipped-tests"] = "true",
["summary-include-passed"] = "true",
["summary-include-skipped"] = "true",
};

// Act
Expand Down
25 changes: 0 additions & 25 deletions GitHubActionsTestLogger/MtpExtensionBase.cs

This file was deleted.

44 changes: 26 additions & 18 deletions GitHubActionsTestLogger/MtpLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using GitHubActionsTestLogger.GitHub;
Expand All @@ -15,44 +16,51 @@

namespace GitHubActionsTestLogger;

internal class MtpLogger(ICommandLineOptions commandLineOptions)
: MtpExtensionBase,
// This is the extension point to subscribe to data messages published to the platform.
// The type should then be registered as a data consumer in the test host.
IDataConsumer,
// This is the extension point to subscribe to test session lifetime events.
// The type should then be registered as a test session lifetime handler in the test host.
ITestSessionLifetimeHandler
internal class MtpLogger : IDataConsumer, ITestSessionLifetimeHandler
{
private readonly TestReportingContext _context = new(
GitHubWorkflow.Default,
TestReportingOptions.Resolve(commandLineOptions)
);

// MTP does not provide a built-in way to measure test run duration, so we do it manually
private readonly bool _isEnabled;
private readonly TestReportingContext _context;
private readonly Stopwatch _stopwatch = new();

private TestRunStartInfo? _testRunStartInfo;
private List<TestResult> _testResults = [];

public MtpLogger(ICommandLineOptions commandLineOptions)
{
var options = MtpLoggerOptionsProvider.Resolve(out var isEnabled, commandLineOptions);
_isEnabled = isEnabled || GitHubEnvironment.IsRunningInActions;
_context = new TestReportingContext(GitHubWorkflow.Default, options);
}

public string Uid => "GitHubActionsTestLogger";

public string Version { get; } = typeof(MtpLogger).Assembly.TryGetVersionString() ?? "1.0.0";

public string DisplayName => "GitHub Actions Test Logger";

public string Description => "Reports test results to GitHub Actions";

public Type[] DataTypesConsumed { get; } = [typeof(TestNodeUpdateMessage)];

public Task<bool> IsEnabledAsync() => Task.FromResult(_isEnabled);

public Task OnTestSessionStartingAsync(
SessionUid sessionUid,
CancellationToken cancellationToken
)
{
_stopwatch.Restart();

// MTP test host runs within the test assembly, so we can infer the test suite name
// and target framework directly from the assembly metadata.
var testAssembly = Assembly.GetEntryAssembly();

var testRunStartInfo = new TestRunStartInfo(
sessionUid.Value,
// MTP test host runs within the test assembly, so we can infer the test suite name
// and target framework directly from the assembly metadata.
testAssembly?.GetName().Name,
testAssembly
?.GetCustomAttribute<System.Runtime.Versioning.TargetFrameworkAttribute>()
?.FrameworkName
?.FrameworkName ?? RuntimeInformation.FrameworkDescription
);

_testRunStartInfo = testRunStartInfo;
Expand Down Expand Up @@ -135,7 +143,7 @@ CancellationToken cancellationToken
)
{
if (_testRunStartInfo is null)
throw new InvalidOperationException("The test run has not been started.");
throw new InvalidOperationException("Test run has not been started.");

_stopwatch.Stop();

Expand Down
162 changes: 124 additions & 38 deletions GitHubActionsTestLogger/MtpLoggerOptionsProvider.cs
Original file line number Diff line number Diff line change
@@ -1,57 +1,110 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GitHubActionsTestLogger.GitHub;
using GitHubActionsTestLogger.Reporting;
using GitHubActionsTestLogger.Utils.Extensions;
using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Extensions.CommandLine;

namespace GitHubActionsTestLogger;

internal class MtpLoggerOptionsProvider : MtpExtensionBase, ICommandLineOptionsProvider
internal partial class MtpLoggerOptionsProvider : ICommandLineOptionsProvider
{
private static readonly CommandLineOption IsEnabledOption = new(
"github-actions",
$"Enables the GitHub Actions test reporter. Default is '{GitHubEnvironment.IsRunningInActions}'.",
ArgumentArity.Zero,
false
);

private static readonly CommandLineOption AnnotationsTitleFormatOption = new(
"github-actions-annotations-title-format",
"Specifies the title format for GitHub Annotations. See documentation for available replacement tokens. "
+ $"Default is '{TestReportingOptions.Default.AnnotationTitleFormat}'.",
ArgumentArity.ExactlyOne,
false
);

private static readonly CommandLineOption AnnotationsMessageFormatOption = new(
"github-actions-annotations-message-format",
"Specifies the message format for GitHub Annotations. See documentation for available replacement tokens. "
+ $"Default is '{TestReportingOptions.Default.AnnotationMessageFormat}'.",
ArgumentArity.ExactlyOne,
false
);

private static readonly CommandLineOption SummaryAllowEmptyOption = new(
"github-actions-summary-allow-empty",
"Whether to produce a summary entry for test runs where no tests were executed. "
+ $"Default is '{TestReportingOptions.Default.SummaryAllowEmpty}'.",
ArgumentArity.ZeroOrOne,
false
);

private static readonly CommandLineOption SummaryIncludePassedTestsOption = new(
"github-actions-summary-include-passed-tests",
"Whether to include passed tests (in addition to failed tests) in the GitHub Actions summary. "
+ $"Default is '{TestReportingOptions.Default.SummaryIncludePassedTests}'.",
ArgumentArity.ZeroOrOne,
false
);

private static readonly CommandLineOption SummaryIncludeSkippedTestsOption = new(
"github-actions-summary-include-skipped-tests",
"Whether to include skipped tests (in addition to failed tests) in the GitHub Actions summary. "
+ $"Default is '{TestReportingOptions.Default.SummaryIncludeSkippedTests}'.",
ArgumentArity.ZeroOrOne,
false
);

public string Uid => "GitHubActionsTestLogger/OptionsProvider";

public string Version { get; } =
typeof(MtpLoggerOptionsProvider).Assembly.TryGetVersionString() ?? "1.0.0";

public string DisplayName => "GitHub Actions Test Logger Options Provider";

public string Description => "Provides command line options for the GitHub Actions Test Logger";

public Task<bool> IsEnabledAsync() => Task.FromResult(true);

public IReadOnlyCollection<CommandLineOption> GetCommandLineOptions() =>
[
new(
TestReportingOptions.CommandLineNames.AnnotationsTitleFormat,
$"Specifies the title format for GitHub Annotations. See documentation for available replacement tokens. "
+ $"Default is '{TestReportingOptions.Default.AnnotationTitleFormat}'.",
ArgumentArity.ExactlyOne,
false
),
new(
TestReportingOptions.CommandLineNames.AnnotationsMessageFormat,
$"Specifies the message format for GitHub Annotations. See documentation for available replacement tokens. "
+ $"Default is '{TestReportingOptions.Default.AnnotationMessageFormat}'.",
ArgumentArity.ExactlyOne,
false
),
new(
TestReportingOptions.CommandLineNames.SummaryIncludePassedTests,
$"Whether to include passed tests (in addition to failed tests) in the GitHub Actions summary. "
+ $"Default is '{TestReportingOptions.Default.SummaryIncludePassedTests}'.",
ArgumentArity.ZeroOrOne,
false
),
new(
TestReportingOptions.CommandLineNames.SummaryIncludeSkippedTests,
$"Whether to include skipped tests (in addition to failed tests) in the GitHub Actions summary. "
+ $"Default is '{TestReportingOptions.Default.SummaryIncludeSkippedTests}'.",
ArgumentArity.ZeroOrOne,
false
),
new(
TestReportingOptions.CommandLineNames.SummaryAllowEmpty,
$"Whether to produce a summary entry for test runs where no tests were executed. "
+ $"Default is '{TestReportingOptions.Default.SummaryAllowEmpty}'.",
ArgumentArity.ZeroOrOne,
false
),
IsEnabledOption,
AnnotationsTitleFormatOption,
AnnotationsMessageFormatOption,
SummaryAllowEmptyOption,
SummaryIncludePassedTestsOption,
SummaryIncludeSkippedTestsOption,
];

// This method is called once after all options are parsed and is used to validate the combination of options.
public Task<ValidationResult> ValidateCommandLineOptionsAsync(
ICommandLineOptions commandLineOptions
) => ValidationResult.ValidTask;
)
{
if (!commandLineOptions.IsOptionSet(IsEnabledOption.Name))
{
foreach (
var optionName in GetCommandLineOptions()
.Select(o => o.Name)
.Where(n => !string.Equals(n, IsEnabledOption.Name, StringComparison.Ordinal))
)
{
if (commandLineOptions.IsOptionSet(optionName))
{
return ValidationResult.InvalidTask(
$"Option '--{optionName}' can only be used when '--{IsEnabledOption.Name}' is also specified."
);
}
}
}

return ValidationResult.ValidTask;
}

// This method is called once per option declared and is used to validate the arguments of the given option.
// The arity of the option is checked before this method is called.
Expand All @@ -60,3 +113,36 @@ public Task<ValidationResult> ValidateOptionArgumentsAsync(
string[] arguments
) => ValidationResult.ValidTask;
}

internal partial class MtpLoggerOptionsProvider
{
public static TestReportingOptions Resolve(
out bool isEnabled,
ICommandLineOptions commandLineOptions
)
{
isEnabled = commandLineOptions.IsOptionSet(IsEnabledOption.Name);

return new TestReportingOptions
{
AnnotationTitleFormat =
commandLineOptions.GetOptionArgumentOrDefault(AnnotationsTitleFormatOption.Name)
?? TestReportingOptions.Default.AnnotationTitleFormat,
AnnotationMessageFormat =
commandLineOptions.GetOptionArgumentOrDefault(AnnotationsMessageFormatOption.Name)
?? TestReportingOptions.Default.AnnotationMessageFormat,
SummaryAllowEmpty =
commandLineOptions
.GetOptionArgumentOrDefault(SummaryAllowEmptyOption.Name, "true")
?.Pipe(bool.Parse) ?? TestReportingOptions.Default.SummaryAllowEmpty,
SummaryIncludePassedTests =
commandLineOptions
.GetOptionArgumentOrDefault(SummaryIncludePassedTestsOption.Name, "false")
?.Pipe(bool.Parse) ?? TestReportingOptions.Default.SummaryIncludePassedTests,
SummaryIncludeSkippedTests =
commandLineOptions
.GetOptionArgumentOrDefault(SummaryIncludeSkippedTestsOption.Name, "true")
?.Pipe(bool.Parse) ?? TestReportingOptions.Default.SummaryIncludeSkippedTests,
};
}
}
Loading
Loading