clashlib/solution/
test_result.rs

1pub enum CommandExit {
2    Ok,
3    Error,
4    Timeout,
5}
6
7/// Represents the outcome of running a testcase. [TestResult::Success] means
8/// the output of a solution command matched the `test_out` field of the
9/// [Testcase](crate::clash::Testcase).
10#[derive(Debug, Clone)]
11pub enum TestResult {
12    /// Solution command produced the expected output. A test run is considered
13    /// a success even if it runs into a runtime error or times out if its
14    /// output was correct (just like it works on CodinGame).
15    Success,
16    /// Solution command failed to run. This may happen for example if the
17    /// executable does not exist or if the current user does not have
18    /// permission to execute it.
19    UnableToRun { error_msg: String },
20    /// Solution command exited normally but did not produce the expected
21    /// output.
22    WrongOutput { stdout: String, stderr: String },
23    /// Solution command encountered a runtime error (exited non-zero).
24    RuntimeError { stdout: String, stderr: String },
25    /// Solution command timed out.
26    Timeout { stdout: String, stderr: String },
27}
28
29impl TestResult {
30    pub(crate) fn from_output(
31        expected: &str,
32        stdout: Vec<u8>,
33        stderr: Vec<u8>,
34        exit_status: CommandExit,
35    ) -> Self {
36        let stdout = String::from_utf8(stdout)
37            .unwrap_or_default()
38            .replace("\r\n", "\n")
39            .trim_end()
40            .to_string();
41        let stderr = String::from_utf8(stderr).unwrap_or_default();
42
43        match exit_status {
44            _ if stdout == expected.trim_end() => TestResult::Success,
45            CommandExit::Timeout => TestResult::Timeout { stdout, stderr },
46            CommandExit::Ok => TestResult::WrongOutput { stdout, stderr },
47            CommandExit::Error => TestResult::RuntimeError { stdout, stderr },
48        }
49    }
50
51    /// Returns true if the testcase passed. A testcase passes if the output
52    /// of the solution command matches the expected output.
53    pub fn is_success(&self) -> bool {
54        matches!(self, TestResult::Success)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_testresult_success() {
64        let result = TestResult::from_output("123", "123".into(), vec![], CommandExit::Ok);
65        assert!(matches!(result, TestResult::Success));
66    }
67
68    #[test]
69    fn test_testresult_success_with_trailing_whitespace() {
70        let result = TestResult::from_output("abc\n", "abc".into(), vec![], CommandExit::Ok);
71        assert!(matches!(result, TestResult::Success));
72        let result = TestResult::from_output("abc", "abc\r\n".into(), vec![], CommandExit::Ok);
73        assert!(matches!(result, TestResult::Success));
74    }
75
76    #[test]
77    fn test_testresult_success_normalized_line_endings() {
78        let result = TestResult::from_output("a\nb\nc", "a\r\nb\r\nc".into(), vec![], CommandExit::Ok);
79        assert!(matches!(result, TestResult::Success));
80    }
81
82    #[test]
83    fn test_testresult_success_on_timeout() {
84        let result = TestResult::from_output("123", "123".into(), vec![], CommandExit::Timeout);
85        assert!(
86            matches!(result, TestResult::Success),
87            "TestResult should be `Success` when stdout is correct even if execution timed out"
88        )
89    }
90
91    #[test]
92    fn test_testresult_success_on_runtime_error() {
93        let result = TestResult::from_output("123", "123".into(), vec![], CommandExit::Error);
94        assert!(
95            matches!(result, TestResult::Success),
96            "TestResult should be `Success` when stdout is correct even if a runtime error occurred"
97        )
98    }
99
100    #[test]
101    fn test_testresult_wrong_output() {
102        let result = TestResult::from_output("x\ny\nz", "yyy".into(), "zzz".into(), CommandExit::Ok);
103        match result {
104            TestResult::WrongOutput { stdout, stderr } => {
105                assert_eq!(stdout, "yyy");
106                assert_eq!(stderr, "zzz");
107            }
108            other => panic!("expected TestResult::WrongOutput but found {:?}", other),
109        }
110    }
111
112    #[test]
113    fn test_testresult_timed_out() {
114        let result = TestResult::from_output("xxx", "yyy".into(), "zzz".into(), CommandExit::Timeout);
115        match result {
116            TestResult::Timeout { stdout, stderr } => {
117                assert_eq!(stdout, "yyy");
118                assert_eq!(stderr, "zzz");
119            }
120            other => panic!("expected TestResult::Timeout but found {:?}", other),
121        }
122    }
123
124    #[test]
125    fn test_testresult_runtime_error() {
126        let result = TestResult::from_output("xxx", "yyy".into(), "zzz".into(), CommandExit::Error);
127        match result {
128            TestResult::RuntimeError { stdout, stderr } => {
129                assert_eq!(stdout, "yyy");
130                assert_eq!(stderr, "zzz");
131            }
132            other => panic!("expected TestResult::RuntimeError but found {:?}", other),
133        }
134    }
135}