Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate from vstest to Microsoft.Testing.Platform #43

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
36 changes: 18 additions & 18 deletions GitHubActionsTestLogger.Tests/AnnotationSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public void I_can_use_the_logger_to_produce_annotations_for_failed_tests()
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
TestLoggerOptions.Default
TestReporterOptions.Default
);

// Act
Expand Down Expand Up @@ -54,9 +54,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_source_info
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
TestLoggerOptions.Default
TestReporterOptions.Default
);

// Act
Expand Down Expand Up @@ -104,9 +104,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_source_info
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
TestLoggerOptions.Default
TestReporterOptions.Default
);

// Act
Expand Down Expand Up @@ -155,9 +155,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_the_test_na
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
new TestLoggerOptions
new TestReporterOptions
{
AnnotationTitleFormat = "<@test>",
AnnotationMessageFormat = "[@test]",
Expand All @@ -184,9 +184,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_test_traits
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
new TestLoggerOptions
new TestReporterOptions
{
AnnotationTitleFormat = "<@traits.Category -> @test>",
AnnotationMessageFormat = "[@traits.Category -> @test]",
Expand Down Expand Up @@ -218,9 +218,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_the_error_m
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
new TestLoggerOptions
new TestReporterOptions
{
AnnotationTitleFormat = "<@test: @error>",
AnnotationMessageFormat = "[@test: @error]",
Expand Down Expand Up @@ -251,9 +251,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_the_error_s
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
new TestLoggerOptions
new TestReporterOptions
{
AnnotationTitleFormat = "<@test: @trace>",
AnnotationMessageFormat = "[@test: @trace]",
Expand Down Expand Up @@ -284,9 +284,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_the_target_
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
new TestLoggerOptions
new TestReporterOptions
{
AnnotationTitleFormat = "<@test (@framework)>",
AnnotationMessageFormat = "[@test (@framework)]",
Expand Down Expand Up @@ -319,9 +319,9 @@ public void I_can_use_the_logger_to_produce_annotations_that_include_line_breaks
// Arrange
using var commandWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(commandWriter, TextWriter.Null),
new TestLoggerOptions { AnnotationMessageFormat = "foo\\nbar" }
new TestReporterOptions { AnnotationMessageFormat = "foo\\nbar" }
);

// Act
Expand Down
4 changes: 2 additions & 2 deletions GitHubActionsTestLogger.Tests/InitializationSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void I_can_use_the_logger_with_the_default_configuration()

// Assert
logger.Context.Should().NotBeNull();
logger.Context?.Options.Should().BeEquivalentTo(TestLoggerOptions.Default);
logger.Context?.Options.Should().BeEquivalentTo(TestReporterOptions.Default);
}

[Fact]
Expand All @@ -35,7 +35,7 @@ public void I_can_use_the_logger_with_an_empty_configuration()

// Assert
logger.Context.Should().NotBeNull();
logger.Context?.Options.Should().BeEquivalentTo(TestLoggerOptions.Default);
logger.Context?.Options.Should().BeEquivalentTo(TestReporterOptions.Default);
}

[Fact]
Expand Down
24 changes: 12 additions & 12 deletions GitHubActionsTestLogger.Tests/SummarySpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public void I_can_use_the_logger_to_produce_a_summary_that_includes_the_test_sui
// Arrange
using var summaryWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(TextWriter.Null, summaryWriter),
TestLoggerOptions.Default
TestReporterOptions.Default
);

// Act
Expand All @@ -38,9 +38,9 @@ public void I_can_use_the_logger_to_produce_a_summary_that_includes_the_list_of_
// Arrange
using var summaryWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(TextWriter.Null, summaryWriter),
new TestLoggerOptions { SummaryIncludePassedTests = true }
new TestReporterOptions { SummaryIncludePassedTests = true }
);

// Act
Expand Down Expand Up @@ -85,9 +85,9 @@ public void I_can_use_the_logger_to_produce_a_summary_that_includes_the_list_of_
// Arrange
using var summaryWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(TextWriter.Null, summaryWriter),
TestLoggerOptions.Default
TestReporterOptions.Default
);

// Act
Expand Down Expand Up @@ -144,9 +144,9 @@ public void I_can_use_the_logger_to_produce_a_summary_that_includes_the_list_of_
// Arrange
using var summaryWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(TextWriter.Null, summaryWriter),
new TestLoggerOptions { SummaryIncludeSkippedTests = true }
new TestReporterOptions { SummaryIncludeSkippedTests = true }
);

