Skip to content

Commit f268bdf

Browse files
cosmithclaude
andcommitted
Make cache-key required and fix cache restore
- Make cache-key input required to prevent configuration mistakes - Fix cache key format: use run_id suffix for saves, prefix matching for restores - Add cache-key to test workflow - Document cache behavior in README (branch scoping, cross-branch sharing) - Add CLAUDE.md for Claude Code guidance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3019ec7 commit f268bdf

File tree

4 files changed

+76
-5
lines changed

4 files changed

+76
-5
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ jobs:
4747
pattern: 'tests/dummy/*.test.ts'
4848
total: 3
4949
index: ${{ matrix.index }}
50+
cache-key: integration-tests
5051

5152
- name: Show assigned tests
5253
run: echo "Worker ${{ matrix.index }} running:${{ steps.split.outputs.tests }}"
@@ -88,6 +89,7 @@ jobs:
8889
with:
8990
command: merge
9091
prefix: 'timing-*/timing-'
92+
cache-key: integration-tests
9193

9294
- name: Show saved timings
9395
run: cat .fairsplice-timings.json

CLAUDE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Fairsplice is a TypeScript/Bun CLI tool and GitHub Action that optimizes test distribution across parallel workers. It provides CircleCI-style test splitting based on historical timing data for GitHub Actions.
8+
9+
## Commands
10+
11+
```bash
12+
# Run locally
13+
bun run index.ts
14+
15+
# Run all tests
16+
bun test
17+
18+
# Run tests in src directory
19+
bun test src/
20+
21+
# Run a specific test file
22+
bun test src/lib/splitFiles.test.ts
23+
24+
# Compile to standalone binary
25+
bun build ./index.ts --compile --outfile fairsplice
26+
```
27+
28+
## Architecture
29+
30+
**Entry Point**: `index.ts` - CLI with three commands: `split`, `convert`, `merge`
31+
32+
**Source Structure**:
33+
- `src/commands/` - CLI command implementations
34+
- `split.ts` - Distributes test files across workers using bin packing
35+
- `merge.ts` - Aggregates timing JSON files and updates history
36+
- `convert.ts` - Converts JUnit XML to timing JSON
37+
- `src/lib/` - Core algorithms
38+
- `splitFiles.ts` - Greedy bin packing algorithm (assigns heaviest tests first to balance workload)
39+
- `junit.ts` - JUnit XML parser using `fast-xml-parser`
40+
- `average.ts` - Timing averaging utility
41+
- `src/backend/` - Storage layer
42+
- `fileStorage.ts` - JSON-based timing persistence with rolling window of last 10 timings per file
43+
- `src/config.ts` - Constants (`NUMBER_OF_TIMINGS_TO_KEEP=10`, `DEFAULT_TIMING_IF_MISSING=10000ms`)
44+
45+
**GitHub Action**: `action.yml` - Composite action wrapping the CLI with automatic cache handling
46+
47+
**Data Flow**:
48+
1. `split` loads cached timings, globs test files, applies bin packing, outputs bucket assignments
49+
2. Tests run in parallel workers, each outputting JUnit XML
50+
3. `convert` transforms JUnit XML to timing JSON (one per worker)
51+
4. `merge` aggregates timing JSONs into cached timings history
52+
53+
## Testing
54+
55+
Tests are co-located with source files (`*.test.ts`). Test fixtures for JUnit parsing are in `src/lib/fixtures/`.
56+
57+
The CI workflow (`.github/workflows/test.yml`) runs unit tests plus a 3-worker integration test that exercises the full split→run→convert→merge pipeline.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
pattern: 'tests/**/*.py'
2323
total: 3
2424
index: ${{ matrix.index }}
25+
cache-key: python-tests
2526

2627
- name: Run tests
2728
run: pytest ${{ steps.split.outputs.tests }} --junit-xml=junit-${{ matrix.index }}.xml
@@ -44,6 +45,7 @@ jobs:
4445
with:
4546
command: merge
4647
prefix: 'junit-*/junit-'
48+
cache-key: python-tests
4749
```
4850
4951
That's it! Caching is handled automatically.
@@ -116,12 +118,24 @@ That's it! Caching is handled automatically.
116118
| Input | Required | Description |
117119
|-------|----------|-------------|
118120
| `command` | Yes | `split` or `merge` |
121+
| `cache-key` | Yes | Cache key for storing timings (use different keys for frontend/backend workflows) |
119122
| `timings-file` | No | JSON file for timings (default: `.fairsplice-timings.json`) |
120123
| `pattern` | For split | Glob pattern to match test files |
121124
| `total` | For split | Total number of workers |
122125
| `index` | For split | Current worker index (0-based) |
123126
| `prefix` | For merge | Prefix to match JUnit XML files |
124127

128+
### Cache Behavior
129+
130+
Fairsplice uses GitHub Actions cache for storing timing history. Important characteristics:
131+
132+
- **Repository-scoped, branch-gated**: Caches are repository-scoped but restore access is gated by branch context
133+
- **Default branch is global**: Caches saved from the default branch (usually `main`) are restorable by all branches
134+
- **Immutable, single-writer**: Each cache key can only be written once; updates require a new key (handled automatically via run ID suffix)
135+
- **Asymmetric cross-branch sharing**: Restore is permissive (branches can read from main), save is restricted (branches can only write to their own scope)
136+
137+
To seed shared timings for all branches, run the workflow on `main` first. Subsequent PRs and feature branches will restore timings from main's cache.
138+
125139
### Outputs
126140

127141
| Output | Description |

action.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ inputs:
1414
default: '.fairsplice-timings.json'
1515
cache-key:
1616
description: 'Cache key for storing timings (use different keys for frontend/backend workflows)'
17-
required: false
18-
default: 'default'
17+
required: true
1918
# split inputs
2019
pattern:
2120
description: 'Glob pattern to match test files (for split)'
@@ -61,9 +60,8 @@ runs:
6160
uses: actions/cache/restore@v4
6261
with:
6362
path: ${{ inputs.timings-file }}
64-
key: fairsplice-${{ inputs.cache-key }}-${{ github.repository }}
65-
restore-keys: |
66-
fairsplice-${{ inputs.cache-key }}-${{ github.repository }}
63+
key: fairsplice-${{ inputs.cache-key }}-${{ github.repository }}-${{ github.run_id }}
64+
restore-keys: fairsplice-${{ inputs.cache-key }}-${{ github.repository }}-
6765

6866
- name: Run split
6967
id: split

0 commit comments

Comments
 (0)