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