// Act
Expand Down Expand Up @@ -191,9 +191,9 @@ public void I_can_use_the_logger_to_produce_a_summary_that_includes_empty_test_s
// Arrange
using var summaryWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(TextWriter.Null, summaryWriter),
TestLoggerOptions.Default
TestReporterOptions.Default
);

// Act
Expand All @@ -212,9 +212,9 @@ public void I_can_use_the_logger_to_produce_a_summary_that_does_not_include_empt
// Arrange
using var summaryWriter = new StringWriter();

var context = new TestLoggerContext(
var context = new TestReporterContext(
new GitHubWorkflow(TextWriter.Null, summaryWriter),
new TestLoggerOptions { SummaryIncludeNotFoundTests = false }
new TestReporterOptions { SummaryIncludeNotFoundTests = false }
);

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace GitHubActionsTestLogger.Tests.Utils.Extensions;
internal static class TestLoggerContextExtensions
{
public static void SimulateTestRun(
this TestLoggerContext context,
this TestReporterContext context,
string testSuiteFilePath,
string targetFrameworkName,
params IReadOnlyList<TestResult> testResults
Expand Down Expand Up @@ -65,13 +65,13 @@ params IReadOnlyList<TestResult> testResults
}

public static void SimulateTestRun(
this TestLoggerContext context,
this TestReporterContext context,
string testSuiteName,
params IReadOnlyList<TestResult> testResults
) => context.SimulateTestRun(testSuiteName, "FakeTargetFramework", testResults);

public static void SimulateTestRun(
this TestLoggerContext context,
this TestReporterContext context,
params IReadOnlyList<TestResult> testResults
) => context.SimulateTestRun("FakeTests.dll", testResults);
}
89 changes: 89 additions & 0 deletions GitHubActionsTestLogger/CliOptionsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Extensions.CommandLine;

namespace GitHubActionsTestLogger;

internal sealed class CliOptionsProvider(GitHubTestReporterExtension extension) : ICommandLineOptionsProvider
{
public const string ReportGitHubOption = "report-github";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of const string, we could have an initialized static readonly CommandLineOption here, so it's easier to refer to it later, for example in ValidateOptionArgumentsAsync (wouldn't need to compare names then).

public const string ReportGitHubSummaryOption = "report-github-summary";
public const string ReportGitHubTitleOption = "report-github-title";
public const string ReportGitHubMessageOption = "report-github-message";

public static class ReportGitHubSummaryArguments
{
public const string IncludePassedTests = "includePassedTests";
public const string IncludeSkippedTests = "includeSkippedTests";
public const string IncludeNotFoundTests = "includeNotFoundTests";
}

public string Uid => extension.Uid;
public string Version => extension.Version;
public string DisplayName => extension.DisplayName;
public string Description => extension.Description;

public IReadOnlyCollection<CommandLineOption> GetCommandLineOptions() =>
[
new(ReportGitHubOption, "Reports test run information to GitHub Actions", ArgumentArity.Zero, isHidden: false),
// TODO: Do you prefer multiple zero argument options or a single option with argument?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it were me, I'd probably prefer individual ones as then when consuming via MSBuild it would be conceptually one-to-one when building up the arguments to pass through with TestingPlatformCommandLineArguments so conditionally deciding what to turn on/off is simpler.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So with the current implementation, would this option be used like so?

... --report-github-summary includePassedTests includeSkippedTests

In that case, I feel like it'd be more user friendly and idiomatic to have a separate option for each flag:

--report-github-summary-include-passed-tests
--report-github-summary-include-skipped-tests

It's kind of a mouthful though, which is why I opted for the "nested" convention in the current version in the first place.

new(ReportGitHubSummaryOption, "Defines the information to include in the summary", ArgumentArity.OneOrMore, isHidden: false),
new(ReportGitHubTitleOption, "Defines the annotation title format used for reporting test failures", ArgumentArity.ExactlyOne, isHidden: false),
new(ReportGitHubMessageOption, "Defines the annotation message format used for reporting test failures", ArgumentArity.ExactlyOne, isHidden: false),
];

public Task<bool> IsEnabledAsync() => extension.IsEnabledAsync();

// 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)
{
// We just want to validate that the sub options are set only if the main option is set.
if (commandLineOptions.IsOptionSet(ReportGitHubOption))
{
return ValidationResult.ValidTask;
}

if (commandLineOptions.IsOptionSet(ReportGitHubSummaryOption)
|| commandLineOptions.IsOptionSet(ReportGitHubTitleOption)
|| commandLineOptions.IsOptionSet(ReportGitHubMessageOption))
Comment on lines +50 to +52
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to refer to the list of available options via GetCommandLineOptions() here so as to avoid having multiple sources of truths?

{
return ValidationResult.InvalidTask("The options 'report-github-summary', 'report-github-title', and 'report-github-message' can only be used if 'report-github' is set.");
}

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.
public Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments)
{
if (commandOption.Name == ReportGitHubSummaryOption)
{
if (arguments.Length > 3)
{
return ValidationResult.InvalidTask($"The option '{ReportGitHubSummaryOption}' can have at most 3 arguments.");
}

if (arguments.Distinct().Count() != arguments.Length)
{
return ValidationResult.InvalidTask($"The option '{ReportGitHubSummaryOption}' cannot have duplicate arguments.");
}

for (int i = 0; i < arguments.Length; i++)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be simplified by just having a HashSet with the known argument names in it and then returning the error if the Except of the two is non-zero?

{
if (arguments[i] != ReportGitHubSummaryArguments.IncludePassedTests
&& arguments[i] != ReportGitHubSummaryArguments.IncludeSkippedTests
&& arguments[i] != ReportGitHubSummaryArguments.IncludeNotFoundTests)
{
return ValidationResult.InvalidTask($"The option '{ReportGitHubSummaryOption}' can only have the arguments '{ReportGitHubSummaryArguments.IncludePassedTests}', '{ReportGitHubSummaryArguments.IncludeSkippedTests}', and '{ReportGitHubSummaryArguments.IncludeNotFoundTests}'.");
}
}
}

return ValidationResult.ValidTask;
}
}
4 changes: 2 additions & 2 deletions GitHubActionsTestLogger/GitHubActionsTestLogger.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net9.0</TargetFrameworks>
Expand All @@ -24,7 +24,7 @@

