Merge branch 'release/3.1' => 'master' (dotnet/extensions#2382)
\n\nCommit migrated from f0bbe1da7d
This commit is contained in:
commit
45fc922633
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.JSInterop
|
namespace Microsoft.JSInterop
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -15,6 +17,11 @@ namespace Microsoft.JSInterop
|
||||||
/// <returns>An instance of <see cref="DotNetObjectReference{TValue}" />.</returns>
|
/// <returns>An instance of <see cref="DotNetObjectReference{TValue}" />.</returns>
|
||||||
public static DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class
|
public static DotNetObjectReference<TValue> Create<TValue>(TValue value) where TValue : class
|
||||||
{
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
return new DotNetObjectReference<TValue>(value);
|
return new DotNetObjectReference<TValue>(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a lifecycle for attributes or classes that want to know about tests starting
|
||||||
|
/// or ending. Implement this on a test class, or attribute at the method/class/assembly level.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Requires defining <see cref="AspNetTestFramework"/> as the test framework.
|
||||||
|
/// </remarks>
|
||||||
|
public interface ITestMethodLifecycle
|
||||||
|
{
|
||||||
|
Task OnTestStartAsync(TestContext context, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a test multiple times to stress flaky tests that are believed to be fixed.
|
||||||
|
/// This can be used on an assembly, class, or method name. Requires using the AspNetCore test framework.
|
||||||
|
/// </summary>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)]
|
||||||
|
public class RepeatAttribute : Attribute
|
||||||
|
{
|
||||||
|
public RepeatAttribute(int runCount = 10)
|
||||||
|
{
|
||||||
|
RunCount = runCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of times to run a test.
|
||||||
|
/// </summary>
|
||||||
|
public int RunCount { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class RepeatContext
|
||||||
|
{
|
||||||
|
private static AsyncLocal<RepeatContext> _current = new AsyncLocal<RepeatContext>();
|
||||||
|
|
||||||
|
public static RepeatContext Current
|
||||||
|
{
|
||||||
|
get => _current.Value;
|
||||||
|
internal set => _current.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepeatContext(int limit)
|
||||||
|
{
|
||||||
|
Limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Limit { get; }
|
||||||
|
|
||||||
|
public int CurrentIteration { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright(c) .NET Foundation.All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to specify that <see cref="TestFileOutputContext.TestClassName"/> should used the
|
||||||
|
/// unqualified class name. This is needed when a fully-qualified class name exceeds
|
||||||
|
/// max path for logging.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false)]
|
||||||
|
public class ShortClassNameAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to contextual information about the running tests. Get access by
|
||||||
|
/// implementing <see cref="ITestMethodLifecycle"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Requires defining <see cref="AspNetTestFramework"/> as the test framework.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class TestContext
|
||||||
|
{
|
||||||
|
private Lazy<TestFileOutputContext> _files;
|
||||||
|
|
||||||
|
public TestContext(
|
||||||
|
Type testClass,
|
||||||
|
object[] constructorArguments,
|
||||||
|
MethodInfo testMethod,
|
||||||
|
object[] methodArguments,
|
||||||
|
ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
TestClass = testClass;
|
||||||
|
ConstructorArguments = constructorArguments;
|
||||||
|
TestMethod = testMethod;
|
||||||
|
MethodArguments = methodArguments;
|
||||||
|
Output = output;
|
||||||
|
|
||||||
|
_files = new Lazy<TestFileOutputContext>(() => new TestFileOutputContext(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type TestClass { get; }
|
||||||
|
public MethodInfo TestMethod { get; }
|
||||||
|
public object[] ConstructorArguments { get; }
|
||||||
|
public object[] MethodArguments { get; }
|
||||||
|
public ITestOutputHelper Output { get; }
|
||||||
|
public TestFileOutputContext FileOutput => _files.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to file storage for the running test. Get access by
|
||||||
|
/// implementing <see cref="ITestMethodLifecycle"/>, and accessing <see cref="TestContext.FileOutput"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Requires defining <see cref="AspNetTestFramework"/> as the test framework.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class TestFileOutputContext
|
||||||
|
{
|
||||||
|
private static char[] InvalidFileChars = new char[]
|
||||||
|
{
|
||||||
|
'\"', '<', '>', '|', '\0',
|
||||||
|
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
|
||||||
|
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
|
||||||
|
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
|
||||||
|
(char)31, ':', '*', '?', '\\', '/', ' ', (char)127
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly TestContext _parent;
|
||||||
|
|
||||||
|
public TestFileOutputContext(TestContext parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
TestName = GetTestMethodName(parent.TestMethod, parent.MethodArguments);
|
||||||
|
TestClassName = GetTestClassName(parent.TestClass);
|
||||||
|
|
||||||
|
AssemblyOutputDirectory = GetAssemblyBaseDirectory(_parent.TestClass.Assembly);
|
||||||
|
if (!string.IsNullOrEmpty(AssemblyOutputDirectory))
|
||||||
|
{
|
||||||
|
TestClassOutputDirectory = Path.Combine(AssemblyOutputDirectory, TestClassName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TestName { get; }
|
||||||
|
|
||||||
|
public string TestClassName { get; }
|
||||||
|
|
||||||
|
public string AssemblyOutputDirectory { get; }
|
||||||
|
|
||||||
|
public string TestClassOutputDirectory { get; }
|
||||||
|
|
||||||
|
public string GetUniqueFileName(string prefix, string extension)
|
||||||
|
{
|
||||||
|
if (prefix == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension != null && !extension.StartsWith(".", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The extension must start with '.' if one is provided.", nameof(extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = Path.Combine(TestClassOutputDirectory, $"{prefix}{extension}");
|
||||||
|
|
||||||
|
var i = 1;
|
||||||
|
while (File.Exists(path))
|
||||||
|
{
|
||||||
|
path = Path.Combine(TestClassOutputDirectory, $"{prefix}{i++}{extension}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the output directory without appending the TFM or assembly name.
|
||||||
|
public static string GetOutputDirectory(Assembly assembly)
|
||||||
|
{
|
||||||
|
var attribute = assembly.GetCustomAttributes().OfType<TestOutputDirectoryAttribute>().FirstOrDefault();
|
||||||
|
return attribute?.BaseDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAssemblyBaseDirectory(Assembly assembly, string baseDirectory = null)
|
||||||
|
{
|
||||||
|
var attribute = assembly.GetCustomAttributes().OfType<TestOutputDirectoryAttribute>().FirstOrDefault();
|
||||||
|
baseDirectory = baseDirectory ?? attribute?.BaseDirectory;
|
||||||
|
if (string.IsNullOrEmpty(baseDirectory))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(baseDirectory, assembly.GetName().Name, attribute.TargetFramework);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetTestClassName(Type type)
|
||||||
|
{
|
||||||
|
var shortNameAttribute =
|
||||||
|
type.GetCustomAttribute<ShortClassNameAttribute>() ??
|
||||||
|
type.Assembly.GetCustomAttribute<ShortClassNameAttribute>();
|
||||||
|
var name = shortNameAttribute == null ? type.FullName : type.Name;
|
||||||
|
|
||||||
|
// Try to shorten the class name using the assembly name
|
||||||
|
var assemblyName = type.Assembly.GetName().Name;
|
||||||
|
if (name.StartsWith(assemblyName + "."))
|
||||||
|
{
|
||||||
|
name = name.Substring(assemblyName.Length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetTestMethodName(MethodInfo method, object[] arguments)
|
||||||
|
{
|
||||||
|
var name = arguments.Aggregate(method.Name, (a, b) => $"{a}-{(b ?? "null")}");
|
||||||
|
return RemoveIllegalFileChars(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RemoveIllegalFileChars(string s)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var c in s)
|
||||||
|
{
|
||||||
|
if (InvalidFileChars.Contains(c))
|
||||||
|
{
|
||||||
|
if (sb.Length > 0 && sb[sb.Length - 1] != '_')
|
||||||
|
{
|
||||||
|
sb.Append('_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class TestOutputDirectoryAttribute : Attribute
|
||||||
|
{
|
||||||
|
public TestOutputDirectoryAttribute(string targetFramework, string baseDirectory = null)
|
||||||
|
{
|
||||||
|
TargetFramework = targetFramework;
|
||||||
|
BaseDirectory = baseDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BaseDirectory { get; }
|
||||||
|
public string TargetFramework { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class AspNetTestAssemblyRunner : XunitTestAssemblyRunner
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Type, object> _assemblyFixtureMappings = new Dictionary<Type, object>();
|
||||||
|
|
||||||
|
public AspNetTestAssemblyRunner(
|
||||||
|
ITestAssembly testAssembly,
|
||||||
|
IEnumerable<IXunitTestCase> testCases,
|
||||||
|
IMessageSink diagnosticMessageSink,
|
||||||
|
IMessageSink executionMessageSink,
|
||||||
|
ITestFrameworkExecutionOptions executionOptions)
|
||||||
|
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task AfterTestAssemblyStartingAsync()
|
||||||
|
{
|
||||||
|
await base.AfterTestAssemblyStartingAsync();
|
||||||
|
|
||||||
|
// Find all the AssemblyFixtureAttributes on the test assembly
|
||||||
|
Aggregator.Run(() =>
|
||||||
|
{
|
||||||
|
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly)
|
||||||
|
.Assembly
|
||||||
|
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
|
||||||
|
.Cast<AssemblyFixtureAttribute>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Instantiate all the fixtures
|
||||||
|
foreach (var fixtureAttribute in fixturesAttributes)
|
||||||
|
{
|
||||||
|
var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) });
|
||||||
|
if (ctorWithDiagnostics != null)
|
||||||
|
{
|
||||||
|
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task BeforeTestAssemblyFinishedAsync()
|
||||||
|
{
|
||||||
|
// Dispose fixtures
|
||||||
|
foreach (var disposable in _assemblyFixtureMappings.Values.OfType<IDisposable>())
|
||||||
|
{
|
||||||
|
Aggregator.Run(disposable.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.BeforeTestAssemblyFinishedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<RunSummary> RunTestCollectionAsync(
|
||||||
|
IMessageBus messageBus,
|
||||||
|
ITestCollection testCollection,
|
||||||
|
IEnumerable<IXunitTestCase> testCases,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
|
=> new AspNetTestCollectionRunner(
|
||||||
|
_assemblyFixtureMappings,
|
||||||
|
testCollection,
|
||||||
|
testCases,
|
||||||
|
DiagnosticMessageSink,
|
||||||
|
messageBus,
|
||||||
|
TestCaseOrderer,
|
||||||
|
new ExceptionAggregator(Aggregator),
|
||||||
|
cancellationTokenSource).RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
internal class AspNetTestCaseRunner : XunitTestCaseRunner
|
||||||
|
{
|
||||||
|
public AspNetTestCaseRunner(
|
||||||
|
IXunitTestCase testCase,
|
||||||
|
string displayName,
|
||||||
|
string skipReason,
|
||||||
|
object[] constructorArguments,
|
||||||
|
object[] testMethodArguments,
|
||||||
|
IMessageBus messageBus,
|
||||||
|
ExceptionAggregator aggregator,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
|
: base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
|
||||||
|
{
|
||||||
|
return new AspNetTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
internal class AspNetTestClassRunner : XunitTestClassRunner
|
||||||
|
{
|
||||||
|
public AspNetTestClassRunner(
|
||||||
|
ITestClass testClass,
|
||||||
|
IReflectionTypeInfo @class,
|
||||||
|
IEnumerable<IXunitTestCase> testCases,
|
||||||
|
IMessageSink diagnosticMessageSink,
|
||||||
|
IMessageBus messageBus,
|
||||||
|
ITestCaseOrderer testCaseOrderer,
|
||||||
|
ExceptionAggregator aggregator,
|
||||||
|
CancellationTokenSource cancellationTokenSource,
|
||||||
|
IDictionary<Type, object> collectionFixtureMappings)
|
||||||
|
: base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource, collectionFixtureMappings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<RunSummary> RunTestMethodAsync(ITestMethod testMethod, IReflectionMethodInfo method, IEnumerable<IXunitTestCase> testCases, object[] constructorArguments)
|
||||||
|
{
|
||||||
|
var runner = new AspNetTestMethodRunner(
|
||||||
|
testMethod,
|
||||||
|
Class,
|
||||||
|
method,
|
||||||
|
testCases,
|
||||||
|
DiagnosticMessageSink,
|
||||||
|
MessageBus,
|
||||||
|
new ExceptionAggregator(Aggregator),
|
||||||
|
CancellationTokenSource,
|
||||||
|
constructorArguments);
|
||||||
|
return runner.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class AspNetTestCollectionRunner : XunitTestCollectionRunner
|
||||||
|
{
|
||||||
|
private readonly IDictionary<Type, object> _assemblyFixtureMappings;
|
||||||
|
private readonly IMessageSink _diagnosticMessageSink;
|
||||||
|
|
||||||
|
public AspNetTestCollectionRunner(
|
||||||
|
Dictionary<Type, object> assemblyFixtureMappings,
|
||||||
|
ITestCollection testCollection,
|
||||||
|
IEnumerable<IXunitTestCase> testCases,
|
||||||
|
IMessageSink diagnosticMessageSink,
|
||||||
|
IMessageBus messageBus,
|
||||||
|
ITestCaseOrderer testCaseOrderer,
|
||||||
|
ExceptionAggregator aggregator,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
|
: base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
|
||||||
|
{
|
||||||
|
_assemblyFixtureMappings = assemblyFixtureMappings;
|
||||||
|
_diagnosticMessageSink = diagnosticMessageSink;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task AfterTestCollectionStartingAsync()
|
||||||
|
{
|
||||||
|
await base.AfterTestCollectionStartingAsync();
|
||||||
|
|
||||||
|
// note: We pass the assembly fixtures into the runner as ICollectionFixture<> - this seems to work OK without any
|
||||||
|
// drawbacks. It's reasonable that we could add IAssemblyFixture<> and related plumbing if it ever became required.
|
||||||
|
//
|
||||||
|
// The reason for assembly fixture is when we want to start/stop something as the project scope - tests can only be
|
||||||
|
// in one test collection at a time.
|
||||||
|
foreach (var mapping in _assemblyFixtureMappings)
|
||||||
|
{
|
||||||
|
CollectionFixtureMappings.Add(mapping.Key, mapping.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task BeforeTestCollectionFinishedAsync()
|
||||||
|
{
|
||||||
|
// We need to remove the assembly fixtures so they won't get disposed.
|
||||||
|
foreach (var mapping in _assemblyFixtureMappings)
|
||||||
|
{
|
||||||
|
CollectionFixtureMappings.Remove(mapping.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.BeforeTestCollectionFinishedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
|
||||||
|
{
|
||||||
|
var runner = new AspNetTestClassRunner(
|
||||||
|
testClass,
|
||||||
|
@class,
|
||||||
|
testCases,
|
||||||
|
DiagnosticMessageSink,
|
||||||
|
MessageBus,
|
||||||
|
TestCaseOrderer,
|
||||||
|
new ExceptionAggregator(Aggregator),
|
||||||
|
CancellationTokenSource,
|
||||||
|
CollectionFixtureMappings);
|
||||||
|
return runner.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class AspNetTestFramework : XunitTestFramework
|
||||||
|
{
|
||||||
|
public AspNetTestFramework(IMessageSink messageSink)
|
||||||
|
: base(messageSink)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
|
||||||
|
=> new AspNetTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class AspNetTestFrameworkExecutor : XunitTestFrameworkExecutor
|
||||||
|
{
|
||||||
|
public AspNetTestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider, IMessageSink diagnosticMessageSink)
|
||||||
|
: base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
|
||||||
|
{
|
||||||
|
using (var assemblyRunner = new AspNetTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions))
|
||||||
|
{
|
||||||
|
await assemblyRunner.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
internal class AspNetTestInvoker : XunitTestInvoker
|
||||||
|
{
|
||||||
|
public AspNetTestInvoker(
|
||||||
|
ITest test,
|
||||||
|
IMessageBus messageBus,
|
||||||
|
Type testClass,
|
||||||
|
object[] constructorArguments,
|
||||||
|
MethodInfo testMethod,
|
||||||
|
object[] testMethodArguments,
|
||||||
|
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
|
||||||
|
ExceptionAggregator aggregator,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
|
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<decimal> InvokeTestMethodAsync(object testClassInstance)
|
||||||
|
{
|
||||||
|
var output = new TestOutputHelper();
|
||||||
|
output.Initialize(MessageBus, Test);
|
||||||
|
|
||||||
|
var context = new TestContext(TestClass, ConstructorArguments, TestMethod, TestMethodArguments, output);
|
||||||
|
var lifecycleHooks = GetLifecycleHooks(testClassInstance, TestClass, TestMethod);
|
||||||
|
|
||||||
|
await Aggregator.RunAsync(async () =>
|
||||||
|
{
|
||||||
|
foreach (var lifecycleHook in lifecycleHooks)
|
||||||
|
{
|
||||||
|
await lifecycleHook.OnTestStartAsync(context, CancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var time = await base.InvokeTestMethodAsync(testClassInstance);
|
||||||
|
|
||||||
|
await Aggregator.RunAsync(async () =>
|
||||||
|
{
|
||||||
|
var exception = Aggregator.HasExceptions ? Aggregator.ToException() : null;
|
||||||
|
foreach (var lifecycleHook in lifecycleHooks)
|
||||||
|
{
|
||||||
|
await lifecycleHook.OnTestEndAsync(context, exception, CancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<ITestMethodLifecycle> GetLifecycleHooks(object testClassInstance, Type testClass, MethodInfo testMethod)
|
||||||
|
{
|
||||||
|
foreach (var attribute in testMethod.GetCustomAttributes(inherit: true).OfType<ITestMethodLifecycle>())
|
||||||
|
{
|
||||||
|
yield return attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testClassInstance is ITestMethodLifecycle instance)
|
||||||
|
{
|
||||||
|
yield return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var attribute in testClass.GetCustomAttributes(inherit: true).OfType<ITestMethodLifecycle>())
|
||||||
|
{
|
||||||
|
yield return attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var attribute in testClass.Assembly.GetCustomAttributes(inherit: true).OfType<ITestMethodLifecycle>())
|
||||||
|
{
|
||||||
|
yield return attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
internal class AspNetTestMethodRunner : XunitTestMethodRunner
|
||||||
|
{
|
||||||
|
private readonly object[] _constructorArguments;
|
||||||
|
private readonly IMessageSink _diagnosticMessageSink;
|
||||||
|
|
||||||
|
public AspNetTestMethodRunner(
|
||||||
|
ITestMethod testMethod,
|
||||||
|
IReflectionTypeInfo @class,
|
||||||
|
IReflectionMethodInfo method,
|
||||||
|
IEnumerable<IXunitTestCase> testCases,
|
||||||
|
IMessageSink diagnosticMessageSink,
|
||||||
|
IMessageBus messageBus,
|
||||||
|
ExceptionAggregator aggregator,
|
||||||
|
CancellationTokenSource cancellationTokenSource,
|
||||||
|
object[] constructorArguments)
|
||||||
|
: base(testMethod, @class, method, testCases, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource, constructorArguments)
|
||||||
|
{
|
||||||
|
_diagnosticMessageSink = diagnosticMessageSink;
|
||||||
|
_constructorArguments = constructorArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<RunSummary> RunTestCaseAsync(IXunitTestCase testCase)
|
||||||
|
{
|
||||||
|
if (testCase.GetType() == typeof(XunitTestCase))
|
||||||
|
{
|
||||||
|
// If we get here this is a 'regular' test case, not something that represents a skipped test.
|
||||||
|
//
|
||||||
|
// We can take control of it's invocation thusly.
|
||||||
|
var runner = new AspNetTestCaseRunner(
|
||||||
|
testCase,
|
||||||
|
testCase.DisplayName,
|
||||||
|
testCase.SkipReason,
|
||||||
|
_constructorArguments,
|
||||||
|
testCase.TestMethodArguments,
|
||||||
|
MessageBus,
|
||||||
|
new ExceptionAggregator(Aggregator),
|
||||||
|
CancellationTokenSource);
|
||||||
|
return runner.RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testCase.GetType() == typeof(XunitTheoryTestCase))
|
||||||
|
{
|
||||||
|
// If we get here this is a 'regular' theory test case, not something that represents a skipped test.
|
||||||
|
//
|
||||||
|
// We can take control of it's invocation thusly.
|
||||||
|
var runner = new AspNetTheoryTestCaseRunner(
|
||||||
|
testCase,
|
||||||
|
testCase.DisplayName,
|
||||||
|
testCase.SkipReason,
|
||||||
|
_constructorArguments,
|
||||||
|
_diagnosticMessageSink,
|
||||||
|
MessageBus,
|
||||||
|
new ExceptionAggregator(Aggregator),
|
||||||
|
CancellationTokenSource);
|
||||||
|
return runner.RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.RunTestCaseAsync(testCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
internal class AspNetTestRunner : XunitTestRunner
|
||||||
|
{
|
||||||
|
public AspNetTestRunner(
|
||||||
|
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, beforeAfterAttributes, aggregator, cancellationTokenSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
|
||||||
|
{
|
||||||
|
var repeatAttribute = GetRepeatAttribute(TestMethod);
|
||||||
|
if (repeatAttribute == null)
|
||||||
|
{
|
||||||
|
return await InvokeTestMethodCoreAsync(aggregator);
|
||||||
|
}
|
||||||
|
|
||||||
|
var repeatContext = new RepeatContext(repeatAttribute.RunCount);
|
||||||
|
RepeatContext.Current = repeatContext;
|
||||||
|
|
||||||
|
var timeTaken = 0.0M;
|
||||||
|
for (repeatContext.CurrentIteration = 0; repeatContext.CurrentIteration < repeatContext.Limit; repeatContext.CurrentIteration++)
|
||||||
|
{
|
||||||
|
timeTaken = await InvokeTestMethodCoreAsync(aggregator);
|
||||||
|
if (aggregator.HasExceptions)
|
||||||
|
{
|
||||||
|
return timeTaken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeTaken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<decimal> InvokeTestMethodCoreAsync(ExceptionAggregator aggregator)
|
||||||
|
{
|
||||||
|
var invoker = new AspNetTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource);
|
||||||
|
return invoker.RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RepeatAttribute GetRepeatAttribute(MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
var attributeCandidate = methodInfo.GetCustomAttribute<RepeatAttribute>();
|
||||||
|
if (attributeCandidate != null)
|
||||||
|
{
|
||||||
|
return attributeCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeCandidate = methodInfo.DeclaringType.GetCustomAttribute<RepeatAttribute>();
|
||||||
|
if (attributeCandidate != null)
|
||||||
|
{
|
||||||
|
return attributeCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodInfo.DeclaringType.Assembly.GetCustomAttribute<RepeatAttribute>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing.xunit
|
||||||
|
{
|
||||||
|
internal class AspNetTheoryTestCaseRunner : XunitTheoryTestCaseRunner
|
||||||
|
{
|
||||||
|
public AspNetTheoryTestCaseRunner(
|
||||||
|
IXunitTestCase testCase,
|
||||||
|
string displayName,
|
||||||
|
string skipReason,
|
||||||
|
object[] constructorArguments,
|
||||||
|
IMessageSink diagnosticMessageSink,
|
||||||
|
IMessageBus messageBus,
|
||||||
|
ExceptionAggregator aggregator,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
|
: base(testCase, displayName, skipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
|
||||||
|
{
|
||||||
|
return new AspNetTestRunner(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
|
public class AssemblyFixtureAttribute : Attribute
|
||||||
|
{
|
||||||
|
public AssemblyFixtureAttribute(Type fixtureType)
|
||||||
|
{
|
||||||
|
FixtureType = fixtureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type FixtureType { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
|
@ -63,5 +64,24 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason)
|
base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason)
|
||||||
: base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow);
|
: base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<IXunitTestCase> CreateTestCasesForSkippedDataRow(
|
||||||
|
ITestFrameworkDiscoveryOptions discoveryOptions,
|
||||||
|
ITestMethod testMethod,
|
||||||
|
IAttributeInfo theoryAttribute,
|
||||||
|
object[] dataRow,
|
||||||
|
string skipReason)
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
|
protected override IXunitTestCase CreateTestCaseForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason)
|
||||||
|
{
|
||||||
|
return new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
/// to <c>xunit.console.exe</c>. Similarly, it can run only flaky tests using <c>-trait "Flaky:AzP:OS:all=true" -trait "Flaky:AzP:OS:Darwin=true"</c>
|
/// to <c>xunit.console.exe</c>. Similarly, it can run only flaky tests using <c>-trait "Flaky:AzP:OS:all=true" -trait "Flaky:AzP:OS:Darwin=true"</c>
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </example>
|
/// </example>
|
||||||
[TraitDiscoverer("Microsoft.AspNetCore.Testing." + nameof(FlakyTestDiscoverer), "Microsoft.AspNetCore.Testing")]
|
[TraitDiscoverer("Microsoft.AspNetCore.Testing." + nameof(FlakyTraitDiscoverer), "Microsoft.AspNetCore.Testing")]
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
|
||||||
public sealed class FlakyAttribute : Attribute, ITraitAttribute
|
public sealed class FlakyAttribute : Attribute, ITraitAttribute
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using Xunit.Sdk;
|
||||||
// Do not change this namespace without changing the usage in FlakyAttribute
|
// Do not change this namespace without changing the usage in FlakyAttribute
|
||||||
namespace Microsoft.AspNetCore.Testing
|
namespace Microsoft.AspNetCore.Testing
|
||||||
{
|
{
|
||||||
public class FlakyTestDiscoverer : ITraitDiscoverer
|
public class FlakyTraitDiscoverer : ITraitDiscoverer
|
||||||
{
|
{
|
||||||
public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
|
public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
|
||||||
{
|
{
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Xunit.Sdk;
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
|
@ -33,8 +35,11 @@ namespace Microsoft.AspNetCore.Testing
|
||||||
|
|
||||||
public override void Deserialize(IXunitSerializationInfo data)
|
public override void Deserialize(IXunitSerializationInfo data)
|
||||||
{
|
{
|
||||||
base.Deserialize(data);
|
|
||||||
_skipReason = data.GetValue<string>(nameof(_skipReason));
|
_skipReason = data.GetValue<string>(nameof(_skipReason));
|
||||||
|
|
||||||
|
// We need to call base after reading our value, because Deserialize will call
|
||||||
|
// into GetSkipReason.
|
||||||
|
base.Deserialize(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Serialize(IXunitSerializationInfo data)
|
public override void Serialize(IXunitSerializationInfo data)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
// This is a workaround for https://github.com/xunit/xunit/issues/1782 - as such, this code is a copy-paste
|
||||||
|
// from xUnit with the exception of fixing the bug.
|
||||||
|
//
|
||||||
|
// This will only work with [ConditionalTheory].
|
||||||
|
internal class WORKAROUND_SkippedDataRowTestCase : XunitTestCase
|
||||||
|
{
|
||||||
|
string skipReason;
|
||||||
|
|
||||||
|
/// <summary/>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
|
||||||
|
public WORKAROUND_SkippedDataRowTestCase() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="XunitSkippedDataRowTestCase"/> 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="skipReason">The reason that this test case will be skipped</param>
|
||||||
|
/// <param name="testMethodArguments">The arguments for the test method.</param>
|
||||||
|
[Obsolete("Please call the constructor which takes TestMethodDisplayOptions")]
|
||||||
|
public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink,
|
||||||
|
TestMethodDisplay defaultMethodDisplay,
|
||||||
|
ITestMethod testMethod,
|
||||||
|
string skipReason,
|
||||||
|
object[] testMethodArguments = null)
|
||||||
|
: this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, skipReason, testMethodArguments) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="XunitSkippedDataRowTestCase"/> 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="skipReason">The reason that this test case will be skipped</param>
|
||||||
|
/// <param name="testMethodArguments">The arguments for the test method.</param>
|
||||||
|
public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink,
|
||||||
|
TestMethodDisplay defaultMethodDisplay,
|
||||||
|
TestMethodDisplayOptions defaultMethodDisplayOptions,
|
||||||
|
ITestMethod testMethod,
|
||||||
|
string skipReason,
|
||||||
|
object[] testMethodArguments = null)
|
||||||
|
: base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments)
|
||||||
|
{
|
||||||
|
this.skipReason = skipReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Deserialize(IXunitSerializationInfo data)
|
||||||
|
{
|
||||||
|
// SkipReason has to be read before we call base.Deserialize, this is the workaround.
|
||||||
|
this.skipReason = data.GetValue<string>("SkipReason");
|
||||||
|
|
||||||
|
base.Deserialize(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string GetSkipReason(IAttributeInfo factAttribute)
|
||||||
|
{
|
||||||
|
return skipReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Serialize(IXunitSerializationInfo data)
|
||||||
|
{
|
||||||
|
base.Serialize(data);
|
||||||
|
|
||||||
|
data.AddValue("SkipReason", skipReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
// We include a collection and assembly fixture to verify that they both still work.
|
||||||
|
[Collection("MyCollection")]
|
||||||
|
[TestCaseOrderer("Microsoft.AspNetCore.Testing.AlphabeticalOrderer", "Microsoft.AspNetCore.Testing.Tests")]
|
||||||
|
public class AssemblyFixtureTest
|
||||||
|
{
|
||||||
|
public AssemblyFixtureTest(TestAssemblyFixture assemblyFixture, TestCollectionFixture collectionFixture)
|
||||||
|
{
|
||||||
|
AssemblyFixture = assemblyFixture;
|
||||||
|
CollectionFixture = collectionFixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestAssemblyFixture AssemblyFixture { get; }
|
||||||
|
public TestCollectionFixture CollectionFixture { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void A()
|
||||||
|
{
|
||||||
|
Assert.NotNull(AssemblyFixture);
|
||||||
|
Assert.Equal(0, AssemblyFixture.Count);
|
||||||
|
|
||||||
|
Assert.NotNull(CollectionFixture);
|
||||||
|
Assert.Equal(0, CollectionFixture.Count);
|
||||||
|
|
||||||
|
AssemblyFixture.Count++;
|
||||||
|
CollectionFixture.Count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void B()
|
||||||
|
{
|
||||||
|
Assert.Equal(1, AssemblyFixture.Count);
|
||||||
|
Assert.Equal(1, CollectionFixture.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CollectionDefinition("MyCollection", DisableParallelization = true)]
|
||||||
|
public class MyCollection : ICollectionFixture<TestCollectionFixture>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
[assembly: Repeat(1)]
|
||||||
|
[assembly: AssemblyFixture(typeof(TestAssemblyFixture))]
|
||||||
|
[assembly: TestFramework("Microsoft.AspNetCore.Testing.AspNetTestFramework", "Microsoft.AspNetCore.Testing")]
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
[Repeat]
|
||||||
|
public class RepeatTest
|
||||||
|
{
|
||||||
|
public static int _runCount = 0;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Repeat(5)]
|
||||||
|
public void RepeatLimitIsSetCorrectly()
|
||||||
|
{
|
||||||
|
Assert.Equal(5, RepeatContext.Current.Limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Repeat(5)]
|
||||||
|
public void RepeatRunsTestSpecifiedNumberOfTimes()
|
||||||
|
{
|
||||||
|
Assert.Equal(RepeatContext.Current.CurrentIteration, _runCount);
|
||||||
|
_runCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RepeatCanBeSetOnClass()
|
||||||
|
{
|
||||||
|
Assert.Equal(10, RepeatContext.Current.Limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoggedTestXunitRepeatAssemblyTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RepeatCanBeSetOnAssembly()
|
||||||
|
{
|
||||||
|
Assert.Equal(1, RepeatContext.Current.Limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class TestAssemblyFixture
|
||||||
|
{
|
||||||
|
public int Count { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class TestCollectionFixture
|
||||||
|
{
|
||||||
|
public int Count { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
public class TestContextTest : ITestMethodLifecycle
|
||||||
|
{
|
||||||
|
public TestContext Context { get; private set; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FullName_IsUsed_ByDefault()
|
||||||
|
{
|
||||||
|
Assert.Equal(GetType().FullName, Context.FileOutput.TestClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing.Tests
|
||||||
|
{
|
||||||
|
public class TestContextNameShorteningTest : ITestMethodLifecycle
|
||||||
|
{
|
||||||
|
public TestContext Context { get; private set; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NameIsShortenedWhenAssemblyNameIsAPrefix()
|
||||||
|
{
|
||||||
|
Assert.Equal(GetType().Name, Context.FileOutput.TestClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Testing
|
||||||
|
{
|
||||||
|
[ShortClassName]
|
||||||
|
public class TestContextTestClassShortNameAttributeTest : ITestMethodLifecycle
|
||||||
|
{
|
||||||
|
public TestContext Context { get; private set; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShortClassNameUsedWhenShortClassNameAttributeSpecified()
|
||||||
|
{
|
||||||
|
Assert.Equal(GetType().Name, Context.FileOutput.TestClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue