Commit 1d2f211b authored by Michael Herndon's avatar Michael Herndon

WORK: mettle. importing xunit runners to extend and override

parent 46359fe8
using System.Collections.Generic;
using System.Linq;
namespace Xunit.Sdk
{
static class CollectionExtensions
{
public static List<T> CastOrToList<T>(this IEnumerable<T> source)
{
return source as List<T> ?? source.ToList();
}
public static T[] CastOrToArray<T>(this IEnumerable<T> source)
{
return source as T[] ?? source.ToArray();
}
}
}
using System;
using System.Reflection;
static class ExceptionExtensions
{
const string RETHROW_MARKER = "$$RethrowMarker$$";
/// <summary>
/// Rethrows an exception object without losing the existing stack trace information
/// </summary>
/// <param name="ex">The exception to re-throw.</param>
/// <remarks>
/// For more information on this technique, see
/// http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2004/03/03/8353.aspx.
/// The remote_stack_trace string is here to support Mono.
/// </remarks>
public static void RethrowWithNoStackTraceLoss(this Exception ex)
{
#if NET35
FieldInfo remoteStackTraceString =
typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic) ??
typeof(Exception).GetField("remote_stack_trace", BindingFlags.Instance | BindingFlags.NonPublic);
remoteStackTraceString.SetValue(ex, ex.StackTrace + RETHROW_MARKER);
throw ex;
#else
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw();
#endif
}
/// <summary>
/// Unwraps an exception to remove any wrappers, like <see cref="TargetInvocationException"/>.
/// </summary>
/// <param name="ex">The exception to unwrap.</param>
/// <returns>The unwrapped exception.</returns>
public static Exception Unwrap(this Exception ex)
{
while (true)
{
if (!(ex is TargetInvocationException tiex))
return ex;
ex = tiex.InnerException;
}
}
}
using System;
using System.Linq;
using System.Reflection;
/// <summary>
/// Methods which help bridge and contain the differences between Type and TypeInfo.
/// </summary>
static class NewReflectionExtensions
{
// New methods
public static Assembly GetAssembly(this Type type)
{
#if NET35
return type.Assembly;
#else
return type.GetTypeInfo().Assembly;
#endif
}
public static Attribute[] GetCustomAttributes(this Assembly assembly)
{
#if NET35
return assembly.GetCustomAttributes(false).Cast<Attribute>().ToArray();
#else
return assembly.GetCustomAttributes<Attribute>().ToArray();
#endif
}
public static bool IsEnum(this Type type)
{
#if NET35
return type.IsEnum;
#else
return type.GetTypeInfo().IsEnum;
#endif
}
public static bool IsFromLocalAssembly(this Type type)
{
var assemblyName = type.GetAssembly().GetName().Name;
try
{
#if NET35
Assembly.Load(assemblyName);
#else
Assembly.Load(new AssemblyName { Name = assemblyName });
#endif
return true;
}
catch
{
return false;
}
}
public static bool IsGenericType(this Type type)
{
#if NET35
return type.IsGenericType;
#else
return type.GetTypeInfo().IsGenericType;
#endif
}
public static bool IsGenericTypeDefinition(this Type type)
{
#if NET35
return type.IsGenericTypeDefinition;
#else
return type.GetTypeInfo().IsGenericTypeDefinition;
#endif
}
public static bool IsNullableEnum(this Type type)
{
return type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(Nullable<>) && type.GetGenericArguments()[0].IsEnum();
}
public static bool IsValueType(this Type type)
{
#if NET35
return type.IsValueType;
#else
return type.GetTypeInfo().IsValueType;
#endif
}
public static Type UnwrapNullable(this Type type)
{
if (!type.IsGenericType())
return type;
if (type.GetGenericTypeDefinition() != typeof(Nullable<>))
return type;
return type.GetGenericArguments()[0];
}
// Existing methods
#if !NET35
public static Type[] GetGenericArguments(this Type type)
{
return type.GetTypeInfo().GenericTypeArguments;
}
public static Type[] GetInterfaces(this Type type)
{
return type.GetTypeInfo().ImplementedInterfaces.ToArray();
}
public static bool IsAssignableFrom(this Type type, Type otherType)
{
return type.GetTypeInfo().IsAssignableFrom(otherType.GetTypeInfo());
}
#endif
}
This diff is collapsed.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading;
namespace Mettle
namespace Mettle.Xunit.Sdk
{
/// <summary>
/// Default implementation of <see cref="IXunitTestCase"/> for xUnit v2 that supports tests decorated with
/// 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
{
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;
public class UnitTestCase : Xunit.Sdk.TestMethodTestCase, IXunitTestCase
{
private static ConcurrentDictionary<string, IEnumerable<IAttributeInfo>> assemblyTraitAttributeCache =
new ConcurrentDictionary<string, IEnumerable<IAttributeInfo>>(StringComparer.OrdinalIgnoreCase);
private static ConcurrentDictionary<string, IEnumerable<IAttributeInfo>> typeTraitAttributeCache =
new ConcurrentDictionary<string, IEnumerable<IAttributeInfo>>(StringComparer.OrdinalIgnoreCase);
/// <summary/>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public MettleTestCase()
{
// No way for us to get access to the message sink on the execution deserialization path, but that should
// be okay, because we assume all the issues were reported during discovery.
DiagnosticMessageSink = new NullMessageSink();
}
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestCase"/> class.
/// </summary>
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages</param>
/// <param name="defaultMethodDisplay">Default method display to use (when not customized).</param>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
[Obsolete("Please call the constructor which takes TestMethodDisplayOptions")]
public MettleTestCase(IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
ITestMethod testMethod,
object[] testMethodArguments = null)
: this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, testMethodArguments) { }
private int timeout;
internal protected new TestMethodDisplay DefaultMethodDisplay { get; }
public UnitTestCase(IMessageSink diagnosticMessageSink,
internal protected new TestMethodDisplayOptions DefaultMethodDisplayOptions { get; }
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestCase"/> class.
/// </summary>
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages</param>
/// <param name="defaultMethodDisplay">Default method display to use (when not customized).</param>
/// <param name="defaultMethodDisplayOptions">Default method display options to use (when not customized).</param>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
public MettleTestCase(IMessageSink diagnosticMessageSink,
TestMethodDisplay defaultMethodDisplay,
TestMethodDisplayOptions defaultMethodDisplayOptions,
ITestMethod testMethod,
object[] testMethodArguments = null)
: base(defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments)
{
DiagnosticMessageSink = diagnosticMessageSink;
DefaultMethodDisplay = defaultMethodDisplay;
DefaultMethodDisplayOptions = defaultMethodDisplayOptions;
DiagnosticMessageSink = diagnosticMessageSink;
}
/// <summary>
/// Gets the message sink used to report <see cref="IDiagnosticMessage"/> messages.
/// </summary>
protected IMessageSink DiagnosticMessageSink { get; }
/// <inheritdoc/>
......@@ -42,12 +84,12 @@ namespace Mettle
get
{
EnsureInitialized();
return this.timeout;
return timeout;
}
protected set
{
EnsureInitialized();
this.timeout = value;
timeout = value;
}
}
......@@ -85,12 +127,12 @@ namespace Mettle
{
base.Initialize();
var factAttribute = TestMethod.Method.GetCustomAttributes(typeof(TestCaseAttribute)).First();
var factAttribute = TestMethod.Method.GetCustomAttributes(typeof(FactAttribute)).First();
var baseDisplayName = factAttribute.GetNamedArgument<string>("DisplayName") ?? BaseDisplayName;
this.DisplayName = this.GetDisplayName(factAttribute, baseDisplayName);
this.SkipReason = this.GetSkipReason(factAttribute);
this.Timeout = this.GetTimeout(factAttribute);
DisplayName = GetDisplayName(factAttribute, baseDisplayName);
SkipReason = GetSkipReason(factAttribute);
Timeout = GetTimeout(factAttribute);
foreach (var traitAttribute in GetTraitAttributesData(TestMethod))
{
......@@ -104,19 +146,14 @@ namespace Mettle
}
else
DiagnosticMessageSink.OnMessage(new DiagnosticMessage($"Trait attribute on '{DisplayName}' did not have [TraitDiscoverer]"));
}
}
private static IEnumerable<IAttributeInfo> GetCachedTraitAttributes(IAssemblyInfo assembly)
{
return assemblyTraitAttributeCache.GetOrAdd(assembly.Name,
(k) => assembly.GetCustomAttributes(typeof(ITraitAttribute)) );
}
static IEnumerable<IAttributeInfo> GetCachedTraitAttributes(IAssemblyInfo assembly)
=> assemblyTraitAttributeCache.GetOrAdd(assembly.Name, () => assembly.GetCustomAttributes(typeof(ITraitAttribute)));
static IEnumerable<IAttributeInfo> GetCachedTraitAttributes(ITypeInfo type)
=> typeTraitAttributeCache.GetOrAdd(type.Name, (k) => type.GetCustomAttributes(typeof(ITraitAttribute)));
=> typeTraitAttributeCache.GetOrAdd(type.Name, () => type.GetCustomAttributes(typeof(ITraitAttribute)));
static IEnumerable<IAttributeInfo> GetTraitAttributesData(ITestMethod testMethod)
{
......
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
public class MettleTestFramework : TestFramework
{
/// <summary>
/// Initializes a new instance of the <see cref="MettleTestFramework"/> class.
/// </summary>
/// <param name="messageSink">The message sink used to send diagnostic messages</param>
public MettleTestFramework(IMessageSink messageSink) : base(messageSink) { }
/// <inheritdoc/>
protected override ITestFrameworkDiscoverer CreateDiscoverer(IAssemblyInfo assemblyInfo)
{
return new XunitTestFrameworkDiscoverer(assemblyInfo, SourceInformationProvider, DiagnosticMessageSink);
}
/// <inheritdoc/>
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
{
return new XunitTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
/// <summary>
/// The implementation of <see cref="ITestFrameworkDiscoverer"/> that supports discovery
/// of unit tests linked against xunit.core.dll, using xunit.execution.dll.
/// </summary>
public class MettleTestFrameworkDiscoverer : TestFrameworkDiscoverer
{
static Type XunitTestCaseType = typeof(XunitTestCase);
/// <summary>
/// Gets the display name of the xUnit.net v2 test framework.
/// </summary>
public static readonly string DisplayName = string.Format(CultureInfo.InvariantCulture, "xUnit.net {0}", new object[] { typeof(MettleTestFrameworkDiscoverer).GetTypeInfo().Assembly.GetName().Version });
static MettleTestFrameworkDiscoverer()
{
DisplayName = string.Format(CultureInfo.InvariantCulture, "xUnit.net {0}", new object[] { typeof(MettleTestFrameworkDiscoverer).GetTypeInfo().Assembly.GetName().Version });
}
/// <summary>
/// Initializes a new instance of the <see cref="XunitTestFrameworkDiscoverer"/> class.
/// </summary>
/// <param name="assemblyInfo">The test assembly.</param>
/// <param name="sourceProvider">The source information provider.</param>
/// <param name="diagnosticMessageSink">The message sink used to send diagnostic messages</param>
/// <param name="collectionFactory">The test collection factory used to look up test collections.</param>
public MettleTestFrameworkDiscoverer(IAssemblyInfo assemblyInfo,
ISourceInformationProvider sourceProvider,
IMessageSink diagnosticMessageSink,
IXunitTestCollectionFactory collectionFactory = null)
: base(assemblyInfo, sourceProvider, diagnosticMessageSink)
{
var collectionBehaviorAttribute = assemblyInfo.GetCustomAttributes(typeof(CollectionBehaviorAttribute)).SingleOrDefault();
var disableParallelization = collectionBehaviorAttribute != null && collectionBehaviorAttribute.GetNamedArgument<bool>("DisableTestParallelization");
string config = null;
#if NETFRAMEWORK
config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
#endif
var testAssembly = new TestAssembly(assemblyInfo, config);
TestCollectionFactory = collectionFactory ?? ExtensibilityPointFactory.GetXunitTestCollectionFactory(diagnosticMessageSink, collectionBehaviorAttribute, testAssembly);
TestFrameworkDisplayName = $"{DisplayName} [{TestCollectionFactory.DisplayName}, {(disableParallelization ? "non-parallel" : "parallel")}]";
}
/// <summary>
/// Gets the mapping dictionary of fact attribute type to discoverer type.
/// </summary>
protected Dictionary<Type, Type> DiscovererTypeCache { get; } = new Dictionary<Type, Type>(); // key is a Type that is or derives from FactAttribute
/// <summary>
/// Gets the test collection factory that makes test collections.
/// </summary>
public IXunitTestCollectionFactory TestCollectionFactory { get; private set; }
/// <inheritdoc/>
protected override ITestClass CreateTestClass(ITypeInfo @class)
{
return new TestClass(TestCollectionFactory.Get(@class), @class);
}
internal ITestClass CreateTestClass(ITypeInfo @class, Guid testCollectionUniqueId)
{
// This method is called for special fact deserialization, to ensure that the test collection unique
// ID lines up with the ones that will be deserialized through normal mechanisms.
var discoveredTestCollection = TestCollectionFactory.Get(@class);
var testCollection = new TestCollection(discoveredTestCollection.TestAssembly, discoveredTestCollection.CollectionDefinition, discoveredTestCollection.DisplayName, testCollectionUniqueId);
return new TestClass(testCollection, @class);
}
/// <summary>
/// Finds the tests on a test method.
/// </summary>
/// <param name="testMethod">The test method.</param>
/// <param name="includeSourceInformation">Set to <c>true</c> to indicate that source information should be included.</param>
/// <param name="messageBus">The message bus to report discovery messages to.</param>
/// <param name="discoveryOptions">The options used by the test framework during discovery.</param>
/// <returns>Return <c>true</c> to continue test discovery, <c>false</c>, otherwise.</returns>
protected internal virtual bool FindTestsForMethod(ITestMethod testMethod, bool includeSourceInformation, IMessageBus messageBus, ITestFrameworkDiscoveryOptions discoveryOptions)
{
var factAttributes = testMethod.Method.GetCustomAttributes(typeof(FactAttribute)).CastOrToList();
if (factAttributes.Count > 1)
{
var message = $"Test method '{testMethod.TestClass.Class.Name}.{testMethod.Method.Name}' has multiple [Fact]-derived attributes";
var testCase = new ExecutionErrorTestCase(DiagnosticMessageSink, TestMethodDisplay.ClassAndMethod, TestMethodDisplayOptions.None, testMethod, message);
return ReportDiscoveredTestCase(testCase, includeSourceInformation, messageBus);
}
var factAttribute = factAttributes.FirstOrDefault();
if (factAttribute == null)
return true;
var factAttributeType = (factAttribute as IReflectionAttributeInfo)?.Attribute.GetType();
Type discovererType = null;
if (factAttributeType == null || !DiscovererTypeCache.TryGetValue(factAttributeType, out discovererType))
{
var testCaseDiscovererAttribute = factAttribute.GetCustomAttributes(typeof(XunitTestCaseDiscovererAttribute)).FirstOrDefault();
if (testCaseDiscovererAttribute != null)
{
var args = testCaseDiscovererAttribute.GetConstructorArguments().Cast<string>().ToList();
discovererType = SerializationHelper.GetType(args[1], args[0]);
}
if (factAttributeType != null)
DiscovererTypeCache[factAttributeType] = discovererType;
}
if (discovererType == null)
return true;
var discoverer = GetDiscoverer(discovererType);
if (discoverer == null)
return true;
foreach (var testCase in discoverer.Discover(discoveryOptions, testMethod, factAttribute))
if (!ReportDiscoveredTestCase(testCase, includeSourceInformation, messageBus))
return false;
return true;
}
/// <inheritdoc/>
protected override bool FindTestsForType(ITestClass testClass, bool includeSourceInformation, IMessageBus messageBus, ITestFrameworkDiscoveryOptions discoveryOptions)
{
foreach (var method in testClass.Class.GetMethods(true))
{
var testMethod = new TestMethod(testClass, method);
if (!FindTestsForMethod(testMethod, includeSourceInformation, messageBus, discoveryOptions))
return false;
}
return true;
}
/// <summary>
/// Gets the test case discover instance for the given discoverer type. The instances are cached
/// and reused, since they should not be stateful.
/// </summary>
/// <param name="discovererType">The discoverer type.</param>
/// <returns>Returns the test case discoverer instance.</returns>
protected IXunitTestCaseDiscoverer GetDiscoverer(Type discovererType)
{
try
{
return ExtensibilityPointFactory.GetXunitTestCaseDiscoverer(DiagnosticMessageSink, discovererType);
}
catch (Exception ex)
{
DiagnosticMessageSink.OnMessage(new DiagnosticMessage($"Discoverer type '{discovererType.FullName}' could not be created or does not implement IXunitTestCaseDiscoverer: {ex.Unwrap()}"));
return null;
}
}
/// <inheritdoc/>
public override string Serialize(ITestCase testCase)
{
if (testCase.GetType() == XunitTestCaseType)
{
var xunitTestCase = (MettleTestCase)testCase;
var className = testCase.TestMethod?.TestClass?.Class?.Name;
var methodName = testCase.TestMethod?.Method?.Name;
if (className != null && methodName != null && (xunitTestCase.TestMethodArguments == null || xunitTestCase.TestMethodArguments.Length == 0))
return $":F:{className.Replace(":", "::")}:{methodName.Replace(":", "::")}"+
$":{(int)xunitTestCase.DefaultMethodDisplay}:{(int)xunitTestCase.DefaultMethodDisplayOptions}"+
$":{testCase.TestMethod.TestClass.TestCollection.UniqueID.ToString("N")}";
}
return base.Serialize(testCase);
}
}
}
\ No newline at end of file
using System;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
public class MettleTestFrameworkTypeDiscoverer : ITestFrameworkTypeDiscoverer
{
/// <inheritdoc/>
public Type GetTestFrameworkType(IAttributeInfo attribute)
{
var type = attribute.GetNamedArgument<Type>("Type");
if(type != null)
return type;
return typeof(MettleTestFramework);
}
}
}
\ No newline at end of file
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
[TestFrameworkDiscoverer("Mettle.Xunit.Sdk.MettleTestFramework", "Mettle.Xunit")]
[System.AttributeUsage(System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
sealed class MettleXunitFrameworkAttribute : System.Attribute, ITestFrameworkAttribute
{
public string Type { get; set; }
public string Assembly { get; set; }
/// <summary>
///
/// </summary>
public MettleXunitFrameworkAttribute()
{
}
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
using System.Diagnostics;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Mettle.Xunit.Sdk
{
/// <summary>
/// The default implementation of <see cref="ITestCollection"/>.
/// </summary>
[DebuggerDisplay(@"\{ id = {UniqueID}, display = {DisplayName} \}")]
public class TestCollection : LongLivedMarshalByRefObject, ITestCollection
{
/// <summary/>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public TestCollection() { }
/// <summary>
/// Initializes a new instance of the <see cref="TestCollection"/> class.
/// </summary>
/// <param name="testAssembly">The test assembly the collection belongs to</param>
/// <param name="collectionDefinition">The optional type which contains the collection definition</param>
/// <param name="displayName">The display name for the test collection</param>
public TestCollection(ITestAssembly testAssembly, ITypeInfo collectionDefinition, string displayName)
: this(testAssembly, collectionDefinition, displayName, Guid.NewGuid()) { }
internal TestCollection(ITestAssembly testAssembly, ITypeInfo collectionDefinition, string displayName, Guid uniqueId)
{
if(testAssembly == null)
throw new ArgumentNullException(nameof(testAssembly));
CollectionDefinition = collectionDefinition;
DisplayName = displayName;
TestAssembly = testAssembly;
UniqueID = uniqueId;
}
/// <inheritdoc/>
public ITypeInfo CollectionDefinition { get; set; }
/// <inheritdoc/>
public string DisplayName { get; set; }
/// <inheritdoc/>
public ITestAssembly TestAssembly { get; set; }
/// <inheritdoc/>
public Guid UniqueID { get; set; }
/// <inheritdoc/>
public virtual void Serialize(IXunitSerializationInfo info)
{
info.AddValue("DisplayName", DisplayName);
info.AddValue("TestAssembly", TestAssembly);
info.AddValue("UniqueID", UniqueID.ToString());
if (CollectionDefinition != null)
{
info.AddValue("DeclarationAssemblyName", CollectionDefinition.Assembly.Name);
info.AddValue("DeclarationTypeName", CollectionDefinition.Name);
}
else
{
info.AddValue("DeclarationAssemblyName", null);
info.AddValue("DeclarationTypeName", null);
}
}
/// <inheritdoc/>
public virtual void Deserialize(IXunitSerializationInfo info)
{
DisplayName = info.GetValue<string>("DisplayName");
TestAssembly = info.GetValue<ITestAssembly>("TestAssembly");
UniqueID = Guid.Parse(info.GetValue<string>("UniqueID"));
var assemblyName = info.GetValue<string>("DeclarationAssemblyName");
var typeName = info.GetValue<string>("DeclarationTypeName");
if (!string.IsNullOrWhiteSpace(assemblyName) && !string.IsNullOrWhiteSpace(typeName))