blob: a75822a4e20a44620c4c47bbabe5362d2a822841 [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 {
crate::{
component_instance::{ComponentInstanceForAnalyzer, TopInstanceForAnalyzer},
match_absolute_component_urls,
node_path::NodePath,
route::{RouteMap, RouteSegment, VerifyRouteResult},
PkgUrlMatch,
},
anyhow::{anyhow, Context, Result},
cm_moniker::InstancedRelativeMoniker,
cm_rust::{
CapabilityDecl, CapabilityPath, CapabilityTypeName, ComponentDecl, ExposeDecl,
ExposeDeclCommon, ProgramDecl, ResolverRegistration, UseDecl, UseEventStreamDecl,
UseStorageDecl,
},
fidl::prelude::*,
fidl_fuchsia_sys2 as fsys,
fuchsia_url::AbsoluteComponentUrl,
fuchsia_zircon_status as zx_status,
futures::FutureExt,
moniker::{AbsoluteMoniker, AbsoluteMonikerBase, ChildMoniker},
routing::{
capability_source::{
CapabilitySourceInterface, ComponentCapability, StorageCapabilitySource,
},
component_id_index::ComponentIdIndex,
component_instance::{
ComponentInstanceInterface, ExtendedInstanceInterface, TopInstanceInterface,
},
config::RuntimeConfig,
environment::{
component_has_relative_url, find_first_absolute_ancestor_url, RunnerRegistry,
},
error::{AvailabilityRoutingError, ComponentInstanceError, RoutingError},
policy::GlobalPolicyChecker,
route_capability, route_event_stream_capability, route_storage_and_backing_directory,
DebugRouteMapper, RouteRequest, RouteSource,
},
serde::{Deserialize, Serialize},
std::{
collections::{HashMap, HashSet},
sync::Arc,
},
thiserror::Error,
url::Url,
};
/// Errors that may occur when building a `ComponentModelForAnalyzer` from
/// a set of component manifests.
#[derive(Clone, Debug, Deserialize, Error, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BuildAnalyzerModelError {
#[error("no component declaration found for url `{0}` requested by node `{1}`")]
ComponentDeclNotFound(String, String),
#[error("invalid child declaration containing url `{0}` at node `{1}`")]
InvalidChildDecl(String, String),
#[error("no node found with path `{0}`")]
ComponentNodeNotFound(String),
#[error("environment `{0}` requested by child `{1}` not found at node `{2}`")]
EnvironmentNotFound(String, String, String),
#[error("multiple resolvers found for scheme `{0}`")]
DuplicateResolverScheme(String),
#[error("malformed url {0} for component instance {1}")]
MalformedUrl(String, String),
#[error("dynamic component with url {0} an invalid moniker")]
DynamicComponentInvalidMoniker(String),
#[error("dynamic component at {0} with url {1} is not part of a collection")]
DynamicComponentWithoutCollection(String, String),
}
/// Errors that a `ComponentModelForAnalyzer` may detect in the component graph.
#[derive(Clone, Debug, Error, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum AnalyzerModelError {
#[error("the source instance `{0}` is not executable")]
SourceInstanceNotExecutable(String),
#[error("the capability `{0}` is not a valid source for the capability `{1}`")]
InvalidSourceCapability(String, String),
#[error("uses Event capability `{0}` without using the EventSource protocol")]
MissingEventSourceProtocol(String),
#[error("no resolver found in environment for scheme `{0}`")]
MissingResolverForScheme(String),
#[error(transparent)]
ComponentInstanceError(#[from] ComponentInstanceError),
#[error(transparent)]
RoutingError(#[from] RoutingError),
}
impl AnalyzerModelError {
pub fn as_zx_status(&self) -> zx_status::Status {
match self {
Self::SourceInstanceNotExecutable(_) => zx_status::Status::UNAVAILABLE,
Self::InvalidSourceCapability(_, _) => zx_status::Status::UNAVAILABLE,
Self::MissingEventSourceProtocol(_) => zx_status::Status::UNAVAILABLE,
Self::MissingResolverForScheme(_) => zx_status::Status::NOT_FOUND,
Self::ComponentInstanceError(err) => err.as_zx_status(),
Self::RoutingError(err) => err.as_zx_status(),
}
}
}
/// Builds a `ComponentModelForAnalyzer` from a set of component manifests.
pub struct ModelBuilderForAnalyzer {
default_root_url: Url,
}
/// The type returned by `ModelBuilderForAnalyzer::build()`. May contain some
/// errors even if `model` is `Some`.
pub struct BuildModelResult {
pub model: Option<Arc<ComponentModelForAnalyzer>>,
pub errors: Vec<anyhow::Error>,
}
impl BuildModelResult {
fn new() -> Self {
Self { model: None, errors: Vec::new() }
}
}
impl ModelBuilderForAnalyzer {
pub fn new(default_root_url: Url) -> Self {
Self { default_root_url }
}
fn load_dynamic_components(
input: HashMap<NodePath, (AbsoluteComponentUrl, Option<String>)>,
) -> (HashMap<AbsoluteMoniker, Vec<Child>>, Vec<anyhow::Error>) {
let mut errors: Vec<anyhow::Error> = vec![];
let mut dynamic_components: HashMap<AbsoluteMoniker, Vec<Child>> = HashMap::new();
for (node_path, (url, environment)) in input.into_iter() {
let mut moniker_vec = node_path.as_vec();
let child_moniker_str = moniker_vec.pop();
if child_moniker_str.is_none() {
errors.push(
BuildAnalyzerModelError::DynamicComponentInvalidMoniker(url.to_string()).into(),
);
continue;
}
let child_moniker_str = child_moniker_str.unwrap();
let abs_moniker: AbsoluteMoniker = moniker_vec.into();
let child_moniker: ChildMoniker = child_moniker_str.into();
if child_moniker.collection.is_none() {
errors.push(
BuildAnalyzerModelError::DynamicComponentWithoutCollection(
node_path.to_string(),
url.to_string(),
)
.into(),
);
continue;
}
let children = dynamic_components.entry(abs_moniker.clone()).or_insert_with(|| vec![]);
match Url::parse(&url.to_string()) {
Ok(url) => {
children.push(Child { child_moniker, url, environment });
}
Err(_) => {
let node_path: NodePath = abs_moniker.into();
errors.push(
BuildAnalyzerModelError::MalformedUrl(
url.to_string(),
node_path.to_string(),
)
.into(),
);
}
}
}
(dynamic_components, errors)
}
pub fn build(
self,
decls_by_url: HashMap<Url, ComponentDecl>,
runtime_config: Arc<RuntimeConfig>,
component_id_index: Arc<ComponentIdIndex>,
runner_registry: RunnerRegistry,
) -> BuildModelResult {
self.build_with_dynamic_components(
HashMap::new(),
decls_by_url,
runtime_config,
component_id_index,
runner_registry,
)
}
pub fn build_with_dynamic_components(
self,
dynamic_components: HashMap<NodePath, (AbsoluteComponentUrl, Option<String>)>,
decls_by_url: HashMap<Url, ComponentDecl>,
runtime_config: Arc<RuntimeConfig>,
component_id_index: Arc<ComponentIdIndex>,
runner_registry: RunnerRegistry,
) -> BuildModelResult {
let mut result = BuildModelResult::new();
let (dynamic_components, mut dynamic_component_errors) =
Self::load_dynamic_components(dynamic_components);
result.errors.append(&mut dynamic_component_errors);
// Initialize the model with an empty `instances` map.
let mut model = ComponentModelForAnalyzer {
top_instance: TopInstanceForAnalyzer::new(
runtime_config.namespace_capabilities.clone(),
runtime_config.builtin_capabilities.clone(),
),
instances: HashMap::new(),
policy_checker: GlobalPolicyChecker::new(Arc::clone(&runtime_config)),
component_id_index,
};
let root_url = match &runtime_config.root_component_url {
None => Some(self.default_root_url.clone()),
Some(url) => match Url::parse(url.as_str()) {
Ok(url) => Some(url),
Err(err) => {
result.errors.push(anyhow!(
r#"Failed to parse root cm URL "{}" as generic URL: {:?}"#,
url.as_str(),
err
));
None
}
},
};
// If `root_url` matches a `ComponentDecl` in `decls_by_url`, construct the root
// instance and then recursively add child instances to the model.
if let Some(root_url) = root_url {
match Self::get_decl_by_url(&decls_by_url, &root_url) {
Err(err) => {
result
.errors
.push(err.context("Failed to parse root URL as fuchsia package URL"));
}
Ok(None) => {
result
.errors
.push(anyhow!("Failed to locate root component with URL: {}", &root_url));
}
Ok(Some(root_decl)) => {
let root_instance = ComponentInstanceForAnalyzer::new_root(
root_decl.clone(),
root_url.to_string(),
Arc::clone(&model.top_instance),
Arc::clone(&runtime_config),
model.policy_checker.clone(),
Arc::clone(&model.component_id_index),
runner_registry,
false,
);
Self::add_descendants(
&root_instance,
&decls_by_url,
&dynamic_components,
&mut model,
&mut result,
);
model
.instances
.insert(NodePath::from(root_instance.abs_moniker().clone()), root_instance);
result.model = Some(Arc::new(model));
}
}
}
result
}
// Adds all descendants of `instance` to `model`, also inserting each new instance
// in the `children` map of its parent, including children denoted in
// `dynamic_components`.
fn add_descendants(
instance: &Arc<ComponentInstanceForAnalyzer>,
decls_by_url: &HashMap<Url, ComponentDecl>,
dynamic_components: &HashMap<AbsoluteMoniker, Vec<Child>>,
model: &mut ComponentModelForAnalyzer,
result: &mut BuildModelResult,
) {
let mut children = vec![];
for child_decl in instance.decl.children.iter() {
match Self::get_absolute_child_url(&child_decl.url, instance) {
Ok(url) => {
children.push(Child {
child_moniker: ChildMoniker::new(child_decl.name.clone(), None),
url,
environment: child_decl.environment.clone(),
});
}
Err(err) => {
result.errors.push(anyhow!(err));
}
}
}
if let Some(dynamic_children) = dynamic_components.get(instance.abs_moniker()) {
children.append(
&mut dynamic_children
.into_iter()
.map(|dynamic_child| dynamic_child.clone())
.collect(),
);
}
for child in children.iter() {
match Self::get_absolute_child_url(&child.url.to_string(), instance) {
Ok(url) => {
let absolute_url = url;
if child.child_moniker.name.is_empty() {
result.errors.push(anyhow!(BuildAnalyzerModelError::InvalidChildDecl(
absolute_url.to_string(),
NodePath::from(instance.abs_moniker().clone()).to_string(),
)));
continue;
}
match Self::get_decl_by_url(decls_by_url, &absolute_url)
.context("Failed to parse absolute child URL")
{
Err(err) => {
result.errors.push(err);
}
Ok(Some(child_component_decl)) => {
match ComponentInstanceForAnalyzer::new_for_child(
child,
absolute_url.to_string(),
child_component_decl.clone(),
Arc::clone(instance),
model.policy_checker.clone(),
Arc::clone(&model.component_id_index),
) {
Ok(child_instance) => {
Self::add_descendants(
&child_instance,
decls_by_url,
dynamic_components,
model,
result,
);
instance.add_child(
child.child_moniker.clone(),
Arc::clone(&child_instance),
);
model.instances.insert(
NodePath::from(child_instance.abs_moniker().clone()),
child_instance,
);
}
Err(err) => {
result.errors.push(anyhow!(err));
}
}
}
Ok(None) => {
result.errors.push(anyhow!(
BuildAnalyzerModelError::ComponentDeclNotFound(
absolute_url.to_string(),
NodePath::from(instance.abs_moniker().clone()).to_string(),
)
));
}
}
}
Err(err) => {
result.errors.push(anyhow!(err));
}
}
}
}
// Given a component instance and the url `child_url` of a child of that instance,
// returns an absolute url for the child.
fn get_absolute_child_url(
child_url: &str,
instance: &Arc<ComponentInstanceForAnalyzer>,
) -> Result<Url, BuildAnalyzerModelError> {
let err = BuildAnalyzerModelError::MalformedUrl(
instance.url().to_string(),
instance.node_path().to_string(),
);
match Url::parse(child_url) {
Ok(url) => Ok(url),
Err(url::ParseError::RelativeUrlWithoutBase) => {
let absolute_prefix = match component_has_relative_url(instance) {
true => find_first_absolute_ancestor_url(instance).map_err(|_| err),
false => Url::parse(instance.url()).map_err(|_| err),
}?;
Ok(absolute_prefix
.join(child_url)
.expect("failed to join child URL to absolute prefix"))
}
_ => Err(err),
}
}
fn get_decl_by_url<'a>(
decls_by_url: &'a HashMap<Url, ComponentDecl>,
url: &Url,
) -> Result<Option<&'a ComponentDecl>> {
// Non-`fuchsia-pkg` URLs are not matched with nuance: they must precisely match an entry in
// `decls_by_url`.
if url.scheme() != "fuchsia-pkg" {
return Ok(decls_by_url.get(url));
}
let fuchsia_component_url = AbsoluteComponentUrl::parse(url.as_str())
.context("Failed to parse component fuchsia-pkg URL as absolute package URL")?;
// Gather both strong and weak URL matches against `fuchsia_component_url`.
let decl_url_matches = decls_by_url
.keys()
.filter_map(|decl_url| {
if decl_url.scheme() != "fuchsia-pkg" {
None
} else if let Ok(decl_fuchsia_pkg_url) =
AbsoluteComponentUrl::parse(decl_url.as_str())
{
match match_absolute_component_urls(
&decl_fuchsia_pkg_url,
&fuchsia_component_url,
) {
PkgUrlMatch::NoMatch => None,
pkg_url_match => Some((decl_url, pkg_url_match)),
}
} else {
None
}
})
.collect::<Vec<(&Url, PkgUrlMatch)>>();
// Return best match. Emit warning or error when multiple matches are found.
if decl_url_matches.len() == 0 {
return Ok(None);
} else if decl_url_matches.len() == 1 {
if decl_url_matches[0].1 == PkgUrlMatch::WeakMatch {
log::warn!("Weak component URL match: {} matches {}", url, decl_url_matches[0].0);
}
return Ok(decls_by_url.get(decl_url_matches[0].0));
} else {
let strong_decl_url_matches = decl_url_matches
.iter()
.filter_map(|(url, url_match)| match url_match {
PkgUrlMatch::StrongMatch => Some(*url),
_ => None,
})
.collect::<Vec<&Url>>();
if strong_decl_url_matches.len() == 0 {
log::warn!(
"Multiple weak component URL matches for {}; matching to first: {}",
url,
decl_url_matches[0].0
);
return Ok(decls_by_url.get(decl_url_matches[0].0));
} else {
if strong_decl_url_matches.len() > 1 {
log::error!(
"Multiple strong package URL matches for {}; matching to first: {}",
url,
strong_decl_url_matches[0]
);
}
return Ok(decls_by_url.get(strong_decl_url_matches[0]));
}
}
}
}
/// `ComponentModelForAnalyzer` owns a representation of the v2 component graph and
/// supports lookup of component instances by `NodePath`.
#[derive(Default)]
pub struct ComponentModelForAnalyzer {
top_instance: Arc<TopInstanceForAnalyzer>,
instances: HashMap<NodePath, Arc<ComponentInstanceForAnalyzer>>,
policy_checker: GlobalPolicyChecker,
component_id_index: Arc<ComponentIdIndex>,
}
impl ComponentModelForAnalyzer {
/// Returns the number of component instances in the model, not counting the top instance.
pub fn len(&self) -> usize {
self.instances.len()
}
pub fn get_root_instance(
self: &Arc<Self>,
) -> Result<Arc<ComponentInstanceForAnalyzer>, ComponentInstanceError> {
self.get_instance(&NodePath::absolute_from_vec(vec![]))
}
/// Returns the component instance corresponding to `id` if it is present in the model, or an
/// `InstanceNotFound` error if not.
pub fn get_instance(
self: &Arc<Self>,
id: &NodePath,
) -> Result<Arc<ComponentInstanceForAnalyzer>, ComponentInstanceError> {
match self.instances.get(id) {
Some(instance) => Ok(Arc::clone(instance)),
None => Err(ComponentInstanceError::instance_not_found(
AbsoluteMoniker::parse_str(&id.to_string()).unwrap(),
)),
}
}
/// Checks the routing for all capabilities of the specified types that are `used` by `target`.
pub fn check_routes_for_instance(
self: &Arc<Self>,
target: &Arc<ComponentInstanceForAnalyzer>,
capability_types: &HashSet<CapabilityTypeName>,
) -> HashMap<CapabilityTypeName, Vec<VerifyRouteResult>> {
let mut results = HashMap::new();
for capability_type in capability_types.iter() {
results.insert(capability_type.clone(), vec![]);
}
for use_decl in target.decl.uses.iter().filter(|&u| capability_types.contains(&u.into())) {
let type_results = results
.get_mut(&CapabilityTypeName::from(use_decl))
.expect("expected results for capability type");
for result in self.check_use_capability(use_decl, &target) {
type_results.push(result);
}
}
for expose_decl in
target.decl.exposes.iter().filter(|&e| capability_types.contains(&e.into()))
{
let type_results = results
.get_mut(&CapabilityTypeName::from(expose_decl))
.expect("expected results for capability type");
if let Some(result) = self.check_use_exposed_capability(expose_decl, &target) {
type_results.push(result);
}
}
if capability_types.contains(&CapabilityTypeName::Runner) {
if let Some(ref program) = target.decl.program {
let type_results = results
.get_mut(&CapabilityTypeName::Runner)
.expect("expected results for capability type");
if let Some(result) = self.check_program_runner(program, &target) {
type_results.push(result);
}
}
}
if capability_types.contains(&CapabilityTypeName::Resolver) {
let type_results = results
.get_mut(&CapabilityTypeName::Resolver)
.expect("expected results for capability type");
type_results.push(self.check_resolver(&target));
}
results
}
/// Given a `UseDecl` for a capability at an instance `target`, first routes the capability
/// to its source and then validates the source.
pub fn check_use_capability(
self: &Arc<Self>,
use_decl: &UseDecl,
target: &Arc<ComponentInstanceForAnalyzer>,
) -> Vec<VerifyRouteResult> {
let mut results = Vec::new();
let route_result = match use_decl.clone() {
UseDecl::Directory(use_directory_decl) => {
let capability = use_directory_decl.source_name.clone();
match Self::route_capability_sync(
RouteRequest::UseDirectory(use_directory_decl),
target,
) {
Ok((source, route)) => (Ok((source, vec![route])), capability),
// Ignore any route that failed due to a void offer to a target with an
// optional dependency on the capability.
Err(RoutingError::AvailabilityRoutingError(
AvailabilityRoutingError::OfferFromVoidToOptionalTarget,
)) => return vec![],
Err(err) => (Err(err.into()), capability),
}
}
UseDecl::Event(use_event_decl) => {
let capability = use_event_decl.target_name.clone();
match self.uses_event_source_protocol(&target.decl) {
true => {
match Self::route_capability_sync(
RouteRequest::UseEvent(use_event_decl),
target,
) {
Ok((source, route)) => (Ok((source, vec![route])), capability),
// Ignore any route that failed due to a void offer to a target with an
// optional dependency on the capability.
Err(RoutingError::AvailabilityRoutingError(
AvailabilityRoutingError::OfferFromVoidToOptionalTarget,
)) => return vec![],
Err(err) => (Err(err.into()), capability),
}
}
false => (
Err(AnalyzerModelError::MissingEventSourceProtocol(capability.to_string())),
capability,
),
}
}
UseDecl::Protocol(use_protocol_decl) => {
let capability = use_protocol_decl.source_name.clone();
match Self::route_capability_sync(
RouteRequest::UseProtocol(use_protocol_decl),
target,
) {
Ok((source, route)) => (Ok((source, vec![route])), capability),
// Ignore any route that failed due to a void offer to a target with an
// optional dependency on the capability.
Err(RoutingError::AvailabilityRoutingError(
AvailabilityRoutingError::OfferFromVoidToOptionalTarget,
)) => return vec![],
Err(err) => (Err(err.into()), capability),
}
}
UseDecl::Service(use_service_decl) => {
let capability = use_service_decl.source_name.clone();
match Self::route_capability_sync(
RouteRequest::UseService(use_service_decl.clone()),
target,
) {
Ok((source, route)) => (Ok((source, vec![route])), capability),
// Ignore any route that failed due to a void offer to a target with an
// optional dependency on the capability.
Err(RoutingError::AvailabilityRoutingError(
AvailabilityRoutingError::OfferFromVoidToOptionalTarget,
)) => return vec![],
Err(err) => (Err(err.into()), capability),
}
}
UseDecl::Storage(use_storage_decl) => {
let capability = use_storage_decl.source_name.clone();
match Self::route_storage_and_backing_directory_sync(use_storage_decl, target) {
Ok((storage_source, _relative_moniker, storage_route, dir_route)) => (
Ok((
RouteSource::StorageBackingDirectory(storage_source),
vec![storage_route, dir_route],
)),
capability,
),
// Ignore any route that failed due to a void offer to a target with an
// optional dependency on the capability.
Err(RoutingError::AvailabilityRoutingError(
AvailabilityRoutingError::OfferFromVoidToOptionalTarget,
)) => return vec![],
Err(err) => (Err(err.into()), capability),
}
}
UseDecl::EventStream(use_event_stream_decl) => {
let capability = use_event_stream_decl.source_name.clone();
match Self::route_capability_sync(
RouteRequest::UseEventStream(use_event_stream_decl),
target,
) {
Ok((source, route)) => (Ok((source, vec![route])), capability),
Err(err) => (Err(err.into()), capability),
}
}
_ => unimplemented![],
};
match route_result {
(Ok((source, routes)), capability) => match self.check_use_source(&source) {
Ok(()) => {
for route in routes.into_iter() {
results.push(VerifyRouteResult {
using_node: target.node_path(),
capability: capability.clone(),
result: Ok(route.into()),
});
}
}
Err(err) => {
results.push(VerifyRouteResult {
using_node: target.node_path(),
capability: capability.clone(),
result: Err(err.into()),
});
}
},
(Err(err), capability) => results.push(VerifyRouteResult {
using_node: target.node_path(),
capability: capability.clone(),
result: Err(err.into()),
}),
}
results
}
/// Given a `ExposeDecl` for a capability at an instance `target`, checks whether the capability
/// can be used from an expose declaration. If so, routes the capability to its source and then
/// validates the source.
pub fn check_use_exposed_capability(
self: &Arc<Self>,
expose_decl: &ExposeDecl,
target: &Arc<ComponentInstanceForAnalyzer>,
) -> Option<VerifyRouteResult> {
match self.request_from_expose(expose_decl) {
Some(request) => {
let result = match Self::route_capability_sync(request, target) {
Ok((source, route)) => match self.check_use_source(&source) {
Ok(()) => Ok(route.into()),
Err(err) => Err(err.into()),
},
Err(err) => Err(AnalyzerModelError::from(err).into()),
};
Some(VerifyRouteResult {
using_node: target.node_path(),
capability: expose_decl.target_name().clone(),
result,
})
}
None => None,
}
}
/// Given a `ProgramDecl` for a component instance, checks whether the specified runner has
/// a valid capability route.
pub fn check_program_runner(
self: &Arc<Self>,
program_decl: &ProgramDecl,
target: &Arc<ComponentInstanceForAnalyzer>,
) -> Option<VerifyRouteResult> {
match program_decl.runner {
Some(ref runner) => {
let mut route = RouteMap::from_segments(vec![RouteSegment::RequireRunner {
node_path: target.node_path(),
runner: runner.clone(),
}]);
match Self::route_capability_sync(RouteRequest::Runner(runner.clone()), target) {
Ok((_source, mut segments)) => {
route.append(&mut segments);
Some(VerifyRouteResult {
using_node: target.node_path(),
capability: runner.clone(),
result: Ok(route.into()),
})
}
Err(err) => Some(VerifyRouteResult {
using_node: target.node_path(),
capability: runner.clone(),
result: Err(AnalyzerModelError::from(err).into()),
}),
}
}
None => None,
}
}
/// Given a component instance, extracts the URL scheme for that instance and looks for a
/// resolver for that scheme in the instance's environment, recording an error if none
/// is found. If a resolver is found, checks that it has a valid capability route.
pub fn check_resolver(
self: &Arc<Self>,
target: &Arc<ComponentInstanceForAnalyzer>,
) -> VerifyRouteResult {
let url = Url::parse(target.url()).expect("failed to parse target URL");
let scheme = url.scheme();
let mut route = vec![RouteSegment::RequireResolver {
node_path: target.node_path(),
scheme: scheme.to_string(),
}];
let check_route = match target.environment.get_registered_resolver(scheme) {
Ok(Some((ExtendedInstanceInterface::Component(instance), resolver))) => {
match Self::route_capability_sync(
RouteRequest::Resolver(resolver.clone()),
&instance,
) {
Ok((_source, route)) => VerifyRouteResult {
using_node: target.node_path(),
capability: resolver.resolver,
result: Ok(route.into()),
},
Err(err) => VerifyRouteResult {
using_node: target.node_path(),
capability: resolver.resolver,
result: Err(AnalyzerModelError::from(err).into()),
},
}
}
Ok(Some((ExtendedInstanceInterface::AboveRoot(_), resolver))) => {
match self.get_builtin_resolver_decl(&resolver) {
Ok(decl) => {
let mut route = RouteMap::new();
route.push(RouteSegment::ProvideAsBuiltin { capability: decl });
VerifyRouteResult {
using_node: target.node_path(),
capability: resolver.resolver,
result: Ok(route.into()),
}
}
Err(err) => VerifyRouteResult {
using_node: target.node_path(),
capability: resolver.resolver,
result: Err(err.into()),
},
}
}
Ok(None) => VerifyRouteResult {
using_node: target.node_path(),
capability: "".into(),
result: Err(AnalyzerModelError::MissingResolverForScheme(scheme.to_string()).into()),
},
Err(err) => VerifyRouteResult {
using_node: target.node_path(),
capability: "".into(),
result: Err(AnalyzerModelError::from(err).into()),
},
};
let check_result = match check_route.result {
Ok(mut segments) => {
route.append(&mut segments);
Ok(route.into())
}
Err(err) => Err(err.into()),
};
VerifyRouteResult {
using_node: target.node_path(),
capability: check_route.capability,
result: check_result,
}
}
// Retrieves the `CapabilityDecl` for a built-in resolver from its registration, or an
// error if the resolver is not provided as a built-in capability.
fn get_builtin_resolver_decl(
&self,
resolver: &ResolverRegistration,
) -> Result<CapabilityDecl, AnalyzerModelError> {
match self.top_instance.builtin_capabilities().iter().find(|&decl| {
if let CapabilityDecl::Resolver(resolver_decl) = decl {
resolver_decl.name == resolver.resolver
} else {
false
}
}) {
Some(decl) => Ok(decl.clone()),
None => Err(AnalyzerModelError::RoutingError(
RoutingError::use_from_component_manager_not_found(resolver.resolver.to_string()),
)),
}
}
// Checks properties of a capability source that are necessary to use the capability
// and that are possible to verify statically.
fn check_use_source(
&self,
route_source: &RouteSource<ComponentInstanceForAnalyzer>,
) -> Result<(), AnalyzerModelError> {
match route_source {
RouteSource::Directory(source, _) => self.check_directory_source(source),
RouteSource::Event(_) => Ok(()),
RouteSource::EventStream(source) => self.check_protocol_source(source),
RouteSource::Protocol(source) => self.check_protocol_source(source),
RouteSource::Service(source) => self.check_service_source(source),
RouteSource::StorageBackingDirectory(source) => self.check_storage_source(source),
_ => unimplemented![],
}
}
// If the source of a directory capability is a component instance, checks that that
// instance is executable.
fn check_directory_source(
&self,
source: &CapabilitySourceInterface<ComponentInstanceForAnalyzer>,
) -> Result<(), AnalyzerModelError> {
match source {
CapabilitySourceInterface::Component { component: weak, .. } => {
self.check_executable(&weak.upgrade()?)
}
CapabilitySourceInterface::Namespace { .. } => Ok(()),
CapabilitySourceInterface::Builtin { .. } => Ok(()),
CapabilitySourceInterface::Framework { .. } => Ok(()),
_ => unimplemented![],
}
}
// If the source of a protocol capability is a component instance, checks that that
// instance is executable.
//
// If the source is a capability, checks that the protocol is the `StorageAdmin`
// protocol and that the source is a valid storage capability.
fn check_protocol_source(
&self,
source: &CapabilitySourceInterface<ComponentInstanceForAnalyzer>,
) -> Result<(), AnalyzerModelError> {
match source {
CapabilitySourceInterface::Component { component: weak, .. } => {
self.check_executable(&weak.upgrade()?)
}
CapabilitySourceInterface::Namespace { .. } => Ok(()),
CapabilitySourceInterface::Capability { source_capability, component: weak } => {
self.check_protocol_capability_source(&weak.upgrade()?, &source_capability)
}
CapabilitySourceInterface::Builtin { .. } => Ok(()),
CapabilitySourceInterface::Framework { .. } => Ok(()),
_ => unimplemented![],
}
}
// A helper function validating a source of type `Capability` for a protocol capability.
// If the protocol is the `StorageAdmin` protocol, then it should have a valid storage
// source.
fn check_protocol_capability_source(
&self,
source_component: &Arc<ComponentInstanceForAnalyzer>,
source_capability: &ComponentCapability,
) -> Result<(), AnalyzerModelError> {
let source_capability_name = source_capability
.source_capability_name()
.expect("failed to get source capability name");
match source_capability.source_name().map(|name| name.to_string()).as_deref() {
Some(fsys::StorageAdminMarker::PROTOCOL_NAME) => {
match source_component.decl.find_storage_source(source_capability_name) {
Some(_) => Ok(()),
None => Err(AnalyzerModelError::InvalidSourceCapability(
source_capability_name.to_string(),
fsys::StorageAdminMarker::PROTOCOL_NAME.to_string(),
)),
}
}
_ => Err(AnalyzerModelError::InvalidSourceCapability(
source_capability_name.to_string(),
source_capability
.source_name()
.map_or_else(|| "".to_string(), |name| name.to_string()),
)),
}
}
// If the source of a service capability is a component instance, checks that that
// instance is executable.
fn check_service_source(
&self,
source: &CapabilitySourceInterface<ComponentInstanceForAnalyzer>,
) -> Result<(), AnalyzerModelError> {
match source {
CapabilitySourceInterface::Component { component: weak, .. } => {
self.check_executable(&weak.upgrade()?)
}
CapabilitySourceInterface::Namespace { .. } => Ok(()),
_ => unimplemented![],
}
}
// If the source of a storage backing directory is a component instance, checks that that
// instance is executable.
fn check_storage_source(
&self,
source: &StorageCapabilitySource<ComponentInstanceForAnalyzer>,
) -> Result<(), AnalyzerModelError> {
if let Some(provider) = &source.storage_provider {
self.check_executable(provider)?
}
Ok(())
}
// A helper function which prepares a route request for capabilities which can be used
// from an expose declaration, and returns None if the capability type cannot be used
// from an expose.
fn request_from_expose(self: &Arc<Self>, expose_decl: &ExposeDecl) -> Option<RouteRequest> {
match expose_decl {
ExposeDecl::Directory(expose_directory_decl) => {
Some(RouteRequest::ExposeDirectory(expose_directory_decl.clone()))
}
ExposeDecl::Protocol(expose_protocol_decl) => {
Some(RouteRequest::ExposeProtocol(expose_protocol_decl.clone()))
}
ExposeDecl::Service(expose_service_decl) => {
Some(RouteRequest::ExposeService(expose_service_decl.clone()))
}
_ => None,
}
}
// A helper function checking whether a component instance is executable.
fn check_executable(
&self,
component: &Arc<ComponentInstanceForAnalyzer>,
) -> Result<(), AnalyzerModelError> {
match component.decl.program {
Some(_) => Ok(()),
None => Err(AnalyzerModelError::SourceInstanceNotExecutable(
component.abs_moniker().to_string(),
)),
}
}
fn uses_event_source_protocol(&self, decl: &ComponentDecl) -> bool {
decl.uses.iter().any(|u| match u {
UseDecl::Protocol(p) => {
p.target_path
== CapabilityPath {
dirname: "/svc".to_string(),
basename: "fuchsia.sys2.EventSource".to_string(),
}
}
_ => false,
})
}
// Routes a capability from a `ComponentInstanceForAnalyzer` and panics if the future returned by
// `route_capability` is not ready immediately.
//
// TODO(fxbug.dev/87204): Remove this function and use `route_capability` directly when Scrutiny's
// `DataController`s allow async function calls.
pub fn route_capability_sync(
request: RouteRequest,
target: &Arc<ComponentInstanceForAnalyzer>,
) -> Result<
(
RouteSource<ComponentInstanceForAnalyzer>,
<<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap,
),
RoutingError>
{
route_capability(request, target).now_or_never().expect("future was not ready immediately")
}
pub fn route_event_stream_sync(
request: UseEventStreamDecl,
target: &Arc<ComponentInstanceForAnalyzer>,
map: &mut Vec<Arc<ComponentInstanceForAnalyzer>>,
) -> Result<
(
RouteSource<ComponentInstanceForAnalyzer>,
<<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap,
),
RoutingError>
{
route_event_stream_capability(request, target, map)
.now_or_never()
.expect("future was not ready immediately")
}
// Routes a storage capability and its backing directory from a `ComponentInstanceForAnalyzer` and
// panics if the future returned by `route_storage_and_backing_directory` is not ready immediately.
//
// TODO(fxbug.dev/87204): Remove this function and use `route_capability` directly when Scrutiny's
// `DataController`s allow async function calls.
fn route_storage_and_backing_directory_sync(
use_decl: UseStorageDecl,
target: &Arc<ComponentInstanceForAnalyzer>,
) -> Result<
(
StorageCapabilitySource<ComponentInstanceForAnalyzer>,
InstancedRelativeMoniker,
<<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap,
<<ComponentInstanceForAnalyzer as ComponentInstanceInterface>::DebugRouteMapper as DebugRouteMapper>::RouteMap,
),
RoutingError>
{
route_storage_and_backing_directory(use_decl, target)
.now_or_never()
.expect("future was not ready immediately")
}
}
#[derive(Clone)]
pub struct Child {
pub child_moniker: ChildMoniker,
pub url: Url,
pub environment: Option<String>,
}
#[cfg(test)]
mod tests {
use {
super::ModelBuilderForAnalyzer,
crate::{environment::BOOT_SCHEME, node_path::NodePath, ComponentModelForAnalyzer},
anyhow::Result,
cm_moniker::InstancedAbsoluteMoniker,
cm_rust::{
Availability, CapabilityName, CapabilityPath, ComponentDecl, DependencyType,
RegistrationSource, ResolverRegistration, RunnerRegistration, UseProtocolDecl,
UseSource, UseStorageDecl,
},
cm_rust_testing::{ChildDeclBuilder, ComponentDeclBuilder, EnvironmentDeclBuilder},
fidl_fuchsia_component_decl as fdecl,
fidl_fuchsia_component_internal as component_internal,
maplit::hashmap,
moniker::{AbsoluteMoniker, AbsoluteMonikerBase, ChildMoniker},
routing::{
component_id_index::ComponentIdIndex,
component_instance::{
ComponentInstanceInterface, ExtendedInstanceInterface,
WeakExtendedInstanceInterface,
},
config::RuntimeConfig,
environment::{EnvironmentInterface, RunnerRegistry},
error::ComponentInstanceError,
RouteRequest,
},
std::{
collections::HashMap,
convert::{TryFrom, TryInto},
iter::FromIterator,
sync::Arc,
},
url::Url,
};
const TEST_URL_PREFIX: &str = "test:///";
fn make_test_url(component_name: &str) -> Url {
Url::parse(&format!("{}{}", TEST_URL_PREFIX, component_name)).unwrap()
}
fn make_decl_map(
components: Vec<(&'static str, ComponentDecl)>,
) -> HashMap<Url, ComponentDecl> {
HashMap::from_iter(components.into_iter().map(|(name, decl)| (make_test_url(name), decl)))
}
// Builds a model with structure `root -- child`, retrieves each of the 2 resulting component
// instances, and tests their public methods.
#[fuchsia::test]
fn build_model() -> Result<()> {
let components = vec![
("root", ComponentDeclBuilder::new().add_lazy_child("child").build()),
("child", ComponentDeclBuilder::new().build()),
];
let config = Arc::new(RuntimeConfig::default());
let url = make_test_url("root");
let build_model_result = ModelBuilderForAnalyzer::new(url).build(
make_decl_map(components),
config,
Arc::new(ComponentIdIndex::default()),
RunnerRegistry::default(),
);
assert_eq!(build_model_result.errors.len(), 0);
assert!(build_model_result.model.is_some());
let model = build_model_result.model.unwrap();
assert_eq!(model.len(), 2);
let root_instance =
model.get_instance(&NodePath::absolute_from_vec(vec![])).expect("root instance");
let child_instance = model
.get_instance(&NodePath::absolute_from_vec(vec!["child"]))
.expect("child instance");
let other_id = NodePath::absolute_from_vec(vec!["other"]);
let get_other_result = model.get_instance(&other_id);
assert_eq!(
get_other_result.err().unwrap().to_string(),
ComponentInstanceError::instance_not_found(
AbsoluteMoniker::parse_str(&other_id.to_string()).unwrap()
)
.to_string()
);
// Include tests for `.instanced_moniker()` alongside `.abs_moniker()`
// until`.instanced_moniker()` is removed from the public API.
assert_eq!(root_instance.abs_moniker(), &AbsoluteMoniker::root());
assert_eq!(root_instance.instanced_moniker(), &InstancedAbsoluteMoniker::root());
assert_eq!(child_instance.abs_moniker(), &AbsoluteMoniker::parse_str("/child").unwrap());
assert_eq!(
child_instance.instanced_moniker(),
&InstancedAbsoluteMoniker::parse_str("/child:0").unwrap()
);
match root_instance.try_get_parent()? {
ExtendedInstanceInterface::AboveRoot(_) => {}
_ => panic!("root instance's parent should be `AboveRoot`"),
}
match child_instance.try_get_parent()? {
ExtendedInstanceInterface::Component(component) => {
assert_eq!(component.abs_moniker(), root_instance.abs_moniker());
assert_eq!(component.instanced_moniker(), root_instance.instanced_moniker())
}
_ => panic!("child instance's parent should be root component"),
}
let get_child = root_instance
.resolve()
.map(|locked| locked.get_child(&ChildMoniker::new("child".to_string(), None)))?;
assert!(get_child.is_some());
assert_eq!(get_child.as_ref().unwrap().abs_moniker(), child_instance.abs_moniker());
assert_eq!(get_child.unwrap().instanced_moniker(), child_instance.instanced_moniker());
let root_environment = root_instance.environment();
let child_environment = child_instance.environment();
assert_eq!(root_environment.name(), None);
match root_environment.parent() {
WeakExtendedInstanceInterface::AboveRoot(_) => {}
_ => panic!("root environment's parent should be `AboveRoot`"),
}
assert_eq!(child_environment.name(), None);
match child_environment.parent() {
WeakExtendedInstanceInterface::Component(component) => {
assert_eq!(component.upgrade()?.abs_moniker(), root_instance.abs_moniker());
assert_eq!(
component.upgrade()?.instanced_moniker(),
root_instance.instanced_moniker()
)
}
_ => panic!("child environment's parent should be root component"),
}
root_instance.try_get_policy_checker()?;
root_instance.try_get_component_id_index()?;
child_instance.try_get_policy_checker()?;
child_instance.try_get_component_id_index()?;
assert!(root_instance.resolve().is_ok());
assert!(child_instance.resolve().is_ok());
Ok(())
}
// Builds a model with structure `root -- child` where the child's URL is expressed in
// the root manifest as a relative URL.
#[fuchsia::test]
fn build_model_with_relative_url() {
let root_decl = ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new().name("child").url("#child").build())
.build();
let child_decl = ComponentDeclBuilder::new().build();
let root_url = make_test_url("root");
let absolute_child_url = Url::parse(&format!("{}#child", root_url)).unwrap();
let mut decls_by_url = HashMap::new();
decls_by_url.insert(root_url.clone(), root_decl);
decls_by_url.insert(absolute_child_url.clone(), child_decl);
let config = Arc::new(RuntimeConfig::default());
let build_model_result = ModelBuilderForAnalyzer::new(root_url).build(
decls_by_url,
config,
Arc::new(ComponentIdIndex::default()),
RunnerRegistry::default(),
);
assert_eq!(build_model_result.errors.len(), 0);
assert!(build_model_result.model.is_some());
let model = build_model_result.model.unwrap();
assert_eq!(model.len(), 2);
let child_instance = model
.get_instance(&NodePath::absolute_from_vec(vec!["child"]))
.expect("child instance");
assert_eq!(child_instance.url(), absolute_child_url.as_str());
}
// Spot-checks that `route_capability` returns immediately when routing a capability from a
// `ComponentInstanceForAnalyzer`. In addition, updates to that method should
// be reviewed to make sure that this property holds; otherwise, `ComponentModelForAnalyzer`'s
// sync methods may panic.
#[fuchsia::test]
fn route_capability_is_sync() {
let components = vec![("root", ComponentDeclBuilder::new().build())];
let config = Arc::new(RuntimeConfig::default());
let url = make_test_url("root");
let build_model_result = ModelBuilderForAnalyzer::new(url).build(
make_decl_map(components),
config,
Arc::new(ComponentIdIndex::default()),
RunnerRegistry::default(),
);
assert_eq!(build_model_result.errors.len(), 0);
assert!(build_model_result.model.is_some());
let model = build_model_result.model.unwrap();
assert_eq!(model.len(), 1);
let root_instance =
model.get_instance(&NodePath::absolute_from_vec(vec![])).expect("root instance");
// Panics if the future returned by `route_capability` was not ready immediately.
// If no panic, discard the result.
let _ = ComponentModelForAnalyzer::route_capability_sync(
RouteRequest::UseProtocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "bar_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
}),
&root_instance,
);
}
// Checks that `route_capability` returns immediately when routing a capability from a
// `ComponentInstanceForAnalyzer`. In addition, updates to that method should
// be reviewed to make sure that this property holds; otherwise, `ComponentModelForAnalyzer`'s
// sync methods may panic.
#[fuchsia::test]
fn route_storage_and_backing_directory_is_sync() {
let components = vec![("root", ComponentDeclBuilder::new().build())];
let config = Arc::new(RuntimeConfig::default());
let cm_url = cm_types::Url::new(make_test_url("root").to_string())
.expect("failed to parse root component url");
let url = Url::parse(cm_url.as_str()).expect("failed to parse root component url");
let build_model_result = ModelBuilderForAnalyzer::new(url).build(
make_decl_map(components),
config,
Arc::new(ComponentIdIndex::default()),
RunnerRegistry::default(),
);
assert_eq!(build_model_result.errors.len(), 0);
assert!(build_model_result.model.is_some());
let model = build_model_result.model.unwrap();
assert_eq!(model.len(), 1);
let root_instance =
model.get_instance(&NodePath::absolute_from_vec(vec![])).expect("root instance");
// Panics if the future returned by `route_storage_and_backing_directory` was not ready immediately.
// If no panic, discard the result.
let _ = ComponentModelForAnalyzer::route_storage_and_backing_directory_sync(
UseStorageDecl {
source_name: "cache".into(),
target_path: "/storage".try_into().unwrap(),
availability: Availability::Required,
},
&root_instance,
);
}
// Builds a model with structure `root -- child` in which the child environment extends the root's.
// Checks that the child has access to the inherited runner and resolver registrations through its
// environment.
#[fuchsia::test]
fn environment_inherits() -> Result<()> {
let child_env_name = "child_env";
let child_runner_registration = RunnerRegistration {
source_name: "child_env_runner".into(),
source: RegistrationSource::Self_,
target_name: "child_env_runner".into(),
};
let child_resolver_registration = ResolverRegistration {
resolver: "child_env_resolver".into(),
source: RegistrationSource::Self_,
scheme: "child_resolver_scheme".into(),
};
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.add_child(
ChildDeclBuilder::new_lazy_child("child").environment(child_env_name),
)
.add_environment(
EnvironmentDeclBuilder::new()
.name(child_env_name)
.extends(fdecl::EnvironmentExtends::Realm)
.add_resolver(child_resolver_registration.clone())
.add_runner(child_runner_registration.clone())
.build(),
)
.build(),
),
("child", ComponentDeclBuilder::new().build()),
];
// Set up the RuntimeConfig to register the `fuchsia-boot` resolver as a built-in,
// in addition to `builtin_runner`.
let mut config = RuntimeConfig::default();
config.builtin_boot_resolver = component_internal::BuiltinBootResolver::Boot;
let builtin_runner_name = CapabilityName("builtin_runner".into());
let builtin_runner_registration = RunnerRegistration {
source_name: builtin_runner_name.clone(),
source: RegistrationSource::Self_,
target_name: builtin_runner_name.clone(),
};
let cm_url = cm_types::Url::new(make_test_url("root").to_string())
.expect("failed to parse root component url");
let url = Url::parse(cm_url.as_str()).expect("failed to parse root component url");
let build_model_result = ModelBuilderForAnalyzer::new(url).build(
make_decl_map(components),
Arc::new(config),
Arc::new(ComponentIdIndex::default()),
RunnerRegistry::from_decl(&vec![builtin_runner_registration]),
);
assert_eq!(build_model_result.errors.len(), 0);
assert!(build_model_result.model.is_some());
let model = build_model_result.model.unwrap();
assert_eq!(model.len(), 2);
let child_instance = model
.get_instance(&NodePath::absolute_from_vec(vec!["child"]))
.expect("child instance");
let get_child_runner_result = child_instance
.environment()
.get_registered_runner(&child_runner_registration.target_name)?;
assert!(get_child_runner_result.is_some());
let (child_runner_registrar, child_runner) = get_child_runner_result.unwrap();
match child_runner_registrar {
ExtendedInstanceInterface::Component(instance) => {
assert_eq!(instance.abs_moniker(), &AbsoluteMoniker::from(vec![]));
}
ExtendedInstanceInterface::AboveRoot(_) => {
panic!("expected child_env_runner to be registered by the root instance")
}
}
assert_eq!(child_runner_registration, child_runner);
let get_child_resolver_result = child_instance
.environment
.get_registered_resolver(&child_resolver_registration.scheme)?;
assert!(get_child_resolver_result.is_some());
let (child_resolver_registrar, child_resolver) = get_child_resolver_result.unwrap();
match child_resolver_registrar {
ExtendedInstanceInterface::Component(instance) => {
assert_eq!(instance.abs_moniker(), &AbsoluteMoniker::from(vec![]));
}
ExtendedInstanceInterface::AboveRoot(_) => {
panic!("expected child_env_resolver to be registered by the root instance")
}
}
assert_eq!(child_resolver_registration, child_resolver);
let get_builtin_runner_result = child_instance
.environment()
.get_registered_runner(&CapabilityName::from(builtin_runner_name))?;
assert!(get_builtin_runner_result.is_some());
let (builtin_runner_registrar, _builtin_runner) = get_builtin_runner_result.unwrap();
match builtin_runner_registrar {
ExtendedInstanceInterface::Component(_) => {
panic!("expected builtin runner to be registered above the root")
}
ExtendedInstanceInterface::AboveRoot(_) => {}
}
let get_builtin_resolver_result =
child_instance.environment.get_registered_resolver(&BOOT_SCHEME.to_string())?;
assert!(get_builtin_resolver_result.is_some());
let (builtin_resolver_registrar, _builtin_resolver) = get_builtin_resolver_result.unwrap();
match builtin_resolver_registrar {
ExtendedInstanceInterface::Component(_) => {
panic!("expected boot resolver to be registered above the root")
}
ExtendedInstanceInterface::AboveRoot(_) => {}
}
Ok(())
}
fn decl(id: &str) -> ComponentDecl {
// Identify decls by a single child named `id`.
ComponentDeclBuilder::new().add_child(ChildDeclBuilder::new_lazy_child(id)).build()
}
#[fuchsia::test]
fn get_decl_by_url_none() {
let beta_beta_urls = vec![
Url::parse("fuchsia-pkg://test.fuchsia.com/beta#beta.cm").unwrap(),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#beta.cm").unwrap(),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap(),
];
let decls_by_url_no_beta_beta = hashmap! {
Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"),
Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"),
};
for beta_beta_url in beta_beta_urls.iter() {
let result =
ModelBuilderForAnalyzer::get_decl_by_url(&decls_by_url_no_beta_beta, beta_beta_url);
assert!(result.is_ok());
assert_eq!(None, result.ok().unwrap());
}
}
#[fuchsia::test]
fn get_decl_by_url_fuchsia_boot() {
let fuchsia_boot_url = Url::parse("fuchsia-boot:///#meta/boot.cm").unwrap();
let fuchsia_boot_component = decl("boot");
let decls_by_url_with_fuchsia_boot = hashmap! {
Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"),
Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"),
fuchsia_boot_url.clone() => fuchsia_boot_component.clone(),
};
let result = ModelBuilderForAnalyzer::get_decl_by_url(
&decls_by_url_with_fuchsia_boot,
&fuchsia_boot_url,
);
assert!(result.is_ok());
assert_eq!(Some(&fuchsia_boot_component), result.ok().unwrap());
}
#[fuchsia::test]
fn get_decl_by_url_bad_url() {
let bad_url =
Url::parse("fuchsia-pkg:///test.fuchsia.com/alpha?hash=notahexvalue#meta/alpha.cm")
.unwrap();
let empty_decls_by_url = hashmap! {};
let result = ModelBuilderForAnalyzer::get_decl_by_url(&empty_decls_by_url, &bad_url);
assert!(result.is_err());
}
#[fuchsia::test]
fn get_decl_by_url_strong() {
let beta_beta_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_decl = decl("beta_beta");
let decls_by_url_with_beta_beta = hashmap! {
Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"),
Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"),
beta_beta_url.clone() => beta_beta_decl.clone(),
};
let result =
ModelBuilderForAnalyzer::get_decl_by_url(&decls_by_url_with_beta_beta, &beta_beta_url);
assert!(result.is_ok());
assert_eq!(Some(&beta_beta_decl), result.ok().unwrap());
}
#[fuchsia::test]
fn get_decl_by_url_strongest() {
let beta_beta_strong_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_strong_decl = decl("beta_beta_strong");
let beta_beta_weak_url_1 =
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#beta.cm").unwrap();
let beta_beta_weak_decl_1 = decl("beta_beta_weak_1");
let beta_beta_weak_url_2 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_weak_decl_2 = decl("beta_beta_weak_2");
let beta_beta_weak_url_3 =
Url::parse("fuchsia-pkg://test.fuchsia.com/beta#beta.cm").unwrap();
let beta_beta_weak_decl_3 = decl("beta_beta_weak_3");
let decls_by_url_with_4_beta_betas = hashmap! {
beta_beta_weak_url_1 => beta_beta_weak_decl_1,
beta_beta_weak_url_2 => beta_beta_weak_decl_2,
beta_beta_weak_url_3 => beta_beta_weak_decl_3,
beta_beta_strong_url.clone() => beta_beta_strong_decl.clone(),
};
let result = ModelBuilderForAnalyzer::get_decl_by_url(
&decls_by_url_with_4_beta_betas,
&beta_beta_strong_url,
);
assert!(result.is_ok());
assert_eq!(Some(&beta_beta_strong_decl), result.ok().unwrap());
}
#[fuchsia::test]
fn get_decl_by_url_weak() {
let beta_beta_strong_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_weak_url = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_decl = decl("beta_beta");
let decls_by_url_with_strong_beta_beta = hashmap! {
Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"),
Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"),
beta_beta_strong_url.clone() => beta_beta_decl.clone(),
};
let result = ModelBuilderForAnalyzer::get_decl_by_url(
&decls_by_url_with_strong_beta_beta,
&beta_beta_weak_url,
);
assert!(result.is_ok());
assert_eq!(Some(&beta_beta_decl), result.ok().unwrap());
}
#[fuchsia::test]
fn get_decl_by_url_weak_any() {
let beta_beta_url_1 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_decl_1 = decl("beta_beta_strong");
let beta_beta_url_2 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#beta.cm").unwrap();
let beta_beta_decl_2 = decl("beta_beta_weak_1");
let beta_beta_url_3 = Url::parse("fuchsia-pkg://test.fuchsia.com/beta?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap();
let beta_beta_decl_3 = decl("beta_beta_weak_2");
let beta_beta_weakest_url =
Url::parse("fuchsia-pkg://test.fuchsia.com/beta#beta.cm").unwrap();
let decls_by_url_3_weak_matches = hashmap! {
Url::parse("fuchsia-pkg://test.fuchsia.com/alpha#beta.cm").unwrap() => decl("alpha_beta"),
Url::parse("fuchsia-pkg://test.fuchsia.com/beta/0#alpha.cm").unwrap() => decl("beta_alpha"),
Url::parse("fuchsia-pkg://test.fuchsia.com/gamma?hash=0000000000000000000000000000000000000000000000000000000000000000#beta.cm").unwrap() => decl("gamma_beta"),
beta_beta_url_1 => beta_beta_decl_1.clone(),
beta_beta_url_2 => beta_beta_decl_2.clone(),
beta_beta_url_3 => beta_beta_decl_3.clone(),
};
let result = ModelBuilderForAnalyzer::get_decl_by_url(
&decls_by_url_3_weak_matches,
&beta_beta_weakest_url,
);
assert!(result.is_ok());
let actual_decl = result.ok().unwrap().unwrap();
assert!(
&beta_beta_decl_1 == actual_decl
|| &beta_beta_decl_2 == actual_decl
|| &beta_beta_decl_3 == actual_decl
);
}
}