1use flex_fuchsia_test_manager as ftest_manager;
6use std::fmt;
7use std::sync::Arc;
8use thiserror::Error;
9
10#[derive(Debug, Clone)]
11pub enum Outcome {
12 Passed,
13 Failed,
14 Inconclusive,
15 Timedout,
16 Cancelled,
18 DidNotFinish,
22 Error {
23 origin: Arc<RunTestSuiteError>,
24 },
25}
26
27#[derive(Debug, Clone, PartialEq)]
29pub struct ExtendedOutcome {
30 pub outcome: Outcome,
31 pub setup_succeeded: Option<bool>,
32 pub teardown_succeeded: Option<bool>,
33}
34
35impl Outcome {
36 pub(crate) fn error<E: Into<RunTestSuiteError>>(e: E) -> Self {
37 Self::Error { origin: Arc::new(e.into()) }
38 }
39}
40
41impl PartialEq for Outcome {
42 fn eq(&self, other: &Self) -> bool {
43 match (self, other) {
44 (Self::Passed, Self::Passed)
45 | (Self::Failed, Self::Failed)
46 | (Self::Inconclusive, Self::Inconclusive)
47 | (Self::Timedout, Self::Timedout)
48 | (Self::Cancelled, Self::Cancelled)
49 | (Self::DidNotFinish, Self::DidNotFinish) => true,
50 (Self::Error { origin }, Self::Error { origin: other_origin }) => {
51 format!("{}", origin.as_ref()) == format!("{}", other_origin.as_ref())
52 }
53 (_, _) => false,
54 }
55 }
56}
57
58impl fmt::Display for Outcome {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Outcome::Passed => write!(f, "PASSED"),
62 Outcome::Failed => write!(f, "FAILED"),
63 Outcome::Inconclusive => write!(f, "INCONCLUSIVE"),
64 Outcome::Timedout => write!(f, "TIMED OUT"),
65 Outcome::Cancelled => write!(f, "CANCELLED"),
66 Outcome::DidNotFinish => write!(f, "DID_NOT_FINISH"),
67 Outcome::Error { .. } => write!(f, "ERROR"),
68 }
69 }
70}
71
72#[derive(Error, Debug)]
73pub enum RunTestSuiteError {
76 #[error("fidl error: {0:?}")]
77 Fidl(#[from] fidl::Error),
78 #[error("error launching test suite: {}", convert_launch_error_to_str(.0))]
79 Launch(ftest_manager::LaunchError),
80 #[error("error reporting test results: {0:?}")]
81 Io(#[from] std::io::Error),
82 #[error("unexpected event: {0:?}")]
83 UnexpectedEvent(#[from] UnexpectedEventError),
84 #[error("Error connecting to SuiteRunner protocol: {0:?}")]
85 Connection(#[from] ConnectionError),
86}
87
88#[derive(Error, Debug)]
92pub enum UnexpectedEventError {
93 #[error(
94 "received a 'started' event for case with id {test_case_id:?} but no 'case_found' event"
95 )]
96 CaseStartedButNotFound { test_case_id: u32 },
97 #[error(
98 "invalid case event to '{next_state:?}' received while in state '{last_state:?}' for case {test_case_name:?} with id {test_case_id:?}"
99 )]
100 InvalidCaseEvent {
101 last_state: Lifecycle,
102 next_state: Lifecycle,
103 test_case_name: String,
104 test_case_id: u32,
105 },
106 #[error(
107 "received an 'artifact' event for case with id {test_case_id:?} but no 'case_found' event"
108 )]
109 CaseArtifactButNotFound { test_case_id: u32 },
110 #[error(
111 "received an 'artifact' event for case with id {test_case_id:?} but the case is already finished"
112 )]
113 CaseArtifactButFinished { test_case_id: u32 },
114 #[error(
115 "received a '{next_state:?}' event for case with id {test_case_id:?} but no 'case_found' event"
116 )]
117 CaseEventButNotFound { next_state: Lifecycle, test_case_id: u32 },
118 #[error("received a 'stopped' event for case with id {test_case_id:?} but no 'started' event")]
119 UnrecognizedTestCaseResult { result: ftest_manager::TestCaseResult, test_case_id: u32 },
120 #[error("server closed channel without reporting finish for cases: {cases:?}")]
121 CasesDidNotFinish { cases: Vec<String> },
122 #[error("invalid event to '{next_state:?}' received while in state '{last_state:?}'")]
123 InvalidEvent { last_state: Lifecycle, next_state: Lifecycle },
124 #[error("received an unhandled suite result: {result:?}")]
125 UnrecognizedSuiteResult { result: ftest_manager::SuiteResult },
126 #[error("server closed channel without reporting a result for the suite")]
127 SuiteDidNotReportStop,
128 #[error("received an InternalError suite result")]
129 InternalErrorSuiteResult,
130 #[error("missing required field {field} in {containing_struct}")]
131 MissingRequiredField { containing_struct: &'static str, field: &'static str },
132}
133
134#[derive(Debug, Error)]
135#[error(transparent)]
136pub struct ConnectionError(pub anyhow::Error);
137
138#[derive(Clone, Copy, Debug, PartialEq)]
142pub enum Lifecycle {
143 Found,
144 Started,
145 Stopped,
146 Finished,
147}
148
149impl RunTestSuiteError {
150 pub fn is_internal_error(&self) -> bool {
153 match self {
154 Self::Fidl(_) => true,
155 Self::Launch(ftest_manager::LaunchError::InternalError) => true,
156 Self::Launch(_) => false,
157 Self::Io(_) => true,
158 Self::UnexpectedEvent(_) => true,
159 Self::Connection(_) => true,
160 }
161 }
162}
163
164impl From<ftest_manager::LaunchError> for RunTestSuiteError {
165 fn from(launch: ftest_manager::LaunchError) -> Self {
166 Self::Launch(launch)
167 }
168}
169
170fn convert_launch_error_to_str(e: &ftest_manager::LaunchError) -> &'static str {
171 match e {
172 ftest_manager::LaunchError::CaseEnumeration => {
173 "Cannot enumerate test. This may mean `fuchsia.test.Suite` was not configured correctly. Refer to: \
174 https://fuchsia.dev/go/components/test-errors"
175 }
176 ftest_manager::LaunchError::ResourceUnavailable => "Resource unavailable",
177 ftest_manager::LaunchError::InstanceCannotResolve => "Cannot resolve test.",
178 ftest_manager::LaunchError::InvalidArgs => {
179 "Invalid args passed to builder while adding suite. Please file bug"
180 }
181 ftest_manager::LaunchError::FailedToConnectToTestSuite => {
182 "Cannot communicate with the tests. This may mean `fuchsia.test.Suite` was not \
183 configured correctly. Refer to: \
184 https://fuchsia.dev/go/components/test-errors"
185 }
186 ftest_manager::LaunchError::InternalError => "Internal error, please file bug",
187 ftest_manager::LaunchError::NoMatchingCases =>
188 {
192 "No test cases matched the specified filters.\n\
193 If you specified a test filter, verify the available test cases with \
194 'ffx test list-cases <test suite url>'.\n\
195 If the list of available tests contains only a single test case called either \
196 'legacy_test' or 'main', the suite likely uses either the legacy_test_runner or \
197 elf_test_runner. In these cases, --test-filter will not work. Instead, \
198 you can pass test arguments directly to the test instead. Refer to: \
199 https://fuchsia.dev/go/components/test-runners"
200 }
201 ftest_manager::LaunchError::InvalidManifest => {
202 "The test manifest is invalid or has invalid facets/arguments. Please check logs for detailed error."
203 }
204 ftest_manager::LaunchErrorUnknown!() => "Unrecognized launch error",
205 }
206}