How to Implement Test Sharding for Parallel Execution in Catch2

Catch2 implements test sharding through the --shard-count and --shard-index CLI flags (or Bazel environment variables), which partition the test suite deterministically across workers via the createShard algorithm in src/catch2/internal/catch_sharding.hpp.

Test sharding enables parallel execution of large test suites by splitting tests across multiple workers, CPU cores, or CI nodes. In the catchorg/Catch2 framework, this capability is built directly into the test runner and requires no external tools. By configuring test sharding parameters, you can distribute your test load evenly while maintaining deterministic assignment and standard reporting workflows.

Command-Line Interface for Test Sharding

Catch2 exposes sharding control through two primary command-line options parsed during startup:

  • --shard-count=<n> – Specifies the total number of shards to divide the test suite into.
  • --shard-index=<i> – Specifies the zero-based index of the current shard to execute.

These values are parsed into the Config object defined in src/catch2/catch_config.hpp and accessed at runtime via Config::shardCount() and Config::shardIndex(). If sharding options are omitted, shardCount defaults to 1, causing the runner to execute the entire suite without partitioning.


# Execute shard 2 of 4 total shards

./tests/MyTests --shard-count=4 --shard-index=2

Bazel Integration

When running under Bazel, Catch2 detects the BAZEL_TEST=1 environment variable and reads sharding configuration from standard Bazel environment variables instead of CLI flags:

Environment Variable Equivalent CLI Flag Purpose
TEST_TOTAL_SHARDS --shard-count Total number of shards
TEST_SHARD_INDEX --shard-index Index of current shard
TEST_SHARD_STATUS_FILE N/A Path to status file created on successful completion

The integration is verified by the test script in tests/TestScripts/testBazelSharding.py, which demonstrates launching the binary with these variables and validating the creation of the status file.

export BAZEL_TEST=1
export TEST_TOTAL_SHARDS=4
export TEST_SHARD_INDEX=1
export TEST_SHARD_STATUS_FILE=/tmp/shard.status
./tests/MyTests

The Sharding Algorithm

The core partitioning logic resides in src/catch2/internal/catch_sharding.hpp within the createShard function. This algorithm ensures deterministic and even distribution using integer arithmetic:

  1. Validation – Verifies that shardCount is greater than shardIndex.
  2. Base calculation – Computes totalTestCount / shardCount as the base number of tests per shard.
  3. Remainder distribution – The first totalTestCount % shardCount shards receive one additional test to handle uneven division.
  4. Slice extraction – Calculates start and end indices for the requested shard, then constructs a new container containing only the tests for that slice.

Because the algorithm relies solely on deterministic arithmetic, test assignment remains stable across runs provided the underlying test order is consistent (e.g., using --order decl).

Implementation Flow

Test sharding integrates into the execution pipeline in src/catch2/catch_session.cpp. The Session class orchestrates the following sequence:

  1. Configuration parsing – Command-line options populate the Config object.

  2. Test collection – All test cases matching the current TestSpec are gathered into a TestGroup.

  3. Shard creation – The session calls createShard with the collected tests, shardCount, and shardIndex:

    m_tests = createShard(
        m_tests,
        m_config->shardCount(),
        m_config->shardIndex());
  4. Execution – The TestGroup runs the filtered (sharded) test set, with reporters processing results as usual.

Practical Examples

Manual Sharding via CLI

Run a binary across three separate terminal sessions to parallelize execution:


# Terminal 1

./build/Tests/MyTests --shard-count=3 --shard-index=0

# Terminal 2

./build/Tests/MyTests --shard-count=3 --shard-index=1

# Terminal 3

./build/Tests/MyTests --shard-count=3 --shard-index=2

Parallel CI Job Using Bash

Distribute tests across a single machine's cores using background processes:

#!/usr/bin/env bash
TEST_BINARY=./build/Tests/MyTests
TOTAL_SHARDS=5

for i in $(seq 0 $((TOTAL_SHARDS-1))); do
  $TEST_BINARY --shard-count=$TOTAL_SHARDS --shard-index=$i &
done

wait  # Wait for all shards to finish

Bazel Test Rule Configuration

Define sharding directly in your BUILD file:

cc_test(
    name = "my_test",
    srcs = ["my_test.cpp"],
    shard_count = 4,  # Bazel automatically sets TEST_TOTAL_SHARDS

)

Summary

  • Test sharding in Catch2 is controlled via --shard-count and --shard-index flags or Bazel environment variables.
  • The createShard function in src/catch2/internal/catch_sharding.hpp implements deterministic partitioning based on integer division and remainder distribution.
  • Configuration values are accessed through Config::shardCount() and Config::shardIndex() as defined in src/catch2/catch_config.hpp.
  • The Session class applies sharding during test execution in src/catch2/catch_session.cpp before running the filtered test set.
  • Bazel integration requires setting BAZEL_TEST=1 and using TEST_TOTAL_SHARDS, TEST_SHARD_INDEX, and TEST_SHARD_STATUS_FILE.

Frequently Asked Questions

How does Catch2 distribute tests across shards?

Catch2 uses the createShard algorithm in src/catch2/internal/catch_sharding.hpp to divide tests evenly. Each shard receives totalTestCount / shardCount tests, with the first totalTestCount % shardCount shards receiving one additional test to account for remainders.

What happens if I don't specify sharding options?

If --shard-count is not specified, it defaults to 1. The createShard function returns the original test container unchanged, causing the runner to execute the entire suite without partitioning.

How do I enable Bazel-compatible test sharding?

Set the BAZEL_TEST=1 environment variable and provide TEST_TOTAL_SHARDS, TEST_SHARD_INDEX, and optionally TEST_SHARD_STATUS_FILE. Catch2 will automatically detect these variables and skip parsing the corresponding CLI flags.

Is the test distribution deterministic across runs?

Yes, provided the underlying test order is deterministic (e.g., using --order decl or consistent sorting). The sharding algorithm uses only arithmetic operations on the total test count and shard index, ensuring the same tests always map to the same shard indices.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →