асинхронное тестирование в ржавчине - как провалить тест из побочного потока или в панике

Моя библиотека порождает побочный поток, и я пытаюсь написать для него модульные тесты. если библиотека / имитирует панику, я бы не прошел тест.

Я издеваюсь mockall

мой код библиотеки выглядит примерно так:

    #[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();
    }

Воспроизводимый случай:

    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();
    }

1 ответ

Тестовый жгут только проверяет, вызывает ли панику поток, который он породил, вам придется распространить панику внутри вашего собственного потока на поток, который вызвал тест. Для этого можно вернуть JoinHandle в ваш поток, созданный thread::spawn:

fn should_fail() -> std::thread::JoinHandle<()> {
    std::thread::spawn(|| {
        loop {
            assert!(false);
            std::thread::sleep(Duration::from_secs(1));
        }
    })
}

Затем вы можете позвонить .join() на ручке. JoinHandle::join()ожидает завершения связанного потока. Если дочерний поток паникует, Err возвращается с параметром, присвоенным panic!. Таким образом, паника распространяется на основной поток:

#[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.

Другие вопросы по тегам