|
28 | 28 | //! I/O panic: |
29 | 29 | //! |
30 | 30 | //! ```rust |
31 | | -//! use fail::fail_point; |
| 31 | +//! use fail::{fail_point, FailScenario}; |
32 | 32 | //! |
33 | 33 | //! fn do_fallible_work() { |
34 | 34 | //! fail_point!("read-dir"); |
35 | 35 | //! let _dir: Vec<_> = std::fs::read_dir(".").unwrap().collect(); |
36 | 36 | //! // ... do some work on the directory ... |
37 | 37 | //! } |
38 | 38 | //! |
39 | | -//! fail::setup(); |
| 39 | +//! let scenario = FailScenario::setup(); |
40 | 40 | //! do_fallible_work(); |
41 | | -//! fail::teardown(); |
| 41 | +//! scenario.teardown(); |
42 | 42 | //! println!("done"); |
43 | 43 | //! ``` |
44 | 44 | //! |
|
116 | 116 | //! Here's a example to show the process: |
117 | 117 | //! |
118 | 118 | //! ```rust |
119 | | -//! fail::setup(); |
| 119 | +//! use fail::FailScenario; |
| 120 | +//! |
| 121 | +//! let _scenario = FailScenario::setup(); |
120 | 122 | //! fail::cfg("p1", "sleep(100)").unwrap(); |
121 | 123 | //! println!("Global registry: {:?}", fail::list()); |
122 | 124 | //! { |
|
165 | 167 | //! function we used earlier to return a `Result`: |
166 | 168 | //! |
167 | 169 | //! ```rust |
168 | | -//! use fail::fail_point; |
| 170 | +//! use fail::{fail_point, FailScenario}; |
169 | 171 | //! use std::io; |
170 | 172 | //! |
171 | 173 | //! fn do_fallible_work() -> io::Result<()> { |
|
176 | 178 | //! } |
177 | 179 | //! |
178 | 180 | //! fn main() -> io::Result<()> { |
179 | | -//! fail::setup(); |
| 181 | +//! let scenario = FailScenario::setup(); |
180 | 182 | //! do_fallible_work()?; |
181 | | -//! fail::teardown(); |
| 183 | +//! scenario.teardown(); |
182 | 184 | //! println!("done"); |
183 | 185 | //! Ok(()) |
184 | 186 | //! } |
@@ -264,6 +266,7 @@ use std::env::VarError; |
264 | 266 | use std::fmt::Debug; |
265 | 267 | use std::str::FromStr; |
266 | 268 | use std::sync::atomic::{AtomicUsize, Ordering}; |
| 269 | +use std::sync::MutexGuard; |
267 | 270 | use std::sync::{Arc, Condvar, Mutex, RwLock, TryLockError}; |
268 | 271 | use std::time::{Duration, Instant}; |
269 | 272 | use std::{env, thread}; |
@@ -588,63 +591,80 @@ impl FailPointRegistry { |
588 | 591 | lazy_static::lazy_static! { |
589 | 592 | static ref REGISTRY_GROUP: RwLock<HashMap<thread::ThreadId, Arc<RwLock<Registry>>>> = Default::default(); |
590 | 593 | static ref REGISTRY_GLOBAL: FailPointRegistry = Default::default(); |
| 594 | + static ref SCENARIO: Mutex<&'static FailPointRegistry> = Mutex::new(®ISTRY_GLOBAL); |
591 | 595 | } |
592 | 596 |
|
593 | | -/// Set up the global fail points registry. |
594 | | -/// |
595 | | -/// Configures global fail points specified in the `FAILPOINTS` environment variable. |
596 | | -/// It does not otherwise change any existing fail point configuration. |
597 | | -/// |
598 | | -/// The format of `FAILPOINTS` is `failpoint=actions;...`, where |
599 | | -/// `failpoint` is the name of the fail point. For more information |
600 | | -/// about fail point actions see the [`cfg`](fn.cfg.html) function and |
601 | | -/// the [`fail_point`](macro.fail_point.html) macro. |
602 | | -/// |
603 | | -/// `FAILPOINTS` may configure fail points that are not actually defined. In |
604 | | -/// this case the configuration has no effect. |
605 | | -/// |
606 | | -/// This function should generally be called prior to running a test with fail |
607 | | -/// points, and afterward paired with [`teardown`](fn.teardown.html). |
608 | | -/// |
609 | | -/// # Panics |
610 | | -/// |
611 | | -/// Panics if an action is not formatted correctly. |
612 | | -pub fn setup() { |
613 | | - let mut registry = REGISTRY_GLOBAL.registry.write().unwrap(); |
614 | | - cleanup(&mut registry); |
615 | | - |
616 | | - let failpoints = match env::var("FAILPOINTS") { |
617 | | - Ok(s) => s, |
618 | | - Err(VarError::NotPresent) => return, |
619 | | - Err(e) => panic!("invalid failpoints: {:?}", e), |
620 | | - }; |
621 | | - for mut cfg in failpoints.trim().split(';') { |
622 | | - cfg = cfg.trim(); |
623 | | - if cfg.is_empty() { |
624 | | - continue; |
625 | | - } |
626 | | - let (name, order) = partition(cfg, '='); |
627 | | - match order { |
628 | | - None => panic!("invalid failpoint: {:?}", cfg), |
629 | | - Some(order) => { |
630 | | - if let Err(e) = set(&mut registry, name.to_owned(), order) { |
631 | | - panic!("unable to configure failpoint \"{}\": {}", name, e); |
| 597 | +/// Test scenario with configured fail points. |
| 598 | +#[derive(Debug)] |
| 599 | +pub struct FailScenario<'a> { |
| 600 | + scenario_guard: MutexGuard<'a, &'static FailPointRegistry>, |
| 601 | +} |
| 602 | + |
| 603 | +impl<'a> FailScenario<'a> { |
| 604 | + /// Set up the global fail points registry. |
| 605 | + /// |
| 606 | + /// Configures global fail points specified in the `FAILPOINTS` environment variable. |
| 607 | + /// It does not otherwise change any existing fail point configuration. |
| 608 | + /// |
| 609 | + /// The format of `FAILPOINTS` is `failpoint=actions;...`, where |
| 610 | + /// `failpoint` is the name of the fail point. For more information |
| 611 | + /// about fail point actions see the [`cfg`](fn.cfg.html) function and |
| 612 | + /// the [`fail_point`](macro.fail_point.html) macro. |
| 613 | + /// |
| 614 | + /// `FAILPOINTS` may configure fail points that are not actually defined. In |
| 615 | + /// this case the configuration has no effect. |
| 616 | + /// |
| 617 | + /// This function should generally be called prior to running a test with fail |
| 618 | + /// points, and afterward paired with [`teardown`](fn.teardown.html). |
| 619 | + /// |
| 620 | + /// # Panics |
| 621 | + /// |
| 622 | + /// Panics if an action is not formatted correctly. |
| 623 | + pub fn setup() -> Self { |
| 624 | + let scenario_guard = SCENARIO.lock().unwrap_or_else(|e| e.into_inner()); |
| 625 | + let mut registry = scenario_guard.registry.write().unwrap(); |
| 626 | + cleanup(&mut registry); |
| 627 | + |
| 628 | + let failpoints = match env::var("FAILPOINTS") { |
| 629 | + Ok(s) => s, |
| 630 | + Err(VarError::NotPresent) => return Self { scenario_guard }, |
| 631 | + Err(e) => panic!("invalid failpoints: {:?}", e), |
| 632 | + }; |
| 633 | + for mut cfg in failpoints.trim().split(';') { |
| 634 | + cfg = cfg.trim(); |
| 635 | + if cfg.is_empty() { |
| 636 | + continue; |
| 637 | + } |
| 638 | + let (name, order) = partition(cfg, '='); |
| 639 | + match order { |
| 640 | + None => panic!("invalid failpoint: {:?}", cfg), |
| 641 | + Some(order) => { |
| 642 | + if let Err(e) = set(&mut registry, name.to_owned(), order) { |
| 643 | + panic!("unable to configure failpoint \"{}\": {}", name, e); |
| 644 | + } |
632 | 645 | } |
633 | 646 | } |
634 | 647 | } |
| 648 | + Self { scenario_guard } |
| 649 | + } |
| 650 | + |
| 651 | + /// Tear down the global fail points registry. |
| 652 | + /// |
| 653 | + /// Clears the configuration of global fail points. Any paused fail |
| 654 | + /// points will be notified before they are deactivated. |
| 655 | + /// |
| 656 | + /// This function should generally be called after running a test with fail points. |
| 657 | + /// Calling `teardown` without previously calling `setup` results in a no-op. |
| 658 | + pub fn teardown(self) { |
| 659 | + drop(self); |
635 | 660 | } |
636 | 661 | } |
637 | 662 |
|
638 | | -/// Tear down the global fail points registry. |
639 | | -/// |
640 | | -/// Clears the configuration of global fail points. Any paused fail |
641 | | -/// points will be notified before they are deactivated. |
642 | | -/// |
643 | | -/// This function should generally be called after running a test with fail points. |
644 | | -/// Calling `teardown` without previously calling `setup` results in a no-op. |
645 | | -pub fn teardown() { |
646 | | - let mut registry = REGISTRY_GLOBAL.registry.write().unwrap(); |
647 | | - cleanup(&mut registry); |
| 663 | +impl<'a> Drop for FailScenario<'a> { |
| 664 | + fn drop(&mut self) { |
| 665 | + let mut registry = self.scenario_guard.registry.write().unwrap(); |
| 666 | + cleanup(&mut registry); |
| 667 | + } |
648 | 668 | } |
649 | 669 |
|
650 | 670 | /// Clean all registered fail points. |
@@ -1099,7 +1119,7 @@ mod tests { |
1099 | 1119 | "FAILPOINTS", |
1100 | 1120 | "setup_and_teardown1=return;setup_and_teardown2=pause;", |
1101 | 1121 | ); |
1102 | | - setup(); |
| 1122 | + let scenario = FailScenario::setup(); |
1103 | 1123 |
|
1104 | 1124 | let group = FailPointRegistry::new(); |
1105 | 1125 | let handler = thread::spawn(move || { |
@@ -1130,7 +1150,7 @@ mod tests { |
1130 | 1150 | }); |
1131 | 1151 | assert!(rx.recv_timeout(Duration::from_millis(500)).is_err()); |
1132 | 1152 |
|
1133 | | - teardown(); |
| 1153 | + scenario.teardown(); |
1134 | 1154 | assert_eq!(rx.recv_timeout(Duration::from_millis(500)).unwrap(), 0); |
1135 | 1155 | assert_eq!(f1(), 0); |
1136 | 1156 | } |
|
0 commit comments