diff --git a/.github/workflows/nethermind-tests.yml b/.github/workflows/nethermind-tests.yml
index a71c5492c31..6a8d009b0a1 100644
--- a/.github/workflows/nethermind-tests.yml
+++ b/.github/workflows/nethermind-tests.yml
@@ -23,7 +23,7 @@ env:
jobs:
tests:
- name: Run ${{ matrix.project }}
+ name: Run ${{ matrix.project }}${{ matrix.chunk && format(' ({0})', matrix.chunk) || '' }}
runs-on: ubuntu-latest
continue-on-error: true
outputs:
@@ -40,7 +40,6 @@ jobs:
- Ethereum.KeyAddress.Test
- Ethereum.KeyStore.Test
- Ethereum.Legacy.Blockchain.Block.Test
- - Ethereum.Legacy.Blockchain.Test
- Ethereum.Legacy.Transition.Test
- Ethereum.Legacy.VM.Test
- Ethereum.PoW.Test
@@ -91,6 +90,16 @@ jobs:
- Nethermind.TxPool.Test
- Nethermind.Wallet.Test
- Nethermind.Xdc.Test
+ chunk: ['']
+ include:
+ - project: Ethereum.Legacy.Blockchain.Test
+ chunk: 1of4
+ - project: Ethereum.Legacy.Blockchain.Test
+ chunk: 2of4
+ - project: Ethereum.Legacy.Blockchain.Test
+ chunk: 3of4
+ - project: Ethereum.Legacy.Blockchain.Test
+ chunk: 4of4
steps:
- name: Check out repository
uses: actions/checkout@v6
@@ -103,6 +112,8 @@ jobs:
- name: ${{ matrix.project }}
id: test
working-directory: src/Nethermind/${{ matrix.project }}
+ env:
+ TEST_CHUNK: ${{ matrix.chunk }}
run: |
dotnet test --project ${{ matrix.project }}.csproj -c release \
${{ env.COLLECT_COVERAGE == 'true' && '--coverage --coverage-output-format cobertura --coverage-settings $GITHUB_WORKSPACE/src/Nethermind/codecoverage.config' || '' }}
@@ -115,7 +126,7 @@ jobs:
if: success() || failure()
uses: actions/upload-artifact@v4
with:
- name: ${{ matrix.project }}-outcome
+ name: ${{ matrix.project }}${{ matrix.chunk && format('-{0}', matrix.chunk) || '' }}-outcome
path: test.outcome
retention-days: 1
@@ -123,7 +134,7 @@ jobs:
if: env.COLLECT_COVERAGE == 'true'
uses: actions/upload-artifact@v4
with:
- name: ${{ matrix.project }}-coverage
+ name: ${{ matrix.project }}${{ matrix.chunk && format('-{0}', matrix.chunk) || '' }}-coverage
path: src/Nethermind/artifacts/bin/${{ matrix.project }}/release/TestResults/*.cobertura.xml
retention-days: 1
diff --git a/src/Nethermind/Ethereum.Test.Base/TestChunkFilter.cs b/src/Nethermind/Ethereum.Test.Base/TestChunkFilter.cs
new file mode 100644
index 00000000000..027425574ff
--- /dev/null
+++ b/src/Nethermind/Ethereum.Test.Base/TestChunkFilter.cs
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ethereum.Test.Base;
+
+///
+/// Filters tests into chunks for parallel CI execution.
+/// Set TEST_CHUNK environment variable to "1of3", "2of3", "3of3" etc.
+/// Partitions tests by index for consistent and even distribution.
+///
+public static class TestChunkFilter
+{
+ public static IEnumerable FilterByChunk(IEnumerable tests)
+ {
+ (int Index, int Total)? chunkConfig = GetChunkConfig();
+
+ if (chunkConfig is null)
+ {
+ return tests;
+ }
+
+ ICollection testList = tests as ICollection ?? [.. tests];
+ (int chunkIndex, int totalChunks) = chunkConfig.Value;
+
+ int count = testList.Count;
+ int chunkSize = count / totalChunks;
+ int remainder = count % totalChunks;
+
+ int skip = (chunkIndex - 1) * chunkSize;
+ int take = chunkSize + (chunkIndex == totalChunks ? remainder : 0);
+
+ return testList.Skip(skip).Take(take);
+
+ static (int Index, int Total)? GetChunkConfig()
+ {
+ string? chunkEnv = Environment.GetEnvironmentVariable("TEST_CHUNK");
+
+ if (string.IsNullOrEmpty(chunkEnv))
+ {
+ return null;
+ }
+
+ string[] parts = chunkEnv.Split("of", StringSplitOptions.None);
+
+ if (parts.Length != 2 ||
+ !int.TryParse(parts[0], out int index) ||
+ !int.TryParse(parts[1], out int total) ||
+ index < 1 || index > total || total < 1)
+ {
+ throw new ArgumentException($"Invalid TEST_CHUNK format: '{chunkEnv}'. Expected format: '1of3', '2of5', etc.");
+ }
+
+ return (index, total);
+ }
+ }
+}
diff --git a/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs b/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs
index 8d8108d6abc..4c20099ce56 100644
--- a/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs
+++ b/src/Nethermind/Ethereum.Test.Base/TestsSourceLoader.cs
@@ -24,12 +24,15 @@ public TestsSourceLoader(ITestLoadStrategy testLoadStrategy, string path, string
public IEnumerable LoadTests()
{
- return _testLoadStrategy.Load(_path, _wildcard);
+ IEnumerable tests = _testLoadStrategy.Load(_path, _wildcard);
+ return TestChunkFilter.FilterByChunk(tests);
}
+
public IEnumerable LoadTests()
where TTestType : EthereumTest
{
- return _testLoadStrategy.Load(_path, _wildcard).Cast();
+ IEnumerable tests = _testLoadStrategy.Load(_path, _wildcard).Cast();
+ return TestChunkFilter.FilterByChunk(tests);
}
}
}