Skip to main content

perl_test_must/
lib.rs

1//! Safe unwrap replacements for tests.
2//!
3//! This crate provides panic-on-failure helpers that are safe to use in tests,
4//! avoiding explicit `unwrap()` calls which are denied by clippy policy.
5//!
6//! # Overview
7//!
8//! Three helpers cover the common cases:
9//! - [`must`] — extract the value from a `Result`, or panic with the error
10//! - [`must_some`] — extract the value from an `Option`, or panic
11//! - [`must_err`] — extract the error from a `Result`, or panic if `Ok`
12//!
13//! # Example
14//!
15//! ```rust
16//! use perl_test_must::{must, must_some, must_err};
17//!
18//! let result: Result<i32, &str> = Ok(42);
19//! assert_eq!(must(result), 42);
20//!
21//! let opt: Option<i32> = Some(7);
22//! assert_eq!(must_some(opt), 7);
23//!
24//! let err_result: Result<i32, &str> = Err("oops");
25//! assert_eq!(must_err(err_result), "oops");
26//! ```
27
28// This crate provides test helpers that intentionally panic on failure.
29// The must/must_some/must_err helpers are designed to panic in tests.
30#![allow(clippy::panic)]
31
32/// Extract the value from a `Result`, or panic with the error.
33///
34/// This is a test-only replacement for `unwrap` that is compliant
35/// with the "no unwrap/expect" policy.
36///
37/// Note: `#[must_use]` is intentionally omitted. `must()` is frequently
38/// called as an assertion (`must(fs::write(...))`) where the caller intentionally
39/// discards the `()` return value. Adding `#[must_use]` would trigger ~373
40/// spurious warnings across the workspace for those valid use cases.
41#[track_caller]
42pub fn must<T, E: std::fmt::Debug>(r: Result<T, E>) -> T {
43    match r {
44        Ok(v) => v,
45        Err(e) => panic!("unexpected Err<{}>: {e:?}", std::any::type_name::<E>()),
46    }
47}
48
49/// Extract the value from an `Option`, or panic.
50///
51/// This is a test-only replacement for `unwrap` that is compliant
52/// with the "no unwrap/expect" policy.
53#[track_caller]
54#[must_use]
55pub fn must_some<T>(o: Option<T>) -> T {
56    match o {
57        Some(v) => v,
58        None => panic!("unexpected None<{}>", std::any::type_name::<T>()),
59    }
60}
61
62/// Extract the error from a `Result`, or panic if `Ok`.
63///
64/// This is a test-only replacement for `.unwrap_err()` that is compliant
65/// with the "no unwrap/expect" policy.
66#[track_caller]
67#[must_use]
68pub fn must_err<T: std::fmt::Debug, E>(r: Result<T, E>) -> E {
69    match r {
70        Err(e) => e,
71        Ok(v) => panic!(
72            "expected Err<{}>, got Ok<{}>({v:?})",
73            std::any::type_name::<E>(),
74            std::any::type_name::<T>()
75        ),
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::{must, must_err, must_some};
82
83    #[test]
84    fn must_unwraps_ok() {
85        let result: Result<i32, &str> = Ok(42);
86        assert_eq!(must(result), 42);
87    }
88
89    #[test]
90    #[should_panic(expected = "unexpected Err")]
91    fn must_panics_on_err() {
92        let result: Result<i32, &str> = Err("oops");
93        must(result);
94    }
95
96    #[test]
97    fn must_some_unwraps_some() {
98        assert_eq!(must_some(Some(99)), 99);
99    }
100
101    #[test]
102    #[should_panic(expected = "unexpected None")]
103    fn must_some_panics_on_none() {
104        let _ = must_some(Option::<i32>::None);
105    }
106
107    #[test]
108    fn must_err_unwraps_err() {
109        let result: Result<i32, &str> = Err("expected error");
110        assert_eq!(must_err(result), "expected error");
111    }
112
113    #[test]
114    #[should_panic(expected = "expected Err")]
115    fn must_err_panics_on_ok() {
116        let result: Result<i32, &str> = Ok(1);
117        let _ = must_err(result);
118    }
119}