Local runner for GitHub autograder
at main 85 lines 2.1 kB view raw
1use std::{ 2 io::{Read, Write}, 3 process::{Command, Stdio}, 4 time::Duration, 5}; 6 7use anyhow::{Result, anyhow}; 8use wait_timeout::ChildExt; 9 10#[cfg(not(windows))] 11fn spawn_cmd(cmd: &str) -> Command { 12 let mut command = Command::new("/usr/bin/env"); 13 command.arg("sh").arg("-c").arg(cmd); 14 command 15} 16 17#[cfg(windows)] 18fn spawn_cmd(cmd: &str) -> Command { 19 let mut command = Command::new("PowerShell"); 20 command.arg("-Command").arg(cmd); 21 command 22} 23 24pub fn setup_phase(cmd: &str) -> Result<()> { 25 let mut child = spawn_cmd(cmd) 26 .spawn() 27 .map_err(|e| anyhow!("Failed to spawn shell: {e:?}"))?; 28 29 child 30 .wait() 31 .map_err(|e| anyhow!("Failed to execute \"{cmd}\": {e:?}"))?; 32 Ok(()) 33} 34 35pub struct TestResult { 36 pub stdout: String, 37 pub stderr: String, 38 pub status: Result<i32>, 39} 40 41fn read_stream<T>(mut stream: T) -> Option<String> 42where 43 T: Read, 44{ 45 let mut buf = String::new(); 46 stream 47 .read_to_string(&mut buf) 48 .map_err(|e| anyhow!("Failed to read stream: {e:?}")) 49 .ok()?; 50 Some(buf) 51} 52 53pub fn run_phase(cmd: &str, input: &str, timeout: u64) -> Result<TestResult> { 54 let mut child = spawn_cmd(cmd) 55 .stdin(Stdio::piped()) 56 .stdout(Stdio::piped()) 57 .stderr(Stdio::piped()) 58 .spawn() 59 .map_err(|e| anyhow!("Failed to spawn shell: {e:?}"))?; 60 61 let mut stdin = child 62 .stdin 63 .as_ref() 64 .ok_or_else(|| anyhow!("Failed to get stdin"))?; 65 66 write!(stdin, "{input}").map_err(|e| anyhow!("Failed to write to stdin: {e:?}"))?; 67 68 let duration = Duration::from_secs(timeout * 60); 69 70 let res = child.wait_timeout(duration); 71 72 let nested_res = res 73 .map_err(|e| anyhow!("Failed to execute: {e:?}")) 74 .map(|opt| opt.ok_or_else(|| anyhow!("Program Timed Out"))); 75 76 let err = nested_res 77 .and_then(|r| r) 78 .map(|status| status.code().unwrap_or(0)); 79 80 Ok(TestResult { 81 stdout: child.stdout.and_then(read_stream).unwrap_or_default(), 82 stderr: child.stderr.and_then(read_stream).unwrap_or_default(), 83 status: err, 84 }) 85}