Search code examples
rustrust-cargo

Prevent Race conditions in Cargo testing


I wish to simulate the user being present in another directory in my test cases. I have a couple cases in the project root.

| examples/
|  | test1_dir/
|  | test2_dir/
| src/
|  |- main.rs
|  |- lib.rs
|  |- config.rs
|- Cargo.toml
|- Cargo.lock

Inside config.rs, there are two test cases that look like the following:

#[test]
fn config_find_file() {
  let original_dir = env::current_dir().unwrap();
  env::set_current_dir("examples/test1_dir").unwrap();

  // ... 

  env::set_current_dir(original_dir);
} 

#[test]
fn config_find_file_2() {
  let original_dir = env::current_dir().unwrap();
  env::set_current_dir("examples/test2_dir").unwrap();

  // ... 

  env::set_current_dir(original_dir);
} 

When I execute with simply cargo test, it fails 50% of the time. This is because sometimes Rust enters a race condition and tries to locate examples/test1_dir/examples/test2_dir. To make it pass, I have to run cargo test -- --test-threads=1. However, I have multiple tests in this project and I don't wish to run one thread for all of them. Here are a few things I have tried:

  1. Using Absolute Paths - Although this does not make any mistake when trying to find the correct directory to be in, the threads switch environment folders midway through the test and asserts condition when present in the wrong directory.
  2. Trying to tag tests - I could run cargo test --lib config -- --test-threads=1. But there is no way that I can execute all other tests but the config tests, resulting in a plethora of test commands for every lib I define.

How should I approach this issue?


Solution

  • There is a crate called serial_test that provides the proc macro #[serial].

    Any test you use this macro on will run single threaded, with all other tests running in parallel (as is the default for cargo).

    For example, consider these 4 tests. The parallel tests will run in parallel, and the serial tests will wait for other tests to finish then run only one at a time.

    #[test]
    fn test_parallel_one() {}
    
    #[test]
    fn test_parallel_two() {}
    
    #[test]
    #[serial]
    fn test_serial_one() {}
    
    #[test]
    #[serial]
    fn test_serial_another() {}