๐How-to โ test and lint
Verified 2026-07-27.
๐Commands
| command | what |
|---|---|
just test | test-py then cargo nextest run --all-features |
just test-release | cargo nextest run --all-features --no-capture โ what CI runs |
just test-py | cd wrappers/python && uv run --extra dev pytest tests |
just test-watch | cargo watch -x 'nextest run --workspace' |
just lint | lint-rust + lint-py |
just lint-rust | lint-frontend first, then cargo clippy --all-features --all-targets -- -D warnings |
just lint-frontend | clippy against --target wasm32-unknown-unknown |
just fmt / just fmt-check | cargo fmt --all |
just ci | fmt-check lint test lint-py test-py โ the full local gate |
All Rust test commands use --all-features, which enables server and so
triggers the build.rs trunk build. Set EXPMAN_SKIP_FRONTEND_BUILD=1 if you
already have a dist/. See build-and-run.
cli_test.rs uses assert_cmd::cargo::cargo_bin_cmd!("exp"), which needs the
cli feature โ another reason the suite is always run with --all-features.
๐What each suite covers
๐tests/integration_test.rs โ 12 tests, the core engine
Run dir creation ยท vectors โ Parquet ยท params โ YAML ยท
test_log_vector_is_fast (the throughput benchmark, also just bench) ยท
status persisted on close ยท artifact save (relative and absolute paths) ยท
Parquet schema merge ยท latest-scalar readback ยท corrupt YAML metadata ยท
concurrent logging from multiple threads ยท same-step replacement.
๐tests/api_test.rs โ 7 tests, the HTTP layer
In-process via tower::ServiceExt::oneshot against build_router + AppState
โ no network. A TempDir fixture builds one experiment with one run.
Covers: list experiments, get/update experiment metadata, list runs, get run metadata, server config, get metrics.
Not covered: every Jupyter route, every TensorBoard route, both SSE streams, artifact content.
๐tests/cli_test.rs โ 10 tests, the binary
list (with and without --experiment) ยท inspect ยท clean --keep 5 dry-run
(asserts nothing is deleted without --force) ยท export json/csv, with and
without data ยท import error paths ยท export --format tensorboard with no data.
๐wrappers/python/tests/ โ 20 tests
test_experiment.py (6): basic file creation, singleton init, artifact
round-trip, complex types in config.yaml, crash โ status: FAILED, and
test_vectors_vs_scalar โ the important one, pinning that scalars land in
run.yaml and vectors in vectors.parquet, with cross-assertions both ways.
test_tensorboard.py (14): directory structure, context manager, default
log_dir generation, scalars with and without step, add_scalars prefixing,
add_text, add_hparams (including empty and None metrics), a parametrized
sweep asserting all 11 stub methods do not raise, flush() no-op, sequential
writers not interfering, and both log_dir mapping branches.
Not covered: tensorboard_dir; cli.py (the exp console script is never
exercised from Python).
test_default_log_dir(test_tensorboard.py:45) takes notmp_pathand can write aruns/directory into the CWD โ which is whywrappers/python/.gitignorecontains exactlyruns/.
๐What CI runs
ci.yml (push + PR on main, ignoring **.md, examples/**, docs/**,
.gitignore, LICENSE) calls build-assets, then rust.yml and python.yml
in parallel.
rust.ymlโjust fmt-check+just lint-rust, thenjust test-release. Both jobs downloadfrontend-distand setEXPMAN_SKIP_FRONTEND_BUILD=1.python.ymlโjust lint-py(ruff overwrappers/pythonandexamples/), then downloads the CLI artifact,just bundle-cli-bin,uv sync --extra dev, builds and installs a wheel, and runs pytest.
Two gaps worth knowing:
ruff formatnever runs in CI โ onlyruff check. Formatting drift is caught only by pre-commit hooks, which nothing installs automatically.- Python tests only ever run on Linux.
python.yml:29computes the CLI artifact name fromrunner.oswith a nested ternary, implying an intended OS matrix that does not exist; the job is alwaysubuntu-latest.
And the big one: a release does not wait for tests. See release.