Local runner for GitHub autograder
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}