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 bythread::spawn
: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 topanic!
. In this way, the panic is propagated to the main thread:You might not want to return the
JoinHandle
for the sole purpose of testing. HoweverJoinHandle
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.