Skip to content

Commit 59d8dcf

Browse files
Parallelise mutation tests (#2395)
- Run each mutation test project in parallel in its own GitHub Actions job. - Install the .NET 8 SDK when required now we only explicitly install .NET 6 and 9. - Minimise redundant work when running mutation tests.
1 parent f5cbe89 commit 59d8dcf

3 files changed

Lines changed: 83 additions & 47 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ jobs:
6262
with:
6363
dotnet-version: |
6464
6.0.x
65+
8.0.x
6566
6667
- name: Setup .NET SDK
6768
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0

.github/workflows/mutation-tests.yml

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ jobs:
3131
include:
3232
- name: core
3333
target: Core
34+
- name: extensions
35+
target: Extensions
3436
- name: legacy
3537
target: Legacy
38+
- name: ratelimiting
39+
target: RateLimiting
40+
- name: testing
41+
target: Testing
3642

3743
steps:
3844

@@ -46,26 +52,37 @@ jobs:
4652
with:
4753
dotnet-version: |
4854
6.0.x
55+
8.0.x
4956
5057
- name: Setup .NET SDK
5158
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
5259

53-
- name: Setup NuGet cache
54-
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
55-
with:
56-
path: ~/.nuget/packages
57-
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props') }}
58-
restore-keys: ${{ runner.os }}-nuget-
59-
60-
- name: Run mutation tests
60+
- name: Run mutation tests for ${{ matrix.target }}
6161
shell: pwsh
6262
env:
63-
MUTATION_TARGET: 'Mutation${{ matrix.target }}'
63+
MUTATION_TARGET: 'MutationTests${{ matrix.target }}'
64+
SKIP_POLLY_ANALYZERS: 'true'
6465
run: ./build.ps1 -Target ${env:MUTATION_TARGET}
6566

6667
- name: Upload Mutation Report
67-
if: always()
68+
if: ${{ !cancelled() }}
6869
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
6970
with:
7071
name: mutation-report-${{ matrix.name }}
7172
path: ./artifacts/mutation-report
73+
74+
mutation-tests:
75+
needs: [ mutations ]
76+
if: ${{ always() }}
77+
runs-on: ubuntu-latest
78+
env:
79+
MUTATIONS_SUCCESS: ${{ !contains(needs.*.result, 'failure') }}
80+
steps:
81+
- run: |
82+
if [ "$MUTATIONS_SUCCESS" == "true" ]
83+
then
84+
echo 'Mutation tests successful ✅'
85+
else
86+
echo 'One or more mutation test runs failed ❌'
87+
exit 1
88+
fi

build.cake

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,6 @@ Task("__RunTests")
159159
}
160160
});
161161

162-
Task("__RunCoreMutationTests")
163-
.Does((context) =>
164-
{
165-
MutationTestProject(File("./src/Polly.Core/Polly.Core.csproj"), File("./test/Polly.Core.Tests/Polly.Core.Tests.csproj"), "Polly.Core.csproj");
166-
MutationTestProject(File("./src/Polly.RateLimiting/Polly.RateLimiting.csproj"), File("./test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj"), "Polly.RateLimiting.csproj");
167-
MutationTestProject(File("./src/Polly.Extensions/Polly.Extensions.csproj"), File("./test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj"), "Polly.Extensions.csproj");
168-
MutationTestProject(File("./src/Polly.Testing/Polly.Testing.csproj"), File("./test/Polly.Testing.Tests/Polly.Testing.Tests.csproj"), "Polly.Testing.csproj");
169-
});
170-
171-
Task("__RunLegacyMutationTests")
172-
.Does((context) =>
173-
{
174-
MutationTestProject(File("./src/Polly/Polly.csproj"), File("./test/Polly.Specs/Polly.Specs.csproj"), "Polly.csproj");
175-
});
176-
177162
Task("__CreateNuGetPackages")
178163
.Does(() =>
179164
{
@@ -215,9 +200,12 @@ Task("__ValidateDocs")
215200
}
216201
});
217202

218-
Task("__CommonBuild")
203+
Task("__Setup")
219204
.IsDependentOn("__Clean")
220-
.IsDependentOn("__RestoreNuGetPackages")
205+
.IsDependentOn("__RestoreNuGetPackages");
206+
207+
Task("__CommonBuild")
208+
.IsDependentOn("__Setup")
221209
.IsDependentOn("__ValidateDocs")
222210
.IsDependentOn("__BuildSolutions");
223211

@@ -238,13 +226,51 @@ Task("Build")
238226
Task("Default")
239227
.IsDependentOn("Build");
240228

241-
Task("MutationCore")
242-
.IsDependentOn("__CommonBuild")
243-
.IsDependentOn("__RunCoreMutationTests");
229+
///////////////////////////////////////////////////////////////////////////////
230+
// MUTATION TESTING TARGETS
231+
///////////////////////////////////////////////////////////////////////////////
244232

245-
Task("MutationLegacy")
246-
.IsDependentOn("__CommonBuild")
247-
.IsDependentOn("__RunLegacyMutationTests");
233+
Task("MutationTestsCore")
234+
.IsDependentOn("__Setup")
235+
.Does((context) =>
236+
{
237+
RunMutationTests(File("./src/Polly.Core/Polly.Core.csproj"), File("./test/Polly.Core.Tests/Polly.Core.Tests.csproj"));
238+
});
239+
240+
Task("MutationTestsRateLimiting")
241+
.IsDependentOn("__Setup")
242+
.Does((context) =>
243+
{
244+
RunMutationTests(File("./src/Polly.RateLimiting/Polly.RateLimiting.csproj"), File("./test/Polly.RateLimiting.Tests/Polly.RateLimiting.Tests.csproj"));
245+
});
246+
247+
Task("MutationTestsExtensions")
248+
.IsDependentOn("__Setup")
249+
.Does((context) =>
250+
{
251+
RunMutationTests(File("./src/Polly.Extensions/Polly.Extensions.csproj"), File("./test/Polly.Extensions.Tests/Polly.Extensions.Tests.csproj"));
252+
});
253+
254+
Task("MutationTestsTesting")
255+
.IsDependentOn("__Setup")
256+
.Does((context) =>
257+
{
258+
RunMutationTests(File("./src/Polly.Testing/Polly.Testing.csproj"), File("./test/Polly.Testing.Tests/Polly.Testing.Tests.csproj"));
259+
});
260+
261+
Task("MutationTestsLegacy")
262+
.IsDependentOn("__Setup")
263+
.Does((context) =>
264+
{
265+
RunMutationTests(File("./src/Polly/Polly.csproj"), File("./test/Polly.Specs/Polly.Specs.csproj"));
266+
});
267+
268+
Task("MutationTests")
269+
.IsDependentOn("MutationTestsCore")
270+
.IsDependentOn("MutationTestsRateLimiting")
271+
.IsDependentOn("MutationTestsExtensions")
272+
.IsDependentOn("MutationTestsTesting")
273+
.IsDependentOn("MutationTestsLegacy");
248274

249275
///////////////////////////////////////////////////////////////////////////////
250276
// EXECUTION
@@ -261,28 +287,20 @@ string ToolsExePath(string exeFileName) {
261287
return exePath;
262288
}
263289

264-
void MutationTestProject(FilePath proj, FilePath testProj, string project)
290+
void RunMutationTests(FilePath target, FilePath testProject)
265291
{
266-
var dotNetBuildSettings = new DotNetBuildSettings
267-
{
268-
Configuration = "Debug",
269-
Verbosity = DotNetVerbosity.Minimal,
270-
NoRestore = true
271-
};
272-
273-
DotNetBuild(proj.ToString(), dotNetBuildSettings);
274-
275292
var strykerPath = Context.Tools.Resolve("Stryker.CLI.dll");
276-
var mutationScore = XmlPeek(proj, "/Project/PropertyGroup/MutationScore/text()", new XmlPeekSettings { SuppressWarning = true });
293+
var mutationScore = XmlPeek(target, "/Project/PropertyGroup/MutationScore/text()", new XmlPeekSettings { SuppressWarning = true });
277294
var score = int.Parse(mutationScore);
295+
var targetFileName = target.GetFilename();
278296

279-
Information($"Running mutation tests for '{proj}'. Test Project: '{testProj}'");
297+
Information($"Running mutation tests for '{targetFileName}'. Test Project: '{testProject}'");
280298

281-
var args = $"{strykerPath} --project {project} --test-project {testProj.FullPath} --break-at {score} --config-file {strykerConfig} --output {strykerOutput}/{project}";
299+
var args = $"{strykerPath} --project {targetFileName} --test-project {testProject.FullPath} --break-at {score} --config-file {strykerConfig} --output {strykerOutput}/{targetFileName}";
282300

283301
var result = StartProcess("dotnet", args);
284302
if (result != 0)
285303
{
286-
throw new InvalidOperationException($"The mutation testing of '{project}' project failed.");
304+
throw new InvalidOperationException($"The mutation testing of '{targetFileName}' project failed.");
287305
}
288306
}

0 commit comments

Comments
 (0)