use std::env; use std::fs::File; use std::io::prelude::*; use std::process::Command; 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), } }; } #[test] fn server_smoke() { let c = t!(Client::new(1)); drop(c.acquire().unwrap()); drop(c.acquire().unwrap()); } #[test] fn server_multiple() { let c = t!(Client::new(2)); let a = c.acquire().unwrap(); let b = c.acquire().unwrap(); drop((a, b)); } #[test] fn server_available() { let c = t!(Client::new(10)); assert_eq!(c.available().unwrap(), 10); let a = c.acquire().unwrap(); assert_eq!(c.available().unwrap(), 9); drop(a); assert_eq!(c.available().unwrap(), 10); } #[test] fn server_none_available() { let c = t!(Client::new(2)); assert_eq!(c.available().unwrap(), 2); let a = c.acquire().unwrap(); assert_eq!(c.available().unwrap(), 1); let b = c.acquire().unwrap(); assert_eq!(c.available().unwrap(), 0); drop(a); assert_eq!(c.available().unwrap(), 1); drop(b); assert_eq!(c.available().unwrap(), 2); } #[test] fn server_blocks() { let c = t!(Client::new(1)); 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] fn make_as_a_single_thread_client() { let c = t!(Client::new(1)); let td = tempfile::tempdir().unwrap(); let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string()); let mut cmd = Command::new(prog); cmd.current_dir(td.path()); t!(t!(File::create(td.path().join("Makefile"))).write_all( b" all: foo bar foo: \techo foo bar: \techo bar " )); // The jobserver protocol means that the `make` process itself "runs with a // token", so we acquire our one token to drain the jobserver, and this // should mean that `make` itself never has a second token available to it. let _a = c.acquire(); c.configure(&mut cmd); let output = t!(cmd.output()); println!( "\n\t=== stderr\n\t\t{}", String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t") ); println!( "\t=== stdout\n\t\t{}", String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t") ); assert!(output.status.success()); assert!(output.stderr.is_empty()); let stdout = String::from_utf8_lossy(&output.stdout).replace("\r\n", "\n"); let a = "\ echo foo foo echo bar bar "; let b = "\ echo bar bar echo foo foo "; assert!(stdout == a || stdout == b); } #[test] fn make_as_a_multi_thread_client() { let c = t!(Client::new(1)); let td = tempfile::tempdir().unwrap(); let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string()); let mut cmd = Command::new(prog); cmd.current_dir(td.path()); t!(t!(File::create(td.path().join("Makefile"))).write_all( b" all: foo bar foo: \techo foo bar: \techo bar " )); // We're leaking one extra token to `make` sort of violating the makefile // jobserver protocol. It has the desired effect though. c.configure(&mut cmd); let output = t!(cmd.output()); println!( "\n\t=== stderr\n\t\t{}", String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t") ); println!( "\t=== stdout\n\t\t{}", String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t") ); assert!(output.status.success()); } #[test] fn zero_client() { let client = t!(Client::new(0)); let (tx, rx) = mpsc::channel(); let helper = client .into_helper_thread(move |a| drop(tx.send(a))) .unwrap(); helper.request_token(); helper.request_token(); for _ in 0..1000 { assert!(rx.try_recv().is_err()); } }