unlit_wgpu_test_util/native.rs
1//! The native half of the harness: `libtest-mimic` over the catalog.
2//!
3//! Natively a test runs in the process that reports it, which is what
4//! `cargo nextest` drives. The runner is `libtest-mimic` rather than libtest's
5//! generated harness because the catalog, not a set of `#[test]` items, is
6//! what a browser can also offer — so one source file feeds both runners and a
7//! test keeps a single name in both.
8//!
9//! `libtest-mimic` has no `#[should_panic]`, so the comparison lives here and
10//! matches what the browser's panic hook does: a caught panic passes when its
11//! message contains the message the test declared, and fails otherwise.
12
13use core::any::Any;
14use core::panic::AssertUnwindSafe;
15
16use crate::TestEntry;
17
18/// Run every registered test, reporting through `libtest-mimic`.
19///
20/// Never returns: it exits the process with libtest's status.
21pub fn run(tests: Vec<TestEntry>) -> ! {
22 let args = libtest_mimic::Arguments::from_args();
23 // A trial's name is the test's plain name, which is what libtest reports
24 // for an integration test's `#[test] fn` — so a filter or a recorded name
25 // does not have to learn a new spelling.
26 let trials = tests
27 .into_iter()
28 .map(|entry| libtest_mimic::Trial::test(entry.name, move || run_one(entry)))
29 .collect();
30
31 libtest_mimic::run(&args, trials).exit()
32}
33
34/// Drive one test's future to completion, deciding it against the panic the
35/// test declared.
36fn run_one(entry: TestEntry) -> Result<(), libtest_mimic::Failed> {
37 let outcome = std::panic::catch_unwind(AssertUnwindSafe(|| {
38 pollster::block_on((entry.body)());
39 }));
40
41 match (outcome, entry.expected_panic) {
42 (Ok(()), None) => Ok(()),
43 (Ok(()), Some(expected)) => Err(libtest_mimic::Failed::from(format!(
44 "expected a panic whose message contains {expected:?}, \
45 but the test returned without panicking"
46 ))),
47 // The default hook has already printed the panic and its backtrace, so
48 // the message alone is enough to fail the test with.
49 (Err(payload), None) => Err(libtest_mimic::Failed::from(format!(
50 "the test panicked: {}",
51 panic_message(&*payload)
52 ))),
53 (Err(payload), Some(expected)) => {
54 let message = panic_message(&*payload);
55 if message.contains(expected) {
56 Ok(())
57 } else {
58 Err(libtest_mimic::Failed::from(format!(
59 "expected a panic whose message contains {expected:?}, \
60 but the message was:\n{message}"
61 )))
62 }
63 }
64 }
65}
66
67/// The text of a panic payload.
68///
69/// `panic!` with a literal produces a `&str` and with formatting a `String`;
70/// anything else is a caller's own payload, which has no message to compare.
71fn panic_message(payload: &(dyn Any + Send)) -> String {
72 if let Some(message) = payload.downcast_ref::<&str>() {
73 (*message).to_owned()
74 } else if let Some(message) = payload.downcast_ref::<String>() {
75 message.clone()
76 } else {
77 "a panic payload that is neither `&str` nor `String`".to_owned()
78 }
79}