serial_test
Attribute macros that serialize or parallelize Rust tests via named locks.
Repository Health
Technical Analysis
serial_test gives Rust test suites fine-grained control over which tests may run concurrently. Adding #[serial] to a test function (or a whole mod block) guarantees it never runs at the same time as any other test sharing the same lock name, while #[parallel] marks tests that may run alongside each other but never alongside a serial test. This solves the common problem of tests that mutate shared global state — a database, an environment variable, a singleton, a file on disk — racing each other under Rust’s default multi-threaded test runner.
Beyond in-process locking, file_serial/file_parallel extend the same guarantees across process boundaries using OS file locks, which matters for doctests and multi-binary integration test suites that can’t share an in-memory mutex. The crate also supports named lock groups for independent serialization pools, an inner_attrs mechanism for composing with other test attributes like timeouts, and optional async support for #[tokio::test]-style tests — all without requiring any manual lock management in test code.
What You Get
- The
#[serial]and#[parallel]attribute macros, appliable to individual test functions or entiremodblocks - Named lock keys (
#[serial(some_key)]) to create independent serialization groups so unrelated tests don’t block each other #[file_serial]/#[file_parallel]for cross-process locking viafslock, covering doctests and multi-binary integration tests- An
inner_attrsargument to apply attributes likentest::timeoutto only the test body, not the lock-wait window - A
crate = <path>override for wrapper crates that re-exportserial_testunder a different import path - Optional async support (
asyncfeature) so#[tokio::test]functions can be serialized the same way as sync tests - A public
is_locked_serially()helper for asserting that code is only reachable from within a serialized test
Common Use Cases
- Serializing tests that share an external resource — a test database, a temp directory, or process-wide environment variables — that can’t tolerate concurrent mutation
- Enforcing cross-process ordering for doctests and separate integration-test binaries via file-based locks
- Stabilizing legacy suites with global mutable state without a full refactor away from shared singletons
- Isolating and debugging flaky, order-dependent CI failures by scoping the fix to a named lock group instead of serializing the whole suite
Under The Hood
Architecture: serial_test is split across a runtime crate (serial_test) and a procedural-macro crate (serial_test_derive). The derive crate (serial_test_derive/src/lib.rs, ~1,350 lines) parses the #[serial]/#[parallel]/#[file_serial]/#[file_parallel] attribute arguments with syn/quote/proc-macro2 and rewrites the annotated function (or every test function inside an annotated mod block) to call one of the runtime crate’s *_core functions — e.g. local_serial_core, local_parallel_core, fs_serial_core — passing the lock key(s) and a closure wrapping the original test body. At runtime, code_lock.rs maintains a global LockMap (a Mutex<HashMap<String, UniqueReentrantMutex>> behind a once_cell::sync::OnceCell) keyed by lock name; serial_code_lock.rs acquires that lock exclusively while parallel_code_lock.rs acquires it in a shared mode built on a custom reader/writer primitive in rwlock.rs, so parallel tests can run concurrently with each other but never with a serial one. file_lock.rs/serial_file_lock.rs/parallel_file_lock.rs mirror this design using OS-level file locks via fslock for cross-process guarantees.
Tech Stack: Pure Rust, edition 2018, MSRV 1.93.1, organized as a three-crate Cargo workspace (serial_test, serial_test_derive, serial_test_test). Runtime dependencies are deliberately minimal: once_cell for the global lock table, parking_lot for the underlying mutex/lock primitives, and optional fslock, futures-executor/futures-util (async feature), log/env_logger (logging features), and document-features (docs.rs feature documentation). The derive crate depends only on syn, quote, and proc-macro2. Feature flags (logging, async, file_locks) keep the default build lean while a cargo-all-features CI matrix exercises every combination.
Code Quality: Each runtime module carries its own #[cfg(test)] mod tests block (36 test functions across the src tree by direct count), plus a dedicated serial_test/tests/tests.rs integration suite and a separate serial_test_test crate whose first.rs/second.rs/third.rs/inner_attrs.rs tests exercise real cross-process and cross-binary ordering behavior — the kind of test a unit test alone can’t verify. lib.rs sets #![deny(unused_variables)], #![deny(missing_docs)], and #![deny(unused_imports)], so every public item is required to carry documentation and the crate can’t silently accumulate dead imports. Naming is consistent across the sync/async/file variants (local_serial_core / local_async_serial_core / fs_serial_core), and mutex poisoning is handled by intentional .unwrap() panics, which is the conventional and correct choice for a test-only locking primitive.
API Design: The public surface is almost entirely attribute macros — adding #[serial] or #[parallel] above a test function (or a mod block) is the whole integration cost, with no manual lock acquisition or teardown required. Optional named keys, inner_attrs composition, and the crate = <path> re-export escape hatch cover the real-world edge cases (grouped locks, combining with other test attributes, wrapper crates) without complicating the common case. The module-level lib.rs documentation embeds runnable no_run/should_panic doctests for every feature, so the README and the crate docs stay in sync by construction.
Used by 16 apps in this directory
AppFlowy
Productivity · Project Management · Collaboration
The open-source AI workspace that puts your data, your rules — with local LLMs, CRDT collaboration, and full self-hosting built in.
CubeSandbox
Developer Tools · Security · AI Agents
Instant, concurrent, hardware-isolated MicroVM sandboxes for AI agents — E2B-API compatible, sub-60ms cold starts, and a built-in zero-trust egress proxy, all self-hostable at scale.
Fern
Developer Tools
Fern turns a single OpenAPI, AsyncAPI, or Protobuf definition into type-safe SDKs for nine languages and a hosted API documentation site, all from one CLI and one source of truth.
GitButler
Developer Tools · Devops · AI Development
Git, but better — a modern version control client with stacked branches, parallel workflows, unlimited undo, and first-class support for AI-powered development.
iii
Developer Tools · Devops
Compose, extend, and observe every backend service in real time using three primitives: Workers, Functions, and Triggers.
LanceDB
Databases · AI Development
Open-source, embedded vector database built on the Lance columnar format for fast multimodal search across billions of vectors, backed by Y Combinator (W23).
Latitude
AI Agents · Monitoring
Open-source AI agent monitoring that catches what will break next before your users do.
liteparse
Developer Tools
A fast, lightweight, open-source document parser that extracts spatial text, bounding boxes, and Markdown from PDFs and Office files — entirely on your machine.
Memgraph
Databases · AI Development
High-performance in-memory graph database for AI context and real-time analytics