//! Waiting for `loopd serve` to be up, by the line it prints last before it serves. //! //! The socket appears before `loopd` has set its mode and printed "serving on", so a test that //! waits for the socket and then stops `loopd` can lose those lines (M5c-1 review, finding 7). //! Every startup notice comes before "serving on", so once it is read, the rest has been printed. use std::io::{BufRead, BufReader, Read}; use std::process::Child; use std::sync::mpsc; use std::time::{Duration, Instant}; /// How long `loopd` may take to pass its self-test and bind, on a loaded machine. const UP: Duration = Duration::from_secs(60); /// Reads `child`'s stderr until the "serving on" line, stops it, and returns whether that line came /// and everything it printed. `child` must have been spawned with `stderr(Stdio::piped())`. pub fn stderr_once_serving(mut child: Child) -> (bool, String) { let stderr = child.stderr.take().expect("stderr is piped"); let (tx, rx) = mpsc::channel::(); let reader = std::thread::spawn(move || { let mut lines = BufReader::new(stderr); let mut line = String::new(); loop { line.clear(); match lines.read_line(&mut line) { Ok(0) | Err(_) => break, Ok(_) => { if tx.send(line.clone()).is_err() { break; } } } } let mut rest = String::new(); let _ = lines.read_to_string(&mut rest); rest }); let deadline = Instant::now() + UP; let mut text = String::new(); let mut up = false; while let Some(left) = deadline.checked_duration_since(Instant::now()) { match rx.recv_timeout(left) { Ok(line) => { up = line.starts_with("loopd: serving on "); text.push_str(&line); if up { break; } } Err(_) => break, } } let _ = child.kill(); let _ = child.wait(); // The reader ends at the pipe's end once `loopd` is gone; join it before draining, so no line // it sends after the drain is lost. let rest = reader.join().unwrap_or_default(); while let Ok(line) = rx.try_recv() { text.push_str(&line); } text.push_str(&rest); (up, text) }