Passing --test-threads=1 to cargo test by default?

I'm writing a Rust wrapper for a C library. This C library contains some global state and therefore cannot be used by multiple threads at the same time without precautions. This means that when I run cargo test, I'm getting segfaults because Rust runs multiple unit tests in parallel using threads. To make my tests pass, I need to pass --test-threads=1.

Is there a way, e.g. in Cargo.toml, to specify that this should be done by default when running cargo test? Alternatively are there options that could tell Cargo that tests should be run in distinct processes instead of distinct threads?

Unfortunately, there is no way to configure the default testing behavior

But, if your tests are segfaulting, then your library is not sound. It should protect the global state, such as with a mutex.

2 Likes

Thanks for your answer.

Just to clarify: the global state is deep down inside a dependency of the C library (with global initialization and finalization functions, so if a test calls the finalization function while other tests/threads still use the library, these tests are just going to crash). Besides, this library is not meant to be used in a multithreaded context so even though I could make the Rust binding thread-safe, it would be an overkill (with performance overhead).

Even if you can't add mutex to protect bindings (which is definitely a problem IMO, but I don't know all there details) you can add mutex to your tests:

static LIBRARY_USER: Mutex<Executor> = Mutex::new(Executor);

#[derive(Clone, Copy)]
struct Executor;

impl Executor {
    fn run_test(self, f: impl FnOnce()) {
        f();
    }
}

#[test]
fn my_test() {
    LIBRARY_USER.lock().unwrap().run_test(||
        println!("Hello, world!")
    )
}
3 Likes

Do what you want with your own code for your own purposes, but if you plan to publish it for others to use, this is not sufficient. A key element of Rust's “fearless concurrency” is that libraries are thread-safe — more precisely, that libraries are sound under all possible interactions with them from safe code (which includes multithreaded code).

8 Likes

Mutexes are pretty cheap without contentious. In single threaded context it can be considered nearly zero cost for most use cases. You can acquire a global mutex at the entry of public API or even throw panic if it's not called from main thread depending on the actual requirements the C library has.

The help info says:

$ cargo test -- --help

...

By default, all tests are run in parallel. This can be altered with the
--test-threads flag or the RUST_TEST_THREADS environment variable when running
tests (set it to 1).

...

so you can try RUST_TEST_THREADS=1 cargo test.


Another general way is to define a custom command alias via .cargo/config.toml in your project, like:

# in path_to_your_project/.cargo/config.toml
[alias]
t = "test -- --test-threads=1"

then you can use cargo t[1] to run tests.


  1. cargo t is the default shorthand form of cargo test ↩︎

Thanks all, all good material to work with :slight_smile:

My problem appeared in a -sys package for which I'm adding tests. This is a crate that just provides the FFI bindings for the C library, not the place to add mechanisms to make it rust-oriented and safe (let alone thread-safe). I do plan to write a safe package on top of that, with a rust-oriented API (using a static Mutex in particular to protect calls to the C API). But my understanding is that it's not the role of a -sys package to provide anything more than the FFI bindings, hence such a package, even published, will not be safe.

A lock inside the tests is the best solution.

Multi-threaded cargo test is the standard way of launching the tests, and if that doesn't work properly, you'll have hard time explaining it to everyone and everything that may use the package.

1 Like