My library spawns a side thread and I'm trying to write unit tests for it. if the library/mocks panic I would like to fail the test.
I'm mocking with mockall
my lib code is something like:
#[cfg_attr(test, automock)]
trait T1 { fn foo(&self, val: String) }
#[cfg_attr(test, automock)]
trait T2 { fn bar(&self, val: String) }
#[cfg_attr(test, automock)]
trait T3 { fn fa(&self, val: String) }
struct MyLib<A: T1 + Send + Sync, B:T2 + Send + Sync, C:T3 + Send + Sync> {f1: Arc<A>, f2: Arc<B>, f3: Arc<C>};
impl My_Lib {
fn do_something (&self) {
let f1 = Arc::clone(&self.f1);
let f2 = Arc::clone(&self.f2);
let f3 = Arc::clone(&self.f3);
thread::Builder::new().name("lib_thread".to_string()).spawn(move || {
// does something in an infinite loop with lots of logic and calls out to the traits which I am mocking in the tests
loop {
// some logic
// ....
f1.foo("something happened here which is interesting so check in test if it occured".to_string());
// more logic
// ...
f2.bar("more interesting things happened here".to_string());
// more logic
// ...
f3.fa("abc".to_string());
thread::sleep(interval)
}
});
}
}
#[test]
fn test_lib() {
let mut t1 = Mock_T1::new();
t1.expect_foo().return_const(()); // all sorts of mocks...
// all sorts of fancy logic
let mut c = Mock_T3::new();
c.expect_fa().withf(|x: String| {
// some complex assertion here which failed and it was called on "lib_thread"
// i.e.
assert!(x == "some interesting value which should be here!");
assert(false); // should make the test fail but instead get a panic on the thread
// thread 'lib_thread' panicked at 'MockT3::fa: No matching expectation found
// test is still running and never completes
}
//block the test from exiting until I want to finish
std::thread::park();
}
Reproducible case:
fn should_fail() {
std::thread::spawn(|| {
loop {
assert!(false); // test should complete here
std::thread::sleep(Duration::from_secs(1));
}
});
}
#[test]
fn fail() {
should_fail();
std::thread::park();
}
The test harness only checks if the thread it spawned panics, you'll have to propagate the panic inside your own thread to the thread that invoked the test. To do this, you can return a JoinHandle
to your thread, which is created by thread::spawn
:
fn should_fail() -> std::thread::JoinHandle<()> {
std::thread::spawn(|| {
loop {
assert!(false);
std::thread::sleep(Duration::from_secs(1));
}
})
}
You can then call .join()
on the handle. JoinHandle::join()
waits for the associated thread to finish. If the child thread panics, Err
is returned with the parameter given to panic!
. In this way, the panic is propagated to the main thread:
#[test]
fn test() {
let join_handle = should_fail();
// unwrap will panic if `should_fail` panics
join_handle.join().unwrap();
}
You might not want to return the JoinHandle
for the sole purpose of testing. However JoinHandle
is so much more than that. In fact, there was even discussion about marking it #[must_use]
! Here is an excellent post about Joining your threads .
There are a couple other ways to wait for a thread to finish without blocking, such as using channels, or refcounters that are discussed here .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.