<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.30.6" PrivateAssets="all" />
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="17.12.0" />
<PackageReference Include="Microsoft.Testing.Platform" Version="1.6.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<PackageReference Include="Microsoft.Testing.Platform" Version="1.6.2" />
<PackageReference Include="Microsoft.Testing.Platform" Version="1.6.2" />

<PackageReference Include="PolyShim" Version="1.15.0" PrivateAssets="all" />
<PackageReference Include="RazorBlade" Version="0.8.0" ExcludeAssets="compile;runtime" PrivateAssets="all" />
Expand Down
24 changes: 24 additions & 0 deletions GitHubActionsTestLogger/GitHubReportExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.Testing.Platform.Builder;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Services;

namespace GitHubActionsTestLogger;

public static class GitHubReportExtensions
{
/// <summary>
/// Adds GitHub report support to the Testing Platform Builder.
/// </summary>
/// <param name="testApplicationBuilder">The test application builder.</param>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <param name="testApplicationBuilder">The test application builder.</param>

I typically don't include docs for parameters if their usage is obvious.

public static void AddGitHubReportProvider(this ITestApplicationBuilder testApplicationBuilder)
{
var extension = new GitHubTestReporterExtension();

var compositeExtension = new CompositeExtensionFactory<GitHubTestReporter>(serviceProvider =>
new GitHubTestReporter(extension, serviceProvider.GetCommandLineOptions()));
testApplicationBuilder.TestHost.AddDataConsumer(compositeExtension);
testApplicationBuilder.TestHost.AddTestSessionLifetimeHandle(compositeExtension);

testApplicationBuilder.CommandLine.AddProvider(() => new CliOptionsProvider(extension));
}
}
45 changes: 45 additions & 0 deletions GitHubActionsTestLogger/GitHubTestReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Extensions.TestHost;
using Microsoft.Testing.Platform.TestHost;

namespace GitHubActionsTestLogger;

/// <summary>
/// A Microsoft.Testing.Platform extension that reports test run information to GitHub Actions.
/// </summary>
internal sealed class GitHubTestReporter(GitHubTestReporterExtension extension, ICommandLineOptions commandLineOptions) : IDataConsumer, ITestSessionLifetimeHandler
{
private readonly TestReporterContext _context = new(GitHubWorkflow.Default, TestReporterOptions.Resolve(commandLineOptions));

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

public string Uid => extension.Uid;
public string Version => extension.Version;
public string DisplayName => extension.DisplayName;
public string Description => extension.Description;

public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken)
{
_context.HandleTestResult((TestNodeUpdateMessage)value);
return Task.CompletedTask;
}

public Task<bool> IsEnabledAsync() => extension.IsEnabledAsync();

public Task OnTestSessionFinishingAsync(SessionUid sessionUid, CancellationToken cancellationToken)
{
_context.HandleTestRunComplete();
return Task.CompletedTask;
}

public Task OnTestSessionStartingAsync(SessionUid sessionUid, CancellationToken cancellationToken)
=> Task.CompletedTask;
}
Loading
Loading