diff --git a/src/xunit.core/CollectionBehavior.cs b/src/xunit.core/CollectionBehavior.cs index b1563e99c..024dac8bd 100644 --- a/src/xunit.core/CollectionBehavior.cs +++ b/src/xunit.core/CollectionBehavior.cs @@ -16,6 +16,12 @@ public enum CollectionBehavior /// By default, generates a collection per test class for any test classes that are not /// decorated with . /// - CollectionPerClass + CollectionPerClass, + + /// + /// By default, generates a collection per test method for any test classes that are not + /// decorated with . + /// + CollectionPerMethod } } \ No newline at end of file diff --git a/src/xunit.core/Sdk/IXunitTestCollectionFactory.cs b/src/xunit.core/Sdk/IXunitTestCollectionFactory.cs index 99047f77a..bda996b62 100644 --- a/src/xunit.core/Sdk/IXunitTestCollectionFactory.cs +++ b/src/xunit.core/Sdk/IXunitTestCollectionFactory.cs @@ -16,10 +16,10 @@ public interface IXunitTestCollectionFactory string DisplayName { get; } /// - /// Gets the test collection for a given test class. + /// Gets the test collection for a given test method. /// - /// The test class. + /// The test method. /// The test collection. - ITestCollection Get(ITypeInfo testClass); + ITestCollection Get(IMethodInfo testMethod); } } \ No newline at end of file diff --git a/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs b/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs index dcac6dc0d..2b564ea2b 100644 --- a/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs +++ b/src/xunit.execution/Sdk/ExtensibilityPointFactory.cs @@ -154,9 +154,14 @@ static Type GetTestCollectionFactoryType(IAttributeInfo collectionBehaviorAttrib if (ctorArgs.Count == 1) { - if ((CollectionBehavior)ctorArgs[0] == CollectionBehavior.CollectionPerAssembly) + var collectionBehaviour = (CollectionBehavior) ctorArgs[0]; + + if (collectionBehaviour == CollectionBehavior.CollectionPerAssembly) return typeof(CollectionPerAssemblyTestCollectionFactory); + if (collectionBehaviour == CollectionBehavior.CollectionPerMethod) + return typeof(CollectionPerMethodTestCollectionFactory); + return typeof(CollectionPerClassTestCollectionFactory); } diff --git a/src/xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactory.cs b/src/xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactory.cs index 7883adc7b..60df11d34 100644 --- a/src/xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactory.cs +++ b/src/xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactory.cs @@ -49,11 +49,11 @@ ITestCollection CreateTestCollection(string name) } /// - public ITestCollection Get(ITypeInfo testClass) + public ITestCollection Get(IMethodInfo testMethod) { + var testClass = testMethod.Type; var collectionAttribute = testClass.GetCustomAttributes(typeof(CollectionAttribute)).SingleOrDefault(); - if (collectionAttribute == null) - return defaultCollection; + if (collectionAttribute == null) return defaultCollection; var collectionName = (string)collectionAttribute.GetConstructorArguments().First(); return testCollections.GetOrAdd(collectionName, CreateTestCollection); diff --git a/src/xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactory.cs b/src/xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactory.cs index fc5bbab61..19e26e5b2 100644 --- a/src/xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactory.cs +++ b/src/xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactory.cs @@ -46,8 +46,9 @@ ITestCollection CreateCollection(string name) } /// - public ITestCollection Get(ITypeInfo testClass) + public ITestCollection Get(IMethodInfo testMethod) { + var testClass = testMethod.Type; string collectionName; var collectionAttribute = testClass.GetCustomAttributes(typeof(CollectionAttribute)).SingleOrDefault(); diff --git a/src/xunit.execution/Sdk/Frameworks/CollectionPerMethodTestCollectionFactory.cs b/src/xunit.execution/Sdk/Frameworks/CollectionPerMethodTestCollectionFactory.cs new file mode 100644 index 000000000..03640f836 --- /dev/null +++ b/src/xunit.execution/Sdk/Frameworks/CollectionPerMethodTestCollectionFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; + +namespace Xunit.Sdk +{ + /// + /// Implementation of which creates a new test + /// collection for each test method that isn't decorated with . + /// + public class CollectionPerMethodTestCollectionFactory : IXunitTestCollectionFactory + { + readonly Dictionary collectionDefinitions; + readonly ConcurrentDictionary testCollections = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// The assembly. + public CollectionPerMethodTestCollectionFactory(IAssemblyInfo assemblyInfo) + : this(assemblyInfo, MessageAggregator.Instance) { } + + /// + /// Initializes a new instance of the class. + /// + /// The assembly info. + /// The message aggregator used to report messages. + public CollectionPerMethodTestCollectionFactory(IAssemblyInfo assemblyInfo, IMessageAggregator messageAggregator) + { + collectionDefinitions = TestCollectionFactoryHelper.GetTestCollectionDefinitions(assemblyInfo, messageAggregator); + } + + /// + public string DisplayName + { + get { return "collection-per-method"; } + } + + ITestCollection CreateCollection(string name) + { + ITypeInfo definitionType; + collectionDefinitions.TryGetValue(name, out definitionType); + return new XunitTestCollection { CollectionDefinition = definitionType, DisplayName = name }; + } + + /// + public ITestCollection Get(IMethodInfo testMethod) + { + string collectionName; + var collectionAttribute = testMethod.GetCustomAttributes(typeof(CollectionAttribute)).SingleOrDefault(); + + if (collectionAttribute == null) + collectionName = "Test collection for " + testMethod.Type.Name + "." + testMethod.Name; + else + collectionName = (string)collectionAttribute.GetConstructorArguments().First(); + + return testCollections.GetOrAdd(collectionName, CreateCollection); + } + } +} \ No newline at end of file diff --git a/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs b/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs index e8b6818e1..27e6e30c7 100644 --- a/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs +++ b/src/xunit.execution/Sdk/Frameworks/Runners/XunitTestAssemblyRunner.cs @@ -101,15 +101,18 @@ protected override async Task RunTestCollectionsAsync(IMessageBus me { if (disableParallelization) return await base.RunTestCollectionsAsync(messageBus, cancellationTokenSource); - - var tasks = TestCases.Cast() - .GroupBy(tc => tc.TestCollection, TestCollectionComparer.Instance) - .Select(collectionGroup => Task.Factory.StartNew(() => RunTestCollectionAsync(messageBus, collectionGroup.Key, collectionGroup, cancellationTokenSource), - cancellationTokenSource.Token, - TaskCreationOptions.None, - scheduler)) - .ToArray(); - + var testCollections = TestCases.Cast() + .GroupBy(tc => tc.TestCollection, TestCollectionComparer.Instance) + .ToArray(); + + Console.WriteLine("Queueing {0} test collections.", testCollections.Length); + + var tasks = testCollections.Select(collectionGroup => Task.Factory.StartNew(() => RunTestCollectionAsync(messageBus, collectionGroup.Key, collectionGroup, cancellationTokenSource), + cancellationTokenSource.Token, + TaskCreationOptions.None, + scheduler)) + .ToArray(); + var summaries = await Task.WhenAll(tasks.Select(t => t.Unwrap())); return new RunSummary() diff --git a/src/xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscoverer.cs b/src/xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscoverer.cs index 0e724c8d5..a8056d767 100644 --- a/src/xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscoverer.cs +++ b/src/xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscoverer.cs @@ -93,11 +93,11 @@ protected virtual bool FindTestsForMethod(ITestCollection testCollection, ITypeI /// protected override bool FindTestsForType(ITypeInfo type, bool includeSourceInformation, IMessageBus messageBus) { - var testCollection = TestCollectionFactory.Get(type); - foreach (var method in type.GetMethods(includePrivateMethods: true)) - if (!FindTestsForMethod(testCollection, type, method, includeSourceInformation, messageBus)) - return false; + { + var testCollection = TestCollectionFactory.Get(method); + if (!FindTestsForMethod(testCollection, type, method, includeSourceInformation, messageBus))return false; + } return true; } diff --git a/src/xunit.execution/xunit.execution.csproj b/src/xunit.execution/xunit.execution.csproj index 9ea49082e..ef144402a 100644 --- a/src/xunit.execution/xunit.execution.csproj +++ b/src/xunit.execution/xunit.execution.csproj @@ -77,6 +77,7 @@ + @@ -96,7 +97,7 @@ - + diff --git a/test/test.xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactoryTests.cs b/test/test.xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactoryTests.cs index c5c241926..6f0a63147 100644 --- a/test/test.xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactoryTests.cs +++ b/test/test.xunit.execution/Sdk/Frameworks/CollectionPerAssemblyTestCollectionFactoryTests.cs @@ -7,14 +7,14 @@ public class CollectionPerAssemblyTestCollectionFactoryTests [Fact] public void ReturnsDefaultTestCollectionForUndecoratedTestClass() { - var type1 = Mocks.TypeInfo("type1"); - var type2 = Mocks.TypeInfo("type2"); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("type1")); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("type2")); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerAssemblyTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.Same(result1, result2); Assert.Equal("Test collection for bar.dll", result1.DisplayName); @@ -24,14 +24,14 @@ public void ReturnsDefaultTestCollectionForUndecoratedTestClass() public void ClassesDecoratedWithSameCollectionNameAreInSameTestCollection() { var attr = Mocks.CollectionAttribute("My Collection"); - var type1 = Mocks.TypeInfo("type1", attributes: new[] { attr }); - var type2 = Mocks.TypeInfo("type2", attributes: new[] { attr }); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("type1", attributes: new[] { attr })); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("type2", attributes: new[] { attr })); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerAssemblyTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.Same(result1, result2); Assert.Equal("My Collection", result1.DisplayName); @@ -40,14 +40,14 @@ public void ClassesDecoratedWithSameCollectionNameAreInSameTestCollection() [Fact] public void ClassesWithDifferentCollectionNamesHaveDifferentCollectionObjects() { - var type1 = Mocks.TypeInfo("type1", attributes: new[] { Mocks.CollectionAttribute("Collection 1") }); - var type2 = Mocks.TypeInfo("type2", attributes: new[] { Mocks.CollectionAttribute("Collection 2") }); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("type1", attributes: new[] { Mocks.CollectionAttribute("Collection 1") })); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("type2", attributes: new[] { Mocks.CollectionAttribute("Collection 2") })); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerAssemblyTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.NotSame(result1, result2); Assert.Equal("Collection 1", result1.DisplayName); @@ -57,13 +57,13 @@ public void ClassesWithDifferentCollectionNamesHaveDifferentCollectionObjects() [Fact] public void UsingTestCollectionDefinitionSetsTypeInfo() { - var testType = Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") }); + var testMethod = Mocks.MethodInfo(type: Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") })); var collectionDefinitionType = Mocks.TypeInfo("collectionDefinition", attributes: new[] { Mocks.CollectionDefinitionAttribute("This is a test collection") }); var assembly = Mocks.AssemblyInfo(new[] { collectionDefinitionType }); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerAssemblyTestCollectionFactory(assembly); - var result = factory.Get(testType); + var result = factory.Get(testMethod); Assert.Same(collectionDefinitionType, result.CollectionDefinition); } @@ -72,14 +72,14 @@ public void UsingTestCollectionDefinitionSetsTypeInfo() public void MultiplyDeclaredCollectionsRaisesEnvironmentalWarning() { var broker = Substitute.For(); - var testType = Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") }); + var testMethod = Mocks.MethodInfo(type: Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") })); var collectionDefinition1 = Mocks.TypeInfo("collectionDefinition1", attributes: new[] { Mocks.CollectionDefinitionAttribute("This is a test collection") }); var collectionDefinition2 = Mocks.TypeInfo("collectionDefinition2", attributes: new[] { Mocks.CollectionDefinitionAttribute("This is a test collection") }); var assembly = Mocks.AssemblyInfo(new[] { collectionDefinition1, collectionDefinition2 }); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerAssemblyTestCollectionFactory(assembly, broker); - factory.Get(testType); + factory.Get(testMethod); var captured = broker.Captured(b => b.Add(null)); var warning = captured.Arg(); diff --git a/test/test.xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactoryTests.cs b/test/test.xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactoryTests.cs index 33914e166..2d12f3ad0 100644 --- a/test/test.xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactoryTests.cs +++ b/test/test.xunit.execution/Sdk/Frameworks/CollectionPerClassTestCollectionFactoryTests.cs @@ -8,14 +8,14 @@ public class CollectionPerClassTestCollectionFactoryTests [Fact] public void DefaultCollectionBehaviorIsCollectionPerClass() { - var type1 = Mocks.TypeInfo("FullyQualified.Type.Number1"); - var type2 = Mocks.TypeInfo("FullyQualified.Type.Number2"); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("FullyQualified.Type.Number1")); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("FullyQualified.Type.Number2")); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerClassTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.NotSame(result1, result2); Assert.Equal("Test collection for FullyQualified.Type.Number1", result1.DisplayName); @@ -28,14 +28,14 @@ public void DefaultCollectionBehaviorIsCollectionPerClass() public void ClassesDecoratedWithSameCollectionNameAreInSameTestCollection() { var attr = Mocks.CollectionAttribute("My Collection"); - var type1 = Mocks.TypeInfo("type1", attributes: new[] { attr }); - var type2 = Mocks.TypeInfo("type2", attributes: new[] { attr }); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("type1", attributes: new[] { attr })); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("type2", attributes: new[] { attr })); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerClassTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.Same(result1, result2); Assert.Equal("My Collection", result1.DisplayName); @@ -44,14 +44,14 @@ public void ClassesDecoratedWithSameCollectionNameAreInSameTestCollection() [Fact] public void ClassesWithDifferentCollectionNamesHaveDifferentCollectionObjects() { - var type1 = Mocks.TypeInfo("type1", attributes: new[] { Mocks.CollectionAttribute("Collection 1") }); - var type2 = Mocks.TypeInfo("type2", attributes: new[] { Mocks.CollectionAttribute("Collection 2") }); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("type1", attributes: new[] { Mocks.CollectionAttribute("Collection 1") })); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("type2", attributes: new[] { Mocks.CollectionAttribute("Collection 2") })); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerClassTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.NotSame(result1, result2); Assert.Equal("Collection 1", result1.DisplayName); @@ -61,14 +61,14 @@ public void ClassesWithDifferentCollectionNamesHaveDifferentCollectionObjects() [Fact] public void ExplicitlySpecifyingACollectionWithTheSameNameAsAnImplicitWorks() { - var type1 = Mocks.TypeInfo("type1"); - var type2 = Mocks.TypeInfo("type2", attributes: new[] { Mocks.CollectionAttribute("Test collection for type1") }); + var method1 = Mocks.MethodInfo(type: Mocks.TypeInfo("type1")); + var method2 = Mocks.MethodInfo(type: Mocks.TypeInfo("type2", attributes: new[] { Mocks.CollectionAttribute("Test collection for type1") })); var assembly = Mocks.AssemblyInfo(); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerClassTestCollectionFactory(assembly); - var result1 = factory.Get(type1); - var result2 = factory.Get(type2); + var result1 = factory.Get(method1); + var result2 = factory.Get(method2); Assert.Same(result1, result2); Assert.Equal("Test collection for type1", result1.DisplayName); @@ -77,13 +77,13 @@ public void ExplicitlySpecifyingACollectionWithTheSameNameAsAnImplicitWorks() [Fact] public void UsingTestCollectionDefinitionSetsTypeInfo() { - var testType = Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") }); + var testMethod = Mocks.MethodInfo(type: Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") })); var collectionDefinitionType = Mocks.TypeInfo("collectionDefinition", attributes: new[] { Mocks.CollectionDefinitionAttribute("This is a test collection") }); var assembly = Mocks.AssemblyInfo(new[] { collectionDefinitionType }); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerClassTestCollectionFactory(assembly); - var result = factory.Get(testType); + var result = factory.Get(testMethod); Assert.Same(collectionDefinitionType, result.CollectionDefinition); } @@ -92,14 +92,14 @@ public void UsingTestCollectionDefinitionSetsTypeInfo() public void MultiplyDeclaredCollectionsRaisesEnvironmentalWarning() { var broker = Substitute.For(); - var testType = Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") }); + var testMethod = Mocks.MethodInfo(type: Mocks.TypeInfo("type", attributes: new[] { Mocks.CollectionAttribute("This is a test collection") })); var collectionDefinition1 = Mocks.TypeInfo("collectionDefinition1", attributes: new[] { Mocks.CollectionDefinitionAttribute("This is a test collection") }); var collectionDefinition2 = Mocks.TypeInfo("collectionDefinition2", attributes: new[] { Mocks.CollectionDefinitionAttribute("This is a test collection") }); var assembly = Mocks.AssemblyInfo(new[] { collectionDefinition1, collectionDefinition2 }); assembly.AssemblyPath.Returns(@"C:\Foo\bar.dll"); var factory = new CollectionPerClassTestCollectionFactory(assembly, broker); - factory.Get(testType); + factory.Get(testMethod); var captured = broker.Captured(b => b.Add(null)); var warning = captured.Arg(); diff --git a/test/test.xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscovererTests.cs b/test/test.xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscovererTests.cs index 7600b298d..912b5f0bf 100644 --- a/test/test.xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscovererTests.cs +++ b/test/test.xunit.execution/Sdk/Frameworks/XunitTestFrameworkDiscovererTests.cs @@ -35,6 +35,7 @@ public static void DefaultTestCollectionFactoryIsCollectionPerClass() [Theory] [InlineData(CollectionBehavior.CollectionPerAssembly, typeof(CollectionPerAssemblyTestCollectionFactory), "collection-per-assembly")] [InlineData(CollectionBehavior.CollectionPerClass, typeof(CollectionPerClassTestCollectionFactory), "collection-per-class")] + [InlineData(CollectionBehavior.CollectionPerMethod, typeof(CollectionPerMethodTestCollectionFactory), "collection-per-method")] public static void UserCanChooseFromBuiltInCollectionFactories_NonParallel(CollectionBehavior behavior, Type expectedType, string expectedDisplayText) { var attr = Mocks.CollectionBehaviorAttribute(behavior, disableTestParallelization: true); @@ -67,7 +68,7 @@ class MyTestCollectionFactory : IXunitTestCollectionFactory public MyTestCollectionFactory(IAssemblyInfo assembly) { } - public ITestCollection Get(ITypeInfo testClass) + public ITestCollection Get(IMethodInfo methodInfo) { throw new NotImplementedException(); } @@ -96,7 +97,7 @@ public string DisplayName get { throw new NotImplementedException(); } } - public ITestCollection Get(ITypeInfo testClass) + public ITestCollection Get(IMethodInfo testMethod) { throw new NotImplementedException(); }