Skip to main content

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}