Sharding¶
Sharding splits the test suite into independent groups so multiple CI runners can execute different shards in parallel across separate machines.
Overview¶
CI Runner 1: --shard 1/3 CI Runner 2: --shard 2/3 CI Runner 3: --shard 3/3
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Shard 1 of 3 │ │ Shard 2 of 3 │ │ Shard 3 of 3 │
│ Features A, B │ │ Features C, D │ │ Features E, F │
│ --parallel 4 │ │ --parallel 4 │ │ --parallel 4 │
│ 4 local workers │ │ 4 local workers │ │ 4 local workers │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Each CI runner executes only its assigned shard. Within each shard,
--parallel provides local parallelism as usual.
Usage¶
CLI¶
# 3 CI runners, each with 4 local workers
behave --runner=parallel --parallel 4 --shard 1/3 features/
behave --runner=parallel --parallel 4 --shard 2/3 features/
behave --runner=parallel --parallel 4 --shard 3/3 features/
behave.ini¶
Python API¶
from behave_pool import ShardConfig, run_with_shard
config = ShardConfig(
shard_index=1,
total_shards=3,
features_dir="features/",
parallel=4,
)
failed = run_with_shard(config)
Algorithm¶
- Parse all features and create work units (same as normal planning).
- Sort work units deterministically by ID (feature path).
- Split the sorted list into
TOTALcontiguous groups. The firstlen % TOTALshards receive one extra work unit. - Execute only the
INDEX-th group (1-based).
This ensures:
- Deterministic shard assignment — the same feature always lands in
the same shard given the same
TOTAL. - No overlap — every work unit belongs to exactly one shard.
- Even distribution — shard sizes differ by at most one work unit.
Compatibility¶
Sharding composes with all other behave-pool features:
| Feature | Behavior with sharding |
|---|---|
--parallel N |
Local parallelism within each shard. Shard filtering happens first, then work units are distributed among N workers. |
@serial tag |
Serial scenarios within the shard run sequentially after the parallel phase. |
--tags |
Tag filtering applies before sharding. Only matching scenarios are split into shards. |
--parallel-balance |
LPT/FIFO ordering applies within the shard. |
--parallel-report |
Each shard produces its own report file. |
Execution order¶
1. Tag filtering (--tags)
↓
2. Sharding (--shard INDEX/TOTAL)
↓
3. Serial/parallel split (@serial)
↓
4. LPT ordering (--parallel-balance)
↓
5. Local dispatch (--parallel N)
Validation¶
Invalid shard values raise ShardError with a clear message:
| Input | Error |
|---|---|
--shard 0/3 |
shard_index must be >= 1, got 0 |
--shard 4/3 |
shard_index (4) must be <= total_shards (3) |
--shard 1/0 |
total_shards must be >= 1, got 0 |
--shard invalid |
Invalid shard format 'invalid'. Expected 'INDEX/TOTAL' (e.g. '1/3'). |
--shard 13 |
Invalid shard format '13'. Expected 'INDEX/TOTAL' (e.g. '1/3'). |
Output¶
When sharding is active, the runner logs shard metadata:
CI integration example¶
GitHub Actions¶
jobs:
test:
strategy:
matrix:
shard: [1/3, 2/3, 3/3]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install behave-pool
- run: behave --runner=parallel --parallel 4 --shard ${{ matrix.shard }} features/