Skip to content

Commit

Permalink
Refactor target-specific ApplicationEntryPoint subclasses to have non…
Browse files Browse the repository at this point in the history
…-xunit-specific base classes (#791)
  • Loading branch information
jkoritzinsky committed Dec 9, 2021
1 parent e212a7b commit 5898930
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

#nullable enable
namespace Microsoft.DotNet.XHarness.TestRunners.Common;

/// <summary>
/// Implementors should provide a text writter than will be used to
/// write the logging of the tests that are executed.
/// </summary>
public abstract class AndroidApplicationEntryPointBase : ApplicationEntryPoint
{
public abstract TextWriter? Logger { get; }

/// <summary>
/// Implementors should provide a full path in which the final
/// results of the test run will be written. This property must not
/// return null.
/// </summary>
public abstract string TestsResultsFinalPath { get; }

public override async Task RunAsync()
{
var options = ApplicationOptions.Current;
using TextWriter? resultsFileMaybe = options.EnableXml ? File.CreateText(TestsResultsFinalPath) : null;
await InternalRunAsync(options, Logger, resultsFileMaybe);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

#nullable enable
namespace Microsoft.DotNet.XHarness.TestRunners.Common;

/// <summary>
Expand Down Expand Up @@ -39,32 +39,32 @@ public abstract class ApplicationEntryPoint
/// <summary>
/// Event raised when the test run has started.
/// </summary>
public event EventHandler TestsStarted;
public event EventHandler? TestsStarted;

/// <summary>
/// Event raised when the test run has completed.
/// </summary>
public event EventHandler<TestRunResult> TestsCompleted;
public event EventHandler<TestRunResult>? TestsCompleted;

// fwd the events from the runner so that clients can connect to them

/// <summary>
/// Event raised when a test has started.
/// </summary>
public event EventHandler<string> TestStarted;
public event EventHandler<string>? TestStarted;

/// <summary>
/// Event raised when a test has completed or has been skipped.
/// </summary>
public event EventHandler<(string TestName, TestResult TestResult)> TestCompleted;
public event EventHandler<(string TestName, TestResult TestResult)>? TestCompleted;

protected abstract int? MaxParallelThreads { get; }

/// <summary>
/// Must be implemented and return a class that returns the information
/// of a device. It can return null.
/// </summary>
protected abstract IDevice Device { get; }
protected abstract IDevice? Device { get; }

/// <summary>
/// Returns the IEnumerable with the asseblies that contain the tests
Expand Down Expand Up @@ -97,15 +97,15 @@ public abstract class ApplicationEntryPoint
/// * the 'KLASS:' prefix can be used to ignore all the tests in a class.
/// * the 'Platform32:' prefix can be used to ignore a test but only in a 32b arch device.
/// </summary>
protected virtual string IgnoreFilesDirectory => null;
protected virtual string? IgnoreFilesDirectory => null;

/// <summary>
/// Returns the path to a file that contains the list of traits to ignore in the following format:
/// traitname=traitvalue
///
/// The default implementation will return null and therefore no traits will be ignored.
/// </summary>
protected virtual string IgnoredTraitsFilePath => null;
protected virtual string? IgnoredTraitsFilePath => null;

/// <summary>
/// States if the skipped tests should be logged. Helpful to determine why some tests are executed and others
Expand All @@ -129,9 +129,9 @@ public abstract class ApplicationEntryPoint
/// </summary>
public MinimumLogLevel MinimumLogLevel { get; set; } = MinimumLogLevel.Info;

private void OnTestStarted(object sender, string testName) => TestStarted?.Invoke(sender, testName);
private void OnTestStarted(object? sender, string testName) => TestStarted?.Invoke(sender, testName);

private void OnTestCompleted(object sender, (string TestName, TestResult Testresult) result) => TestCompleted?.Invoke(sender, result);
private void OnTestCompleted(object? sender, (string TestName, TestResult Testresult) result) => TestCompleted?.Invoke(sender, result);

private async Task<List<string>> GetIgnoredCategories()
{
Expand Down Expand Up @@ -173,29 +173,26 @@ internal static void ConfigureRunnerFilters(TestRunner runner, ApplicationOption
}
}

internal static string WriteResults(TestRunner runner, ApplicationOptions options, LogWriter logger, TextWriter writer)
private static void WriteResults(TestRunner runner, ApplicationOptions options, LogWriter logger, TextWriter writer)
{
if (options.EnableXml && writer == null)
{
throw new ArgumentNullException(nameof(writer));
}

string resultsFilePath = null;
if (options.EnableXml)
{
runner.WriteResultsToFile(writer, options.XmlVersion);
logger.Info("Xml file was written to the provided writer.");
}
else
{
resultsFilePath = runner.WriteResultsToFile(options.XmlVersion);
string resultsFilePath = runner.WriteResultsToFile(options.XmlVersion);
logger.Info($"XML results can be found in '{resultsFilePath}'");
}

return resultsFilePath;
}

protected async Task<TestRunner> InternalRunAsync(LogWriter logger)
private async Task<TestRunner> InternalRunAsync(LogWriter logger)
{
logger.MinimumLogLevel = MinimumLogLevel;
var runner = GetTestRunner(logger);
Expand All @@ -220,4 +217,30 @@ protected async Task<TestRunner> InternalRunAsync(LogWriter logger)
TestsCompleted?.Invoke(this, result);
return runner;
}

protected async Task<TestRunner> InternalRunAsync(ApplicationOptions options, TextWriter? loggerWriter, TextWriter? resultsFile)
{
// we generate the logs in two different ways depending if the generate xml flag was
// provided. If it was, we will write the xml file to the provided writer if present, else
// we will write the normal console output using the LogWriter
var logger = (loggerWriter == null || options.EnableXml) ? new LogWriter(Device) : new LogWriter(Device, loggerWriter);
logger.MinimumLogLevel = MinimumLogLevel.Info;
var runner = await InternalRunAsync(logger);

WriteResults(runner, options, logger, resultsFile ?? Console.Out);

logger.Info($"Tests run: {runner.TotalTests} Passed: {runner.PassedTests} Inconclusive: {runner.InconclusiveTests} Failed: {runner.FailedTests} Ignored: {runner.FilteredTests} Skipped: {runner.SkippedTests}");

if (options.AppEndTag != null)
{
logger.Info(options.AppEndTag);
}

if (options.TerminateAfterExecution)
{
TerminateWithSuccess();
}

return runner;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal enum XmlMode
Wrapped = 1,
}

internal class ApplicationOptions
public class ApplicationOptions
{
public static ApplicationOptions Current = new();
private readonly List<string> _singleMethodFilters = new();
Expand Down
11 changes: 7 additions & 4 deletions src/Microsoft.DotNet.XHarness.TestRunners.Common/LogWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.IO;

#nullable enable
namespace Microsoft.DotNet.XHarness.TestRunners.Common;

public class LogWriter
{
private readonly TextWriter _writer;
private readonly IDevice _device;
private readonly IDevice? _device;

public MinimumLogLevel MinimumLogLevel { get; set; } = MinimumLogLevel.Info;

public LogWriter() : this(null, Console.Out) { }

public LogWriter(IDevice device) : this(device, Console.Out) { }
public LogWriter(IDevice? device) : this(device, Console.Out) { }

public LogWriter(TextWriter w) : this(null, w) { }

public LogWriter(IDevice device, TextWriter writer)
public LogWriter(IDevice? device, TextWriter writer)
{
_writer = writer ?? Console.Out;
_device = device;
if (_device != null) // we just write the header if we do have the device info
if (_device is not null) // we just write the header if we do have the device info
{
InitLogging();
}
Expand All @@ -35,6 +37,7 @@ public LogWriter(IDevice device, TextWriter writer)

public void InitLogging()
{
Debug.Assert(_device is not null);
// print some useful info
_writer.WriteLine("[Runner executing:\t{0}]", "Run everything");
_writer.WriteLine("[{0}:\t{1} v{2}]", _device.Model, _device.SystemName, _device.SystemVersion);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

#nullable enable
namespace Microsoft.DotNet.XHarness.TestRunners.Common;

public abstract class WasmApplicationEntryPointBase : ApplicationEntryPoint
{
protected override int? MaxParallelThreads => 1;

protected override IDevice? Device => null;

public override async Task RunAsync()
{
var options = ApplicationOptions.Current;
var runner = await InternalRunAsync(options, null, Console.Out);

LastRunHadFailedTests = runner.FailedTests != 0;
}

public bool LastRunHadFailedTests { get; set; }

protected override void TerminateWithSuccess() => Environment.Exit(0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

#nullable enable
namespace Microsoft.DotNet.XHarness.TestRunners.Common;

public abstract class iOSApplicationEntryPointBase : ApplicationEntryPoint
{
public override async Task RunAsync()
{
var options = ApplicationOptions.Current;
TcpTextWriter? writer;

try
{
writer = options.UseTunnel
? TcpTextWriter.InitializeWithTunnelConnection(options.HostPort)
: TcpTextWriter.InitializeWithDirectConnection(options.HostName, options.HostPort);
}
catch (Exception ex)
{
Console.WriteLine("Failed to initialize TCP writer. Continuing on console." + Environment.NewLine + ex);
writer = null; // null means we will fall back to console output
}

using (writer)
{
var logger = (writer == null || options.EnableXml) ? new LogWriter(Device) : new LogWriter(Device, writer);
logger.MinimumLogLevel = MinimumLogLevel.Info;

await InternalRunAsync(options, writer, writer);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,8 @@

namespace Microsoft.DotNet.XHarness.TestRunners.Xunit;

public abstract class AndroidApplicationEntryPoint : ApplicationEntryPoint
public abstract class AndroidApplicationEntryPoint : AndroidApplicationEntryPointBase
{
/// <summary>
/// Implementors should provide a text writter than will be used to
/// write the logging of the tests that are executed.
/// </summary>
public abstract TextWriter? Logger { get; }

/// <summary>
/// Implementors should provide a full path in which the final
/// results of the test run will be written. This property must not
/// return null.
/// </summary>
public abstract string TestsResultsFinalPath { get; }

protected override bool IsXunit => true;

protected override TestRunner GetTestRunner(LogWriter logWriter)
Expand All @@ -34,45 +21,4 @@ protected override TestRunner GetTestRunner(LogWriter logWriter)
ConfigureRunnerFilters(runner, ApplicationOptions.Current);
return runner;
}

public override async Task RunAsync()
{
var options = ApplicationOptions.Current;
// we generate the logs in two different ways depending if the generate xml flag was
// provided. If it was, we will write the xml file to the tcp writer if present, else
// we will write the normal console output using the LogWriter
var logger = (Logger == null || options.EnableXml) ? new LogWriter(Device) : new LogWriter(Device, Logger);
logger.MinimumLogLevel = MinimumLogLevel.Info;

var runner = await InternalRunAsync(logger);
if (options.EnableXml)
{
if (TestsResultsFinalPath == null)
{
throw new InvalidOperationException("Tests results final path cannot be null.");
}

using (var stream = File.Create(TestsResultsFinalPath))
using (var writer = new StreamWriter(stream))
{
WriteResults(runner, options, logger, writer);
}
}
else
{
WriteResults(runner, options, logger, Console.Out);
}

logger.Info($"Tests run: {runner.TotalTests} Passed: {runner.PassedTests} Inconclusive: {runner.InconclusiveTests} Failed: {runner.FailedTests} Ignored: {runner.FilteredTests}");

if (options.AppEndTag != null)
{
logger.Info(options.AppEndTag);
}

if (options.TerminateAfterExecution)
{
TerminateWithSuccess();
}
}
}
Loading

0 comments on commit 5898930

Please sign in to comment.