Skip to content

qltysh/stet

Repository files navigation

stet

stet — "let it stand." A proofreading term meaning the original text is correct and should not be changed. In the same way, stet identifies the tests that can stand as-is, and reruns only the ones affected by your changes.

stet is an open-source implementation of the selective test execution system described in Stripe's blog post Fast CI for a 50M-Line Ruby Monorepo. It traces which files each test opens, builds a dependency graph, and uses it to select just the tests impacted by your code changes. It works with Ruby/Minitest, Python/Pytest, and Bun on macOS and Linux.

Research preview. stet is functional and tested but is not yet battle-tested in production. APIs, config format, and on-disk storage may change without notice. Feedback and contributions are welcome.

How it works

  1. Recordstet runs your test suite with a shared library injected via DYLD_INSERT_LIBRARIES (macOS) or LD_PRELOAD (Linux) that intercepts file-open syscalls. This produces a map of which source files each test depends on.

  2. Select — When files change, stet diffs the repo inventory against the recorded dependency graph and identifies which tests are affected.

  3. Runstet executes only the affected tests, re-traces their dependencies, and updates the dependency graph for next time. No full re-record needed.

Install

Using the GitHub CLI:

# macOS (Apple Silicon)
gh release download --repo qltysh/stet --pattern 'stet-aarch64-apple-darwin.tar.gz'
tar xzf stet-aarch64-apple-darwin.tar.gz

# Linux (x86_64)
gh release download --repo qltysh/stet --pattern 'stet-x86_64-unknown-linux-gnu.tar.gz'
tar xzf stet-x86_64-unknown-linux-gnu.tar.gz

Then add the extracted directory to your PATH. The archive contains both the stet binary and the libstet_interceptor library.

Build from source

Alternatively, build from source with the Rust toolchain:

git clone https://github.com/qltysh/stet.git
cd stet
bash scripts/setup.sh

Quick start

Run stet init from your project root to auto-detect your test runner and generate a config:

stet init

This scans your repo for dependency files (Gemfile, pyproject.toml, package.json/bun.lockb) and test directories, then writes a commented stet.toml with the right settings. Add .stet/ to your .gitignore.

Then record a baseline and start running:

# Record the baseline (runs your full test suite once with tracing)
stet record

# Make a code change, then run just the affected tests
stet run

stet init accepts --runner minitest|pytest|bun_test to skip detection, --force to overwrite an existing config, and --config <PATH> for a custom output location.

Manual configuration

If you prefer to create stet.toml by hand:

Ruby/Minitest:

test_runner = "minitest"
test_globs = ["test/**/*_test.rb"]
test_args = ["-Itest"]
inventory_excludes = [".git/**", ".stet/**", "tmp/**"]

Python/Pytest:

test_runner = "pytest"
test_globs = ["tests/test_*.py", "tests/**/*_test.py"]
test_args = ["-m", "pytest"]
inventory_excludes = [".git/**", ".stet/**", "__pycache__/**", ".pytest_cache/**"]

Bun:

test_runner = "bun_test"
test_globs = ["test/**/*.test.ts"]
test_args = ["test"]
inventory_excludes = [".git/**", ".stet/**", "node_modules/**"]

state_dir defaults to .stet. runtime is inferred from test_runner (ruby, python3, or bun) and resolved from PATH. Both can be overridden explicitly.

stet looks for stet.toml in the current directory and auto-discovers the interceptor library next to its own binary. Use --config and --interceptor to override.

Commands

stet init

Auto-detects the test runner from your repo contents and generates a commented stet.toml. Supports --runner to skip detection, --force to overwrite, and --config for a custom output path.

stet record

Runs every test with tracing and publishes a new dependency baseline under .stet/bases/. Use this for initial setup or to force a full re-record.

stet select

Reads the latest baseline, diffs current file hashes against it, and outputs the list of affected test units. Doesn't run anything.

stet run

Selects affected tests, runs them with tracing, and updates the baseline. If no baseline exists, bootstraps by running the full suite. This is the steady-state command for day-to-day development.

Advanced configuration

Mandatory tests

Tests that should always run regardless of what changed:

mandatory_test_globs = ["test/health/**/*_test.rb"]

Root preload

A script that runs before tests to generate shared artifacts (e.g., schema files). Changes to files opened by the root preload will select all tests:

root_args = ["-Itest", "test/root_preload.rb"]

Tool routes

Run additional commands when specific files change:

[[tool_routes]]
name = "yaml-lint"
changed_file_globs = ["config/**/*.yml"]
command = ["{runtime}", "script/changed_file_yaml_lint.rb", "{changed_files...}"]

Parallelism

max_workers = 8  # defaults to available CPU cores

Development

Try the interactive demo:

bash scripts/demo-fixture.sh --fixture ruby
bash scripts/demo-fixture.sh --fixture python
bash scripts/demo-fixture.sh --fixture bun

Run the test suite:

bash scripts/setup.sh
cargo test --workspace

Run clippy and format checks:

cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --check

Benchmark:

bash scripts/benchmark-fixture.sh --fixture ruby
bash scripts/benchmark-fixture.sh --fixture python
bash scripts/benchmark-fixture.sh --fixture bun

Linux validation (requires Docker):

bash scripts/validate-linux-docker.sh

License

MIT

About

Run only the tests affected by your changes. Selective test execution inspired by Stripe.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages