serial_test

Attribute macros that serialize or parallelize Rust tests via named locks.

Library
Cargo
v4.0.1
296 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
67 /100 Good
Development Activity 72
Maintenance 72
Community 44
Maturity 60
Momentum 20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
83 /100 Excellent
Architecture 78
Code Quality 82
Innovation 85
Learning Curve 85

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 entire mod blocks
  • 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 via fslock, covering doctests and multi-binary integration tests
  • An inner_attrs argument to apply attributes like ntest::timeout to only the test body, not the lock-wait window
  • A crate = <path> override for wrapper crates that re-export serial_test under a different import path
  • Optional async support (async feature) 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

Dart
74%
AGPL 3.0

AppFlowy

AI Assistants · Collaboration · Productivity

76,967

The open-source AI workspace that puts your data, your rules — with local LLMs, CRDT collaboration, and full self-hosting built in.

View details
66
Repo Health
81
Technical
67
Dependency
Built with
Dart 74%
Rust 24%
Updated 1 weeks ago
Go
32%
Apache 2.0

CubeSandbox

AI Agents · Developer Tools · Security

12,728

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.

View details
82
Repo Health
88
Technical
63
Dependency
Built with
Go 32%
Rust 26%
C 22%
Updated 1 weeks ago
TypeScript
75%
Apache 2.0

Fern

Developer Tools

3,787

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.

View details
90
Repo Health
86
Technical
66
Dependency
Built with
TypeScript 75%
Updated 4 days ago
Rust
65%
Other

GitButler

AI Development · Developer Tools · Devops

21,726

Git, but better — a modern version control client with stacked branches, parallel workflows, unlimited undo, and first-class support for AI-powered development.

View details
89
Repo Health
84
Technical
66
Dependency
Built with
Rust 65%
TypeScript 22%
Svelte 10%
Updated 4 days ago
Rust
73%
Other

iii

Developer Tools · Devops

18,814

Compose, extend, and observe every backend service in real time using three primitives: Workers, Functions, and Triggers.

View details
87
Repo Health
85
Technical
65
Dependency
Built with
Rust 73%
TypeScript 16%
Updated 5 days ago
Rust
43%
Apache 2.0

LanceDB

AI Development · Databases

11,544

Open-source, embedded vector database built on the Lance columnar format for fast multimodal search across billions of vectors, backed by Y Combinator (W23).

View details
90
Repo Health
86
Technical
71
Dependency
Built with
Rust 43%
Python 25%
HTML 23%
Updated 5 days ago
TypeScript
78%
LGPL 3.0

Latitude

AI Agents · Monitoring

4,686

Open-source AI agent monitoring that catches what will break next before your users do.

View details
86
Repo Health
88
Technical
67
Dependency
Built with
TypeScript 78%
Rust 11%
Python 11%
Updated 1 weeks ago
Rust
84%
Apache 2.0

liteparse

Developer Tools

12,682

A fast, lightweight, open-source document parser that extracts spatial text, bounding boxes, and Markdown from PDFs and Office files — entirely on your machine.

View details
82
Repo Health
80
Technical
74
Dependency
Built with
Rust 84%
Updated 1 weeks ago
C++
66%
Other

Memgraph

AI Development · Databases

4,581

High-performance in-memory graph database for AI context and real-time analytics

View details
90
Repo Health
79
Technical
69
Dependency
Built with
C++ 66%
Python 18%
Updated 5 days ago

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers