blob: 2243d609517c6efc97e0cd68cd9416bba0e6f5aa [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 {
anyhow::{Context as _, Error},
diagnostics_data::{hierarchy, Data, DiagnosticsHierarchy, InspectHandleName, Property},
fake_archive_accessor::FakeArchiveAccessor,
fidl_fuchsia_test_manager as ftest_manager,
ftest_manager::{CaseStatus, RunOptions, SuiteStatus},
fuchsia_async as fasync,
fuchsia_component::server::ServiceFs,
fuchsia_component_test::{Capability, ChildOptions, RealmBuilder, Ref, Route},
futures::prelude::*,
paste::paste,
pretty_assertions::assert_eq,
std::collections::BTreeSet,
test_manager_test_lib::{GroupRunEventByTestCase, RunEvent},
};
#[derive(Debug)]
struct IntegrationCaseStatus {
events: Vec<RunEvent>,
selectors_requested: Vec<BTreeSet<String>>,
}
async fn run_test(
test_url: &str,
fake_archive_output: Vec<String>,
archive_service_name: &'static str,
) -> Result<IntegrationCaseStatus, Error> {
let builder = RealmBuilder::new().await.expect("create realm builder");
let fake = FakeArchiveAccessor::new(&fake_archive_output, None);
let fake_clone = fake.clone();
let test_manager = builder
.add_child("test_manager", "test_manager#meta/test_manager.cm", ChildOptions::new())
.await?;
let fake_archivist = builder
.add_local_child(
"fake_archivist",
move |handles| {
let fake = fake_clone.clone();
async move {
let _ = &handles;
let mut fs = ServiceFs::new();
fs.dir("svc").add_fidl_service_at(archive_service_name, move |req| {
let fake = fake.clone();
fuchsia_async::Task::spawn(async move {
fake.serve_stream(req)
.await
.map_err(|e| println!("Fake stream had error {}", e))
.ok();
})
.detach();
});
fs.serve_connection(handles.outgoing_dir).expect("serve fake archivist");
fs.collect::<()>().await;
Ok(())
}
.boxed()
},
ChildOptions::new(),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
.capability(Capability::protocol_by_name("fuchsia.component.resolution.Resolver"))
.capability(Capability::storage("tmp"))
.capability(Capability::storage("data"))
.capability(Capability::directory("boot"))
.capability(Capability::event_stream("started"))
.capability(Capability::event_stream("stopped"))
.capability(Capability::event_stream("destroyed"))
.capability(Capability::event_stream("capability_requested"))
.from(Ref::parent())
.to(&test_manager),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.diagnostics.ArchiveAccessor"))
.capability(Capability::protocol_by_name(
"fuchsia.diagnostics.FeedbackArchiveAccessor",
))
.capability(Capability::protocol_by_name(
"fuchsia.diagnostics.LegacyMetricsArchiveAccessor",
))
.from(&fake_archivist)
.to(&test_manager),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol::<ftest_manager::RunBuilderMarker>())
.from(&test_manager)
.to(Ref::parent()),
)
.await?;
let instance = builder.build().await?;
let run_builder =
instance.root.connect_to_protocol_at_exposed_dir::<ftest_manager::RunBuilderMarker>()?;
let run_builder = test_manager_test_lib::TestBuilder::new(run_builder);
let suite_instance = run_builder
.add_suite(
test_url,
RunOptions { run_disabled_tests: Some(false), parallel: Some(1), ..Default::default() },
)
.await
.context("Cannot create suite instance")?;
let builder_run = fasync::Task::spawn(async move { run_builder.run().await });
let (events, _logs) = test_runners_test_lib::process_events(suite_instance, false).await?;
builder_run.await.context("builder execution failed")?;
Ok(IntegrationCaseStatus {
events: events,
selectors_requested: fake.get_selectors_requested(),
})
}
fn filter_out_println(event: RunEvent) -> Option<RunEvent> {
match event {
RunEvent::CaseStdout { name, stdout_message } => {
println!("Test stdout [{}]: {}", name, stdout_message);
None
}
e => Some(e),
}
}
#[fuchsia::test]
async fn launch_and_test_sample_test() {
let test_url =
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/sample_inspect_tests.cm";
let fake_data = vec![
Data::for_inspect(
"bootstrap/archivist",
Some(hierarchy! {
root: {
version: "1.0",
}
}),
0,
"no-url",
Some(InspectHandleName::filename("fake-file-name")),
vec![],
),
Data::for_inspect(
"bootstrap/archivist",
Some(hierarchy! {
root: {
events: {
event_counts: {
log_sink_requested: 2i64,
}
}
}
}),
0,
"no-url",
Some(InspectHandleName::filename("fake-file-name")),
vec![],
),
// Inject one that is missing data to ensure we retry correctly.
Data::for_inspect(
"bootstrap/archivist",
Some(hierarchy! {root: {}}),
0,
"no-url",
Some(InspectHandleName::filename("fake-file-name")),
vec![],
),
Data::for_inspect(
"bootstrap/archivist",
Some(hierarchy! {
root: {
events: {
recent_events: {
"0": {
event: "log_sink_requested",
}
}
}
}
}),
0,
"no-url",
Some(InspectHandleName::filename("fake-file-name")),
vec![],
),
]
.into_iter()
.map(|d| serde_json::to_string_pretty(&d))
.collect::<Result<Vec<_>, _>>()
.expect("format fake data");
let IntegrationCaseStatus { events, selectors_requested } =
run_test(test_url, fake_data, "fuchsia.diagnostics.ArchiveAccessor").await.unwrap();
assert_eq!(
vec![
vec!["bootstrap/archivist:root"],
vec!["bootstrap/archivist:root/events/event_counts:log_sink_requested"],
vec!["bootstrap/archivist:root/events/recent_events/*:event"],
vec!["bootstrap/archivist:root/events/recent_events/*:event"],
],
selectors_requested
.into_iter()
.map(|set| set.into_iter().collect::<Vec<String>>())
.collect::<Vec<Vec<String>>>()
);
let mut expected_events = vec![RunEvent::suite_started()];
expected_events.extend(
vec![
"bootstrap/archivist:root",
"bootstrap/archivist:root/events/recent_events/*:event WHERE [a] Count(Filter(Fn([b], b == 'log_sink_requested'), a)) > 0",
"bootstrap/archivist:root/events/event_counts:log_sink_requested WHERE [a] a > 1",
]
.into_iter()
.map(|case_name| vec![
RunEvent::case_found(case_name),RunEvent::case_started(case_name),
RunEvent::case_stopped(case_name, CaseStatus::Passed),RunEvent::case_finished(case_name)
])
.flatten()
);
expected_events.push(RunEvent::suite_stopped(SuiteStatus::Passed));
// Compare events, ignoring stdout messages.
assert_eq!(
expected_events.into_iter().group_by_test_case_unordered(),
events.into_iter().filter_map(filter_out_println).group_by_test_case_unordered()
);
}
/// Options to construct example data.
struct ExampleDataOpts {
/// If set, publish the given value as "version". Otherwise omit it.
version: Option<&'static str>,
/// If set, publish the given value as "value". Otherwise omit it.
value: Option<u64>,
}
// Create a hierarchy with optional values of the following structure:
//
// root:
// version: <version>
// value: <value>
//
// The example tests expect version to be present and value to be in range [5, 10).
fn create_example_data(opts: ExampleDataOpts) -> Data<diagnostics_data::Inspect> {
// Create the list of properties, leaving out those that were not set.
let properties = vec![
opts.version.as_ref().map(|v| Property::String("version".to_string(), v.to_string())),
opts.value.as_ref().map(|v| Property::Uint("value".to_string(), *v)),
]
.into_iter()
.filter_map(|v| v)
.collect();
Data::for_inspect(
"example",
Some(DiagnosticsHierarchy::new("root", properties, vec![])),
0,
"no-url",
Some(InspectHandleName::filename("fake-file-name")),
vec![],
)
}
async fn example_test_success(test_url: &'static str, accessor_service: &'static str) {
let fake_data = vec![
create_example_data(ExampleDataOpts { version: None, value: Some(5) }),
create_example_data(ExampleDataOpts { version: Some("1.0"), value: None }),
]
.into_iter()
.map(|d| serde_json::to_string_pretty(&d))
.collect::<Result<Vec<_>, _>>()
.expect("format fake data");
let IntegrationCaseStatus { events, selectors_requested } =
run_test(test_url, fake_data, accessor_service).await.unwrap();
assert_eq!(
vec![vec!["example:root:value"], vec!["example:root:version"],],
selectors_requested
.into_iter()
.map(|set| set.into_iter().collect::<Vec<String>>())
.collect::<Vec<Vec<String>>>()
);
let mut expected_events = vec![RunEvent::suite_started()];
expected_events.extend(
vec!["example:root:value WHERE [a] And(a >= 5, a < 10)", "example:root:version"]
.into_iter()
.map(|case_name| {
vec![
RunEvent::case_found(case_name),
RunEvent::case_started(case_name),
RunEvent::case_stopped(case_name, CaseStatus::Passed),
RunEvent::case_finished(case_name),
]
})
.flatten(),
);
expected_events.push(RunEvent::suite_stopped(SuiteStatus::Passed));
// Compare events, ignoring stdout messages.
assert_eq!(
expected_events.into_iter().group_by_test_case_unordered(),
events.into_iter().filter_map(filter_out_println).group_by_test_case_unordered()
);
}
#[derive(Clone, Copy, PartialEq)]
enum FailureMode {
// Don't artificially create a failure, depend on a failure unrelated to values.
NoValueFailure,
// Set value to be too small
ValueTooSmall,
// Set value to be too large
ValueTooLarge,
// Do not set value at all
MissingValue,
// Do not set version at all
MissingVersion,
}
async fn example_test_failure(
test_url: &'static str,
accessor_service: &'static str,
failure_mode: FailureMode,
) {
let (fake_data, expected_results) = match failure_mode {
FailureMode::NoValueFailure => (
vec![create_example_data(ExampleDataOpts { version: Some("1.0"), value: Some(5) })],
vec![CaseStatus::Failed, CaseStatus::Failed],
),
FailureMode::ValueTooSmall => (
vec![create_example_data(ExampleDataOpts { version: Some("1.0"), value: Some(4) })],
vec![CaseStatus::Failed, CaseStatus::Passed],
),
FailureMode::ValueTooLarge => (
vec![create_example_data(ExampleDataOpts { version: Some("1.0"), value: Some(10) })],
vec![CaseStatus::Failed, CaseStatus::Passed],
),
FailureMode::MissingValue => (
vec![create_example_data(ExampleDataOpts { version: Some("1.0"), value: None })],
vec![CaseStatus::Failed, CaseStatus::Passed],
),
FailureMode::MissingVersion => (
vec![create_example_data(ExampleDataOpts { version: None, value: Some(5) })],
vec![CaseStatus::Passed, CaseStatus::Failed],
),
};
let fake_data = fake_data
.into_iter()
.cycle()
.take(2000) // Create a repeat of the value so that repeated reads keep finding the same values.
.map(|d| serde_json::to_string_pretty(&d))
.collect::<Result<Vec<_>, _>>()
.expect("format fake data");
let IntegrationCaseStatus { events, selectors_requested } =
run_test(test_url, fake_data, accessor_service).await.unwrap();
let selectors_requested = selectors_requested.into_iter().flatten().collect::<BTreeSet<_>>();
if failure_mode == FailureMode::NoValueFailure {
// The only way the tests failed is if no requests succeeded.
assert_eq!(BTreeSet::new(), selectors_requested);
} else {
assert_eq!(
vec!["example:root:value", "example:root:version"]
.into_iter()
.map(str::to_string)
.collect::<BTreeSet<_>>(),
selectors_requested
);
}
let mut expected_events: Vec<RunEvent> = vec![RunEvent::suite_started()];
expected_events.extend(
vec!["example:root:value WHERE [a] And(a >= 5, a < 10)", "example:root:version"]
.into_iter()
.zip(expected_results.into_iter())
.map(|(case_name, result)| {
vec![
RunEvent::case_found(case_name),
RunEvent::case_started(case_name),
RunEvent::case_stopped(case_name, result),
RunEvent::case_finished(case_name),
]
})
.flatten(),
);
expected_events.push(RunEvent::suite_stopped(SuiteStatus::Failed));
// Compare events, ignoring stdout messages.
assert_eq!(
expected_events.into_iter().group_by_test_case_unordered(),
events.into_iter().filter_map(filter_out_println).group_by_test_case_unordered()
);
}
macro_rules! make_tests {
($name:ident, $pkg:expr, $correct_accessor:expr, $wrong_accessor:expr) => {
paste! {
#[fuchsia::test]
async fn [<$name _success>]() {
example_test_success($pkg, $correct_accessor).await;
}
#[fuchsia::test]
async fn [<$name _failure_wrong_accessor>]() {
example_test_failure($pkg, $wrong_accessor, FailureMode::NoValueFailure).await;
}
#[fuchsia::test]
async fn [<$name _failure_value_too_small>]() {
example_test_failure($pkg, $correct_accessor, FailureMode::ValueTooSmall).await;
}
#[fuchsia::test]
async fn [<$name _failure_value_too_large>]() {
example_test_failure($pkg, $correct_accessor, FailureMode::ValueTooLarge).await;
}
#[fuchsia::test]
async fn [<$name _failure_value_missing>]() {
example_test_failure($pkg, $correct_accessor, FailureMode::MissingValue).await;
}
#[fuchsia::test]
async fn [<$name _failure_version_missing>]() {
example_test_failure($pkg, $correct_accessor, FailureMode::MissingVersion).await;
}
}
};
}
make_tests!(
archive_example,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/archive_example.cm",
"fuchsia.diagnostics.ArchiveAccessor",
"fuchsia.diagnostics.FeedbackArchiveAccessor"
);
make_tests!(
feedback_example,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/feedback_example.cm",
"fuchsia.diagnostics.FeedbackArchiveAccessor",
"fuchsia.diagnostics.ArchiveAccessor"
);
make_tests!(
legacy_example,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/legacy_example.cm",
"fuchsia.diagnostics.LegacyMetricsArchiveAccessor",
"fuchsia.diagnostics.ArchiveAccessor"
);
async fn test_failure_case(url: &str) {
let output = run_test(url, vec![], "fuchsia.diagnostics.ArchiveAccessor").await;
println!("Output was {:?}", output);
assert!(output.is_err());
}
macro_rules! make_failure_test {
($name: ident, $url: expr) => {
paste! {
#[fuchsia::test]
async fn [<$name _failure>]() {
test_failure_case($url).await;
}
}
};
}
make_failure_test!(
invalid_case,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/invalid_case.cm"
);
make_failure_test!(
invalid_evaluation,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/invalid_evaluation.cm"
);
make_failure_test!(
missing_program,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/missing_program.cm"
);
make_failure_test!(
unknown_pipeline,
"fuchsia-pkg://fuchsia.com/inspect-runner-integration-test#meta/unknown_pipeline.cm"
);