如何避免 运行 一些并行测试?
How can I avoid running some tests in parallel?
我有一组测试。有一些测试需要访问共享资源(外部 library/API/hardware 设备)。如果这些测试中的任何一个 运行 并行,它们就会失败。
我知道我可以 运行 使用 --test-threads=1
的一切,但我发现这对一些特殊测试来说不方便。
有什么方法可以使所有测试保持 运行 并行进行并有少数例外?理想情况下,我想说不要同时 运行 X、Y、Z。
作为, you can use a Mutex
防止多段代码同时被运行ning:
use once_cell::sync::Lazy; // 1.4.0
use std::{sync::Mutex, thread::sleep, time::Duration};
static THE_RESOURCE: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
type TestResult<T = (), E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
#[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(())
}
如果您 运行 与 cargo test -- --nocapture
,您可以看到行为上的差异:
无锁
running 2 tests
Starting test one
Starting test two
Finishing test two
Finishing test one
test one ... ok
test two ... ok
带锁
running 2 tests
Starting test one
Finishing test one
Starting test two
test one ... ok
Finishing test two
test two ... ok
理想情况下,您将外部资源 本身 放在 Mutex
中,以使代码表示它是单例的事实,并且无需记住锁定其他未使用的 Mutex
.
这确实有 巨大的 缺点,即测试中的恐慌(a.k.a assert!
失败)将导致 Mutex
中毒。那么这就会导致后续的测试获取不到锁。如果你需要避免这种情况并且你知道锁定的资源处于良好状态(并且 ()
应该没问题......)你可以处理中毒:
let _shared = THE_RESOURCE.lock().unwrap_or_else(|e| e.into_inner());
如果您需要运行 一组有限线程的并行能力,您可以使用信号量。在这里,我使用 Condvar
和 Mutex
:
构建了一个糟糕的
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);
}
THE_COUNT.guarded(|| {
eprintln!("Starting test {}", id);
sleep(Duration::from_secs(1));
eprintln!("Finishing test {}", id);
Ok(())
})
另请参阅:
- How to limit the number of test threads in Cargo.toml?
您始终可以提供自己的测试工具。您可以通过将 [[test]]
条目添加到 Cargo.toml
:
来做到这一点
[[test]]
name = "my_test"
# If your test file is not `tests/my_test.rs`, add this key:
#path = "path/to/my_test.rs"
harness = false
在那种情况下,cargo test
将my_test.rs
编译为普通的可执行文件。这意味着您必须提供一个 main
函数并自己添加所有 "run tests" 逻辑。是的,这是一些工作,但至少您可以自己决定有关 运行ning 测试的所有内容。
您还可以创建两个测试文件:
tests/
- sequential.rs
- parallel.rs
然后您将需要 运行 cargo test --test sequential -- --test-threads=1
和 cargo test --test parallel
。所以它不适用于单个 cargo test
,但您不需要编写自己的测试工具逻辑。
使用 serial_test 箱子。添加这个箱子后,您输入代码:
#[serial]
在您想要的任何测试前面 运行 按顺序。
我有一组测试。有一些测试需要访问共享资源(外部 library/API/hardware 设备)。如果这些测试中的任何一个 运行 并行,它们就会失败。
我知道我可以 运行 使用 --test-threads=1
的一切,但我发现这对一些特殊测试来说不方便。
有什么方法可以使所有测试保持 运行 并行进行并有少数例外?理想情况下,我想说不要同时 运行 X、Y、Z。
作为Mutex
防止多段代码同时被运行ning:
use once_cell::sync::Lazy; // 1.4.0
use std::{sync::Mutex, thread::sleep, time::Duration};
static THE_RESOURCE: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
type TestResult<T = (), E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
#[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(())
}
如果您 运行 与 cargo test -- --nocapture
,您可以看到行为上的差异:
无锁
running 2 tests
Starting test one
Starting test two
Finishing test two
Finishing test one
test one ... ok
test two ... ok
带锁
running 2 tests
Starting test one
Finishing test one
Starting test two
test one ... ok
Finishing test two
test two ... ok
理想情况下,您将外部资源 本身 放在 Mutex
中,以使代码表示它是单例的事实,并且无需记住锁定其他未使用的 Mutex
.
这确实有 巨大的 缺点,即测试中的恐慌(a.k.a assert!
失败)将导致 Mutex
中毒。那么这就会导致后续的测试获取不到锁。如果你需要避免这种情况并且你知道锁定的资源处于良好状态(并且 ()
应该没问题......)你可以处理中毒:
let _shared = THE_RESOURCE.lock().unwrap_or_else(|e| e.into_inner());
如果您需要运行 一组有限线程的并行能力,您可以使用信号量。在这里,我使用 Condvar
和 Mutex
:
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);
}
THE_COUNT.guarded(|| {
eprintln!("Starting test {}", id);
sleep(Duration::from_secs(1));
eprintln!("Finishing test {}", id);
Ok(())
})
另请参阅:
- How to limit the number of test threads in Cargo.toml?
您始终可以提供自己的测试工具。您可以通过将 [[test]]
条目添加到 Cargo.toml
:
[[test]]
name = "my_test"
# If your test file is not `tests/my_test.rs`, add this key:
#path = "path/to/my_test.rs"
harness = false
在那种情况下,cargo test
将my_test.rs
编译为普通的可执行文件。这意味着您必须提供一个 main
函数并自己添加所有 "run tests" 逻辑。是的,这是一些工作,但至少您可以自己决定有关 运行ning 测试的所有内容。
您还可以创建两个测试文件:
tests/
- sequential.rs
- parallel.rs
然后您将需要 运行 cargo test --test sequential -- --test-threads=1
和 cargo test --test parallel
。所以它不适用于单个 cargo test
,但您不需要编写自己的测试工具逻辑。
使用 serial_test 箱子。添加这个箱子后,您输入代码:
#[serial]
在您想要的任何测试前面 运行 按顺序。