chaos_rs/
macros.rs

1/// Returns `Err(tag.into())` or a custom error when the failpoint is enabled.
2///
3/// # Examples
4/// ```rust
5/// fn perform_action() -> Result<&'static str, String> {
6///     chaos_rs::maybe_fail!("network_fail");
7///     Ok("done")
8/// }
9/// ```
10///
11/// With custom error:
12/// ```rust
13/// fn perform_action() -> Result<&'static str, String> {
14///     chaos_rs::maybe_fail!("db_fail", "Database unreachable".to_string());
15///     Ok("done")
16/// }
17/// ```
18#[macro_export]
19macro_rules! maybe_fail {
20    ($tag:literal) => {
21        #[cfg(feature = "chaos")]
22        {
23            if $crate::__failpoint_internal::is_failpoint_enabled($tag) {
24                return Err($tag.into());
25            }
26        }
27    };
28    ($tag:literal, $err:expr) => {
29        #[cfg(feature = "chaos")]
30        {
31            if $crate::__failpoint_internal::is_failpoint_enabled($tag) {
32                return Err($err);
33            }
34        }
35    };
36}
37
38/// Panics when the failpoint is enabled.
39///
40/// # Example
41/// ```rust
42/// fn critical() {
43///     chaos_rs::maybe_panic!("unexpected_panic");
44/// }
45/// ```
46#[macro_export]
47macro_rules! maybe_panic {
48    ($tag:literal) => {
49        #[cfg(feature = "chaos")]
50        {
51            if $crate::__failpoint_internal::is_failpoint_enabled($tag) {
52                panic!($tag);
53            }
54        }
55    };
56}
57
58/// Sleeps for a given number of milliseconds when the failpoint is enabled.
59///
60/// # Example
61/// ```rust
62/// chaos_rs::maybe_sleep!("slow_io", 500);
63/// ```
64#[macro_export]
65macro_rules! maybe_sleep {
66    ($tag:literal, $millis:literal) => {
67        #[cfg(feature = "chaos")]
68        {
69            if $crate::__failpoint_internal::is_failpoint_enabled($tag) {
70                std::thread::sleep(std::time::Duration::from_millis($millis));
71            }
72        }
73    };
74}
75
76/// If the specified failpoint is enabled, this macro will pause the asynchronous
77/// execution for a given number of milliseconds.
78///
79/// # Example
80/// ```rust
81/// chaos_rs::maybe_sleep_async!("slow_io", 500);
82/// ```
83#[macro_export]
84macro_rules! maybe_sleep_async {
85    ($tag:literal, $millis:literal) => {
86        #[cfg(feature = "chaos")]
87        {
88            if $crate::__failpoint_internal::is_failpoint_enabled($tag) {
89                let duration = std::time::Duration::from_millis($millis);
90                $crate::__failpoint_internal::sleep_async_internal(duration).await;
91            }
92        }
93    };
94}
95
96/// Runs a code block with a failpoint enabled and validates its effect.
97///
98/// Supported modes:
99/// - `panic`: Expects the code to panic when the failpoint is active.
100/// - `error`: Expects the code to return `Err` when the failpoint is active.
101/// - Sleep validation: Verifies that code sleeps somewhere in the range of `min_ms` - `tolerance` and `min_ms` + `tolerance` when failpoint is active.
102///
103/// # Examples
104///
105/// Expects a panic:
106/// ```rust
107/// chaos_rs::with_failpoint!("panic_test", panic, {
108///     chaos_rs::maybe_panic!("panic_test");
109/// });
110/// ```
111///
112/// Expects an error:
113/// ```rust
114/// chaos_rs::with_failpoint!("error_test", error, {
115///     fn test() -> Result<(), ()> {
116///         chaos_rs::maybe_fail!("error_test", ());
117///         Ok(())
118///     }
119///     test()
120/// });
121/// ```
122///
123/// Expects the operation to sleep for 200 ± 50ms (150 - 250 range):
124/// ```rust
125/// chaos_rs::with_failpoint!("sleep_test", 200, 50, {
126///     chaos_rs::maybe_sleep!("sleep_test", 200);
127/// });
128/// ```
129#[macro_export]
130macro_rules! with_failpoint {
131    ($tag:literal, panic, $code:expr) => {{
132        #[cfg(feature = "chaos")]
133        {
134            $crate::__failpoint_internal::enable_failpoint($tag);
135            let result = std::panic::catch_unwind(|| $code);
136            $crate::__failpoint_internal::disable_failpoint($tag);
137            match result {
138                Ok(_) => panic!(
139                    "Expected panic from failpoint '{}', but none occurred",
140                    $tag
141                ),
142                Err(_) => {}
143            }
144        }
145    }};
146
147    ($tag:literal, error, $code:expr) => {{
148        #[cfg(feature = "chaos")]
149        {
150            $crate::__failpoint_internal::enable_failpoint($tag);
151            let result = $code;
152            $crate::__failpoint_internal::disable_failpoint($tag);
153
154            match result {
155                Err(_) => {}
156                Ok(_) => panic!(
157                    "Expected error from failpoint '{}', but function returned Ok",
158                    $tag
159                ),
160            }
161        }
162    }};
163
164    ($tag:literal, $min_ms:literal, $tolerance_ms:literal, $code:expr) => {{
165        #[cfg(feature = "chaos")]
166        {
167            $crate::__failpoint_internal::enable_failpoint($tag);
168            let start = std::time::Instant::now();
169            $code;
170            let elapsed = start.elapsed();
171            $crate::__failpoint_internal::disable_failpoint($tag);
172
173            let max = std::time::Duration::from_millis($min_ms + $tolerance_ms);
174            let min = std::time::Duration::from_millis($min_ms - $tolerance_ms);
175
176            assert!(
177                elapsed <= max && elapsed >= min,
178                "Expected sleep between {:?} and {:?} from failpoint '{}', got {:?}",
179                min,
180                max,
181                $tag,
182                elapsed
183            );
184        }
185    }};
186}
187#[macro_export]
188macro_rules! with_failpoint_async {
189    ($tag:literal, $min_ms:literal, $tolerance_ms:literal, $code:expr) => {{
190        #[cfg(feature = "chaos")]
191        {
192            $crate::__failpoint_internal::enable_failpoint($tag);
193            let start = std::time::Instant::now();
194            $code.await;
195            let elapsed = start.elapsed();
196            $crate::__failpoint_internal::disable_failpoint($tag);
197
198            let max = std::time::Duration::from_millis($min_ms + $tolerance_ms);
199            let min = std::time::Duration::from_millis($min_ms - $tolerance_ms);
200
201            assert!(
202                elapsed <= max && elapsed >= min,
203                "Expected sleep between {:?} and {:?} from failpoint '{}', got {:?}",
204                min,
205                max,
206                $tag,
207                elapsed
208            );
209        }
210    }};
211}