| 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( | 
|---|
| 43 | desc: &TestDesc, | 
|---|
| 44 | panic_payload: Option<&(dyn Any + Send)>, | 
|---|
| 45 | time_opts: Option<&time::TestTimeOptions>, | 
|---|
| 46 | exec_time: Option<&time::TestExecTime>, | 
|---|
| 47 | ) -> TestResult { | 
|---|
| 48 | let result = match (desc.should_panic, panic_payload) { | 
|---|
| 49 | // The test did or didn't panic, as expected. | 
|---|
| 50 | (ShouldPanic::No, None) | (ShouldPanic::Yes, Some(_)) => TestResult::TrOk, | 
|---|
| 51 |  | 
|---|
| 52 | // Check the actual panic message against the expected message. | 
|---|
| 53 | (ShouldPanic::YesWithMessage(msg), Some(err)) => { | 
|---|
| 54 | let maybe_panic_str = err | 
|---|
| 55 | .downcast_ref::<String>() | 
|---|
| 56 | .map(|e| &**e) | 
|---|
| 57 | .or_else(|| err.downcast_ref::<&'static str>().copied()); | 
|---|
| 58 |  | 
|---|
| 59 | if maybe_panic_str.map(|e| e.contains(msg)).unwrap_or(false) { | 
|---|
| 60 | TestResult::TrOk | 
|---|
| 61 | } else if let Some(panic_str) = maybe_panic_str { | 
|---|
| 62 | TestResult::TrFailedMsg(format!( | 
|---|
| 63 | r#"panic did not contain expected string | 
|---|
| 64 |       panic message: {panic_str:?} | 
|---|
| 65 |  expected substring: {msg:?} "# | 
|---|
| 66 | )) | 
|---|
| 67 | } else { | 
|---|
| 68 | TestResult::TrFailedMsg(format!( | 
|---|
| 69 | r#"expected panic with string value, | 
|---|
| 70 |  found non-string value: `{:?} ` | 
|---|
| 71 |      expected substring: {msg:?} "#, | 
|---|
| 72 | (*err).type_id() | 
|---|
| 73 | )) | 
|---|
| 74 | } | 
|---|
| 75 | } | 
|---|
| 76 |  | 
|---|
| 77 | // The test should have panicked, but didn't panic. | 
|---|
| 78 | (ShouldPanic::Yes, None) | (ShouldPanic::YesWithMessage(_), None) => { | 
|---|
| 79 | let fn_location = if !desc.source_file.is_empty() { | 
|---|
| 80 | &format!( " at {} :{} :{} ", desc.source_file, desc.start_line, desc.start_col) | 
|---|
| 81 | } else { | 
|---|
| 82 | "" | 
|---|
| 83 | }; | 
|---|
| 84 | TestResult::TrFailedMsg(format!( "test did not panic as expected{} ", fn_location)) | 
|---|
| 85 | } | 
|---|
| 86 |  | 
|---|
| 87 | // The test should not have panicked, but did panic. | 
|---|
| 88 | (ShouldPanic::No, Some(_)) => TestResult::TrFailed, | 
|---|
| 89 | }; | 
|---|
| 90 |  | 
|---|
| 91 | // If test is already failed (or allowed to fail), do not change the result. | 
|---|
| 92 | if result != TestResult::TrOk { | 
|---|
| 93 | return result; | 
|---|
| 94 | } | 
|---|
| 95 |  | 
|---|
| 96 | // Check if test is failed due to timeout. | 
|---|
| 97 | if let (Some(opts), Some(time)) = (time_opts, exec_time) { | 
|---|
| 98 | if opts.error_on_excess && opts.is_critical(desc, time) { | 
|---|
| 99 | return TestResult::TrTimedFail; | 
|---|
| 100 | } | 
|---|
| 101 | } | 
|---|
| 102 |  | 
|---|
| 103 | result | 
|---|
| 104 | } | 
|---|
| 105 |  | 
|---|
| 106 | /// Creates a `TestResult` depending on the exit code of test subprocess. | 
|---|
| 107 | pub(crate) fn get_result_from_exit_code( | 
|---|
| 108 | desc: &TestDesc, | 
|---|
| 109 | status: ExitStatus, | 
|---|
| 110 | time_opts: Option<&time::TestTimeOptions>, | 
|---|
| 111 | exec_time: Option<&time::TestExecTime>, | 
|---|
| 112 | ) -> TestResult { | 
|---|
| 113 | let result = match status.code() { | 
|---|
| 114 | Some(TR_OK) => TestResult::TrOk, | 
|---|
| 115 | #[ cfg(windows)] | 
|---|
| 116 | Some(STATUS_FAIL_FAST_EXCEPTION) => TestResult::TrFailed, | 
|---|
| 117 | #[ cfg(unix)] | 
|---|
| 118 | None => match status.signal() { | 
|---|
| 119 | Some(libc::SIGABRT) => TestResult::TrFailed, | 
|---|
| 120 | Some(signal) => { | 
|---|
| 121 | TestResult::TrFailedMsg(format!( "child process exited with signal {signal} ")) | 
|---|
| 122 | } | 
|---|
| 123 | None => unreachable!( "status.code() returned None but status.signal() was None"), | 
|---|
| 124 | }, | 
|---|
| 125 | // Upon an abort, Fuchsia returns the status code ZX_TASK_RETCODE_EXCEPTION_KILL. | 
|---|
| 126 | #[ cfg(target_os = "fuchsia")] | 
|---|
| 127 | Some(ZX_TASK_RETCODE_EXCEPTION_KILL) => TestResult::TrFailed, | 
|---|
| 128 | #[ cfg(not(unix))] | 
|---|
| 129 | None => TestResult::TrFailedMsg(format!( "unknown return code")), | 
|---|
| 130 | #[ cfg(any(windows, unix))] | 
|---|
| 131 | Some(code) => TestResult::TrFailedMsg(format!( "got unexpected return code {code} ")), | 
|---|
| 132 | #[ cfg(not(any(windows, unix)))] | 
|---|
| 133 | Some(_) => TestResult::TrFailed, | 
|---|
| 134 | }; | 
|---|
| 135 |  | 
|---|
| 136 | // If test is already failed (or allowed to fail), do not change the result. | 
|---|
| 137 | if result != TestResult::TrOk { | 
|---|
| 138 | return result; | 
|---|
| 139 | } | 
|---|
| 140 |  | 
|---|
| 141 | // Check if test is failed due to timeout. | 
|---|
| 142 | if let (Some(opts), Some(time)) = (time_opts, exec_time) { | 
|---|
| 143 | if opts.error_on_excess && opts.is_critical(desc, time) { | 
|---|
| 144 | return TestResult::TrTimedFail; | 
|---|
| 145 | } | 
|---|
| 146 | } | 
|---|
| 147 |  | 
|---|
| 148 | result | 
|---|
| 149 | } | 
|---|
| 150 |  | 
|---|