diff --git a/src/xunit.core/Sdk/ITestCollectionOrderer.cs b/src/xunit.core/Sdk/ITestCollectionOrderer.cs new file mode 100644 index 000000000..656f44e71 --- /dev/null +++ b/src/xunit.core/Sdk/ITestCollectionOrderer.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; + +namespace Xunit.Sdk +{ + /// + /// A class implements this interface to participate in ordering test collections + /// for the test runner. Test collection orderers are applied using the + /// , which can be applied at + /// the assembly level. + /// + public interface ITestCollectionOrderer + { + /// + /// Orders test collections for execution. + /// + /// The test collections to be ordered. + /// The test collections in the order to be run. + IEnumerable> OrderTestCollections(IEnumerable> testCollections) where TTestCase : ITestCase; + } +} \ No newline at end of file diff --git a/src/xunit.core/TestCollectionOrdererAttribute.cs b/src/xunit.core/TestCollectionOrdererAttribute.cs new file mode 100644 index 000000000..31b1a6a87 --- /dev/null +++ b/src/xunit.core/TestCollectionOrdererAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Xunit +{ + /// + /// Used to decorate an assembly to allow + /// the use a custom . + /// + [AttributeUsage(AttributeTargets.Assembly, Inherited = true, AllowMultiple = false)] + public sealed class TestCollectionOrdererAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type name of the orderer class (that implements ). + /// The assembly that exists in. + public TestCollectionOrdererAttribute(string ordererTypeName, string ordererAssemblyName) { } + } +} \ No newline at end of file diff --git a/src/xunit.core/xunit.core.csproj b/src/xunit.core/xunit.core.csproj index 8e80244a6..9406f7550 100644 --- a/src/xunit.core/xunit.core.csproj +++ b/src/xunit.core/xunit.core.csproj @@ -56,6 +56,8 @@ + + diff --git a/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs b/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs index 2b564ea2b..07c946332 100644 --- a/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs +++ b/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs @@ -78,6 +78,14 @@ public static ITestCaseOrderer GetTestCaseOrderer(Type ordererType) return Get(ordererType); } + /// + /// Gets a test collection orderer. + /// + public static ITestCollectionOrderer GetTestCollectionOrderer(Type ordererType) + { + return Get(ordererType); + } + /// /// Gets a test case orderer, as specified in a reflected . /// @@ -93,6 +101,17 @@ public static ITestCaseOrderer GetTestCaseOrderer(IAttributeInfo testCaseOrderer return GetTestCaseOrderer(ordererType); } + + public static ITestCollectionOrderer GetTestCollectionOrderer(IAttributeInfo testCollectionOrdererAttribute) + { + var args = testCollectionOrdererAttribute.GetConstructorArguments().Cast().ToList(); + var ordererType = Reflector.GetType(args[1], args[0]); + if (ordererType == null) + return null; + + return GetTestCollectionOrderer(ordererType); + } + /// /// Gets a trait discoverer. /// diff --git a/src/xunit.execution/Sdk/Frameworks/Runners/TestAssemblyRunner.cs b/src/xunit.execution/Sdk/Frameworks/Runners/TestAssemblyRunner.cs index e7ff1d8e8..5b3a51b2d 100644 --- a/src/xunit.execution/Sdk/Frameworks/Runners/TestAssemblyRunner.cs +++ b/src/xunit.execution/Sdk/Frameworks/Runners/TestAssemblyRunner.cs @@ -63,6 +63,11 @@ public abstract class TestAssemblyRunner : IDisposable /// protected ITestCaseOrderer TestCaseOrderer { get; set; } + /// + /// Gets or sets the test collection orderer that will be used to decide how to order test collections. + /// + protected ITestCollectionOrderer TestCollectionOrderer { get; set; } + /// /// Gets or sets the test cases to be run. /// diff --git a/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs b/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs index 27e6e30c7..19f645a6c 100644 --- a/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs +++ b/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs @@ -92,20 +92,25 @@ protected override void OnAssemblyStarting() scheduler = GetTaskScheduler(maxParallelThreads); var ordererAttribute = AssemblyInfo.GetCustomAttributes(typeof(TestCaseOrdererAttribute)).SingleOrDefault(); - if (ordererAttribute != null) - TestCaseOrderer = ExtensibilityPointFactory.GetTestCaseOrderer(ordererAttribute); + if (ordererAttribute != null) TestCaseOrderer = ExtensibilityPointFactory.GetTestCaseOrderer(ordererAttribute); + + var collectionOrdererAttribute = AssemblyInfo.GetCustomAttributes(typeof(TestCollectionOrdererAttribute)).SingleOrDefault(); + if (collectionOrdererAttribute != null) TestCollectionOrderer = ExtensibilityPointFactory.GetTestCollectionOrderer(collectionOrdererAttribute); } /// protected override async Task RunTestCollectionsAsync(IMessageBus messageBus, CancellationTokenSource cancellationTokenSource) { - if (disableParallelization) - return await base.RunTestCollectionsAsync(messageBus, cancellationTokenSource); - var testCollections = TestCases.Cast() - .GroupBy(tc => tc.TestCollection, TestCollectionComparer.Instance) - .ToArray(); + if (disableParallelization) return await base.RunTestCollectionsAsync(messageBus, cancellationTokenSource); + var testCollections = TestCases.GroupBy(tc => tc.TestCollection, TestCollectionComparer.Instance).ToArray(); + if (TestCollectionOrderer != null) + { + testCollections = TestCollectionOrderer.OrderTestCollections(testCollections).ToArray(); + } + var order = testCollections.Aggregate(String.Empty, (value, group) => value + Environment.NewLine + group.Key.DisplayName); Console.WriteLine("Queueing {0} test collections.", testCollections.Length); + Console.WriteLine("Test collection will be queued in the following order: {0}", order); var tasks = testCollections.Select(collectionGroup => Task.Factory.StartNew(() => RunTestCollectionAsync(messageBus, collectionGroup.Key, collectionGroup, cancellationTokenSource), cancellationTokenSource.Token,