| 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; |
| use tempdir::TempDir; |
| |
| 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_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 = TempDir::new("foo").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 = TempDir::new("foo").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()); |
| } |
| } |