We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modules, Visibility, Testing and Docs step 20 of 22
Build a test runner that catches panics
Implement a small test runner that executes cases, catches unwinding panics and reports whether each result matches its expectation.
The site compiles submissions as ordinary binaries, without rustc --test.
Its current compiler and Clippy commands do not enable cfg(test), so code
inside a #[cfg(test)] module is not type-checked or run by those commands.
This exercise exposes the runner as a function the grading harness can call.
pub struct Case {
pub name: String,
pub op: String,
pub args: Vec<i64>,
pub should_panic: Option<String>,
pub ignore: bool,
}
pub struct Outcome {
pub name: String,
pub status: String,
}
pub fn run_suite(cases: Vec<Case>) -> Vec<Outcome>
Keep run_op unchanged. Return one outcome per input case, in input order,
preserving each name exactly. Duplicate and empty names are allowed; an
empty suite returns an empty vector.
Choosing the status
Check ignore before executing a case. Otherwise call run_op inside
catch_unwind and compare the result with should_panic.
| Condition | Status |
|---|---|
ignore is true |
ignored, without running the body |
| Returned normally and no panic was expected | ok |
| Panicked with no expectation, or returned normally when a panic was expected | failed |
| Panicked and its message contains the expected substring | panicked_as_expected |
| Panicked but its message lacks the expected substring | wrong_panic_message |
Matching is case-sensitive substring containment, not equality. For example,
Some("boom") matches "boom 7", while Some("BOOM") does not.
Some("") accepts any panic because every message contains the empty string.
It still fails when the body returns normally. Use a meaningful substring
when you need to distinguish an intended panic from an unrelated one.
The supplied operations
op |
Behavior |
|---|---|
ok, or any unrecognized name |
Sum args; an empty list sums to zero |
div |
Evaluate args[0] / args[1] |
idx |
Index [10_i64, 20, 30] using args[0] as usize |
boom |
Panic with "boom" |
boom_fmt |
Panic with the formatted message "boom <args[0]>" |
halt |
Exit the process with status 97 |
Missing required arguments cause indexing panics. Division by zero and
i64::MIN / -1 also panic. An index outside the three-element table panics;
the cast to usize follows the target's integer-cast rules. Sum inputs are
chosen so their intermediate additions fit in i64.
The graded halt cases are ignored. They check that ignored bodies really
are skipped: catch_unwind cannot catch process exit. If you run halt,
there is no suite result to return.
Reading a panic message
catch_unwind returns an error payload of type Box<dyn Any + Send>.
Handle both &'static str and String. Do not rely on every operation
always choosing the same payload representation. For a payload of neither
type, use an empty message in this runner.
This complete example supplies both string types explicitly and recovers their messages. Its default panic hook may still print diagnostics:
use std::any::Any;
use std::panic::{catch_unwind, panic_any};
fn message(payload: &(dyn Any + Send)) -> &str {
if let Some(text) = payload.downcast_ref::<&'static str>() {
text
} else if let Some(text) = payload.downcast_ref::<String>() {
text.as_str()
} else {
""
}
}
fn main() {
let borrowed = catch_unwind(|| -> () { panic_any("boom") });
let owned = catch_unwind(|| -> () { panic_any(String::from("boom 7")) });
if let (Err(a), Err(b)) = (borrowed, owned) {
assert_eq!(message(&*a), "boom");
assert_eq!(message(&*b), "boom 7");
} else {
panic!("both calls should panic");
}
}
Suppressing and restoring the hook
The panic hook runs before unwinding, even when catch_unwind later catches
the panic. The default hook prints a diagnostic and may print a backtrace,
depending on configuration.
Save the existing hook with take_hook, install a silent hook, and restore
the saved hook before returning. Restore it for an empty suite too.
This exercise runs the suite serially with no other code changing the hook.
Hooks are process-global: changing one in a general concurrent runner can
affect unrelated threads, even if you restore it afterward.
What catching a panic does not guarantee
UnwindSafe is an advisory trait that draws attention to state which might
be left inconsistent by a panic. It does not prove the absence of logic
bugs. The supplied closure only reads its case data and already meets the
bound; it needs no AssertUnwindSafe wrapper.
If you later capture mutable state, review what happens to that state before
using AssertUnwindSafe. The wrapper overrides the trait check; it does not
restore invariants or roll back changes.
catch_unwind catches unwinding panics, not process exit, aborts or panics
compiled to abort. The supplied operations use string payloads. In more
general code, dropping a custom panic payload can itself panic; a new panic
during an active unwind can abort the process. Prefer Result for ordinary
recoverable failures.
Submissions must compile, pass the tests and pass clippy -D warnings.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.