blob: efc1901905511e7e7ba656952940b06de4f921e5 [file] [log] [blame]
// Copyright 2019 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 {
argh::FromArgs,
diagnostics_data::Severity,
fidl_fuchsia_diagnostics::LogInterestSelector,
fidl_fuchsia_sys2 as fsys,
fidl_fuchsia_test_manager::{LogsIteratorOption, RunBuilderMarker},
};
#[derive(FromArgs, Default, PartialEq, Debug)]
/// Entry point for executing tests.
struct Args {
/// test timeout. Exits with -`ZX_ERR_TIMED_OUT` if the test times out.
#[argh(option, short = 't')]
timeout: Option<u32>,
/// test url. Test should implement `fuchsia.test.Suite` protocol.
#[argh(positional)]
test_url: String,
/// test filter. Glob pattern for matching tests. Can be specified multiple
/// times to pass in multiple patterns. Tests may be excluded by prepending a
/// '-' to the glob pattern.
/// example: --test-filter glob1 --test-filter glob2.
#[argh(option)]
test_filter: Vec<String>,
/// the realm to run the test in. This field is optional and takes the form:
/// /path/to/realm:test_collection. See https://fuchsia.dev/go/components/non-hermetic-tests
#[argh(option)]
realm: Option<String>,
/// whether to also run tests that have been marked disabled/ignored by the test author.
#[argh(switch)]
also_run_disabled_tests: bool,
/// whether to filter ANSI escape sequences from stdout.
#[argh(switch)]
filter_ansi: bool,
/// continue running unfinished suites if a suite times out.
/// By default, unfinished suites are immediately terminated if a suite times out.
/// This option is only relevant when multiple suites are run.
#[argh(switch)]
continue_on_timeout: bool,
/// stop running unfinished suites after the number of provided failures has occurred.
/// By default, all suites are run to completion if a suite fails.
#[argh(option)]
stop_after_failures: Option<u32>,
/// run test cases in parallel, up to the number provided.
#[argh(option)]
parallel: Option<u16>,
/// number of times to run the test. By default run 1 time.
/// If an iteration of test times out, no further iterations
/// would be executed.
#[argh(option)]
count: Option<u32>,
/// when set, only logs with a severity equal to the given one or higher will be printed for
/// the associated component.
///
/// This modifies the minimum log severity level emitted by components during the test
/// execution.
///
/// Specify using the format <component-selector>#<log-level>, or just <log-level> (in which
/// case the severity will apply to all components under the test, including the test component
/// itself) with level as one of FATAL|ERROR|WARN|INFO|DEBUG|TRACE.
/// May be repeated.
#[argh(option, from_str_fn(log_interest_selector_or_severity))]
min_severity_logs: Vec<LogInterestSelector>,
/// when set, the test will fail if any log with a higher severity is emitted.
#[argh(option)]
max_severity_logs: Option<Severity>,
/// when set, saves the output directory on the host.
/// Note - this option is only intended to aid in migrating OOT tests to v2. It will be
/// removed once existing users stop using it, and new users will not be supported.
// TODO(https://fxbug.dev/42178399): remove this option once users are migrated to ffx test.
#[argh(option)]
deprecated_output_directory: Option<String>,
#[argh(positional)]
/// arguments passed to tests following `--`.
test_args: Vec<String>,
}
fn log_interest_selector_or_severity(input: &str) -> Result<LogInterestSelector, String> {
selectors::parse_log_interest_selector_or_severity(input).map_err(|s| s.to_string())
}
const REALM_QUERY_PATH: &str = "/svc/fuchsia.sys2.RealmQuery.root";
const LIFECYCLE_CONTROLLER_PATH: &str = "/svc/fuchsia.sys2.LifecycleController.root";
#[fuchsia::main]
async fn main() {
fuchsia_trace_provider::trace_provider_create_with_fdio();
let args = argh::from_env();
let Args {
timeout,
test_url,
test_filter,
realm,
also_run_disabled_tests,
continue_on_timeout,
stop_after_failures,
parallel,
count,
min_severity_logs,
max_severity_logs,
deprecated_output_directory,
test_args,
filter_ansi,
} = args;
let count = count.unwrap_or(1);
if count == 0 {
println!("--count should be greater than zero.");
std::process::exit(1);
}
if filter_ansi {
println!("Note: Filtering out ANSI escape sequences.");
}
let mut provided_realm = None;
let hermetic_test = realm.is_none();
if let Some(realm) = realm {
let lifecycle_controller = fuchsia_component::client::connect_to_protocol_at_path::<
fsys::LifecycleControllerMarker,
>(LIFECYCLE_CONTROLLER_PATH)
.expect("connecting to LifecycleController");
let realm_query = fuchsia_component::client::connect_to_protocol_at_path::<
fsys::RealmQueryMarker,
>(REALM_QUERY_PATH)
.expect("connecting to RealmQuery");
match run_test_suite_lib::parse_provided_realm(&lifecycle_controller, &realm_query, &realm)
.await
{
Ok(r) => {
provided_realm = Some(r);
}
Err(e) => {
println!("Error parsing realm '{}': {:?}", realm, e);
std::process::exit(1);
}
}
}
let test_filters = if test_filter.len() == 0 { None } else { Some(test_filter) };
let shell_reporter = run_test_suite_lib::output::ShellReporter::new(std::io::stdout());
let dir_reporter = match deprecated_output_directory {
Some(path) => match run_test_suite_lib::output::DirectoryReporter::new(
path.into(),
run_test_suite_lib::output::SchemaVersion::V1,
) {
Ok(reporter) => Some(reporter),
Err(e) => {
println!("Failed to make directory reporter: {:?}", e);
std::process::exit(1);
}
},
None => None,
};
let run_reporter = match (filter_ansi, dir_reporter) {
(true, None) => run_test_suite_lib::output::RunReporter::new_ansi_filtered(shell_reporter),
(false, None) => run_test_suite_lib::output::RunReporter::new(shell_reporter),
(true, Some(dir_reporter)) => run_test_suite_lib::output::RunReporter::new_ansi_filtered(
run_test_suite_lib::output::MultiplexedReporter::new(shell_reporter, dir_reporter),
),
(false, Some(dir_reporter)) => run_test_suite_lib::output::RunReporter::new(
run_test_suite_lib::output::MultiplexedReporter::new(shell_reporter, dir_reporter),
),
};
let run_params = run_test_suite_lib::RunParams {
timeout_behavior: match continue_on_timeout {
false => run_test_suite_lib::TimeoutBehavior::TerminateRemaining,
true => run_test_suite_lib::TimeoutBehavior::Continue,
},
timeout_grace_seconds: 0,
stop_after_failures: match stop_after_failures.map(std::num::NonZeroU32::new) {
None => None,
Some(None) => {
println!("--stop-after-failures should be greater than zero.");
std::process::exit(1);
}
Some(Some(stop_after)) => Some(stop_after),
},
experimental_parallel_execution: None,
accumulate_debug_data: true, // must be true to support coverage via scp
log_protocol: Some(LogsIteratorOption::SocketBatchIterator),
min_severity_logs: min_severity_logs.clone(),
// TODO(https://fxbug.dev/42059408): make this configurable
show_full_moniker: true,
};
let proxy = fuchsia_component::client::connect_to_protocol::<RunBuilderMarker>()
.expect("connecting to RunBuilderProxy");
let start_time = std::time::Instant::now();
let outcome = run_test_suite_lib::run_tests_and_get_outcome(
run_test_suite_lib::SingleRunConnector::new(proxy),
vec![
run_test_suite_lib::TestParams {
test_url,
realm: provided_realm.into(),
timeout_seconds: timeout.and_then(std::num::NonZeroU32::new),
test_filters,
also_run_disabled_tests,
parallel,
test_args,
max_severity_logs,
min_severity_logs,
tags: vec![],
break_on_failure: false,
};
count as usize
],
run_params,
run_reporter,
futures::future::pending(),
)
.await;
tracing::info!("run test suite duration: {:?}", start_time.elapsed().as_secs_f32());
if outcome != run_test_suite_lib::Outcome::Passed {
println!("One or more test runs failed.");
}
let show_realm_warning = outcome == run_test_suite_lib::Outcome::Timedout
|| outcome == run_test_suite_lib::Outcome::Failed
|| outcome == run_test_suite_lib::Outcome::DidNotFinish;
if show_realm_warning && hermetic_test {
println!(
"The test was executed in the hermetic realm. If your test depends on system \
capabilities, pass in correct realm. See https://fuchsia.dev/go/components/non-hermetic-tests"
);
}
match outcome {
run_test_suite_lib::Outcome::Passed => {}
run_test_suite_lib::Outcome::Timedout => {
std::process::exit(-fuchsia_zircon::Status::TIMED_OUT.into_raw());
}
run_test_suite_lib::Outcome::Failed
| run_test_suite_lib::Outcome::Cancelled
| run_test_suite_lib::Outcome::DidNotFinish
| run_test_suite_lib::Outcome::Inconclusive => {
std::process::exit(1);
}
run_test_suite_lib::Outcome::Error { origin } => {
println!("Encountered error trying to run tests: {}", *origin);
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
// As we have custom parsing when user passes "--", making sure everything works fine.
fn test_args() {
let url = "foo.cm";
let mut expected_args = Args { test_url: url.to_string(), ..Default::default() };
expected_args.test_url = url.to_string();
let args = Args::from_args(&["cmd"], &[url]).unwrap();
assert_eq!(args, expected_args);
let args = Args::from_args(&["cmd"], &[url, "--"]).unwrap();
expected_args.test_args = vec![];
assert_eq!(args, expected_args);
// make sure we can parse --help flag when user passes "--"
let early_exit = Args::from_args(&["cmd"], &[url, "--help", "--"]).unwrap_err();
assert_eq!(early_exit.status, Ok(()));
// make sure we can parse --help flag without "--"
let early_exit = Args::from_args(&["cmd"], &[url, "--help"]).unwrap_err();
assert_eq!(early_exit.status, Ok(()));
// make sure we can catch arg errors when user passes "--"
let early_exit = Args::from_args(&["cmd"], &[url, "--timeout", "a", "--"]).unwrap_err();
assert_eq!(early_exit.status, Err(()));
// make sure we can catch arg errors without "--"
let early_exit = Args::from_args(&["cmd"], &[url, "--timeout", "a"]).unwrap_err();
assert_eq!(early_exit.status, Err(()));
// make sure we can parse args when user passes "--"
let args = Args::from_args(&["cmd"], &[url, "--timeout", "2", "--"]).unwrap();
expected_args.timeout = Some(2);
expected_args.test_args = vec![];
assert_eq!(args, expected_args);
// make sure we can parse args without "--"
let args = Args::from_args(&["cmd"], &[url, "--timeout", "2"]).unwrap();
assert_eq!(args, expected_args);
// make sure we can parse args after "--"
let args = Args::from_args(
&["cmd"],
&[url, "--timeout", "2", "--", "--arg1", "some_random_str", "-arg2"],
)
.unwrap();
expected_args.test_args =
vec!["--arg1".to_owned(), "some_random_str".to_owned(), "-arg2".to_owned()];
assert_eq!(args, expected_args);
// Args::from_args works with multiple "--"
let args = Args::from_args(
&["cmd"],
&[url, "--timeout", "2", "--", "--", "--arg1", "some_random_str", "--", "-arg2"],
)
.unwrap();
expected_args.test_args = vec![
"--".to_owned(),
"--arg1".to_owned(),
"some_random_str".to_owned(),
"--".to_owned(),
"-arg2".to_owned(),
];
assert_eq!(args, expected_args);
}
}