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