Commit 85893e8b authored by Michael Herndon's avatar Michael Herndon

WIP: AB#1 finish up importin XUnit runners

parent 00fffc58
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": "Nexus",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Apps/ConsoleTemplate/bin/Debug/netcoreapp3.0/ConsoleTemplate.exe",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"launchBrowser": {
"enabled": true,
"args": "${auto-detect-url}",
"windows": {
"command": "cmd.exe",
"args": "/C start ${auto-detect-url}"
},
"osx": {
"command": "open"
},
"linux": {
"command": "xdg-open"
}
},
"env": {
"DOTNET_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
}]
}
\ No newline at end of file
......@@ -54,6 +54,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{62C1F2BF-BB1
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NerdyMishka.Api.Users", "src\Api\Users\NerdyMishka.Api.Users.csproj", "{CBFD856A-D652-4726-B3D5-C285265E9D2D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{0C17BD1C-2336-4E6E-8088-B7D8BF97597E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mettle.Xunit.Tests", "test\Testing\Mettle.Xunit.Tests\Mettle.Xunit.Tests.csproj", "{4A75359E-A1DF-4679-909A-5CE2BC30AE10}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -120,6 +124,10 @@ Global
{CBFD856A-D652-4726-B3D5-C285265E9D2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBFD856A-D652-4726-B3D5-C285265E9D2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBFD856A-D652-4726-B3D5-C285265E9D2D}.Release|Any CPU.Build.0 = Release|Any CPU
{4A75359E-A1DF-4679-909A-5CE2BC30AE10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A75359E-A1DF-4679-909A-5CE2BC30AE10}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A75359E-A1DF-4679-909A-5CE2BC30AE10}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A75359E-A1DF-4679-909A-5CE2BC30AE10}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -149,6 +157,8 @@ Global
{70060B63-843B-4DC7-A53F-92AE4B66878B} = {6444BF0F-2EED-404A-AC00-CC756F4F4ED0}
{62C1F2BF-BB1B-4EDE-BDB0-839F5B41FE5F} = {DB042499-08A3-4C34-9231-1372043915F2}
{CBFD856A-D652-4726-B3D5-C285265E9D2D} = {62C1F2BF-BB1B-4EDE-BDB0-839F5B41FE5F}
{0C17BD1C-2336-4E6E-8088-B7D8BF97597E} = {DA32C16D-7292-4EEC-90C5-59EC66CC018D}
{4A75359E-A1DF-4679-909A-5CE2BC30AE10} = {0C17BD1C-2336-4E6E-8088-B7D8BF97597E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {16666A01-3CC0-4111-8A58-9A6CA34CD76C}
......
using System;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
public static class ReflectionAbstractionExtensions
{
/// <summary>
/// Creates an instance of the test class for the given test case. Sends the <see cref="ITestClassConstructionStarting"/>
/// and <see cref="ITestClassConstructionFinished"/> messages as appropriate.
/// </summary>
/// <param name="test">The test</param>
/// <param name="testClassType">The type of the test class</param>
/// <param name="constructorArguments">The constructor arguments for the test class</param>
/// <param name="messageBus">The message bus used to send the test messages</param>
/// <param name="timer">The timer used to measure the time taken for construction</param>
/// <param name="cancellationTokenSource">The cancellation token source</param>
/// <returns></returns>
public static object CreateTestClassWithServices(this ITest test,
Type testClassType,
object[] constructorArguments,
IMessageBus messageBus,
ExecutionTimer timer,
CancellationTokenSource cancellationTokenSource)
{
object testClass = null;
if (!messageBus.QueueMessage(new TestClassConstructionStarting(test)))
cancellationTokenSource.Cancel();
else
{
try
{
if (!cancellationTokenSource.IsCancellationRequested)
timer.Aggregate(() => {
testClass = Activator.CreateInstance(testClassType, constructorArguments);
});
}
finally
{
if (!messageBus.QueueMessage(new TestClassConstructionFinished(test)))
cancellationTokenSource.Cancel();
}
}
return testClass;
}
}
}
\ No newline at end of file
using System;
using Xunit.Sdk;
namespace Mettle
{
[XunitTestCaseDiscoverer("Mettle.Xunit.Sdk.TestCaseDiscoverer", "Mettle.Xunit")]
public class FunctionalAttribute : TestCaseAttribute
{
public FunctionalAttribute()
{
this.Tags = this.Tags ?? "functional";
}
}
}
\ No newline at end of file
using System;
namespace Mettle
{
public interface IServiceProviderFactory
{
IServiceProvider CreateProvider();
}
}
\ No newline at end of file
using Xunit.Abstractions;
using Microsoft.Extensions.Logging;
namespace Mettle
{
public interface ITestLogger
{
ITestOutputHelper Helper { get; }
ILogger Log { get; }
}
}
\ No newline at end of file
......@@ -3,12 +3,13 @@ using Xunit.Sdk;
namespace Mettle
{
public class IntegrationAttribute : TagAttribute
{
[XunitTestCaseDiscoverer("Mettle.Xunit.Sdk.TestCaseDiscoverer", "Mettle.Xunit")]
public class IntegrationAttribute : TestCaseAttribute
{
public IntegrationAttribute():base("tag", "integration")
public IntegrationAttribute()
{
this.Tags = this.Tags ?? "integration";
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle
{
/// <summary>
/// The test runner for xUnit.net v2 tests.
/// </summary>
public class MettleTestRunner : TestRunner<IXunitTestCase>
{
readonly IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes;
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestRunner"/> class.
/// </summary>
/// <param name="test">The test that this invocation belongs to.</param>
/// <param name="messageBus">The message bus to report run status to.</param>
/// <param name="testClass">The test class that the test method belongs to.</param>
/// <param name="constructorArguments">The arguments to be passed to the test class constructor.</param>
/// <param name="testMethod">The test method that will be invoked.</param>
/// <param name="testMethodArguments">The arguments to be passed to the test method.</param>
/// <param name="skipReason">The skip reason, if the test is to be skipped.</param>
/// <param name="beforeAfterAttributes">The list of <see cref="BeforeAfterTestAttribute"/>s for this test.</param>
/// <param name="aggregator">The exception aggregator used to run code and collect exceptions.</param>
/// <param name="cancellationTokenSource">The task cancellation token source, used to cancel the test run.</param>
public MettleTestRunner(ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
string skipReason,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, aggregator, cancellationTokenSource)
{
this.beforeAfterAttributes = beforeAfterAttributes;
}
/// <summary>
/// Gets the list of <see cref="BeforeAfterTestAttribute"/>s for this test.
/// </summary>
protected IReadOnlyList<BeforeAfterTestAttribute> BeforeAfterAttributes
=> beforeAfterAttributes;
/// <inheritdoc/>
protected override async Task<Tuple<decimal, string>> InvokeTestAsync(ExceptionAggregator aggregator)
{
var output = string.Empty;
ITestLogger testLogger = null;
TestOutputHelper testOutputHelper = null;
foreach(object obj in this.TestCase.TestMethodArguments)
{
testLogger = obj as ITestLogger;
if(testLogger != null)
break;
}
if(testLogger == null)
{
foreach (object obj in ConstructorArguments)
{
testLogger = obj as ITestLogger;
if(testLogger != null)
break;
testOutputHelper = obj as TestOutputHelper;
if (testOutputHelper != null)
break;
}
}
if(testLogger != null)
testOutputHelper = (TestOutputHelper)testLogger.Helper;
if (testOutputHelper != null)
testOutputHelper.Initialize(MessageBus, Test);
var executionTime = await InvokeTestMethodAsync(aggregator);
if (testOutputHelper != null)
{
output = testOutputHelper.Output;
testOutputHelper.Uninitialize();
}
return Tuple.Create(executionTime, output);
}
/// <summary>
/// Override this method to invoke the test method.
/// </summary>
/// <param name="aggregator">The exception aggregator used to run code and collect exceptions.</param>
/// <returns>Returns the execution time (in seconds) spent running the test method.</returns>
protected virtual Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
=> new XunitTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource).RunAsync();
}
}
\ No newline at end of file
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
public interface IMettleTestCase : IXunitTestCase
{
Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
IServiceProvider serviceProvider);
}
}
\ No newline at end of file
......@@ -22,6 +22,9 @@ namespace Mettle.Xunit.Sdk
SynchronizationContext originalSyncContext;
MaxConcurrencySyncContext syncContext;
public IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestAssemblyRunner"/> class.
/// </summary>
......@@ -84,6 +87,22 @@ namespace Mettle.Xunit.Sdk
if (initialized)
return;
var serviceProviderFactoryAttribute = TestAssembly.Assembly
.GetCustomAttributes(typeof(ServiceProviderFactoryAttribute))
.SingleOrDefault();
if(serviceProviderFactoryAttribute != null)
{
var factoryType = serviceProviderFactoryAttribute.GetNamedArgument<Type>("FactoryType");
// TODO: consider diagnostic message if the type exists but not the interface.
if(factoryType != null && factoryType.GetInterface("IServiceProviderFactory") != null)
{
var serviceFactory = (IServiceProviderFactory)Activator.CreateInstance(factoryType);
this.ServiceProvider = serviceFactory.CreateProvider();
}
}
collectionBehaviorAttribute = TestAssembly.Assembly.GetCustomAttributes(typeof(CollectionBehaviorAttribute)).SingleOrDefault();
if (collectionBehaviorAttribute != null)
{
......@@ -233,7 +252,16 @@ namespace Mettle.Xunit.Sdk
/// <inheritdoc/>
protected override Task<RunSummary> RunTestCollectionAsync(IMessageBus messageBus, ITestCollection testCollection, IEnumerable<IXunitTestCase> testCases, CancellationTokenSource cancellationTokenSource)
=> new XunitTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync();
=> new MettleTestCollectionRunner(
testCollection,
testCases,
DiagnosticMessageSink,
messageBus,
TestCaseOrderer,
new ExceptionAggregator(Aggregator),
cancellationTokenSource,
this.ServiceProvider)
.RunAsync();
[SecuritySafeCritical]
static void SetSynchronizationContext(SynchronizationContext context)
......
......@@ -17,13 +17,15 @@ namespace Mettle.Xunit.Sdk
/// both <see cref="FactAttribute"/> and <see cref="TheoryAttribute"/>.
/// </summary>
[DebuggerDisplay(@"\{ class = {TestMethod.TestClass.Class.Name}, method = {TestMethod.Method.Name}, display = {DisplayName}, skip = {SkipReason} \}")]
public class MettleTestCase : TestMethodTestCase, IXunitTestCase
[Serializable]
public class MettleTestCase : TestMethodTestCase, IXunitTestCase, IMettleTestCase
{
static ConcurrentDictionary<string, IEnumerable<IAttributeInfo>> assemblyTraitAttributeCache = new ConcurrentDictionary<string, IEnumerable<IAttributeInfo>>(StringComparer.OrdinalIgnoreCase);
static ConcurrentDictionary<string, IEnumerable<IAttributeInfo>> typeTraitAttributeCache = new ConcurrentDictionary<string, IEnumerable<IAttributeInfo>>(StringComparer.OrdinalIgnoreCase);
int timeout;
private IServiceProvider serviceProvider;
/// <summary/>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
......@@ -45,8 +47,13 @@ namespace Mettle.Xunit.Sdk
public MettleTestCase(IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
ITestMethod testMethod,
IServiceProvider serviceProvider,
object[] testMethodArguments = null)
: this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, testMethodArguments) { }
: this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, serviceProvider, testMethodArguments)
{
}
internal protected new TestMethodDisplay DefaultMethodDisplay { get; }
......@@ -65,12 +72,14 @@ namespace Mettle.Xunit.Sdk
TestMethodDisplay defaultMethodDisplay,
TestMethodDisplayOptions defaultMethodDisplayOptions,
ITestMethod testMethod,
IServiceProvider serviceProvider,
object[] testMethodArguments = null)
: base(defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments)
{
DefaultMethodDisplay = defaultMethodDisplay;
DefaultMethodDisplayOptions = defaultMethodDisplayOptions;
DiagnosticMessageSink = diagnosticMessageSink;
this.serviceProvider = serviceProvider;
}
/// <summary>
......@@ -122,18 +131,63 @@ namespace Mettle.Xunit.Sdk
protected virtual int GetTimeout(IAttributeInfo factAttribute)
=> factAttribute.GetNamedArgument<int>("Timeout");
/// <inheritdoc/>
protected override void Initialize()
{
base.Initialize();
var factAttribute = TestMethod.Method.GetCustomAttributes(typeof(FactAttribute)).First();
var serviceProviderFactoryAttribute = TestMethod.Method
.GetCustomAttributes(typeof(ServiceProviderFactoryAttribute))
.SingleOrDefault();
if(serviceProviderFactoryAttribute != null)
{
var factoryType = serviceProviderFactoryAttribute.GetNamedArgument<Type>("FactoryType");
// TODO: consider diagnostic message if the type exists but not the interface.
if(factoryType != null && factoryType.GetInterface("IServiceProviderFactory") != null)
{
var serviceFactory = (IServiceProviderFactory)Activator.CreateInstance(factoryType);
this.serviceProvider = serviceFactory.CreateProvider();
}
}
var factAttribute = TestMethod.Method.GetCustomAttributes(typeof(FactAttribute)).FirstOrDefault();
if(factAttribute == null)
factAttribute =TestMethod.Method.GetCustomAttributes(typeof(TestCaseAttribute)).FirstOrDefault();
var baseDisplayName = factAttribute.GetNamedArgument<string>("DisplayName") ?? BaseDisplayName;
DisplayName = GetDisplayName(factAttribute, baseDisplayName);
SkipReason = GetSkipReason(factAttribute);
Timeout = GetTimeout(factAttribute);
string id = factAttribute.GetNamedArgument<string>("Id");
string ticket= factAttribute.GetNamedArgument<string>("Ticket");
string tags = factAttribute.GetNamedArgument<string>("Tags");
if(!string.IsNullOrWhiteSpace(id))
Traits.Add("id", id);
if(!string.IsNullOrWhiteSpace(ticket))
Traits.Add("ticket", ticket);
if(!string.IsNullOrWhiteSpace(tags))
{
var set = tags.Split(';').Select(o => o.Trim()).ToArray();
foreach(var tag in set)
{
Traits.Add("Category", tag);
Traits.Add("tag", tag);
}
}
foreach (var traitAttribute in GetTraitAttributesData(TestMethod))
{
var discovererAttribute = traitAttribute.GetCustomAttributes(typeof(TraitDiscovererAttribute)).FirstOrDefault();
......@@ -162,13 +216,21 @@ namespace Mettle.Xunit.Sdk
.Concat(GetCachedTraitAttributes(testMethod.TestClass.Class));
}
public virtual Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
IServiceProvider serviceProvider)
=> new MettleTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource, this.serviceProvider ?? serviceProvider).RunAsync();
/// <inheritdoc/>
public virtual Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
=> new XunitTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource).RunAsync();
=> new MettleTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource, this.serviceProvider).RunAsync();
/// <inheritdoc/>
public override void Serialize(IXunitSerializationInfo data)
......@@ -185,5 +247,7 @@ namespace Mettle.Xunit.Sdk
Timeout = data.GetValue<int>("Timeout");
}
}
}
\ No newline at end of file
......@@ -16,6 +16,8 @@ namespace Mettle.Xunit.Sdk
{
List<BeforeAfterTestAttribute> beforeAfterAttributes;
private IServiceProvider serviceProvider;
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestCaseRunner"/> class.
/// </summary>
......@@ -34,7 +36,8 @@ namespace Mettle.Xunit.Sdk
object[] testMethodArguments,
IMessageBus messageBus,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
CancellationTokenSource cancellationTokenSource,
IServiceProvider serviceProvider)
: base(testCase, messageBus, aggregator, cancellationTokenSource)
{
DisplayName = displayName;
......@@ -44,10 +47,27 @@ namespace Mettle.Xunit.Sdk
TestClass = TestCase.TestMethod.TestClass.Class.ToRuntimeType();
TestMethod = TestCase.Method.ToRuntimeMethod();
this.serviceProvider = serviceProvider;
var parameters = TestMethod.GetParameters();
var parameterTypes = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
parameterTypes[i] = parameters[i].ParameterType;
testMethodArguments = testMethodArguments ?? new object[0];
if(parameters.Length != testMethodArguments.Length)
{
var methodArgs = new object[parameters.Length];
Array.Copy(testMethodArguments, methodArgs, testMethodArguments.Length);
for(var i = 0; i < parameters.Length; i++)
{
var obj = methodArgs[i];
if(obj == null)
obj = this.serviceProvider?.GetService(parameters[i].ParameterType);
methodArgs[i] = obj;
}
}
TestMethodArguments = Reflector.ConvertArguments(testMethodArguments, parameterTypes);
}
......@@ -105,7 +125,7 @@ namespace Mettle.Xunit.Sdk
/// <summary>
/// Creates the test runner used to run the given test.
/// </summary>
protected virtual XunitTestRunner CreateTestRunner(ITest test,
protected virtual MettleTestRunner CreateTestRunner(ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
......@@ -115,8 +135,8 @@ namespace Mettle.Xunit.Sdk
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
=> new XunitTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments,
skipReason, beforeAfterAttributes, new ExceptionAggregator(aggregator), cancellationTokenSource);
=> new MettleTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments,
skipReason, beforeAfterAttributes, new ExceptionAggregator(aggregator), cancellationTokenSource, this.serviceProvider);
/// <summary>
/// Gets the list of <see cref="BeforeAfterTestAttribute"/> attributes that apply to this test case.
......
......@@ -16,6 +16,7 @@ namespace Mettle.Xunit.Sdk
public class MettleTestClassRunner : TestClassRunner<IXunitTestCase>
{
readonly IDictionary<Type, object> collectionFixtureMappings;
private IServiceProvider serviceProvider;
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestClassRunner"/> class.
......@@ -37,10 +38,12 @@ namespace Mettle.Xunit.Sdk
ITestCaseOrderer testCaseOrderer,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource,
IDictionary<Type, object> collectionFixtureMappings)
IDictionary<Type, object> collectionFixtureMappings,
IServiceProvider serviceProvider)
: base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
{
this.collectionFixtureMappings = collectionFixtureMappings;
this.serviceProvider = serviceProvider;
}
/// <summary>
......@@ -79,9 +82,14 @@ namespace Mettle.Xunit.Sdk
object arg;
if (p.ParameterType == typeof(IMessageSink))
arg = DiagnosticMessageSink;
else
if (!collectionFixtureMappings.TryGetValue(p.ParameterType, out arg))
else if(!collectionFixtureMappings.TryGetValue(p.ParameterType, out arg))
arg = this.serviceProvider?.GetService(p.ParameterType);
if(arg == null)
missingParameters.Add(p);
return arg;
}).ToArray();
......@@ -167,7 +175,8 @@ namespace Mettle.Xunit.Sdk
/// <inheritdoc/>
protected override Task<RunSummary> RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, object[] constructorArguments)
=> new XunitTestMethodRunner(testMethod, Class, method, testCases, DiagnosticMessageSink, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource, constructorArguments).RunAsync();
=> new MettleTestMethodRunner(
testMethod, Class, method, testCases, DiagnosticMessageSink, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource, constructorArguments, this.serviceProvider).RunAsync();
/// <inheritdoc/>
protected override ConstructorInfo SelectTestClassConstructor()
......@@ -187,12 +196,20 @@ namespace Mettle.Xunit.Sdk
/// <inheritdoc/>
protected override bool TryGetConstructorArgument(ConstructorInfo constructor, int index, ParameterInfo parameter, out object argumentValue)
{
if (parameter.ParameterType == typeof(ITestOutputHelper))
object svc = this.serviceProvider?.GetService(parameter.ParameterType);
if(svc != null)
{
argumentValue = svc;
return true;
}
if(parameter.ParameterType == typeof(ITestOutputHelper))
{
argumentValue = new TestOutputHelper();
return true;
}
return ClassFixtureMappings.TryGetValue(parameter.ParameterType, out argumentValue)
|| collectionFixtureMappings.TryGetValue(parameter.ParameterType, out argumentValue);
}
......
......@@ -13,8 +13,12 @@ namespace Mettle.Xunit.Sdk
/// <summary>
/// The test collection runner for xUnit.net v2 tests.
/// </summary>
public class XunitTestCollectionRunner : TestCollectionRunner<IXunitTestCase>
public class MettleTestCollectionRunner : TestCollectionRunner<IXunitTestCase>
{
private IServiceProvider serviceProvider;
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestCollectionRunner"/> class.
/// </summary>
......@@ -25,16 +29,18 @@ namespace Mettle.Xunit.Sdk
/// <param name="testCaseOrderer">The test case orderer that will be used to decide how to order the test.</param>
/// <param name="aggregator">The exception aggregator used to run code and collect exceptions.</param>
/// <param name="cancellationTokenSource">The task cancellation token source, used to cancel the test run.</param>
public XunitTestCollectionRunner(ITestCollection testCollection,
public MettleTestCollectionRunner(ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ITestCaseOrderer testCaseOrderer,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
CancellationTokenSource cancellationTokenSource,
IServiceProvider serviceProvider)
: base(testCollection, testCases, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
{
this.DiagnosticMessageSink = diagnosticMessageSink;
this.serviceProvider = serviceProvider;
}
/// <summary>
......@@ -91,8 +97,12 @@ namespace Mettle.Xunit.Sdk