如何避免并行运行某些测试?

Jua*_*eni 2 rust rust-cargo

我有一组测试。有一些测试需要访问共享资源(外部库/ API /硬件设备)。如果这些测试中的任何一个并行运行,则它们将失败。

我知道我可以使用来运行所有内容,--test-threads=1但是我发现这对一些特殊测试来说很不方便。

有什么办法可以使所有测试并行运行,并且有少数例外?理想情况下,我想说不要同时运行X,Y,Z。

She*_*ter 6

正如mcarton在评论中提到的那样,您可以使用a Mutex来防止同时运行多段代码:

#[macro_use]
extern crate lazy_static; // 1.0.2

use std::{sync::Mutex, thread::sleep, time::Duration};

lazy_static! {
    static ref THE_RESOURCE: Mutex<()> = Mutex::new(());
}

type TestResult<T = ()> = std::result::Result<T, Box<std::error::Error>>;

#[test]
fn one() -> TestResult {
    let _shared = THE_RESOURCE.lock()?;
    eprintln!("Starting test one");
    sleep(Duration::from_secs(1));
    eprintln!("Finishing test one");
    Ok(())
}

#[test]
fn two() -> TestResult {
    let _shared = THE_RESOURCE.lock()?;
    eprintln!("Starting test two");
    sleep(Duration::from_secs(1));
    eprintln!("Finishing test two");
    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

如果使用进行运行cargo test -- --nocapture,则可以看到行为上的差异:

没有锁

#[macro_use]
extern crate lazy_static; // 1.0.2

use std::{sync::Mutex, thread::sleep, time::Duration};

lazy_static! {
    static ref THE_RESOURCE: Mutex<()> = Mutex::new(());
}

type TestResult<T = ()> = std::result::Result<T, Box<std::error::Error>>;

#[test]
fn one() -> TestResult {
    let _shared = THE_RESOURCE.lock()?;
    eprintln!("Starting test one");
    sleep(Duration::from_secs(1));
    eprintln!("Finishing test one");
    Ok(())
}

#[test]
fn two() -> TestResult {
    let _shared = THE_RESOURCE.lock()?;
    eprintln!("Starting test two");
    sleep(Duration::from_secs(1));
    eprintln!("Finishing test two");
    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

带锁

running 2 tests
Starting test one
Starting test two
Finishing test two
Finishing test one
test one ... ok
test two ... ok
Run Code Online (Sandbox Code Playgroud)

理想情况下,您应该将外部资源本身放在中,Mutex以使代码代表事实,即它是单例的,而无需记住锁定未使用的Mutex

这确实有一个巨大的弊端,那就是测试中的恐慌(也就是assert!失败)会导致Mutex中毒。然后,这将导致后续测试无法获取锁。如果您需要避免这种情况,并且知道锁定的资源处于良好状态(()应该没问题...),则可以处理中毒:

let _shared = THE_RESOURCE.lock().unwrap_or_else(|e| e.into_inner());
Run Code Online (Sandbox Code Playgroud)

如果您需要能够并行运行一组有限的线程,则可以使用信号灯。在这里,我已经建立了使用一个穷CondvarMutex

use std::{
    sync::{Condvar, Mutex},
    thread::sleep,
    time::Duration,
};

#[derive(Debug)]
struct Semaphore {
    mutex: Mutex<usize>,
    condvar: Condvar,
}

impl Semaphore {
    fn new(count: usize) -> Self {
        Semaphore {
            mutex: Mutex::new(count),
            condvar: Condvar::new(),
        }
    }

    fn wait(&self) -> TestResult {
        let mut count = self.mutex.lock().map_err(|_| "unable to lock")?;
        while *count == 0 {
            count = self.condvar.wait(count).map_err(|_| "unable to lock")?;
        }
        *count -= 1;
        Ok(())
    }

    fn signal(&self) -> TestResult {
        let mut count = self.mutex.lock().map_err(|_| "unable to lock")?;
        *count += 1;
        self.condvar.notify_one();
        Ok(())
    }

    fn guarded(&self, f: impl FnOnce() -> TestResult) -> TestResult {
        // Not panic-safe!
        self.wait()?;
        let x = f();
        self.signal()?;
        x
    }
}

lazy_static! {
    static ref THE_COUNT: Semaphore = Semaphore::new(4);
}
Run Code Online (Sandbox Code Playgroud)
THE_COUNT.guarded(|| {
    eprintln!("Starting test {}", id);
    sleep(Duration::from_secs(1));
    eprintln!("Finishing test {}", id);
    Ok(())
})
Run Code Online (Sandbox Code Playgroud)

也可以看看:


D G*_*D G 6

使用serial_test箱。添加这个板条箱后,您输入代码:

#[serial]
Run Code Online (Sandbox Code Playgroud)

在您要按顺序运行的任何测试之前。

  • 对我来说与 tokio 测试完美配合。这比笨拙地将互斥体塞在那里要干净得多。 (2认同)