11# Fairsplice
22
3- ** Warning: this project is still in very early development! **
3+ Fairsplice is a CLI tool and GitHub Action that optimizes test distribution across parallel workers. It provides CircleCI-style test splitting based on timing data for GitHub Actions.
44
5- Fairsplice is a CLI tool designed to optimize test distribution across multiple workers. By intelligently splitting and saving test cases, Fairsplice ensures a balanced workload distribution for your CI/CD pipelines, making tests run time more predictable.
5+ ## Quick Start (GitHub Action)
66
7- We found Github Actions lacking when compared to CircleCI which has [ tests splitting] ( https://circleci.com/docs/parallelism-faster-jobs/#how-test-splitting-works ) based on timings.
8-
9- There are a number of projects like [ Split tests] ( https://github.com/marketplace/actions/split-tests ) but they require uploading and downloading Junit XML files and merging them, or committing the Junit files to have them when running the tests.
7+ ``` yaml
8+ jobs :
9+ test :
10+ runs-on : ubuntu-latest
11+ strategy :
12+ matrix :
13+ index : [0, 1, 2]
14+ steps :
15+ - uses : actions/checkout@v4
16+
17+ - name : Split tests
18+ id : split
19+ uses : dashdoc/fairsplice@v1
20+ with :
21+ command : split
22+ pattern : ' tests/**/*.py'
23+ total : 3
24+ index : ${{ matrix.index }}
25+
26+ - name : Run tests
27+ run : pytest ${{ steps.split.outputs.tests }} --junit-xml=junit-${{ matrix.index }}.xml
28+
29+ - uses : actions/upload-artifact@v4
30+ with :
31+ name : junit-${{ matrix.index }}
32+ path : junit-${{ matrix.index }}.xml
33+
34+ save-timings :
35+ needs : test
36+ runs-on : ubuntu-latest
37+ steps :
38+ - uses : actions/checkout@v4
39+
40+ - uses : actions/download-artifact@v4
41+
42+ - name : Merge timings
43+ uses : dashdoc/fairsplice@v1
44+ with :
45+ command : merge
46+ prefix : ' junit-*/junit-'
47+ ` ` `
1048
11- This tool stores test timings in a local JSON file, keeping the last 10 timings for each test file and using the average for splitting. No external database required!
49+ That's it! Caching is handled automatically.
1250
1351## How It Works
1452
@@ -21,7 +59,7 @@ This tool stores test timings in a local JSON file, keeping the last 10 timings
2159 │ 1. SPLIT PHASE │
2260 └─────────────────────┘
2361
24- timings.json fairsplice split
62+ timings (cached) fairsplice split
2563 ┌──────────────────────┐ ┌─────────────────┐
2664 │ { │ │ │
2765 │ "test_a.py" : [2.1],│ ──────▶ │ Load timings │
@@ -39,10 +77,9 @@ This tool stores test timings in a local JSON file, keeping the last 10 timings
3977 ▼ ▼ ▼
4078 ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
4179 │ Worker 0 │ │ Worker 1 │ │ Worker 2 │
42- │ ["test_b.py"] │ │ ["test_a.py", │ │ ["test_c.py"] │
43- │ ~5.3s │ │ "test_c.py"] │ │ ~1.8s │
44- └─────────┬─────────┘ │ ~3.9s │ └─────────┬─────────┘
45- │ └─────────┬─────────┘ │
80+ │ ~5.3s │ │ ~3.9s │ │ ~5.1s │
81+ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘
82+ │ │ │
4683 ▼ ▼ ▼
4784 ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
4885 │ Run tests │ │ Run tests │ │ Run tests │
@@ -57,130 +94,85 @@ This tool stores test timings in a local JSON file, keeping the last 10 timings
5794 ▼
5895 ┌─────────────────────────┐
5996 │ fairsplice merge │
60- │ --prefix junit- │
61- └─────────────────────────┘
62- │
63- ▼
64- ┌─────────────────────────┐
65- │ Extract timings from │
66- │ JUnit XML results │
97+ │ (extracts timings) │
6798 └─────────────────────────┘
6899 │
69100 ▼
70101 ┌──────────────────────┐
71- │ timings.json │
72- │ Updated with new │
73- │ timing data │◀─── Cached/committed
102+ │ timings (cached) │◀─── Auto-cached
74103 └──────────────────────┘ for next run
75104```
76105
77106** Key concepts:**
78- - ** Split phase** : Before tests run, fairsplice distributes test files across workers based on historical timing data
79- - ** Merge phase** : After tests complete, fairsplice extracts timing from JUnit XML and updates the timings file
80- - ** Bin packing** : Tests are assigned to workers to balance total execution time (heaviest tests first)
81- - ** Rolling average** : Keeps last 10 timings per test file, uses average for predictions
107+ - ** Split phase** : Distributes test files across workers based on historical timing data
108+ - ** Merge phase** : Extracts timing from JUnit XML and caches for next run
109+ - ** Bin packing** : Assigns tests to balance total execution time (heaviest tests first)
110+ - ** Rolling average** : Keeps last 10 timings per test file for predictions
82111
83- ## Installation
112+ ## GitHub Action Reference
84113
85- This project is built using [ Bun ] ( https://bun.sh ) .
114+ ### Inputs
86115
87- Ensure you have Bun installed.
88- To launch it, run
116+ | Input | Required | Description |
117+ | -------| ----------| -------------|
118+ | ` command ` | Yes | ` split ` or ` merge ` |
119+ | ` timings-file ` | No | JSON file for timings (default: ` .fairsplice-timings.json ` ) |
120+ | ` pattern ` | For split | Glob pattern to match test files |
121+ | ` total ` | For split | Total number of workers |
122+ | ` index ` | For split | Current worker index (0-based) |
123+ | ` prefix ` | For merge | Prefix to match JUnit XML files |
89124
90- ``` bash
91- bunx fairsplice
92- ```
93-
94- ## Usage
125+ ### Outputs
95126
96- Fairsplice has two commands: ` merge ` and ` split ` . Both require a ` --timings-file ` parameter.
127+ | Output | Description |
128+ | --------| -------------|
129+ | ` tests ` | Space-separated list of test files (when ` index ` provided) |
130+ | ` buckets ` | JSON array of all test buckets |
97131
98- ### Merging test results
132+ ## CLI Usage
99133
100- Save test timings from JUnit XML file(s) :
134+ Install with Bun :
101135
102136``` bash
103- fairsplice merge --timings-file < timings.json> --prefix < prefix>
104- ```
105-
106- - ` --timings-file <file> ` : JSON file to store timings
107- - ` --prefix <prefix> ` : Prefix to match JUnit XML files
108-
109- Example:
110-
111- ``` bash
112- # Merges junit-0.xml, junit-1.xml, junit-2.xml, etc.
113- fairsplice merge --timings-file timings.json --prefix junit-
137+ bunx fairsplice
114138```
115139
116- ### Splitting test cases
117-
118- Split test files across workers based on historical timings:
140+ ### Commands
119141
142+ ** Split tests:**
120143``` bash
121- fairsplice split --timings-file < timings.json> --pattern " <pattern> " --total < total > --out < file >
144+ fairsplice split --timings-file timings.json --pattern " tests/**/*.py " --total 3 --out split.json
122145```
123146
124- - ` --timings-file <file> ` : JSON file with stored timings
125- - ` --pattern "<pattern>" ` : Pattern to match test files (can be used multiple times)
126- - ` --total <total> ` : Total number of workers
127- - ` --out <file> ` : File to write split result to (JSON array of arrays)
128- - ` --replace-from <string> ` : (Optional) Substring to replace in file paths
129- - ` --replace-to <string> ` : (Optional) Replacement string
130-
131- Example:
132-
147+ ** Merge results:**
133148``` bash
134- fairsplice split --timings-file timings.json --pattern " test_*.py " --total 3 --out split.json
149+ fairsplice merge --timings-file timings.json --prefix junit-
135150```
136151
137- ## Using with GitHub Actions
152+ ### CLI Options
138153
139- To persist timings across CI runs, use GitHub Actions cache:
140-
141- ``` yaml
142- - name : Cache test timings
143- uses : actions/cache@v4
144- with :
145- path : timings.json
146- key : fairsplice-timings-${{ github.ref }}
147- restore-keys : |
148- fairsplice-timings-
149-
150- - name : Split tests
151- run : bunx fairsplice split --timings-file timings.json --pattern "tests/**/*.py" --total 3 --out split.json
152154```
153-
154- Alternatively, you can commit the timings file to your repository for simpler persistence.
155-
156- ## Help
157-
158- For a detailed list of commands and options, use the help command:
159-
160- ` ` ` bash
161- fairsplice --help
155+ fairsplice split
156+ -- timings-file < file> JSON file with stored timings
157+ --pattern <pattern> Glob pattern for test files (can repeat)
158+ --total <n> Number of workers
159+ --out <file> Output JSON file
160+
161+ fairsplice merge
162+ --timings-file <file> JSON file to store timings
163+ --prefix <prefix> Prefix to match JUnit XML files
162164```
163165
164166## Contributing
165167
166- Contributions are welcome! Please fork the repository and submit a pull request with your improvements.
167-
168- ### Running locally
169-
170- Launch the development version with:
171-
172168``` bash
169+ # Run locally
173170bun run index.ts
174- ```
175-
176- ### Running tests
177171
178- Launch the following command to run tests:
179-
180- ``` bash
181- bun test [--watch]
172+ # Run tests
173+ bun test
182174```
183175
184176## License
185177
186- Fairsplice is open-source software licensed under the MIT license.
178+ MIT
0 commit comments