Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
@@ -0,0 +1,59 @@
use std::env;
use std::io::prelude::*;
use std::io::BufReader;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::thread;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
fn main() {
if env::var("I_AM_THE_CLIENT").is_ok() {
client();
} else {
server();
}
}
fn server() {
let me = t!(env::current_exe());
let client = t!(Client::new(1));
let mut cmd = Command::new(me);
cmd.env("I_AM_THE_CLIENT", "1").stdout(Stdio::piped());
client.configure(&mut cmd);
let acq = client.acquire().unwrap();
let mut child = t!(cmd.spawn());
let stdout = child.stdout.take().unwrap();
let (tx, rx) = mpsc::channel();
let t = thread::spawn(move || {
for line in BufReader::new(stdout).lines() {
tx.send(t!(line)).unwrap();
}
});
for _ in 0..100 {
assert!(rx.try_recv().is_err());
}
drop(acq);
assert_eq!(rx.recv().unwrap(), "hello!");
t.join().unwrap();
assert!(rx.recv().is_err());
client.acquire().unwrap();
}
fn client() {
let client = unsafe { Client::from_env().unwrap() };
let acq = client.acquire().unwrap();
println!("hello!");
drop(acq);
}
+247
View File
@@ -0,0 +1,247 @@
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();
},
},
];
/// The make binary under test, overridable via the `MAKE` env var.
fn make() -> String {
env::var("MAKE").unwrap_or_else(|_| "make".to_string())
}
/// Whether we are running in a Continuous Integration environment.
pub fn is_ci() -> bool {
env::var_os("CI").is_some()
}
/// The jobserver wire formats to exercise as `make`'s server.
///
/// The `fifo`/`pipe` distinction is Unix-only: on Unix, GNU Make >= 4.4
/// defaults to the named-pipe (`fifo:PATH`) form but can be told to use the
/// legacy `R,W` pipe form via `--jobserver-style`, so we run every test under
/// both to cover both [`Client::from_env`] parse paths. CI must use a make new
/// enough to support both; locally an older make falls back to a single run.
///
/// Windows has neither style (its jobserver is a named semaphore), so just run
/// once with whatever make defaults to.
#[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 {
// Older make defaults to a single style; pass no extra flag.
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);
}
+76
View File
@@ -0,0 +1,76 @@
use jobserver::Client;
use std::sync::atomic::*;
use std::sync::*;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
#[test]
fn helper_smoke() {
let client = t!(Client::new(1));
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.into_helper_thread(|_| ()).unwrap());
}
#[test]
fn acquire() {
let (tx, rx) = mpsc::channel();
let client = t!(Client::new(1));
let helper = client
.into_helper_thread(move |a| drop(tx.send(a)))
.unwrap();
assert!(rx.try_recv().is_err());
helper.request_token();
rx.recv().unwrap().unwrap();
helper.request_token();
rx.recv().unwrap().unwrap();
helper.request_token();
helper.request_token();
rx.recv().unwrap().unwrap();
rx.recv().unwrap().unwrap();
helper.request_token();
helper.request_token();
drop(helper);
}
#[test]
fn prompt_shutdown() {
for _ in 0..100 {
let client = jobserver::Client::new(4).unwrap();
let count = Arc::new(AtomicU32::new(0));
let count2 = count.clone();
let tokens = Arc::new(Mutex::new(Vec::new()));
let helper = client
.into_helper_thread(move |token| {
tokens.lock().unwrap().push(token);
count2.fetch_add(1, Ordering::SeqCst);
})
.unwrap();
// Request more tokens than what are available.
for _ in 0..5 {
helper.request_token();
}
// Wait for at least some of the requests to finish.
while count.load(Ordering::SeqCst) < 3 {
std::thread::yield_now();
}
// Drop helper
let t = std::time::Instant::now();
drop(helper);
let d = t.elapsed();
assert!(d.as_secs_f64() < 0.5);
}
}
@@ -0,0 +1,81 @@
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::process::Command;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
fn main() {
if env::var("_DO_THE_TEST").is_ok() {
std::process::exit(
Command::new(env::var_os("MAKE").unwrap())
.env("MAKEFLAGS", env::var_os("CARGO_MAKEFLAGS").unwrap())
.env_remove("_DO_THE_TEST")
.args(&env::args_os().skip(1).collect::<Vec<_>>())
.status()
.unwrap()
.code()
.unwrap_or(1),
);
}
if let Ok(s) = env::var("TEST_ADDR") {
let mut contents = Vec::new();
t!(t!(TcpStream::connect(&s)).read_to_end(&mut contents));
return;
}
let c = t!(Client::new(1));
let td = tempfile::tempdir().unwrap();
let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
let me = t!(env::current_exe());
let me = me.to_str().unwrap();
let mut cmd = Command::new(&me);
cmd.current_dir(td.path());
cmd.env("MAKE", prog);
cmd.env("_DO_THE_TEST", "1");
t!(t!(File::create(td.path().join("Makefile"))).write_all(
format!(
"\
all: foo bar
foo:
\t{0}
bar:
\t{0}
",
me
)
.as_bytes()
));
// 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 listener = t!(TcpListener::bind("127.0.0.1:0"));
let addr = t!(listener.local_addr());
cmd.env("TEST_ADDR", addr.to_string());
let mut child = t!(cmd.spawn());
// We should get both connections as the two programs should be run
// concurrently.
let a = t!(listener.accept());
let b = t!(listener.accept());
drop((a, b));
assert!(t!(child.wait()).success());
}
+181
View File
@@ -0,0 +1,181 @@
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());
}
}