#![allow(dead_code)] // Shared across test targets; each uses only part of this module. use std::fs; use std::io::{Read, Write}; use std::process::{Child, Command, Output, Stdio}; use std::time::{Duration, Instant}; pub fn spawn(source: &str) -> (tempfile::TempDir, Child) { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("main.js"); fs::write(&path, source).unwrap(); let child = Command::new(env!("CARGO_BIN_EXE_avril")) .arg("run") .arg(path) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); (dir, child) } fn capture(mut pipe: impl Read + Send + 'static) -> std::thread::JoinHandle> { std::thread::spawn(move || { let mut bytes = Vec::new(); pipe.read_to_end(&mut bytes).unwrap(); bytes }) } pub fn wait(mut child: Child) -> Output { let stdout = child.stdout.take().map(capture); let stderr = child.stderr.take().map(capture); let deadline = Instant::now() + Duration::from_secs(5); let status = loop { if let Some(status) = child.try_wait().unwrap() { break status; } if Instant::now() >= deadline { child.kill().unwrap(); child.wait().unwrap(); panic!("Runtime did not exit within five seconds"); } std::thread::sleep(Duration::from_millis(10)); }; Output { status, stdout: stdout .map(|thread| thread.join().unwrap()) .unwrap_or_default(), stderr: stderr .map(|thread| thread.join().unwrap()) .unwrap_or_default(), } } pub fn run(source: &str, input: &[u8]) -> Output { let (_dir, mut child) = spawn(source); let mut stdin = child.stdin.take().unwrap(); let input = input.to_vec(); let writer = std::thread::spawn(move || stdin.write_all(&input)); let output = wait(child); writer.join().unwrap().unwrap(); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); output }