1 use std::env;
2 use std::fs::File;
3 use std::io::Write;
4 use std::process::Command;
5 use std::sync::atomic::{AtomicBool, Ordering};
6 use std::sync::mpsc;
7 use std::sync::Arc;
8 use std::thread;
9 
10 use futures::future::{self, Future};
11 use futures::stream::{self, Stream};
12 use jobserver::Client;
13 use tempdir::TempDir;
14 use tokio_core::reactor::Core;
15 use tokio_process::CommandExt;
16 
17 macro_rules! t {
18     ($e:expr) => {
19         match $e {
20             Ok(e) => e,
21             Err(e) => panic!("{} failed with {}", stringify!($e), e),
22         }
23     };
24 }
25 
26 struct Test {
27     name: &'static str,
28     f: &'static dyn Fn(),
29     make_args: &'static [&'static str],
30     rule: &'static dyn Fn(&str) -> String,
31 }
32 
33 const TESTS: &[Test] = &[
34     Test {
35         name: "no j args",
36         make_args: &[],
37         rule: &|me| me.to_string(),
38         f: &|| {
39             assert!(unsafe { Client::from_env().is_none() });
40         },
41     },
42     Test {
43         name: "no j args with plus",
44         make_args: &[],
45         rule: &|me| format!("+{}", me),
46         f: &|| {
47             assert!(unsafe { Client::from_env().is_none() });
48         },
49     },
50     Test {
51         name: "j args with plus",
52         make_args: &["-j2"],
53         rule: &|me| format!("+{}", me),
54         f: &|| {
55             assert!(unsafe { Client::from_env().is_some() });
56         },
57     },
58     Test {
59         name: "acquire",
60         make_args: &["-j2"],
61         rule: &|me| format!("+{}", me),
62         f: &|| {
63             let c = unsafe { Client::from_env().unwrap() };
64             drop(c.acquire().unwrap());
65             drop(c.acquire().unwrap());
66         },
67     },
68     Test {
69         name: "acquire3",
70         make_args: &["-j3"],
71         rule: &|me| format!("+{}", me),
72         f: &|| {
73             let c = unsafe { Client::from_env().unwrap() };
74             let a = c.acquire().unwrap();
75             let b = c.acquire().unwrap();
76             drop((a, b));
77         },
78     },
79     Test {
80         name: "acquire blocks",
81         make_args: &["-j2"],
82         rule: &|me| format!("+{}", me),
83         f: &|| {
84             let c = unsafe { Client::from_env().unwrap() };
85             let a = c.acquire().unwrap();
86             let hit = Arc::new(AtomicBool::new(false));
87             let hit2 = hit.clone();
88             let (tx, rx) = mpsc::channel();
89             let t = thread::spawn(move || {
90                 tx.send(()).unwrap();
91                 let _b = c.acquire().unwrap();
92                 hit2.store(true, Ordering::SeqCst);
93             });
94             rx.recv().unwrap();
95             assert!(!hit.load(Ordering::SeqCst));
96             drop(a);
97             t.join().unwrap();
98             assert!(hit.load(Ordering::SeqCst));
99         },
100     },
101     Test {
102         name: "acquire_raw",
103         make_args: &["-j2"],
104         rule: &|me| format!("+{}", me),
105         f: &|| {
106             let c = unsafe { Client::from_env().unwrap() };
107             c.acquire_raw().unwrap();
108             c.release_raw().unwrap();
109         },
110     },
111 ];
112 
main()113 fn main() {
114     if let Ok(test) = env::var("TEST_TO_RUN") {
115         return (TESTS.iter().find(|t| t.name == test).unwrap().f)();
116     }
117 
118     let me = t!(env::current_exe());
119     let me = me.to_str().unwrap();
120     let filter = env::args().nth(1);
121 
122     let mut core = t!(Core::new());
123 
124     let futures = TESTS
125         .iter()
126         .filter(|test| match filter {
127             Some(ref s) => test.name.contains(s),
128             None => true,
129         })
130         .map(|test| {
131             let td = t!(TempDir::new("foo"));
132             let makefile = format!(
133                 "\
134 all: export TEST_TO_RUN={}
135 all:
136 \t{}
137 ",
138                 test.name,
139                 (test.rule)(me)
140             );
141             t!(t!(File::create(td.path().join("Makefile"))).write_all(makefile.as_bytes()));
142             let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
143             let mut cmd = Command::new(prog);
144             cmd.args(test.make_args);
145             cmd.current_dir(td.path());
146             future::lazy(move || {
147                 cmd.output_async().map(move |e| {
148                     drop(td);
149                     (test, e)
150                 })
151             })
152         })
153         .collect::<Vec<_>>();
154 
155     println!("\nrunning {} tests\n", futures.len());
156 
157     let stream = stream::iter(futures.into_iter().map(Ok)).buffer_unordered(num_cpus::get());
158 
159     let mut failures = Vec::new();
160     t!(core.run(stream.for_each(|(test, output)| {
161         if output.status.success() {
162             println!("test {} ... ok", test.name);
163         } else {
164             println!("test {} ... FAIL", test.name);
165             failures.push((test, output));
166         }
167         Ok(())
168     })));
169 
170     if failures.is_empty() {
171         println!("\ntest result: ok\n");
172         return;
173     }
174 
175     println!("\n----------- failures");
176 
177     for (test, output) in failures {
178         println!("test {}", test.name);
179         let stdout = String::from_utf8_lossy(&output.stdout);
180         let stderr = String::from_utf8_lossy(&output.stderr);
181 
182         println!("\texit status: {}", output.status);
183         if !stdout.is_empty() {
184             println!("\tstdout ===");
185             for line in stdout.lines() {
186                 println!("\t\t{}", line);
187             }
188         }
189 
190         if !stderr.is_empty() {
191             println!("\tstderr ===");
192             for line in stderr.lines() {
193                 println!("\t\t{}", line);
194             }
195         }
196     }
197 
198     std::process::exit(4);
199 }
200