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 17 apps in this directory
mesh-llm
AI Agents · AI Development
Mesh LLM pools GPUs and memory across every machine you own into one OpenAI-compatible API, so agents tap distributed compute instead of a single GPU box or a metered cloud bill.
OpenShell
AI Agents · Developer Tools
The safe, private runtime that lets autonomous AI agents operate in sandboxed environments governed by declarative YAML policies — blocking data exfiltration, credential leaks, and unauthorized network activity before they happen.
PostHog
Ab Testing Experimentation · Analytics · Developer Tools
The all-in-one open source product platform combining analytics, session replay, feature flags, error tracking, AI observability, and a built-in data warehouse in a single self-hostable stack.
Qdrant
AI Development · Databases · Search
Open-source vector database and search engine built in Rust for production-grade AI applications — from semantic search to RAG pipelines and recommendation systems.
Stalwart
Collaboration
All-in-one secure mail and collaboration server covering IMAP, JMAP, SMTP, CalDAV, CardDAV, and WebDAV in a single memory-safe Rust binary.
Tabby
AI Code Assistants
Self-hosted AI coding assistant — run GitHub Copilot-grade code completion on your own hardware with no cloud dependency.
Temps
Analytics · Devops · Monitoring
A self-hosted Rust PaaS that replaces Vercel, Sentry, PostHog, Pingdom, Resend, and E2B with one binary — plus 440+ CLI operations agents like Claude Code can drive directly.
Windmill
Automation · Developer Tools
Turn scripts into webhooks, workflows, and auto-generated UIs — the fastest self-hostable workflow engine, 13x faster than Airflow.