126
126
//! fail point will immediately return from the function, optionally with a
127
127
//! configurable value.
128
128
//!
129
- //! The setup for early return requires a slightly diferent invocation of the
130
- //! `fail_point!` macro. To illustrate this, let's modify the `do_fallible_work`
129
+ //! The setup for early return is most convenient with the [`try_fail_point`] macro.
130
+ //! To illustrate this, let's modify the `do_fallible_work`
131
131
//! function we used earlier to return a `Result`:
132
132
//!
133
133
//! ```rust
134
134
//! use fail::{fail_point, FailScenario};
135
135
//! use std::io;
136
136
//!
137
137
//! fn do_fallible_work() -> io::Result<()> {
138
- //! fail_point !("read-dir");
138
+ //! try_fail_point !("read-dir");
139
139
//! let _dir: Vec<_> = std::fs::read_dir(".")?.collect();
140
140
//! // ... do some work on the directory ...
141
141
//! Ok(())
150
150
//! }
151
151
//! ```
152
152
//!
153
- //! This example has more proper Rust error handling, with no unwraps
154
- //! anywhere. Instead it uses `?` to propagate errors via the `Result` type
155
- //! return values. This is more realistic Rust code.
156
- //!
157
- //! The "read-dir" fail point though is not yet configured to support early
158
- //! return, so if we attempt to configure it to "return", we'll see an error
159
- //! like
160
- //!
161
- //! ```sh
162
- //! $ FAILPOINTS=read-dir=return cargo run --features fail/failpoints
163
- //! Finished dev [unoptimized + debuginfo] target(s) in 0.13s
164
- //! Running `target/debug/failpointtest`
165
- //! thread 'main' panicked at 'Return is not supported for the fail point "read-dir"', src/main.rs:7:5
166
- //! note: Run with `RUST_BACKTRACE=1` for a backtrace.
167
- //! ```
168
- //!
169
- //! This error tells us that the "read-dir" fail point is not defined correctly
170
- //! to support early return, and gives us the line number of that fail point.
171
- //! What we're missing in the fail point definition is code describring _how_ to
172
- //! return an error value, and the way we do this is by passing `fail_point!` a
173
- //! closure that returns the same type as the enclosing function.
174
- //!
175
- //! Here's a variation that does so:
176
- //!
177
- //! ```rust
178
- //! # use std::io;
179
- //! fn do_fallible_work() -> io::Result<()> {
180
- //! fail::fail_point!("read-dir", |_| {
181
- //! Err(io::Error::new(io::ErrorKind::PermissionDenied, "error"))
182
- //! });
183
- //! let _dir: Vec<_> = std::fs::read_dir(".")?.collect();
184
- //! // ... do some work on the directory ...
185
- //! Ok(())
186
- //! }
187
- //! ```
188
- //!
189
- //! And now if the "read-dir" fail point is configured to "return" we get a
190
- //! different result:
191
- //!
192
- //! ```sh
193
- //! $ FAILPOINTS=read-dir=return cargo run --features fail/failpoints
194
- //! Compiling failpointtest v0.1.0
195
- //! Finished dev [unoptimized + debuginfo] target(s) in 2.38s
196
- //! Running `target/debug/failpointtest`
197
- //! Error: Custom { kind: PermissionDenied, error: StringError("error") }
198
- //! ```
199
- //!
200
153
//! This time, `do_fallible_work` returned the error defined in our closure,
201
154
//! which propagated all the way up and out of main.
202
155
//!
207
160
//! panic and return early. But that's not all they can do. To learn more see
208
161
//! the documentation for [`cfg`](fn.cfg.html),
209
162
//! [`cfg_callback`](fn.cfg_callback.html) and
210
- //! [`fail_point!`](macro.fail_point.html).
211
- //!
163
+ //! [`fail_point!`](macro.fail_point.html) and [`try_fail_point!`].
212
164
//!
213
165
//! ## Usage considerations
214
166
//!
227
179
228
180
use std:: collections:: HashMap ;
229
181
use std:: env:: VarError ;
230
- use std:: fmt:: Debug ;
182
+ use std:: error:: Error ;
183
+ use std:: fmt:: { Debug , Display } ;
231
184
use std:: str:: FromStr ;
232
185
use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
233
186
use std:: sync:: { Arc , Condvar , Mutex , MutexGuard , RwLock , TryLockError } ;
@@ -428,6 +381,39 @@ impl FromStr for Action {
428
381
}
429
382
}
430
383
384
+ /// A synthetic error created as part of [`try_fail_point!`].
385
+ #[ doc( hidden) ]
386
+ #[ derive( Debug ) ]
387
+ pub struct ReturnError ( pub String ) ;
388
+
389
+ impl ReturnError {
390
+ const SYNTHETIC : & str = "synthetic failpoint error" ;
391
+ }
392
+
393
+ impl From < Option < String > > for ReturnError {
394
+ fn from ( msg : Option < String > ) -> Self {
395
+ Self ( msg. unwrap_or_else ( || Self :: SYNTHETIC . to_string ( ) ) )
396
+ }
397
+ }
398
+
399
+ impl Display for ReturnError {
400
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
401
+ f. write_str ( & self . 0 )
402
+ }
403
+ }
404
+
405
+ impl Error for ReturnError {
406
+ fn source ( & self ) -> Option < & ( dyn Error + ' static ) > {
407
+ None
408
+ }
409
+ }
410
+
411
+ impl From < ReturnError > for std:: io:: Error {
412
+ fn from ( e : ReturnError ) -> Self {
413
+ std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , e. 0 )
414
+ }
415
+ }
416
+
431
417
#[ cfg_attr( feature = "cargo-clippy" , allow( clippy:: mutex_atomic) ) ]
432
418
#[ derive( Debug ) ]
433
419
struct FailPoint {
@@ -843,6 +829,44 @@ macro_rules! fail_point {
843
829
} } ;
844
830
}
845
831
832
+ /// A variant of [`fail_point`] designed for a function that returns [`std::result::Result`].
833
+ ///
834
+ /// 1. A failpoint that supports e.g. `FAILPOINTS=a-fail-point=return` to return a synthetic error
835
+ ///
836
+ /// ```rust
837
+ /// # #[macro_use] extern crate fail;
838
+ /// fn fallible_function() -> Result<u32, Box<dyn std::error::Error>> {
839
+ /// try_fail_point!("a-fail-point");
840
+ /// Ok(42)
841
+ /// }
842
+ /// ```
843
+ ///
844
+ /// Like [`fail_point`], this also has a form which accepts a runtime condition:
845
+ ///
846
+ /// 2. A fail point with conditional execution:
847
+ ///
848
+ /// ```rust
849
+ /// # #[macro_use] extern crate fail;
850
+ /// fn function_conditional(enable: bool) -> Result<u32, Box<dyn std::error::Error>> {
851
+ /// try_fail_point!("fail-point-3", enable);
852
+ /// Ok(42)
853
+ /// }
854
+ /// ```
855
+ #[ macro_export]
856
+ #[ cfg( feature = "failpoints" ) ]
857
+ macro_rules! try_fail_point {
858
+ ( $name: expr) => { {
859
+ if let Some ( e) = $crate:: eval( $name, |msg| $crate:: ReturnError :: from( msg) ) {
860
+ return Err ( From :: from( e) ) ;
861
+ }
862
+ } } ;
863
+ ( $name: expr, $cond: expr) => { {
864
+ if $cond {
865
+ $crate:: try_fail_point!( $name) ;
866
+ }
867
+ } } ;
868
+ }
869
+
846
870
/// Define a fail point (disabled, see `failpoints` feature).
847
871
#[ macro_export]
848
872
#[ cfg( not( feature = "failpoints" ) ) ]
@@ -852,6 +876,14 @@ macro_rules! fail_point {
852
876
( $name: expr, $cond: expr, $e: expr) => { { } } ;
853
877
}
854
878
879
+ /// Define a fail point for a Result-returning function (disabled, see `failpoints` feature).
880
+ #[ macro_export]
881
+ #[ cfg( not( feature = "failpoints" ) ) ]
882
+ macro_rules! try_fail_point {
883
+ ( $name: expr) => { { } } ;
884
+ ( $name: expr, $cond: expr) => { { } } ;
885
+ }
886
+
855
887
#[ cfg( test) ]
856
888
mod tests {
857
889
use super :: * ;
@@ -1032,6 +1064,27 @@ mod tests {
1032
1064
}
1033
1065
}
1034
1066
1067
+ #[ test]
1068
+ #[ cfg( feature = "failpoints" ) ]
1069
+ fn test_try_failpoint ( ) -> anyhow:: Result < ( ) > {
1070
+ fn test_anyhow ( ) -> anyhow:: Result < ( ) > {
1071
+ try_fail_point ! ( "tryfail-with-result" ) ;
1072
+ Ok ( ( ) )
1073
+ }
1074
+ fn test_stderr ( ) -> Result < ( ) , Box < dyn Error + Send + Sync + ' static > > {
1075
+ try_fail_point ! ( "tryfail-with-result-2" ) ;
1076
+ Ok ( ( ) )
1077
+ }
1078
+ fn test_stdioerr ( ) -> std:: io:: Result < ( ) > {
1079
+ try_fail_point ! ( "tryfail-with-result-3" ) ;
1080
+ Ok ( ( ) )
1081
+ }
1082
+ test_anyhow ( ) ?;
1083
+ test_stderr ( ) . map_err ( anyhow:: Error :: msg) ?;
1084
+ test_stdioerr ( ) ?;
1085
+ Ok ( ( ) )
1086
+ }
1087
+
1035
1088
// This case should be tested as integration case, but when calling `teardown` other cases
1036
1089
// like `test_pause` maybe also affected, so it's better keep it here.
1037
1090
#[ test]
0 commit comments