How are cargo tests run (serially or in parallel)?


#1

I’m writing a thin wrapper around ImageMagick and it seems that I cannot have more than one test function, otherwise ImageMagick blows up. The error bounces between two different locations in the ImageMagick code, so I suspect some form of timing bug is at play. I believe this has to do with IM’s requirement to start and stop the framework via two function calls, which almost certainly modifies global variables (ugh). This is based on other responses regarding these particular errors, on the IM forum.

My question is thus: when I invoke cargo test, does it run the tests concurrently, or serially? If concurrently, is there an option or config setting to disable that behavior?

If the tests are run serially, then I must have a mistake elsewhere.

Thanks

P.S. Yes, I know there is wand-of-rust, but it’s really old (over a year, that’s equivalent to a dog’s entire lifespan in Rust years!) and I only need a few functions, for now.


#2

They are run in parallel (where the number of parallel tasks can be set using the RUST_TEST_TASKS environment variable). If you’re planning on implementing a safe wrapper (i.e. a wrapper that doesn’t just expose unsafe functions), your library should handle synchronization. If the start/stop calls are the only problem, consider using std::sync::Once. Otherwise, you’ll probably need your own global mutex.

If you really just want to prevent your tests from running in parallel, use a global Mutex in your test file (or set RUST_TEST_TASKS=1 but that’s even more hacky).


#3

It should also be noted that it’s not cargo doing that, cargo just runs the test harness built with rustc --test and that’s where the number of tasks is managed. Look for target/debug/your_binary_name-with_some_hash after you run cargo test.


#4

Thanks for the help, and clarification.

A follow-on question I have is this: does rustc --test support setup and tear-down functions? If so, that would help a lot. I looked in the docs, but didn’t see anything of this nature.


#5

In case someone wonders how to make a global mutex, here is what I did (to avoid concurrent access to the database):

use std::sync::{MUTEX_INIT, MutexGuard, StaticMutex};

static LOCK: StaticMutex = MUTEX_INIT;

struct DatabaseMutex<'a> {
    connection: Connection,
    _data: MutexGuard<'a, ()>,
}

impl<'a> DatabaseMutex<'a> {
    fn new() -> DatabaseMutex<'a> {
        let data = LOCK.lock().unwrap();
        let connection = get_connection();
        DatabaseMutex {
            connection: connection,
            _data: data,
        }
    }
}

And then, in each test, I can use:

let table_mutex = DatabaseMutex::new();

Is there a better way of doing this?


#6

Hey!

I’m also looking for a way to test (in my case redis) db related things. I’ve not tried it yet, but perhaps it’s possible to just synchronise test, by wrapping hole body in mutex? In my opinion it would be a bit simpler to grok.


#7

For tests that produce interesting output, it would be nice to control, e.g. in Cargo.toml, whether tests are run in parallel or not. The environment variable doesn’t really help, we’d have to tell everyone to set it before running “cargo test”. Having the output of all the tests mixed up is not very useful.


#8

Have you filed a cargo issue for this? :slight_smile:


#9

I did not.^^ It seemed beyond the intended scope of “cargo test” for me. And in this concrete case we decided parallel runs are actually okay and we’ll just live with the somewhat broken output. So right now we’re good.


#10

Another way is:

cargo test -- --test-threads 1

Though, again, you need to tell everyone to use this option.


#11

Thats why we use a Makefile everywhere in my day job, even if it is often only a thin wrapper around another buildtool.

But we have a unified interface throughout all our projects which are written in a huge number of languages, using even more (testing) frameworks or build tools. So beeing able to type make test everywhere makes things a lot easier for everyone.