1 | use std::any::Any; |
2 | use std::process::ExitStatus; |
3 | |
4 | #[cfg (unix)] |
5 | use std::os::unix::process::ExitStatusExt; |
6 | |
7 | use super::bench::BenchSamples; |
8 | use super::options::ShouldPanic; |
9 | use super::time; |
10 | use super::types::TestDesc; |
11 | |
12 | pub use self::TestResult::*; |
13 | |
14 | // Return code for secondary process. |
15 | // Start somewhere other than 0 so we know the return code means what we think |
16 | // it means. |
17 | pub const TR_OK: i32 = 50; |
18 | |
19 | // On Windows we use __fastfail to abort, which is documented to use this |
20 | // exception code. |
21 | #[cfg (windows)] |
22 | const STATUS_ABORTED: i32 = 0xC0000409u32 as i32; |
23 | |
24 | #[derive(Debug, Clone, PartialEq)] |
25 | pub enum TestResult { |
26 | TrOk, |
27 | TrFailed, |
28 | TrFailedMsg(String), |
29 | TrIgnored, |
30 | TrBench(BenchSamples), |
31 | TrTimedFail, |
32 | } |
33 | |
34 | /// Creates a `TestResult` depending on the raw result of test execution |
35 | /// and associated data. |
36 | pub fn calc_result<'a>( |
37 | desc: &TestDesc, |
38 | task_result: Result<(), &'a (dyn Any + 'static + Send)>, |
39 | time_opts: &Option<time::TestTimeOptions>, |
40 | exec_time: &Option<time::TestExecTime>, |
41 | ) -> TestResult { |
42 | let result = match (&desc.should_panic, task_result) { |
43 | (&ShouldPanic::No, Ok(())) | (&ShouldPanic::Yes, Err(_)) => TestResult::TrOk, |
44 | (&ShouldPanic::YesWithMessage(msg), Err(err)) => { |
45 | let maybe_panic_str = err |
46 | .downcast_ref::<String>() |
47 | .map(|e| &**e) |
48 | .or_else(|| err.downcast_ref::<&'static str>().copied()); |
49 | |
50 | if maybe_panic_str.map(|e| e.contains(msg)).unwrap_or(false) { |
51 | TestResult::TrOk |
52 | } else if let Some(panic_str) = maybe_panic_str { |
53 | TestResult::TrFailedMsg(format!( |
54 | r#"panic did not contain expected string |
55 | panic message: `{panic_str:?}`, |
56 | expected substring: `{msg:?}`"# |
57 | )) |
58 | } else { |
59 | TestResult::TrFailedMsg(format!( |
60 | r#"expected panic with string value, |
61 | found non-string value: `{:?}` |
62 | expected substring: `{:?}`"# , |
63 | (*err).type_id(), |
64 | msg |
65 | )) |
66 | } |
67 | } |
68 | (&ShouldPanic::Yes, Ok(())) | (&ShouldPanic::YesWithMessage(_), Ok(())) => { |
69 | TestResult::TrFailedMsg("test did not panic as expected" .to_string()) |
70 | } |
71 | _ => TestResult::TrFailed, |
72 | }; |
73 | |
74 | // If test is already failed (or allowed to fail), do not change the result. |
75 | if result != TestResult::TrOk { |
76 | return result; |
77 | } |
78 | |
79 | // Check if test is failed due to timeout. |
80 | if let (Some(opts), Some(time)) = (time_opts, exec_time) { |
81 | if opts.error_on_excess && opts.is_critical(desc, time) { |
82 | return TestResult::TrTimedFail; |
83 | } |
84 | } |
85 | |
86 | result |
87 | } |
88 | |
89 | /// Creates a `TestResult` depending on the exit code of test subprocess. |
90 | pub fn get_result_from_exit_code( |
91 | desc: &TestDesc, |
92 | status: ExitStatus, |
93 | time_opts: &Option<time::TestTimeOptions>, |
94 | exec_time: &Option<time::TestExecTime>, |
95 | ) -> TestResult { |
96 | let result = match status.code() { |
97 | Some(TR_OK) => TestResult::TrOk, |
98 | #[cfg (windows)] |
99 | Some(STATUS_ABORTED) => TestResult::TrFailed, |
100 | #[cfg (unix)] |
101 | None => match status.signal() { |
102 | Some(libc::SIGABRT) => TestResult::TrFailed, |
103 | Some(signal) => { |
104 | TestResult::TrFailedMsg(format!("child process exited with signal {signal}" )) |
105 | } |
106 | None => unreachable!("status.code() returned None but status.signal() was None" ), |
107 | }, |
108 | #[cfg (not(unix))] |
109 | None => TestResult::TrFailedMsg(format!("unknown return code" )), |
110 | #[cfg (any(windows, unix))] |
111 | Some(code) => TestResult::TrFailedMsg(format!("got unexpected return code {code}" )), |
112 | #[cfg (not(any(windows, unix)))] |
113 | Some(_) => TestResult::TrFailed, |
114 | }; |
115 | |
116 | // If test is already failed (or allowed to fail), do not change the result. |
117 | if result != TestResult::TrOk { |
118 | return result; |
119 | } |
120 | |
121 | // Check if test is failed due to timeout. |
122 | if let (Some(opts), Some(time)) = (time_opts, exec_time) { |
123 | if opts.error_on_excess && opts.is_critical(desc, time) { |
124 | return TestResult::TrTimedFail; |
125 | } |
126 | } |
127 | |
128 | result |
129 | } |
130 | |