Skip to content

Commit

Permalink
feat: Run classes in parallel if they are in a single collection (#17)
Browse files Browse the repository at this point in the history
* Run classes in parallel if they are in a single collection

* Don't break default behavior

* Bump version to 2.3.0

* Fix release build

* Update Analyzer to 2.0.162

---------

Co-authored-by: Preben Huybrechts <[email protected]>
  • Loading branch information
pregress and pregress authored Jul 30, 2024
1 parent 3ea52fd commit cd06c29
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

<!-- Analyzers -->
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="1.0.644">
<PackageReference Include="Meziantou.Analyzer" Version="2.0.162">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Meziantou.Xunit.ParallelTestFramework.Tests;

public class CollectionConcurrencyFixture
{
private int concurrency;

public async Task<int> CheckConcurrencyAsync()
{
Interlocked.Increment(ref concurrency);
await Task.Delay(TimeSpan.FromMilliseconds(1000)).ConfigureAwait(false);

var overlap = concurrency;

await Task.Delay(TimeSpan.FromMilliseconds(1000)).ConfigureAwait(false);
Interlocked.Decrement(ref concurrency);

return overlap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Xunit;

namespace Meziantou.Xunit.ParallelTestFramework.Tests;

[Collection("ParallelMultiClass")]
public class ParallelCollectionMultiClass1AttributeTests
{
private readonly CollectionConcurrencyFixture fixture;

public ParallelCollectionMultiClass1AttributeTests(CollectionConcurrencyFixture fixture)
{
this.fixture = fixture;
}

[Fact]
public async Task Fact1()
{
Assert.Equal(2, await fixture.CheckConcurrencyAsync().ConfigureAwait(false));
}

[Fact]
public async Task Fact2()
{
Assert.Equal(2, await fixture.CheckConcurrencyAsync().ConfigureAwait(false));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Xunit;

namespace Meziantou.Xunit.ParallelTestFramework.Tests;

[Collection("ParallelMultiClass")]
public class ParallelCollectionMultiClass2AttributeTests
{
private readonly CollectionConcurrencyFixture fixture;

public ParallelCollectionMultiClass2AttributeTests(CollectionConcurrencyFixture fixture)
{
this.fixture = fixture;
}

[Fact]
public async Task Fact1()
{
Assert.Equal(2, await fixture.CheckConcurrencyAsync().ConfigureAwait(false));
}

[Fact]
public async Task Fact2()
{
Assert.Equal(2, await fixture.CheckConcurrencyAsync().ConfigureAwait(false));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Xunit;

namespace Meziantou.Xunit.ParallelTestFramework.Tests;

[CollectionDefinition("ParallelMultiClass")]
[EnableParallelization]
public class ParallelMultiClassCollectionFixture : ICollectionFixture<CollectionConcurrencyFixture>
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Description>Run xUnit test cases in parallel</Description>
<IsPackable>True</IsPackable>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Version>2.2.0</Version>
<Version>2.3.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

Expand All @@ -12,4 +13,36 @@ public ParallelTestCollectionRunner(ITestCollection testCollection, IEnumerable<

protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
=> new ParallelTestClassRunner(testClass, @class, testCases, DiagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource, CollectionFixtureMappings).RunAsync();


protected override async Task<RunSummary> RunTestClassesAsync()
{

if (TestCollection.CollectionDefinition != null)
{
var enableParallelizationAttribute = TestCollection.CollectionDefinition.GetCustomAttributes(typeof(EnableParallelizationAttribute)).Any();
if (enableParallelizationAttribute)
{
var summary = new RunSummary();

var classTasks = TestCases.GroupBy(tc => tc.TestMethod.TestClass, TestClassComparer.Instance)
.Select(tc => RunTestClassAsync(tc.Key, (IReflectionTypeInfo)tc.Key.Class, tc));

var classSummaries = await Task.WhenAll(classTasks)
#if !NETSTANDARD
.WaitAsync(CancellationTokenSource.Token)
#endif
.ConfigureAwait(false);
foreach (var classSummary in classSummaries)
{
summary.Aggregate(classSummary);
}

return summary;
}
}

// Fall back to default behavior
return await base.RunTestClassesAsync().ConfigureAwait(false);
}
}
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,43 @@ public class SequentialTests
````

The code is greatly inspired by the sample from [Travis Mortimer](https://github.com/tmort93): <https://github.com/xunit/xunit/issues/1986#issuecomment-831322722>


## Parallel in a collection

Using the `EnableParallelizationAttribute` on an `ICollectionFixture<T>` enables the parallel execution between classes in a collection.
If you want to enable parallisation inside a class you still need to add the `EnableParallelizationAttribute` on the test class aswell.

```c#
[CollectionDefinition("MyFixture Collection")]
[EnableParallelization] // This enables the parallel execution of classes in a collection
public class MyFixtureCollection : ICollectionFixture<MyFixture>
{
}

[Collection("MyFixture Collection")]
public class MyFirstTestClass
{
private readonly MyFixture fixture;

public ParallelCollectionMultiClass1AttributeTests(MyFixture fixture)
{
this.fixture = fixture;
}

//...
}

[Collection("MyFixture Collection")]
public class MySecondTestClass
{
private readonly MyFixture fixture;

public ParallelCollectionMultiClass1AttributeTests(MyFixture fixture)
{
this.fixture = fixture;
}

//...
}
```

0 comments on commit cd06c29

Please sign in to comment.