use std::env;
use std::fs::File;
use std::io::Write;
use std::process::{Command, Output};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::Arc;
use std::thread;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
struct Test {
name: &'static str,
f: &'static (dyn Fn() + Send + Sync),
make_args: &'static [&'static str],
rule: &'static (dyn Fn(&str) -> String + Send + Sync),
}
const TESTS: &[Test] = &[
Test {
name: "no j args",
make_args: &[],
rule: &|me| me.to_string(),
f: &|| {
assert!(unsafe { Client::from_env().is_none() });
},
},
Test {
name: "no j args with plus",
make_args: &[],
rule: &|me| format!("+{}", me),
f: &|| {
assert!(unsafe { Client::from_env().is_none() });
},
},
Test {
name: "j args with plus",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
assert!(unsafe { Client::from_env().is_some() });
},
},
Test {
name: "acquire",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
drop(c.acquire().unwrap());
drop(c.acquire().unwrap());
},
},
Test {
name: "acquire3",
make_args: &["-j3"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
let a = c.acquire().unwrap();
let b = c.acquire().unwrap();
drop((a, b));
},
},
Test {
name: "acquire blocks",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
let a = c.acquire().unwrap();
let hit = Arc::new(AtomicBool::new(false));
let hit2 = hit.clone();
let (tx, rx) = mpsc::channel();
let t = thread::spawn(move || {
tx.send(()).unwrap();
let _b = c.acquire().unwrap();
hit2.store(true, Ordering::SeqCst);
});
rx.recv().unwrap();
assert!(!hit.load(Ordering::SeqCst));
drop(a);
t.join().unwrap();
assert!(hit.load(Ordering::SeqCst));
},
},
Test {
name: "acquire_raw",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
c.acquire_raw().unwrap();
c.release_raw().unwrap();
},
},
];
fn make() -> String {
env::var("MAKE").unwrap_or_else(|_| "make".to_string())
}
pub fn is_ci() -> bool {
env::var_os("CI").is_some()
}
#[cfg(unix)]
fn jobserver_styles() -> Vec<&'static str> {
let supports_style = Command::new(make())
.args(["--jobserver-style=fifo", "--version"])
.output()
.map(|o| o.status.success())
.unwrap_or(false);
if supports_style {
vec!["--jobserver-style=fifo", "--jobserver-style=pipe"]
} else if is_ci() {
panic!(
"CI requires a GNU Make supporting `--jobserver-style` (>= 4.4) \
so both jobserver wire formats are tested; `{}` does not",
make()
);
} else {
vec![""]
}
}
#[cfg(windows)]
fn jobserver_styles() -> Vec<&'static str> {
vec![""]
}
fn main() {
if let Ok(test) = env::var("TEST_TO_RUN") {
return (TESTS.iter().find(|t| t.name == test).unwrap().f)();
}
let me = t!(env::current_exe());
let me = me.to_str().unwrap();
let filter = env::args().nth(1);
let styles = jobserver_styles();
let join_handles = TESTS
.iter()
.filter(|test| match filter {
Some(ref s) => test.name.contains(s),
None => true,
})
.flat_map(|test| {
styles.iter().map(move |style| {
let td = t!(tempfile::tempdir());
let makefile = format!(
"\
all: export TEST_TO_RUN={}
all:
\t{}
",
test.name,
(test.rule)(me)
);
t!(t!(File::create(td.path().join("Makefile"))).write_all(makefile.as_bytes()));
let style = *style;
thread::spawn(move || {
let mut cmd = Command::new(make());
if !style.is_empty() {
cmd.arg(style);
}
cmd.args(test.make_args);
cmd.current_dir(td.path());
(test, style, cmd.output().unwrap())
})
})
})
.collect::<Vec<_>>();
println!("\nrunning {} tests\n", join_handles.len());
let failures = join_handles
.into_iter()
.filter_map(|join_handle| {
let (test, style, output): (&Test, &str, Output) = join_handle.join().unwrap();
let name = format!("{} {}", test.name, style);
if output.status.success() {
println!("test {} ... ok", name);
None
} else {
println!("test {} ... FAIL", name);
Some((name, output))
}
})
.collect::<Vec<_>>();
if failures.is_empty() {
println!("\ntest result: ok\n");
return;
}
println!("\n----------- failures");
for (name, output) in failures {
println!("test {}", name);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("\texit status: {}", output.status);
if !stdout.is_empty() {
println!("\tstdout ===");
for line in stdout.lines() {
println!("\t\t{}", line);
}
}
if !stderr.is_empty() {
println!("\tstderr ===");
for line in stderr.lines() {
println!("\t\t{}", line);
}
}
}
std::process::exit(4);
}