blob: 1bdc78e9ee28588a137a542c2015b7a98bfc7cdc [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
fidl_fuchsia_test_manager as ftest_manager,
std::{fmt, sync::Arc},
thiserror::Error,
};
#[derive(Debug, Clone)]
pub enum Outcome {
Passed,
Failed,
Inconclusive,
Timedout,
/// Suite was stopped prematurely due to cancellation by the user.
Cancelled,
/// Suite did not report completion.
// TODO(fxbug.dev/90037) - this outcome indicates an internal error as test manager isn't
// sending expected events. We should return an error instead.
DidNotFinish,
Error {
origin: Arc<RunTestSuiteError>,
},
}
impl Outcome {
pub(crate) fn error<E: Into<RunTestSuiteError>>(e: E) -> Self {
Self::Error { origin: Arc::new(e.into()) }
}
}
impl PartialEq for Outcome {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Passed, Self::Passed)
| (Self::Failed, Self::Failed)
| (Self::Inconclusive, Self::Inconclusive)
| (Self::Timedout, Self::Timedout)
| (Self::Cancelled, Self::Cancelled)
| (Self::DidNotFinish, Self::DidNotFinish) => true,
(Self::Error { origin }, Self::Error { origin: other_origin }) => {
format!("{}", origin.as_ref()) == format!("{}", other_origin.as_ref())
}
(_, _) => false,
}
}
}
impl fmt::Display for Outcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Outcome::Passed => write!(f, "PASSED"),
Outcome::Failed => write!(f, "FAILED"),
Outcome::Inconclusive => write!(f, "INCONCLUSIVE"),
Outcome::Timedout => write!(f, "TIMED OUT"),
Outcome::Cancelled => write!(f, "CANCELLED"),
Outcome::DidNotFinish => write!(f, "DID_NOT_FINISH"),
Outcome::Error { .. } => write!(f, "ERROR"),
}
}
}
#[derive(Error, Debug)]
/// An enum of the different errors that may be encountered while running
/// a test.
pub enum RunTestSuiteError {
#[error("fidl error: {0:?}")]
Fidl(#[from] fidl::Error),
#[error("error launching test suite: {}", convert_launch_error_to_str(.0))]
Launch(ftest_manager::LaunchError),
#[error("error reporting test results: {0:?}")]
Io(#[from] std::io::Error),
#[error("unexpected event: {0:?}")]
UnexpectedEvent(#[from] UnexpectedEventError),
}
/// An error returned when test manager reports an unexpected event.
/// This could occur if test manager violates guarantees about event
/// ordering.
#[derive(Error, Debug)]
pub enum UnexpectedEventError {
#[error(
"received a 'started' event for case with id {identifier:?} but no 'case_found' event"
)]
CaseStartedButNotFound { identifier: u32 },
#[error(
"received duplicate 'started' events for case {test_case_name:?} with id {identifier:?}"
)]
CaseStartedTwice { test_case_name: String, identifier: u32 },
#[error(
"received an 'artifact' event for case with id {identifier:?} but no 'case_found' event"
)]
CaseArtifactButNotFound { identifier: u32 },
#[error(
"received a 'finished' event for case with id {identifier:?} but no 'case_found' event"
)]
CaseFinishedButNotFound { identifier: u32 },
#[error("received a 'stopped' event for case with id {identifier:?} but no 'started' event")]
CaseStoppedButNotStarted { test_case_name: String, identifier: u32 },
#[error("received an unhandled case status for case with id {identifier:?}: {status:?}")]
UnrecognizedCaseStatus { status: ftest_manager::CaseStatus, identifier: u32 },
#[error("received an unhandled suite status: {status:?}")]
UnrecognizedSuiteStatus { status: ftest_manager::SuiteStatus },
#[error("received an InternalError suite status")]
InternalErrorSuiteStatus,
}
impl RunTestSuiteError {
/// Returns true iff the error variant indicates an internal error in
/// Test Manager or ffx.
pub fn is_internal_error(&self) -> bool {
match self {
Self::Fidl(_) => true,
Self::Launch(ftest_manager::LaunchError::InternalError) => true,
Self::Launch(_) => false,
Self::Io(_) => true,
Self::UnexpectedEvent(_) => true,
}
}
}
impl From<ftest_manager::LaunchError> for RunTestSuiteError {
fn from(launch: ftest_manager::LaunchError) -> Self {
Self::Launch(launch)
}
}
fn convert_launch_error_to_str(e: &ftest_manager::LaunchError) -> &'static str {
match e {
ftest_manager::LaunchError::CaseEnumeration => "Cannot enumerate test. This may mean `fuchsia.test.Suite` was not configured correctly. Refer to: \
https://fuchsia.dev/go/components/test-errors",
ftest_manager::LaunchError::ResourceUnavailable => "Resource unavailable",
ftest_manager::LaunchError::InstanceCannotResolve => "Cannot resolve test.",
ftest_manager::LaunchError::InvalidArgs => {
"Invalid args passed to builder while adding suite. Please file bug"
}
ftest_manager::LaunchError::FailedToConnectToTestSuite => {
"Cannot communicate with the tests. This may mean `fuchsia.test.Suite` was not \
configured correctly. Refer to: \
https://fuchsia.dev/go/components/test-errors"
}
ftest_manager::LaunchError::InternalError => "Internal error, please file bug",
ftest_manager::LaunchError::NoMatchingCases =>
// TODO(satsukiu): Ideally, we would expose these error enums up through the library
// and define the error messages in the main files for each tool. This would allow each
// tool (ffx/fx/run-test-suite) to give the correct flags.
"No test cases matched the specified filters.\n\
If you specified a test filter, verify the available test cases with \
'ffx test list-cases <test suite url>'.\n\
If the list of available tests contains only a single test case called either \
'legacy_test' or 'main', the suite likely uses either the legacy_test_runner or \
elf_test_runner. In these cases, --test-filter will not work. Instead, \
you can pass test arguments directly to the test instead. Refer to: \
https://fuchsia.dev/go/components/test-runners",
ftest_manager::LaunchErrorUnknown!() => "Unrecognized launch error",
}
}