blob: 55355c80fb34f8fd18034dc387072af93e67aadb [file] [log] [blame]
// Copyright 2022 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 {
crate::{
above_root_capabilities::AboveRootCapabilitiesForTest,
constants,
debug_data_processor::{DebugDataDirectory, DebugDataProcessor},
debug_data_server,
error::TestManagerError,
facet,
offers::map_offers,
running_suite::{enumerate_test_cases, RunningSuite},
self_diagnostics::RootDiagnosticNode,
test_suite::{Suite, SuiteRealm, TestRunBuilder},
},
fidl::endpoints::ControlHandle,
fidl::Error,
fidl_fuchsia_component_resolution::ResolverProxy,
fidl_fuchsia_test_manager as ftest_manager,
fidl_fuchsia_test_manager::{QueryEnumerateInRealmResponder, QueryEnumerateResponder},
ftest_manager::LaunchError,
fuchsia_async::{self as fasync},
fuchsia_zircon as zx,
futures::prelude::*,
std::sync::Arc,
tracing::warn,
};
/// Start `RunBuilder` server and serve it over `stream`.
pub async fn run_test_manager_run_builder_server(
mut stream: ftest_manager::RunBuilderRequestStream,
resolver: Arc<ResolverProxy>,
above_root_capabilities_for_test: Arc<AboveRootCapabilitiesForTest>,
root_diagnostics: &RootDiagnosticNode,
) -> Result<(), TestManagerError> {
let mut builder = TestRunBuilder { suites: vec![] };
let mut scheduling_options: Option<ftest_manager::SchedulingOptions> = None;
while let Some(event) = stream.try_next().await.map_err(TestManagerError::Stream)? {
match event {
ftest_manager::RunBuilderRequest::AddSuite {
test_url,
options,
controller,
control_handle,
} => {
let controller = match controller.into_stream() {
Ok(c) => c,
Err(e) => {
warn!(
"Cannot add suite {}, invalid controller. Closing connection. error: {}",
test_url,e
);
control_handle.shutdown_with_epitaph(zx::Status::BAD_HANDLE);
break;
}
};
builder.suites.push(Suite {
realm: None,
test_url,
options,
controller,
resolver: resolver.clone(),
above_root_capabilities_for_test: above_root_capabilities_for_test.clone(),
facets: facet::ResolveStatus::Unresolved,
});
}
ftest_manager::RunBuilderRequest::AddSuiteInRealm {
realm,
offers,
test_collection,
test_url,
options,
controller,
control_handle,
} => {
let realm_proxy = match realm.into_proxy() {
Ok(r) => r,
Err(e) => {
warn!(
"Cannot add suite {}, invalid realm. Closing connection. error: {}",
test_url, e
);
control_handle.shutdown_with_epitaph(zx::Status::BAD_HANDLE);
break;
}
};
let controller = match controller.into_stream() {
Ok(c) => c,
Err(e) => {
warn!(
"Cannot add suite {}, invalid controller. Closing connection. error: {}",
test_url,e
);
control_handle.shutdown_with_epitaph(zx::Status::BAD_HANDLE);
break;
}
};
let offers = match map_offers(offers) {
Ok(offers) => offers,
Err(e) => {
warn!("Cannot add suite {}, invalid offers. error: {}", test_url, e);
control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
break;
}
};
builder.suites.push(Suite {
realm: SuiteRealm { realm_proxy, offers, test_collection }.into(),
test_url,
options,
controller,
resolver: resolver.clone(),
above_root_capabilities_for_test: above_root_capabilities_for_test.clone(),
facets: facet::ResolveStatus::Unresolved,
});
}
ftest_manager::RunBuilderRequest::WithSchedulingOptions { options, .. } => {
scheduling_options = Some(options);
}
ftest_manager::RunBuilderRequest::Build { controller, control_handle } => {
let controller = match controller.into_stream() {
Ok(c) => c,
Err(e) => {
warn!("Invalid builder controller. Closing connection. error: {}", e);
control_handle.shutdown_with_epitaph(zx::Status::BAD_HANDLE);
break;
}
};
let persist_diagnostics =
match scheduling_options.as_ref().map(|options| options.max_parallel_suites) {
Some(Some(_)) => true,
Some(None) | None => false,
};
let diagnostics = match persist_diagnostics {
true => root_diagnostics.persistent_child(),
false => root_diagnostics.child(),
};
builder.run(controller, diagnostics, scheduling_options).await;
// clients should reconnect to run new tests.
break;
}
ftest_manager::RunBuilderRequest::_UnknownMethod {
ordinal, control_handle, ..
} => {
warn!("Unknown run builder request received: {}, closing connection", ordinal);
control_handle.shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
break;
}
}
}
Ok(())
}
enum QueryResponder {
Enumerate(QueryEnumerateResponder),
EnumerateInRealm(QueryEnumerateInRealmResponder),
}
impl QueryResponder {
fn send(self, result: Result<(), LaunchError>) -> Result<(), fidl::Error> {
match self {
QueryResponder::Enumerate(responder) => responder.send(result),
QueryResponder::EnumerateInRealm(responder) => responder.send(result),
}
}
}
/// Start `Query` server and serve it over `stream`.
pub async fn run_test_manager_query_server(
mut stream: ftest_manager::QueryRequestStream,
resolver: Arc<ResolverProxy>,
above_root_capabilities_for_test: Arc<AboveRootCapabilitiesForTest>,
root_diagnostics: &RootDiagnosticNode,
) -> Result<(), TestManagerError> {
while let Some(event) = stream.try_next().await.map_err(TestManagerError::Stream)? {
let (test_url, iterator, realm, responder) = match event {
ftest_manager::QueryRequest::Enumerate { test_url, iterator, responder } => {
(test_url, iterator, None, QueryResponder::Enumerate(responder))
}
ftest_manager::QueryRequest::EnumerateInRealm {
test_url,
realm,
offers,
test_collection,
iterator,
responder,
} => {
let realm_proxy = match realm.into_proxy() {
Ok(r) => r,
Err(e) => {
warn!(
"Cannot add suite {}, invalid realm. Closing connection. error: {}",
test_url, e
);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
let offers = match map_offers(offers) {
Ok(offers) => offers,
Err(e) => {
warn!("Cannot add suite {}, invalid offers. error: {}", test_url, e);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
(
test_url,
iterator,
SuiteRealm { realm_proxy, offers, test_collection }.into(),
QueryResponder::EnumerateInRealm(responder),
)
}
ftest_manager::QueryRequest::_UnknownMethod { ordinal, control_handle, .. } => {
warn!("Unknown query request received: {}, closing connection", ordinal);
control_handle.shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
break;
}
};
let mut iterator = match iterator.into_stream() {
Ok(c) => c,
Err(e) => {
warn!("Cannot query test, invalid iterator {}: {}", test_url, e);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
let (_processor, sender) = DebugDataProcessor::new(DebugDataDirectory::Isolated {
parent: constants::ISOLATED_TMP,
});
let diagnostics = root_diagnostics.child();
let launch_fut =
facet::get_suite_facets(test_url.clone(), resolver.clone()).and_then(|facets| {
RunningSuite::launch(
&test_url,
facets,
resolver.clone(),
above_root_capabilities_for_test.clone(),
sender,
&diagnostics,
&realm,
)
});
match launch_fut.await {
Ok(suite_instance) => {
let suite = match suite_instance.connect_to_suite() {
Ok(proxy) => proxy,
Err(e) => {
responder.send(Err(e.into())).ok();
continue;
}
};
let enumeration_result = enumerate_test_cases(&suite, None).await;
let t = fasync::Task::spawn(suite_instance.destroy(root_diagnostics.child()));
match enumeration_result {
Ok(invocations) => {
const NAMES_CHUNK: usize = 50;
let mut names = Vec::with_capacity(invocations.len());
if let Ok(_) = invocations.into_iter().try_for_each(|i| match i.name {
Some(name) => {
names.push(name);
Ok(())
}
None => {
warn!("no name for a invocation in {}", test_url);
Err(())
}
}) {
responder.send(Ok(())).ok();
let mut names = names.chunks(NAMES_CHUNK);
while let Ok(Some(request)) = iterator.try_next().await {
match request {
ftest_manager::CaseIteratorRequest::GetNext { responder } => {
match names.next() {
Some(names) => {
responder
.send(
&names
.into_iter()
.map(|s| ftest_manager::Case {
name: Some(s.into()),
..Default::default()
})
.collect::<Vec<_>>(),
)
.ok();
}
None => {
responder.send(&[]).ok();
}
}
}
}
}
} else {
responder.send(Err(LaunchError::CaseEnumeration)).ok();
}
}
Err(err) => {
warn!(?err, "cannot enumerate tests for {}", test_url);
responder.send(Err(LaunchError::CaseEnumeration)).ok();
}
}
if let Err(err) = t.await {
warn!(?err, "Error destroying test realm for {}", test_url);
}
}
Err(e) => {
responder.send(Err(e.into())).ok();
}
}
}
Ok(())
}
pub async fn serve_early_boot_profiles(
mut stream: ftest_manager::EarlyBootProfileRequestStream,
) -> Result<(), TestManagerError> {
while let Some(req) = stream.try_next().await.map_err(TestManagerError::Stream)? {
match req {
ftest_manager::EarlyBootProfileRequest::RegisterWatcher {
iterator,
control_handle,
} => {
let iterator = match iterator.into_stream() {
Ok(i) => i,
Err(e) => {
warn!("Invalid debug data iterator: {}", e);
control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
break;
}
};
if let Err(e) = debug_data_server::send_kernel_debug_data(iterator).await {
warn!("Err serving kernel profiles: {}", e);
control_handle.shutdown_with_epitaph(zx::Status::INTERNAL);
break;
}
}
ftest_manager::EarlyBootProfileRequest::_UnknownMethod {
ordinal,
control_handle,
..
} => {
warn!("Unknown EarlyBootProfile request received: {}, closing connection", ordinal);
control_handle.shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
break;
}
}
}
Ok(())
}
/// Start `TestCaseEnumerator` server and serve it over `stream`.
pub async fn run_test_manager_test_case_enumerator_server(
mut stream: ftest_manager::TestCaseEnumeratorRequestStream,
resolver: Arc<ResolverProxy>,
above_root_capabilities_for_test: Arc<AboveRootCapabilitiesForTest>,
root_diagnostics: &RootDiagnosticNode,
) -> Result<(), TestManagerError> {
while let Some(req) = stream.try_next().await.map_err(TestManagerError::Stream)? {
match req {
ftest_manager::TestCaseEnumeratorRequest::Enumerate {
test_suite_url,
options,
iterator,
responder,
} => {
let realm = if let Some(realm_options) = options.realm_options {
let realm_proxy = match realm_options
.realm
.map(|r| r.into_proxy())
.unwrap_or(Err(Error::NotNullable))
{
Ok(r) => r,
Err(e) => {
warn!(
"Cannot enumerate test cases {}, invalid realm. Closing connection. error: {}",
test_suite_url, e
);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
let offers = match realm_options
.offers
.map(map_offers)
.unwrap_or(Err(Error::NotNullable.into()))
{
Ok(offers) => offers,
Err(e) => {
warn!(
"Cannot enumerate test cases {}, invalid offers. error: {}",
test_suite_url, e
);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
let test_collection = match realm_options.test_collection {
Some(test_collection) => test_collection,
None => {
warn!(
"Cannot enumerate test cases {}, missing test collection.",
test_suite_url
);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
Some(SuiteRealm { realm_proxy, offers, test_collection })
} else {
None
};
let iterator = match iterator.into_stream() {
Ok(c) => c,
Err(e) => {
warn!("Cannot query test, invalid iterator {}: {}", test_suite_url, e);
responder.send(Err(LaunchError::InvalidArgs)).ok();
break;
}
};
let (_processor, sender) = DebugDataProcessor::new(DebugDataDirectory::Isolated {
parent: constants::ISOLATED_TMP,
});
let diagnostics = root_diagnostics.child();
let launch_fut = facet::get_suite_facets(test_suite_url.clone(), resolver.clone())
.and_then(|facets| {
RunningSuite::launch(
&test_suite_url,
facets,
resolver.clone(),
above_root_capabilities_for_test.clone(),
sender,
&diagnostics,
&realm,
)
});
match launch_fut.await {
Ok(suite_instance) => {
let suite = match suite_instance.connect_to_suite() {
Ok(proxy) => proxy,
Err(e) => {
responder.send(Err(e.into())).ok();
continue;
}
};
let enumeration_result = enumerate_test_cases(&suite, None).await;
let t = fasync::Task::spawn(suite_instance.destroy(diagnostics));
match enumeration_result {
Ok(invocations) => {
if let Ok(names) = invocations
.into_iter()
.map(|i| i.name.ok_or(()))
.collect::<Result<Vec<String>, ()>>()
{
responder.send(Ok(())).ok();
drain_test_case_names(iterator, names).await;
} else {
responder.send(Err(LaunchError::CaseEnumeration)).ok();
}
}
Err(err) => {
warn!(?err, "cannot enumerate tests for {}", test_suite_url);
responder.send(Err(LaunchError::CaseEnumeration)).ok();
}
}
if let Err(err) = t.await {
warn!(?err, "Error destroying test realm for {}", test_suite_url);
}
}
Err(e) => {
responder.send(Err(e.into())).ok();
}
}
}
ftest_manager::TestCaseEnumeratorRequest::_UnknownMethod {
ordinal,
control_handle,
..
} => {
warn!("Unknown query request received: {}, closing connection", ordinal);
control_handle.shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
break;
}
};
}
Ok(())
}
async fn drain_test_case_names(
mut iterator: ftest_manager::TestCaseIteratorRequestStream,
names: Vec<String>,
) {
const NAMES_CHUNK: usize = 50;
let mut names = names.chunks(NAMES_CHUNK);
while let Ok(Some(request)) = iterator.try_next().await {
match request {
ftest_manager::TestCaseIteratorRequest::GetNext { responder } => match names.next() {
Some(names) => {
responder
.send(
&names
.into_iter()
.map(|s| ftest_manager::TestCase {
name: Some(s.into()),
..Default::default()
})
.collect::<Vec<_>>(),
)
.ok();
}
None => {
responder.send(&[]).ok();
}
},
}
}
}
/// Start `SuiteRunner` server and serve it over `stream`.
pub async fn run_test_manager_suite_runner_server(
mut stream: ftest_manager::SuiteRunnerRequestStream,
resolver: Arc<ResolverProxy>,
above_root_capabilities_for_test: Arc<AboveRootCapabilitiesForTest>,
root_diagnostics: &RootDiagnosticNode,
) -> Result<(), TestManagerError> {
while let Some(req) = stream.try_next().await.map_err(TestManagerError::Stream)? {
match req {
ftest_manager::SuiteRunnerRequest::Run {
test_suite_url,
options,
controller,
control_handle,
} => {
let realm = if let Some(realm_options) = options.realm_options {
let realm_proxy = match realm_options
.realm
.map(|r| r.into_proxy())
.unwrap_or(Err(Error::NotNullable))
{
Ok(r) => r,
Err(e) => {
warn!(
"Cannot add suite {}, invalid realm. Closing connection. error: {}",
test_suite_url, e
);
control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
break;
}
};
let offers = match realm_options
.offers
.map(map_offers)
.unwrap_or(Err(Error::NotNullable.into()))
{
Ok(offers) => offers,
Err(e) => {
warn!(
"Cannot add suite {}, invalid offers. error: {}",
test_suite_url, e
);
control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
break;
}
};
let test_collection = match realm_options.test_collection {
Some(test_collection) => test_collection,
None => {
warn!("Cannot add suite {}, missing test collection.", test_suite_url);
control_handle.shutdown_with_epitaph(zx::Status::INVALID_ARGS);
break;
}
};
Some(SuiteRealm { realm_proxy, offers, test_collection })
} else {
None
};
let controller = match controller.into_stream() {
Ok(c) => c,
Err(e) => {
warn!("Invalid builder controller. Closing connection. error: {}", e);
control_handle.shutdown_with_epitaph(zx::Status::BAD_HANDLE);
break;
}
};
let suite = Suite {
realm: realm.into(),
test_url: test_suite_url,
options: ftest_manager::RunOptions {
run_disabled_tests: options.run_disabled_tests,
parallel: options.max_concurrent_test_case_runs,
arguments: options.arguments,
timeout: options.timeout,
case_filters_to_run: options.test_case_filters,
log_iterator: options.logs_iterator_type.map(convert),
log_interest: options.log_interest,
..Default::default()
},
controller,
resolver: resolver.clone(),
above_root_capabilities_for_test: above_root_capabilities_for_test.clone(),
facets: facet::ResolveStatus::Unresolved,
};
let diagnostics = root_diagnostics.child();
suite.run(diagnostics, options.accumulate_debug_data.unwrap_or(false)).await;
}
ftest_manager::SuiteRunnerRequest::_UnknownMethod {
ordinal, control_handle, ..
} => {
warn!("Unknown run builder request received: {}, closing connection", ordinal);
control_handle.shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
break;
}
}
}
Ok(())
}
fn convert(item: ftest_manager::LogsIteratorType) -> ftest_manager::LogsIteratorOption {
match item {
ftest_manager::LogsIteratorType::Batch => ftest_manager::LogsIteratorOption::BatchIterator,
ftest_manager::LogsIteratorType::Socket => {
ftest_manager::LogsIteratorOption::SocketBatchIterator
}
_ => todo!(),
}
}