1mod test_result;
2
3use std::io::Write;
4use std::process::Command;
5use std::time::Duration;
6
7use test_result::CommandExit;
8pub use test_result::TestResult;
9use wait_timeout::ChildExt;
10
11use crate::clash::Testcase;
12
13pub fn lazy_run<'a>(
39 testcases: impl IntoIterator<Item = &'a Testcase>,
40 run_command: &'a mut Command,
41 timeout: &'a Duration,
42) -> impl IntoIterator<Item = (&'a Testcase, TestResult)> {
43 testcases.into_iter().map(|test| {
44 let result = run_testcase(test, run_command, timeout);
45 (test, result)
46 })
47}
48
49pub fn run_testcase(testcase: &Testcase, run_command: &mut Command, timeout: &Duration) -> TestResult {
51 let mut run = match run_command
52 .stdin(std::process::Stdio::piped())
53 .stdout(std::process::Stdio::piped())
54 .stderr(std::process::Stdio::piped())
55 .spawn()
56 {
57 Ok(run) => run,
58 Err(error) => {
59 let program = run_command.get_program().to_str().unwrap_or("Unable to run command");
60 let error_msg = format!("{}: {}", program, error);
61 return TestResult::UnableToRun { error_msg }
62 }
63 };
64
65 run.stdin
66 .as_mut()
67 .expect("STDIN of child process should be captured")
68 .write_all(testcase.test_in.as_bytes())
69 .expect("STDIN of child process should be writable");
70
71 let timed_out = run
72 .wait_timeout(*timeout)
73 .expect("Process should be able to wait for execution")
74 .is_none();
75
76 if timed_out {
77 run.kill().expect("Process should have been killed");
78 }
79
80 let output = run.wait_with_output().expect("Process should allow waiting for its execution");
81
82 let exit_status = if timed_out {
83 CommandExit::Timeout
84 } else if output.status.success() {
85 CommandExit::Ok
86 } else {
87 CommandExit::Error
88 };
89 TestResult::from_output(&testcase.test_out, output.stdout, output.stderr, exit_status)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_passing_solution() {
98 let clash = crate::test_helper::sample_puzzle("stub_and_solution_tester").unwrap();
99 let mut run_cmd = Command::new("tr");
100 run_cmd.arg("X");
101 run_cmd.arg("b");
102 let timeout = Duration::from_secs(1);
103 assert!(lazy_run(clash.testcases(), &mut run_cmd, &timeout)
104 .into_iter()
105 .all(|(_, test_result)| test_result.is_success()))
106 }
107
108 #[test]
109 fn test_failing_solution() {
110 let clash = crate::test_helper::sample_puzzle("stub_and_solution_tester").unwrap();
111 let timeout = Duration::from_secs(1);
112 let mut run_cmd = Command::new("cat");
113 assert!(lazy_run(clash.testcases(), &mut run_cmd, &timeout)
114 .into_iter()
115 .all(|(_, test_result)| !test_result.is_success()))
116 }
117}