test_deps/
lib.rs

1//! `test_deps` allows developers to define dependencies among tests.
2
3use lazy_static::lazy_static;
4use std::collections::HashMap;
5use std::sync::{Arc, Condvar, Mutex, PoisonError};
6pub use test_deps_if::deps;
7
8lazy_static! {
9    static ref TEST_DEPS_REG: Mutex<(HashMap<String, ()>, HashMap<String, Vec<Arc<Condvar>>>)> = {
10        let completed = HashMap::new();
11        let waitlist = HashMap::new();
12        Mutex::new((completed, waitlist))
13    };
14}
15
16#[doc(hidden)]
17#[derive(Debug)]
18pub enum TestDepsHelperError {
19    ThreadSync(String),
20    CorruptedDeps(String),
21    Generic(String),
22}
23
24impl<T> From<PoisonError<T>> for TestDepsHelperError {
25    fn from(_err: PoisonError<T>) -> TestDepsHelperError {
26        TestDepsHelperError::ThreadSync(String::from("A mutex is poisoned"))
27    }
28}
29
30#[doc(hidden)]
31pub fn target_completed(target: &String) -> Result<(), TestDepsHelperError> {
32    let mut reg = TEST_DEPS_REG.lock()?;
33    if reg.0.insert(target.clone(), ()).is_some() {
34        Err(TestDepsHelperError::CorruptedDeps(format!(
35            "A target duplicated on the completed list: {}",
36            target
37        )))
38    } else {
39        if let Some(cvs) = reg.1.get(target) {
40            for cv in cvs {
41                cv.notify_one();
42            }
43        }
44        Ok(())
45    }
46}
47
48#[doc(hidden)]
49pub fn ensure_prereqs(prereqs: &Vec<String>) -> Result<(), TestDepsHelperError> {
50    let mut reg = TEST_DEPS_REG.lock()?;
51    if !check_readiness(&reg.0, prereqs) {
52        let cv = Arc::new(Condvar::new());
53        for prereq in prereqs {
54            if let Some(cvs) = reg.1.get_mut(prereq) {
55                cvs.push(cv.clone());
56            } else {
57                reg.1.insert(prereq.clone(), vec![cv.clone()]);
58            }
59        }
60        while !check_readiness(&reg.0, prereqs) {
61            reg = cv.wait(reg)?;
62        }
63    }
64    Ok(())
65}
66
67fn check_readiness(completed: &HashMap<String, ()>, prereqs: &Vec<String>) -> bool {
68    for prereq in prereqs {
69        if !completed.contains_key(prereq) {
70            return false;
71        }
72    }
73    true
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use std::{thread, time};
80
81    #[test]
82    fn target_completed_no_one_is_waiting_for() {
83        target_completed(&String::from("NO_ONE_IS_WAITING")).unwrap();
84    }
85
86    #[test]
87    fn no_prereq_passes_immediately() {
88        ensure_prereqs(&vec![]).unwrap();
89    }
90
91    #[test]
92    fn wait_one_prereq() {
93        ensure_prereqs(&vec![String::from("FIRST_TARGET")]).unwrap();
94    }
95
96    #[test]
97    fn wait_two_prereqs() {
98        ensure_prereqs(&vec![
99            String::from("FIRST_TARGET"),
100            String::from("SECOND_TARGET"),
101        ])
102        .unwrap();
103    }
104
105    #[test]
106    fn first_target_completed() {
107        // ensure wait_one_prereq() and wait_two_prereqs() go first
108        thread::sleep(time::Duration::from_secs_f64(0.75));
109        target_completed(&String::from("FIRST_TARGET")).unwrap();
110    }
111
112    #[test]
113    fn second_target_completed() {
114        // ensure wait_one_prereq() and wait_two_prereqs() go first
115        thread::sleep(time::Duration::from_secs_f64(0.75));
116        target_completed(&String::from("SECOND_TARGET")).unwrap();
117    }
118
119    #[test]
120    fn prereqs_already_satisfied() {
121        // ensure target_completed_before_waiter_comes() goes first
122        thread::sleep(time::Duration::from_secs_f64(0.75));
123        ensure_prereqs(&vec![String::from("TARGET_GOING_FIRST")]).unwrap();
124    }
125
126    #[test]
127    fn target_completed_before_waiter_comes() {
128        target_completed(&String::from("TARGET_GOING_FIRST")).unwrap();
129    }
130
131    #[test]
132    #[should_panic(expected = "A target duplicated on the completed list: COMPLETES_TWICE")]
133    fn target_completed_twice_unexpectedly() {
134        if let Err(_) = target_completed(&String::from("COMPLETES_TWICE")) {
135            panic!("should not panic here");
136        }
137        target_completed(&String::from("COMPLETES_TWICE")).unwrap();
138    }
139}