Last updated 2026-05-07

Affected Test Runner

stax test runs only the tests for projects whose code actually changed, and skips any project whose inputs hash matches a previous green run. The implementation lives under platform/cli-go/internal/affected/ and internal/testcache/.

How "affected" is computed

  1. Detect projects via registry.LoadOrDetect(). Either a hand-written registry.toml at the repo root, or auto-discovery via marker files (go.mod, package.json, pyproject.toml, Cargo.toml, pubspec.yaml, etc.).
  2. Diff HEAD against --base (default main) to get the list of changed files.
  3. Direct match: mark any project whose path contains a changed file as affected.
  4. Transitive propagation: walk the dependency graph to a fixed point — anything that depends on an affected project is itself affected.
changed files: platform/cli-go/internal/api/api.go
┌─ platform/cli-go ◀── direct match
└─ <projects depending on cli-go> ◀── transitively affected

registry.toml

Optional. When present, it overrides auto-detection.

registry.toml
[packages.cli]
path = "platform/cli-go"
type = "go"
test = "go test ./..."
build = "go build ./..."
[packages.runner]
path = "platform/runner"
type = "node"
test = "npm test"
build = "npm run build"
depends_on = ["cli"] # transitive: changes to cli mark runner affected too

Caching

Each test invocation hashes everything that influences the result: project path contents, transitive dependency contents, the test command itself, and the configured cache namespace. The hash is the cache key.

  • A hit short-circuits the test command and replays the cached log. Output is still printed so CI consumers see green.
  • A miss runs the test command and, if it passes, writes the result + log to the cache.
  • Failures are cached too so flaky retries don't waste time. Use --retry-failed to force a re-run.

Cache backends

The cache layer is pluggable. Backends live in internal/testcache/backends/:

BackendPurpose
local_dirLocal filesystem (default).
httpGeneric HTTP store with GET/PUT.
s3AWS S3 (via aws-sdk-go-v2).
gcsGoogle Cloud Storage.
azureAzure Blob Storage.
layeredRead from one backend, write to another (e.g. local + remote).

Common invocations

# Run only what changed since main
stax test
# Run everything (CI seeding, release verification)
stax test --all
# Test a single project, ignoring the affected graph
stax test --project runner
# Bypass the cache for an authoritative run
stax test --no-cache
# Force-update cache entries
stax test --update-cache
# Inspect the cache
stax test --cache-stats
# Clear it
stax test --cache-clear # everything
stax test --cache-clear runner # one project

Use it in a workflow

name: Test
on: [push, pull_request, workflow_dispatch]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: |
curl -fsSL https://raw.githubusercontent.com/elloloop/stax/main/install.sh | sh
export PATH=$HOME/.local/bin:$PATH
stax test --base ${{ github.base_ref || 'main' }}