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