| 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()); | |
| } | |
| } |