1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
// SPDX-FileCopyrightText: Copyright 2025 Dmitry Marakasov <amdmi3@amdmi3.ru>
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! `faine` stands for _FAultpoint INjection, Exhaustible/Exploring_ and is an
//! implementation of testing technique known as
//! [_fail points_](https://man.freebsd.org/cgi/man.cgi?query=fail),
//! [_fault injection_](https://en.wikipedia.org/wiki/Fault_injection),
//! or [_chaos engineering_](https://en.wikipedia.org/wiki/Chaos_engineering),
//! which allows testing otherwise hard or impossible to reproduce conditions
//! such as I/O errors.
//!
//! # How this works
//!
//! - You instrument the source code, adding (fail)points where normal code flow
//! can be overridden externally, to, for instance, return a specific error instead
//! of calling an I/O function (note that for surrounding code, triggering such
//! failpoint would be the same as named I/O function returning an error).
//! - You trigger these failpoints in the tests, effectively simulating otherwise
//! hard to reproduce failures, and check that your code behaves correctly under
//! these conditions.
//!
//! On top of supporting that, `faine` implements automated execution path exploration,
//! running a tested code multiple times with different combinations of failpoints enabled
//! and disabled (NB: in much more effective way than trying all N² possible combinations).
//! This allows simpler tests (which do not know inner workings of the code, that is to
//! know which failpoints to trigger and which effects to expect), with much higher coverage
//! (as all possible code paths are tested).
//!
//! # Example
//!
//! Let's test a code which is supposed to atomically replace a file with given content.
//!
//! Instrument the code by adding failpoint macros before (or around) each operation
//! you want to simulate failures of:
//!
//! ```
//! # use std::path::Path;
//! # use std::fs::File;
//! # use std::io::{self, Write};
//! use faine::inject_return_io_error;
//!
//! fn atomic_replace_file(path: &Path, content: &str) -> io::Result<()> {
//! inject_return_io_error!("create file"); // <- added failpoint
//! let mut file = File::create(path)?;
//! inject_return_io_error!("write file"); // <- added failpoint
//! file.write_all(content.as_bytes())?;
//! Ok(())
//! }
//! ```
//!
//! Now write a test, utilizing [`faine::Runner`](crate::Runner):
//!
//! ```should_panic
//! # use std::path::Path;
//! # use std::fs::{File, read_to_string};
//! # use std::io::{self, Write};
//! # use faine::inject_return_io_error;
//! # fn atomic_replace_file(path: &Path, content: &str) -> io::Result<()> {
//! # inject_return_io_error!("create file"); // <- added failpoint
//! # let mut file = File::create(path)?;
//! # inject_return_io_error!("write file"); // <- added failpoint
//! # file.write_all(content.as_bytes())?;
//! # Ok(())
//! # }
//! use faine::Runner;
//!
//! #[test]
//! # fn dummy() {}
//! fn test_replace_file_is_atomic() {
//! Runner::default().run(|_| {
//! // prepare filesystem state for testing
//! let tempdir = tempfile::tempdir().unwrap();
//! let path = tempdir.path().join("myfile");
//! File::create(&path).unwrap().write_all(b"old").unwrap();
//! // run the tested code
//! let res = atomic_replace_file(&path, "new");
//! // check resulting filesystem state
//! let contents = read_to_string(path).unwrap();
//! assert!(
//! res.is_ok() && contents == "new" ||
//! res.is_err() && contents == "old"
//! ); // fires!
//! }).unwrap();
//! }
//! # test_replace_file_is_atomic();
//! ```
//!
//! See `examples/atomic_replace_file.rs` for complete code for this example.
//!
//! # Quick reference
//!
//! ## Specifying failpoints which inject early return
//!
//! The complete macro signature allows to specify failpoint name and returned value:
//!
//! ```
//! # use std::io;
//! # use faine::inject_return;
//! inject_return!("failpoint name", Err(io::Error::other("injected error")));
//! # Ok::<(), io::Error>(())
//! ```
//!
//! You can omit failpoint name (in which case it's generated from source file
//! path, line and position), and, as testing I/O related code is quite common
//! case, there are shortcuts which return `Err(io::Error::other()))` right away:
//!
//! ```
//! # use std::io;
//! # use faine::{inject_return, inject_return_io_error};
//! inject_return!(Err(io::Error::other("injected error"))); // name autogenerated
//! inject_return_io_error!("failpoint name"); // return io::Error
//! inject_return_io_error!();
//! # Ok::<(), io::Error>(())
//! ```
//!
//! ## Specifying failpoints which wrap expressions
//!
//! There is a set of macros with the same variations which, instead of returning
//! early, wrap an expression and replace it with something else when failpoint
//! is activated:
//!
//! ```no_run
//! # use std::io;
//! # use std::fs::File;
//! # use faine::{inject_override, inject_override_io_error};
//! let f = inject_override!(File::open("foo"), "failpoint name", Err(io::Error::other("injected error")));
//! let f = inject_override!(File::open("foo"), Err(io::Error::other("injected error")));
//! let f = inject_override_io_error!(File::open("foo"), "failpoint name");
//! let f = inject_override_io_error!(File::open("foo"));
//! # Ok::<(), io::Error>(())
//! ```
//!
//! These are particularly useful if you branch based on I/O operation result:
//!
//! ```
//! # use std::path::Path;
//! # use std::fs::File;
//! # use std::io;
//! # use faine::{inject_override, inject_override_io_error};
//! fn open_with_fallback() -> io::Result<File> {
//! if let Ok(file) = inject_override_io_error!(File::open("main.dat")) {
//! Ok(file)
//! } else {
//! inject_override_io_error!(File::open("backup.dat"))
//! }
//! }
//! ```
//!
//! There's also a similar set of macros `inject_override_with_side_effect*`
//! which do the same, but still call an expression if a failpoint is activated,
//! allowing its possible side effect to happen.
//!
//! ## Executing the instrumented code
//!
//! In the test, construct a default [`Runner`](crate::Runner) and call its
//! [`run()`](crate::Runner::run) method with a code to test (with optional
//! preparation code an asserts, just like a normal test):
//!
//! ```
//! # use faine::Runner;
//! # fn tested_code() -> bool { true }
//! #[test]
//! # fn dummy() {}
//! fn test_foobar() {
//! Runner::default().run(|_| {
//! // ...preparation...
//! let res = tested_code();
//! assert!(res);
//! }).unwrap();
//! }
//! ```
//!
//! ## Controlling execution
//!
//! [`Runner`](crate::Runner) has methods to tune its behavior:
//!
//! - [`with_branch_preference()`](crate::Runner::with_branch_preference)
//!
//! ## Enabling failpoints
//!
//! You can toggle failpoints processing with [`enable_failpoints`](crate::enable_failpoints)
//! macro. This is particularly useful to test how subsequent runs of the code
//! recover from any previous errors:
//!
//! ```
//! # use std::io;
//! # use faine::inject_return_io_error;
//! use faine::enable_failpoints;
//! # fn tested_code() {}
//! tested_code(); // this fails in all possible ways
//! tested_code(); // this also fails, and sees the previous errors
//! enable_failpoints!(false);
//! tested_code(); // this recovers
//! # Ok::<(), io::Error>(())
//! ```
//!
//! ## Introspection
//!
//! You may inspect which failpoints the code execution has passed through,
//! and which of these were activated. It is possible from both the executed
//! code (to examine current execution trace) and after [`Runner::run`] completion
//! (to examine traces for all performed executions with different failpoint paths).
//!
//! ```
//! # use faine::{Runner, Branch};
//! # fn tested_code() -> bool { true }
//! #[test]
//! # fn dummy() {}
//! fn test_foobar() {
//! let report = Runner::default().run(|handle| {
//! let res = tested_code();
//! eprintln!("current trace: {:?}", handle.trace());
//! // assert logic may take failpoint status in the current run into account
//! if handle.trace().failpoint_status_first("commit transaction") == Some(Branch::Skip) {
//! assert!(res);
//! }
//! }).unwrap();
//! // prints the same set of traces
//! eprintln!("all traces: {:#?}", report.traces);
//! }
//! ```
//!
//! By default, traces are printed in a fancy way to make them most readable. This may
//! be disabled through `fancy-traces` feature. An example [`Report::traces`] dump for
//! a linear code with three failpoints:
//!
//! ```text
//! [
//! (💥create temp file),
//! (create temp file)→(💥write temp file),
//! (create temp file)→(write temp file)→(💥replace file),
//! (create temp file)→(write temp file)→(replace file),
//! ]
//! ```
//!
//! Run `cargo test --example atomic_replace_file -- --no-capture` to reproduce.
//!
//! # Other implementations of the same concept
//!
//! Neither supports path exploration as far as I know.
//!
//! - [chaos-rs](https://crates.io/crates/chaos-rs)
//! - [fail](https://crates.io/crates/fail)
//! - [fail-parallel](https://crates.io/crates/fail-parallel)
//! - [failpoints](https://crates.io/crates/failpoints)
//! - [fault-injection](https://crates.io/crates/fault-injection)
pub use Branch;
pub use Error;
pub use ;
pub use Runner;