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