use std::io::{BufRead as _, BufReader, Read as _};
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::time::{Duration, Instant};
const BIN: &str = env!("CARGO_BIN_EXE_modelpipe");
const BACKEND: &str = "http://127.0.0.1:9";
const DEAD_RELAY: &str = "https://127.0.0.1:1/";
const PATIENCE: Duration = Duration::from_secs(40);
struct Run {
stdout: Vec<String>,
stderr: String,
exit: Option<bool>,
}
fn serve(extra: &[&str]) -> Run {
let mut child = Command::new(BIN)
.args(["serve", BACKEND])
.args([
"--insecure-no-auth",
"--no-qr",
"--no-discovery",
"--no-portmap",
])
.args(["--relay", DEAD_RELAY])
.args(extra)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("the binary cargo just built has to run");
let out = child.stdout.take().expect("piped above");
let (lines, incoming) = mpsc::channel();
let reader = std::thread::spawn(move || {
for line in BufReader::new(out).lines().map_while(Result::ok) {
if lines.send(line).is_err() {
break;
}
}
});
let deadline = Instant::now() + PATIENCE;
let mut stdout = Vec::new();
let mut exit = None;
loop {
match incoming.recv_timeout(Duration::from_millis(100)) {
Ok(line) => {
let ticket = line.starts_with("ticket:");
stdout.push(line);
if ticket {
break;
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
std::thread::sleep(Duration::from_millis(50));
}
Err(mpsc::RecvTimeoutError::Timeout) => {}
}
if let Some(status) = child.try_wait().expect("the child is ours to wait on") {
exit = Some(status.success());
break;
}
assert!(
Instant::now() < deadline,
"`serve {extra:?}` neither printed a ticket nor exited within {PATIENCE:?}"
);
}
if exit.is_none() {
let _ = child.kill();
let _ = child.wait();
}
while let Ok(line) = incoming.try_recv() {
stdout.push(line);
}
drop(incoming);
let _ = reader.join();
let mut stderr = String::new();
child
.stderr
.take()
.expect("piped above")
.read_to_string(&mut stderr)
.expect("stderr is not binary");
Run {
stdout,
stderr,
exit,
}
}
#[test]
fn a_ticket_that_names_nowhere_is_refused_rather_than_printed() {
let run = serve(&["--relay-only"]);
assert_eq!(
run.exit,
Some(false),
"serve must fail rather than park on a listener nobody can dial; \
stdout was {:?} and stderr {:?}",
run.stdout,
run.stderr
);
assert!(
run.stdout.is_empty(),
"nothing may reach the stream people pipe: {:?}",
run.stdout
);
assert!(
run.stderr.contains("names nowhere") && run.stderr.contains("--relay-only"),
"the refusal has to name the real cause: {:?}",
run.stderr
);
}
#[test]
fn a_ticket_that_names_somewhere_is_printed() {
let run = serve(&[]);
assert_eq!(
run.exit, None,
"serve had to still be running, having printed {:?} and said {:?}",
run.stdout, run.stderr
);
let ticket = run
.stdout
.iter()
.find(|line| line.starts_with("ticket:"))
.unwrap_or_else(|| {
panic!(
"no ticket was printed — does this machine have a non-loopback \
address? stdout {:?}, stderr {:?}",
run.stdout, run.stderr
)
});
assert!(
ticket.contains("pipe"),
"and it has to be a ticket: {ticket:?}"
);
}