简体   繁体   中英

async testing in rust - how to fail a test from a side thread or by panic

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM