parcheck
is a concurrency testing tool for Rust. It helps uncover bugs by running testing
scenarios with different thread/task interleavings.
Currently in alpha version, interface will change soon, but the idea remains the same.
Add it to your dependencies:
# Cargo.toml
[dependencies]
parcheck = { version = "0.1.0-alpha.2", git = "https://github.com/stepantubanov/parcheck.git" }
[dev-dependencies]
parcheck = { version = "0.1.0-alpha.2", features = ["enable"], git = "https://github.com/stepantubanov/parcheck.git" }
Note that enable
feature is enabled only in dev-dependecies
. Without this feature calls to
parcheck::task
and parcheck::operation
expand to underlying future without any additional logic,
so effect on production code should be as minimal as possible.
In order for the code to be controlled, it has to be instrumented with calls to parcheck::task
and
parcheck::operation
. "Tasks" are threads of execution (currently parcheck
does not support
intra-task concurrency, but that's coming soon) and "operations" are futures that parcheck
will
control. Specifically it'll execute operations in a deterministic linearized schedule (when there
are multiple tasks running, only a single operation from one of the tasks is selected and allowed to
execute according to currently selected schedule, then after it completes, next operation is
selected, etc).
async fn handle_http_request() {
parcheck::task!("name_of_this_request", {
async {
// ... code that isn't controlled by parcheck
let value = parcheck::operation!("name_of_operation", {
// ... perform an async operation: execute an SQL query, send a message, set/get
// in redis, etc.
async {}
}).await;
// ... some other code that isn't controlled
parcheck::operation!("name_of_next_operation", {
// ... another operation, that will be controlled by parcheck
async {}
}).await;
}
}).await;
}
We can test interleavings between two concurrent executions of this async function by adding a test case and running it via parcheck::runner
.
#[tokio::test]
async fn test_concurrent_scenarios() {
parcheck::runner()
.run(["name_of_this_request", "name_of_this_request"], || async {
join!(
handle_http_request(),
handle_http_request(),
);
// ... assert that state after 2 concurrent requests is as expected
}).await;
}
Runner accepts names of tasks to test and starts to control their execution when they are started
(parcheck::task
called). This test will run 2 concurrent handle_http_request
multiple times,
each time sequence of operations will be different. If code panics under one of schedules, then
parcheck
will print that schedule and it can be used to reproduce it again.