Skip to content

Commit d7cc281

Browse files
committed
Add try_fail_point!
In my project I have a ton of code which uses `anyhow::Result`. I want a convenient way to force a function to return a stock error from a failpoint. Today this requires something like: ``` fail::fail_point!("main", true, |msg| { let msg = msg.as_deref().unwrap_or("synthetic error"); Err(anyhow::anyhow!("{msg}")) }); ``` which is cumbersome to copy around. Now, I conservatively made this a new macro. I am not sure how often the use case of a fail point for an infallible (i.e. non-`Result`) function occurs. It may make sense to require those to take a distinct `inject_point!` or something? Signed-off-by: Colin Walters <[email protected]>
1 parent 5bc95a1 commit d7cc281

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ log = { version = "0.4", features = ["std"] }
1818
once_cell = "1.9.0"
1919
rand = "0.8"
2020

21+
[dev-dependencies]
22+
anyhow = "1.0"
23+
2124
[features]
2225
failpoints = []
2326

src/lib.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@
227227

228228
use std::collections::HashMap;
229229
use std::env::VarError;
230-
use std::fmt::Debug;
230+
use std::error::Error;
231+
use std::fmt::{Debug, Display};
231232
use std::str::FromStr;
232233
use std::sync::atomic::{AtomicUsize, Ordering};
233234
use std::sync::{Arc, Condvar, Mutex, MutexGuard, RwLock, TryLockError};
@@ -428,6 +429,33 @@ impl FromStr for Action {
428429
}
429430
}
430431

432+
/// A synthetic error created as part of [`try_fail_point!`].
433+
#[doc(hidden)]
434+
#[derive(Debug)]
435+
pub struct ReturnError(pub String);
436+
437+
impl ReturnError {
438+
const SYNTHETIC: &str = "synthetic failpoint error";
439+
}
440+
441+
impl From<Option<String>> for ReturnError {
442+
fn from(msg: Option<String>) -> Self {
443+
Self(msg.unwrap_or_else(|| Self::SYNTHETIC.to_string()))
444+
}
445+
}
446+
447+
impl Display for ReturnError {
448+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449+
f.write_str(&self.0)
450+
}
451+
}
452+
453+
impl Error for ReturnError {
454+
fn source(&self) -> Option<&(dyn Error + 'static)> {
455+
None
456+
}
457+
}
458+
431459
#[cfg_attr(feature = "cargo-clippy", allow(clippy::mutex_atomic))]
432460
#[derive(Debug)]
433461
struct FailPoint {
@@ -843,6 +871,32 @@ macro_rules! fail_point {
843871
}};
844872
}
845873

874+
/// A variant of [`fail_point`] designed for a function that returns [`std::result::Result`].
875+
///
876+
/// ```rust
877+
/// # #[macro_use] extern crate fail;
878+
/// fn fallible_function() -> Result<u32, Box<dyn std::error::Error>> {
879+
/// try_fail_point!("a-fail-point");
880+
/// Ok(42)
881+
/// }
882+
/// ```
883+
///
884+
/// This
885+
#[macro_export]
886+
#[cfg(feature = "failpoints")]
887+
macro_rules! try_fail_point {
888+
($name:expr) => {{
889+
if let Some(e) = $crate::eval($name, |msg| $crate::ReturnError::from(msg)) {
890+
return Err(From::from(e));
891+
}
892+
}};
893+
($name:expr, $cond:expr) => {{
894+
if $cond {
895+
$crate::try_fail_point!($name);
896+
}
897+
}};
898+
}
899+
846900
/// Define a fail point (disabled, see `failpoints` feature).
847901
#[macro_export]
848902
#[cfg(not(feature = "failpoints"))]
@@ -852,6 +906,14 @@ macro_rules! fail_point {
852906
($name:expr, $cond:expr, $e:expr) => {{}};
853907
}
854908

909+
/// Define a fail point for a Result-returning function (disabled, see `failpoints` feature).
910+
#[macro_export]
911+
#[cfg(not(feature = "failpoints"))]
912+
macro_rules! try_fail_point {
913+
($name:expr) => {{}};
914+
($name:expr, $cond:expr) => {{}};
915+
}
916+
855917
#[cfg(test)]
856918
mod tests {
857919
use super::*;
@@ -1032,6 +1094,22 @@ mod tests {
10321094
}
10331095
}
10341096

1097+
#[test]
1098+
#[cfg(feature = "failpoints")]
1099+
fn test_try_failpoint() -> anyhow::Result<()> {
1100+
fn test_anyhow() -> anyhow::Result<()> {
1101+
try_fail_point!("tryfail-with-result");
1102+
Ok(())
1103+
}
1104+
fn test_stderr() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
1105+
try_fail_point!("tryfail-with-result-2");
1106+
Ok(())
1107+
}
1108+
test_anyhow()?;
1109+
test_stderr().map_err(anyhow::Error::msg)?;
1110+
Ok(())
1111+
}
1112+
10351113
// This case should be tested as integration case, but when calling `teardown` other cases
10361114
// like `test_pause` maybe also affected, so it's better keep it here.
10371115
#[test]

0 commit comments

Comments
 (0)