1use std::any::Any;
2use std::process::ExitStatus;
3
4#[cfg(unix)]
5use std::os::unix::process::ExitStatusExt;
6
7use super::bench::BenchSamples;
8use super::options::ShouldPanic;
9use super::time;
10use super::types::TestDesc;
11
12pub 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.
17pub 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)]
22const STATUS_ABORTED: i32 = 0xC0000409u32 as i32;
23
24#[derive(Debug, Clone, PartialEq)]
25pub 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.
36pub 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.
90pub 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