Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.6.0 - 2025-08-31

- Add `crate-isolation` feature for crate seperation of fail points (#85)

# 0.5.1 - 2022-10-08

- Switch to 2021 edition and use once cell (#61)
Expand Down
12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fail"
version = "0.5.1"
version = "0.6.0"
authors = ["The TiKV Project Developers"]
license = "Apache-2.0"
keywords = ["failpoints", "fail"]
Expand All @@ -20,6 +20,16 @@ rand = "0.8"

[features]
failpoints = []
# Automatically prefix fail point names with the name of the crate the fail point
# is currently located.
#
# This can be used to prevent conflicting fail points from two seperate libraries
# being triggered accidentally.
#
# WARNING:
# This feature should *NOT* be enabled outside of tests/dev-dependencies
# due to crate features being additive in nature.
crate-isolation = []

[package.metadata.docs.rs]
all-features = true
55 changes: 53 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@
//! - Fail points might have the same name, in which case they take the
//! same actions. Be careful about duplicating fail point names, either within
//! a single crate, or across multiple crates.
//!
//! ### Crate isolation of fail points
//!
//! When the `crate-isolation` feature is enabled, any fail point name given
//! will be prefixed with the name of the crate where the fail point is defined
//! provided by Cargo's `CARGO_PKG_NAME` compile time environment variable.
//!
//! This feature should **not** be enabled outside `dev-dependencies` / test dependencies
//! due to crate features being additive in nature.
//!

#![deny(missing_docs, missing_debug_implementations)]

Expand Down Expand Up @@ -670,6 +680,12 @@ pub fn eval<R, F: FnOnce(Option<String>) -> R>(name: &str, f: F) -> Option<R> {
/// The `FAILPOINTS` environment variable accepts this same syntax for its fail
/// point actions.
///
/// ### Crate isolation of fail points
///
/// When the `crate-isolation` feature is enabled, any fail point name given
/// will be prefixed with the name of the crate where the fail point is defined
/// provided by Cargo's `CARGO_PKG_NAME` compile time environment variable.
///
/// A call to `cfg` with a particular fail point name overwrites any existing actions for
/// that fail point, including those set via the `FAILPOINTS` environment variable.
pub fn cfg<S: Into<String>>(name: S, actions: &str) -> Result<(), String> {
Expand Down Expand Up @@ -819,19 +835,35 @@ fn set(
/// and`$e` must be a function or closure that accepts an `Option<String>` and
/// returns the same type as the enclosing function.
///
/// ### Automatic crate separation
///
/// When the `crate-isolation` feature is enabled, any fail point name given
/// will be prefixed with the name of the crate where the fail point is defined
/// provided by Cargo's `CARGO_PKG_NAME` compile time environment variable.
///
/// For example, if our crate name is `my-crate`, the following snippet will be
/// only be triggered when targeting the `my-crate::fail-point-1` fail point:
///
/// ```rust
/// # #[macro_use] extern crate fail;
/// fn function_return_unit() {
/// fail_point!("fail-point-1");
/// }
/// ```
///
/// For more examples see the [crate documentation](index.html). For more
/// information about controlling fail points see the [`cfg`](fn.cfg.html)
/// function.
#[macro_export]
#[cfg(feature = "failpoints")]
macro_rules! fail_point {
($name:expr) => {{
$crate::eval($name, |_| {
$crate::eval($crate::fail_point_name!($name), |_| {
panic!("Return is not supported for the fail point \"{}\"", $name);
});
}};
($name:expr, $e:expr) => {{
if let Some(res) = $crate::eval($name, $e) {
if let Some(res) = $crate::eval($crate::fail_point_name!($name), $e) {
return res;
}
}};
Expand All @@ -851,6 +883,24 @@ macro_rules! fail_point {
($name:expr, $cond:expr, $e:expr) => {{}};
}

#[doc(hidden)]
#[macro_export]
#[cfg(all(feature = "failpoints", feature = "crate-isolation"))]
macro_rules! fail_point_name {
($name:expr) => {
concat!(env!("CARGO_PKG_NAME"), "::", $name)
};
}

#[doc(hidden)]
#[macro_export]
#[cfg(all(feature = "failpoints", not(feature = "crate-isolation")))]
macro_rules! fail_point_name {
($name:expr) => {
$name
};
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -1035,6 +1085,7 @@ mod tests {
// like `test_pause` maybe also affected, so it's better keep it here.
#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_setup_and_teardown() {
let f1 = || {
fail_point!("setup_and_teardown1", |_| 1);
Expand Down
1 change: 1 addition & 0 deletions tests/no_use_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::*;

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_return() {
let f = || {
fail::fail_point!("return", |s: Option<String>| s
Expand Down
51 changes: 51 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::*;
use fail::fail_point;

#[test]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_off() {
let f = || {
fail_point!("off", |_| 2);
Expand All @@ -21,6 +22,7 @@ fn test_off() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_return() {
let f = || {
fail_point!("return", |s: Option<String>| s
Expand All @@ -38,6 +40,7 @@ fn test_return() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_sleep() {
let f = || {
fail_point!("sleep");
Expand All @@ -55,6 +58,7 @@ fn test_sleep() {
#[test]
#[should_panic]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_panic() {
let f = || {
fail_point!("panic");
Expand All @@ -65,6 +69,7 @@ fn test_panic() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_print() {
struct LogCollector(Arc<Mutex<Vec<String>>>);
impl log::Log for LogCollector {
Expand Down Expand Up @@ -99,6 +104,7 @@ fn test_print() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_pause() {
let f = || {
fail_point!("pause");
Expand Down Expand Up @@ -131,6 +137,7 @@ fn test_pause() {
}

#[test]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_yield() {
let f = || {
fail_point!("yield");
Expand All @@ -141,6 +148,7 @@ fn test_yield() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_callback() {
let f1 = || {
fail_point!("cb");
Expand All @@ -162,6 +170,7 @@ fn test_callback() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_delay() {
let f = || fail_point!("delay");
let timer = Instant::now();
Expand All @@ -172,6 +181,7 @@ fn test_delay() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_freq_and_count() {
let f = || {
fail_point!("freq_and_count", |s: Option<String>| s
Expand All @@ -193,6 +203,7 @@ fn test_freq_and_count() {

#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
#[cfg_attr(feature = "crate-isolation", ignore)]
fn test_condition() {
let f = |_enabled| {
fail_point!("condition", _enabled, |_| 2);
Expand All @@ -214,3 +225,43 @@ fn test_list() {
fail::cfg("list", "return").unwrap();
assert!(fail::list().contains(&("list".to_string(), "return".to_string())));
}

#[cfg(feature = "crate-isolation")]
#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
fn test_crate_isolation_return_parse() {
let f = || {
fail_point!("isolated_return_parse", |s: Option<String>| s
.map_or(2, |s| s.parse().unwrap()));
0
};
assert_eq!(f(), 0);

fail::cfg("isolated_return_parse", "return(1000)").unwrap();
assert_eq!(f(), 0);

fail::cfg("fail::isolated_return_parse", "return(1000)").unwrap();
assert_eq!(f(), 1000);

fail::cfg("fail::isolated_return_parse", "return").unwrap();
assert_eq!(f(), 2);
}

#[cfg(feature = "crate-isolation")]
#[test]
#[cfg_attr(not(feature = "failpoints"), ignore)]
fn test_crate_isolation_return_default() {
let f = || {
fail_point!("isolated_return_default");
0
};
assert_eq!(f(), 0);

fail::cfg("isolated_return_default", "return(1000)").unwrap();
let result = std::panic::catch_unwind(f)
.expect("callback should not panic as fail point should not be set");
assert_eq!(result, 0);

fail::cfg("fail::isolated_return_default", "return(1000)").unwrap();
std::panic::catch_unwind(f).expect_err("callback should panic as fail point is set");
}