blob: 1c15b6c9612ad952c52950f2609f2578608ca130 [file] [log] [blame]
// Copyright 2020 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.
mod collection;
mod collector;
mod controller;
use {
crate::verify::{
collector::component_model::V2ComponentModelDataCollector,
controller::{
build::VerifyBuildController,
capability_routing::{CapabilityRouteController, V2ComponentModelMappingController},
component_resolvers::ComponentResolversController,
route_sources::RouteSourcesController,
},
},
cm_fidl_analyzer::{
node_path::NodePath,
route::{CapabilityRouteError, RouteSegment},
serde_ext::ErrorWithMessage,
},
cm_rust::{CapabilityName, CapabilityTypeName},
scrutiny::prelude::*,
serde::{Deserialize, Serialize},
std::{collections::HashSet, path::PathBuf, sync::Arc},
};
pub use controller::route_sources::{
RouteSourceError, Source, VerifyRouteSourcesResult, VerifyRouteSourcesResults,
};
plugin!(
VerifyPlugin,
PluginHooks::new(
collectors! {
"V2ComponentModelDataCollector" => V2ComponentModelDataCollector::new(),
},
controllers! {
"/verify/build" => VerifyBuildController::default(),
"/verify/v2_component_model" => V2ComponentModelMappingController::default(),
"/verify/capability_routes" => CapabilityRouteController::default(),
"/verify/route_sources" => RouteSourcesController::default(),
"/verify/component_resolvers" => ComponentResolversController::default(),
}
),
vec![PluginDescriptor::new("CorePlugin")]
);
/// Top-level result type for `CapabilityRouteController` query result.
#[derive(Deserialize, Serialize)]
pub struct CapabilityRouteResults {
pub deps: HashSet<PathBuf>,
pub results: Vec<ResultsForCapabilityType>,
}
/// `CapabilityRouteController` query results grouped by severity.
#[derive(Clone, Deserialize, Serialize)]
pub struct ResultsForCapabilityType {
pub capability_type: CapabilityTypeName,
pub results: ResultsBySeverity,
}
/// Results from `CapabilityRouteController` grouped by severity (error,
/// warning, ok).
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct ResultsBySeverity {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub errors: Vec<ErrorResult>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub warnings: Vec<WarningResult>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub ok: Vec<OkResult>,
}
/// Error-severity results from `CapabilityRouteController`.
#[derive(Clone, Deserialize, PartialEq, Serialize)]
pub struct ErrorResult {
pub using_node: NodePath,
pub capability: CapabilityName,
pub error: ErrorWithMessage<CapabilityRouteError>,
}
/// Warning-severity results from `CapabilityRouteController`.
#[derive(Clone, Deserialize, Serialize)]
pub struct WarningResult {
pub using_node: NodePath,
pub capability: CapabilityName,
pub warning: ErrorWithMessage<CapabilityRouteError>,
}
/// Ok-severity results from `CapabilityRouteController`.
#[derive(Clone, Deserialize, Serialize)]
pub struct OkResult {
pub using_node: NodePath,
pub capability: CapabilityName,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub route: Vec<RouteSegment>,
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
core::collection::{
testing::fake_component_src_pkg, Component, Components, CoreDataDeps, Manifest,
ManifestData, Manifests, Zbi,
},
verify::{
collection::V2ComponentModel,
collector::component_model::{DEFAULT_CONFIG_PATH, DEFAULT_ROOT_URL},
},
},
anyhow::Result,
cm_fidl_analyzer::component_model::ModelBuilderForAnalyzer,
cm_rust::{
Availability, CapabilityDecl, CapabilityName, CapabilityPath, ChildDecl, ComponentDecl,
DependencyType, DirectoryDecl, FidlIntoNative, NativeIntoFidl, OfferDecl,
OfferDirectoryDecl, OfferProtocolDecl, OfferSource, OfferTarget, ProgramDecl, UseDecl,
UseDirectoryDecl, UseProtocolDecl, UseSource,
},
fidl::encoding::encode_persistent_with_context,
fidl_fuchsia_component_decl as fdecl,
fidl_fuchsia_component_internal as component_internal, fidl_fuchsia_io as fio,
maplit::hashset,
moniker::{AbsoluteMoniker, AbsoluteMonikerBase},
routing::{
component_id_index::ComponentIdIndex, component_instance::ComponentInstanceInterface,
config::RuntimeConfig, environment::RunnerRegistry,
},
scrutiny_testing::fake::*,
serde_json::json,
std::{collections::HashMap, convert::TryFrom},
url::Url,
};
static CORE_DEP_STR: &str = "core_dep";
fn data_model() -> Arc<DataModel> {
fake_data_model()
}
fn new_child_decl(name: String, url: String) -> ChildDecl {
ChildDecl {
name,
url,
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
}
}
fn new_component_decl(children: Vec<ChildDecl>) -> ComponentDecl {
ComponentDecl {
program: None,
uses: vec![],
exposes: vec![],
offers: vec![],
capabilities: vec![],
children,
collections: vec![],
facets: None,
environments: vec![],
config: None,
}
}
fn new_component_with_capabilities(
uses: Vec<UseDecl>,
offers: Vec<OfferDecl>,
capabilities: Vec<CapabilityDecl>,
children: Vec<ChildDecl>,
) -> ComponentDecl {
let mut program = ProgramDecl::default();
program.runner = Some("elf".into());
ComponentDecl {
program: Some(program),
uses,
exposes: vec![],
offers,
capabilities,
children,
collections: vec![],
facets: None,
environments: vec![],
config: None,
}
}
fn new_use_directory_decl(
source: UseSource,
source_name: CapabilityName,
rights: fio::Operations,
) -> UseDirectoryDecl {
UseDirectoryDecl {
source,
source_name,
target_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
rights,
subdir: None,
dependency_type: DependencyType::Strong,
availability: Availability::Required,
}
}
fn new_offer_directory_decl(
source: OfferSource,
source_name: CapabilityName,
target: OfferTarget,
target_name: CapabilityName,
rights: Option<fio::Operations>,
) -> OfferDirectoryDecl {
OfferDirectoryDecl {
source,
source_name,
target,
target_name,
rights,
subdir: None,
dependency_type: DependencyType::Strong,
availability: Availability::Required,
}
}
fn new_directory_decl(name: CapabilityName, rights: fio::Operations) -> DirectoryDecl {
DirectoryDecl { name, source_path: None, rights }
}
fn new_use_protocol_decl(source: UseSource, source_name: CapabilityName) -> UseProtocolDecl {
UseProtocolDecl {
source,
source_name,
target_path: CapabilityPath {
dirname: "/dir".to_string(),
basename: "svc".to_string(),
},
dependency_type: DependencyType::Strong,
availability: Availability::Required,
}
}
fn new_offer_protocol_decl(
source: OfferSource,
source_name: CapabilityName,
target: OfferTarget,
target_name: CapabilityName,
) -> OfferProtocolDecl {
OfferProtocolDecl {
source,
source_name,
target,
target_name,
dependency_type: DependencyType::Strong,
availability: Availability::Required,
}
}
fn make_v2_component(id: i32, url: String) -> Component {
let url = Url::parse(&url).unwrap();
Component { id, url, version: 2, source: fake_component_src_pkg() }
}
fn make_v2_manifest(component_id: i32, decl: ComponentDecl) -> Result<Manifest> {
let mut decl_fidl: fdecl::Component = decl.native_into_fidl();
let decl_base64 = base64::encode(&encode_persistent_with_context(
&fidl::encoding::Context { wire_format_version: fidl::encoding::WireFormatVersion::V2 },
&mut decl_fidl,
)?);
Ok(Manifest { component_id, manifest: ManifestData::Version2(decl_base64), uses: vec![] })
}
// Creates a data model with a ZBI containing one component manifest and the provided component
// id index.
fn single_v2_component_model(
root_component_url: Option<String>,
component_id_index_path: Option<String>,
component_id_index: component_id_index::Index,
) -> Result<Arc<DataModel>> {
let model = data_model();
let root_id = 0;
let root_component = make_v2_component(
root_id,
root_component_url.clone().unwrap_or(DEFAULT_ROOT_URL.to_string()),
);
let root_manifest = make_v2_manifest(root_id, new_component_decl(vec![]))?;
let deps = hashset! { CORE_DEP_STR.to_string().into() };
model.set(Components::new(vec![root_component]))?;
model.set(Manifests::new(vec![root_manifest]))?;
model
.set(Zbi { ..zbi(root_component_url, component_id_index_path, component_id_index) })?;
model.set(CoreDataDeps { deps })?;
Ok(model)
}
// Creates a data model with a ZBI containing 4 component manifests and a default component id index.
// The structure of the component instance tree is:
//
// root
// / \
// foo bar
// /
// baz
//
fn multi_v2_component_model() -> Result<Arc<DataModel>> {
let model = data_model();
let root_id = 0;
let foo_id = 1;
let bar_id = 2;
let baz_id = 3;
let root_url = DEFAULT_ROOT_URL.to_string();
let foo_url = "fuchsia-boot:///#meta/foo.cm".to_string();
let bar_url = "fuchsia-boot:///#meta/bar.cm".to_string();
let baz_url = "fuchsia-boot:///#meta/baz.cm".to_string();
let root_component = make_v2_component(root_id, root_url);
let foo_component = make_v2_component(foo_id, foo_url.clone());
let bar_component = make_v2_component(bar_id, bar_url.clone());
let baz_component = make_v2_component(baz_id, baz_url.clone());
let root_decl = new_component_decl(vec![
new_child_decl("foo".to_string(), foo_url),
new_child_decl("bar".to_string(), bar_url),
]);
let foo_decl = new_component_decl(vec![new_child_decl("baz".to_string(), baz_url)]);
let bar_decl = new_component_decl(vec![]);
let baz_decl = new_component_decl(vec![]);
let root_manifest = make_v2_manifest(root_id, root_decl)?;
let foo_manifest = make_v2_manifest(foo_id, foo_decl)?;
let bar_manifest = make_v2_manifest(bar_id, bar_decl)?;
let baz_manifest = make_v2_manifest(baz_id, baz_decl)?;
let deps = hashset! { CORE_DEP_STR.to_string().into() };
model.set(Components::new(vec![
root_component,
foo_component,
bar_component,
baz_component,
]))?;
model.set(Manifests::new(vec![root_manifest, foo_manifest, bar_manifest, baz_manifest]))?;
model.set(Zbi { ..zbi(None, None, component_id_index::Index::default()) })?;
model.set(CoreDataDeps { deps })?;
Ok(model)
}
fn cmls_to_model(cmls: Vec<(&'static str, serde_json::Value)>) -> Result<Arc<DataModel>> {
let model = data_model();
let mut id = 0;
let mut components = vec![];
let mut manifests = vec![];
for (uri, json) in cmls {
let decl = cml::compile(&serde_json::from_value(json)?, None)?.fidl_into_native();
let manifest = make_v2_manifest(id, decl)?;
let component = make_v2_component(id, uri.to_owned());
components.push(component);
manifests.push(manifest);
id += 1;
}
model.set(Components::new(components))?;
model.set(Zbi { ..zbi(None, None, component_id_index::Index::default()) })?;
model.set(Manifests::new(manifests))?;
model.set(CoreDataDeps { deps: hashset! { CORE_DEP_STR.to_string().into() } })?;
V2ComponentModelDataCollector::new().collect(model.clone())?;
Ok(model)
}
fn two_instance_component_model() -> Result<Arc<DataModel>> {
let model = data_model();
let root_url = &*DEFAULT_ROOT_URL;
let child_url = Url::parse("fuchsia-boot:///#meta/child.cm").unwrap();
let child_name = "child".to_string();
let missing_child_name = "missing_child".to_string();
let good_dir_name = CapabilityName("good_dir".to_string());
let bad_dir_name = CapabilityName("bad_dir".to_string());
let offer_rights = fio::Operations::CONNECT;
let protocol_name = CapabilityName("protocol".to_string());
let root_offer_good_dir = new_offer_directory_decl(
OfferSource::Self_,
good_dir_name.clone(),
OfferTarget::static_child(child_name.clone()),
good_dir_name.clone(),
Some(offer_rights),
);
let root_offer_protocol = new_offer_protocol_decl(
OfferSource::static_child(missing_child_name.clone()),
protocol_name.clone(),
OfferTarget::static_child(child_name.clone()),
protocol_name.clone(),
);
let root_good_dir_decl = new_directory_decl(good_dir_name.clone(), offer_rights);
let child_use_good_dir =
new_use_directory_decl(UseSource::Parent, good_dir_name.clone(), offer_rights);
let child_use_bad_dir =
new_use_directory_decl(UseSource::Parent, bad_dir_name.clone(), offer_rights);
let child_use_protocol = new_use_protocol_decl(UseSource::Parent, protocol_name.clone());
let mut decls = HashMap::new();
decls.insert(
root_url.clone(),
new_component_with_capabilities(
vec![],
vec![
OfferDecl::Directory(root_offer_good_dir),
OfferDecl::Protocol(root_offer_protocol),
],
vec![CapabilityDecl::Directory(root_good_dir_decl)],
vec![new_child_decl(child_name, child_url.to_string())],
),
);
decls.insert(
child_url,
new_component_with_capabilities(
vec![
UseDecl::Directory(child_use_good_dir),
UseDecl::Directory(child_use_bad_dir),
UseDecl::Protocol(child_use_protocol),
],
vec![],
vec![],
vec![],
),
);
let build_model_result = ModelBuilderForAnalyzer::new(root_url.clone()).build(
decls,
Arc::new(RuntimeConfig::default()),
Arc::new(ComponentIdIndex::default()),
RunnerRegistry::default(),
);
assert!(build_model_result.errors.is_empty());
assert!(build_model_result.model.is_some());
let component_model = build_model_result.model.unwrap();
assert_eq!(component_model.len(), 2);
let deps = hashset! { "v2_component_tree_dep".to_string().into() };
model.set(V2ComponentModel::new(deps, component_model, build_model_result.errors))?;
Ok(model)
}
fn zbi(
root_component_url: Option<String>,
component_id_index_path: Option<String>,
component_id_index: component_id_index::Index,
) -> Zbi {
let mut bootfs: HashMap<String, Vec<u8>> = HashMap::default();
let mut runtime_config = component_internal::Config::EMPTY;
runtime_config.root_component_url = root_component_url;
runtime_config.component_id_index_path = component_id_index_path.clone();
if let Some(path) = component_id_index_path {
let split_index_path: Vec<&str> = path.split_inclusive("/").collect();
if split_index_path.as_slice()[..2] == ["/", "boot/"] {
bootfs.insert(
split_index_path[2..].join(""),
fidl::encoding::encode_persistent_with_context(
&fidl::encoding::Context {
wire_format_version: fidl::encoding::WireFormatVersion::V2,
},
&mut component_internal::ComponentIdIndex::try_from(component_id_index)
.expect("failed to convert component id index to fidl"),
)
.expect("failed to encode component id index as persistent fidl"),
);
}
}
bootfs.insert(
DEFAULT_CONFIG_PATH.to_string(),
fidl::encoding::encode_persistent_with_context(
&fidl::encoding::Context {
wire_format_version: fidl::encoding::WireFormatVersion::V2,
},
&mut runtime_config,
)
.unwrap(),
);
return Zbi { sections: Vec::default(), bootfs, cmdline: "".to_string() };
}
// Prepares a ZBI with a nonempty component ID index, collects a `V2ComponentModel` with one
// component instance, and checks that the component ID index provided by that component instance
// contains the expected entry.
#[test]
fn collect_component_model_with_id_index() -> Result<()> {
let iid = "0".repeat(64);
let model = single_v2_component_model(
None,
Some("/boot/index_path".to_string()),
component_id_index::Index {
instances: vec![component_id_index::InstanceIdEntry {
instance_id: Some(iid.clone()),
appmgr_moniker: None,
moniker: Some(AbsoluteMoniker::parse_str("/a/b/c").unwrap()),
}],
..component_id_index::Index::default()
},
)?;
V2ComponentModelDataCollector::new().collect(model.clone())?;
let collection =
&model.get::<V2ComponentModel>().expect("failed to find the v2 component model");
assert!(collection.errors.is_empty());
let root_instance = collection.component_model.get_root_instance()?;
assert_eq!(
Some(&iid),
root_instance
.try_get_component_id_index()?
.look_up_moniker(&AbsoluteMoniker::parse_str("/a/b/c").unwrap())
);
Ok(())
}
#[test]
fn test_component_resolvers_child_source() -> Result<()> {
let model = cmls_to_model(vec![
(
"fuchsia-boot:///#meta/root.cm",
json!({
"capabilities": [
{
"protocol": "protocol",
"path": "/protocol",
},
],
"offer": [
{
"protocol": [
"protocol",
],
"from" : "#self",
"to": "#my-resolver"
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
"environment" : "#myenv",
},
{
"name" : "my-resolver",
"url" : "fuchsia-pkg://fuchsia.com/resolver#meta/resolver.cm",
},
],
"environments" : [
{
"name": "myenv",
"extends": "realm",
"resolvers" : [ {
"resolver" : "my-resolver",
"from" : "#my-resolver",
"scheme": "fuchsia-pkg",
},
],
},
]
}),
),
(
"fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
json!({
"program" : {
"runner" : "elf",
"binary" : "bin/logger",
},
}),
),
(
"fuchsia-pkg://fuchsia.com/resolver#meta/resolver.cm",
json!({
"program": {
"runner": "elf",
"binary": "bin/universe_resolver",
},
"capabilities": [
{
"resolver": "my-resolver",
"path": "/svc/fuchsia.component.resolution.Resolver",
},
{ "protocol": "fuchsia.component.resolution.Resolver" },
],
"use": [
{
"protocol": [
"protocol",
],
},
],
"expose": [
{
"resolver": "my-resolver",
"from": "self",
},
{
"protocol": "fuchsia.component.resolution.Resolver",
"from": "self",
},
],
}),
),
])?;
let controller = ComponentResolversController::default();
let response = controller.query(
model.clone(),
json!({ "scheme": "fuchsia-pkg", "moniker": "/my-resolver", "protocol": "protocol"}),
)?;
assert_eq!(
response,
json!({
"deps": ["core_dep"],
"monikers": ["/logger"],
})
);
Ok(())
}
#[test]
fn test_component_resolvers_self_source() -> Result<()> {
let model = cmls_to_model(vec![
(
"fuchsia-boot:///#meta/root.cm",
json!({
"capabilities": [
{
"protocol": "protocol",
"path": "/protocol",
},
{
"resolver": "my-resolver",
"path": "/svc/fuchsia.component.resolution.Resolver",
},
],
"use": [
{
"protocol": [
"protocol",
],
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
"environment": "#myenv"
},
],
"environments" : [
{
"name": "myenv",
"extends": "realm",
"resolvers" : [
{
"resolver" : "my-resolver",
"scheme": "fuchsia-pkg",
"from": "self",
}
]
}
]
}),
),
(
"fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
json!({
"program" : {
"runner" : "elf",
"binary" : "bin/logger",
},
}),
),
])?;
let controller = ComponentResolversController::default();
let response = controller.query(
model.clone(),
json!({ "scheme": "fuchsia-pkg", "moniker": "/", "protocol": "protocol"}),
)?;
assert_eq!(
response,
json!({
"deps": ["core_dep"],
"monikers": ["/logger"],
})
);
Ok(())
}
#[test]
fn test_component_resolvers_parent_source() -> Result<()> {
let model = cmls_to_model(vec![
(
"fuchsia-boot:///#meta/root.cm",
json!({
"capabilities": [
{
"protocol": "protocol",
"path": "/protocol",
},
{
"resolver": "my-resolver",
"path": "/svc/fuchsia.component.resolution.Resolver",
},
],
"offer": [
{
"resolver": "my-resolver",
"from" : "self",
"to": "#logger"
},
],
"use": [
{
"protocol": [
"protocol",
],
},
],
"children": [
{
"name": "logger",
"url": "fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
},
],
}),
),
(
"fuchsia-pkg://fuchsia.com/logger#meta/logger.cm",
json!({
"children" : [
{
"name": "log-child",
"url": "fuchsia-pkg://fuchsia.com/log-child#meta/log-child.cm",
"environment" : "#env",
},
],
"environments" : [
{
"name": "env",
"extends": "none",
"resolvers" : [ {
"resolver" : "my-resolver",
"from" : "parent",
"scheme": "fuchsia-pkg",
},
],
},
],
}),
),
(
"fuchsia-pkg://fuchsia.com/log-child#meta/log-child.cm",
json!({
"program" : {
"runner" : "elf",
"binary" : "bin/logger",
},
}),
),
])?;
let controller = ComponentResolversController::default();
let response = controller.query(
model.clone(),
json!({ "scheme": "fuchsia-pkg", "moniker": "/", "protocol": "protocol"}),
)?;
assert_eq!(
response,
json!({
"deps": ["core_dep"],
"monikers": ["/logger/log-child"],
})
);
Ok(())
}
#[test]
fn test_component_resolvers_ignores_invalid_resolver_registration() -> Result<()> {
let model = cmls_to_model(vec![
(
"fuchsia-boot:///#meta/root.cm",
json!({
"children": [
{
"name": "bootstrap",
"url": "fuchsia-boot:///#meta/bootstrap.cm",
"startup": "eager",
},
{
"name": "core",
"url": "fuchsia-pkg://fuchsia.com/core#meta/core.cm",
"environment": "#core-env",
},
],
"offer": [
{
"protocol": [
"fuchsia.component.resolution.Resolver",
],
"from" : "#bootstrap",
"to": "#core"
},
],
"environments" : [
{
"name": "core-env",
"extends": "realm",
"resolvers" : [ {
"resolver" : "base_resolver",
"from" : "#bootstrap",
"scheme": "fuchsia-pkg",
},
],
},
]
}),
),
(
"fuchsia-boot:///#meta/bootstrap.cm",
json!({
"children": [
{
"name": "base_resolver",
// A declared but undefined child component, so its capabilities cannot
// be analyzed.
"url": "fuchsia-boot:///#meta/base-resolver.cm",
},
],
"expose": [
{
"protocol": "fuchsia.component.resolution.Resolver",
"from": "#base_resolver",
},
{
"resolver": "base_resolver",
"from": "#base_resolver",
},
],
}),
),
(
"fuchsia-pkg://fuchsia.com/core#meta/core.cm",
json!({
"children": [
{
"name": "resolved-from-realm",
"url": "fuchsia-pkg://fuchsia.com/resolve-me#meta/resolve-me.cm",
},
{
"name": "resolved-from-custom",
"url": "fuchsia-pkg://fuchsia.com/resolve-me#meta/resolve-me.cm",
"environment": "#custom-resolver-env",
},
{
"name": "custom-resolver",
"url": "fuchsia-pkg://fuchsia.com/custom-resolver#meta/custom-resolver.cm",
},
],
"expose": [
{
"protocol": "fuchsia.component.resolution.Resolver",
"from": "#base_resolver",
},
{
"resolver": "base_resolver",
"from": "#base_resolver",
},
],
"capabilities": [
{
"protocol": "fuchsia.test.SpecialProtocol",
"path": "/fake-for-test",
},
],
"offer": [
{
"protocol": [
"fuchsia.test.SpecialProtocol",
],
"from" : "#self",
"to": "#custom-resolver"
},
],
"environments" : [
{
"name": "custom-resolver-env",
"extends": "realm",
"resolvers" : [ {
"resolver" : "custom-resolver",
"from" : "#custom-resolver",
"scheme": "fuchsia-pkg",
},
],
},
]
}),
),
// Don't provide a definition of the base-resolver component
// to ensure the walker ignores invalid resolver configurations
/*
(
"fuchsia-boot:///#meta/base-resolver.cm",
json!({
"program" : {
"runner" : "elf",
"binary" : "bin/foo",
},
}),
),
*/
(
"fuchsia-pkg://fuchsia.com/resolve-me#meta/resolve-me.cm",
json!({
"program" : {
"runner" : "elf",
"binary" : "bin/app",
},
}),
),
(
"fuchsia-pkg://fuchsia.com/custom-resolver#meta/custom-resolver.cm",
json!({
"program": {
"runner": "elf",
"binary": "bin/custom_resolver",
},
"capabilities": [
{
"resolver": "custom-resolver",
"path": "/svc/fuchsia.component.resolution.Resolver",
},
{ "protocol": "fuchsia.component.resolution.Resolver" },
],
"use": [
{
"protocol": [
"fuchsia.test.SpecialProtocol",
],
},
],
"expose": [
{
"resolver": "custom-resolver",
"from": "self",
},
{
"protocol": "fuchsia.component.resolution.Resolver",
"from": "self",
},
],
}),
),
])?;
let controller = ComponentResolversController::default();
// Even with an invalid component resolver, ensure queries for other component resolvers
// find the expected instances.
let response = controller.query(
model.clone(),
json!({ "scheme": "fuchsia-pkg", "moniker": "/core/custom-resolver", "protocol": "fuchsia.test.SpecialProtocol"}),
)?;
assert_eq!(
response,
json!({
"deps": ["core_dep"],
"monikers": ["/core/resolved-from-custom"],
})
);
Ok(())
}
#[test]
fn test_map_tree_single_node_default_url() -> Result<()> {
let model = single_v2_component_model(None, None, component_id_index::Index::default())?;
V2ComponentModelDataCollector::new().collect(model.clone())?;
let controller = V2ComponentModelMappingController::default();
let response = controller.query(model.clone(), json!("{}"))?;
assert_eq!(
response,
json!({"instances": [{ "instance": "/", "url": DEFAULT_ROOT_URL.to_string() }]})
);
Ok(())
}
#[test]
fn test_map_tree_single_node_custom_url() -> Result<()> {
let root_url = "fuchsia-boot:///#meta/foo.cm".to_string();
let model = single_v2_component_model(
Some(root_url.clone()),
None,
component_id_index::Index::default(),
)?;
V2ComponentModelDataCollector::new().collect(model.clone())?;
let controller = V2ComponentModelMappingController::default();
let response = controller.query(model.clone(), json!("{}"))?;
assert_eq!(response, json!({"instances": [ {"instance": "/", "url": root_url }]}));
Ok(())
}
#[test]
fn test_map_component_model_multi_instance() -> Result<()> {
let model = multi_v2_component_model()?;
V2ComponentModelDataCollector::new().collect(model.clone())?;
let controller = V2ComponentModelMappingController::default();
let response = controller.query(model.clone(), json!("{}"))?;
assert!(
(response
== json!({"instances": [{"instance": "/", "url": DEFAULT_ROOT_URL.to_string()},
{"instance": "/foo","url": "fuchsia-boot:///#meta/foo.cm"},
{"instance": "/bar", "url": "fuchsia-boot:///#meta/bar.cm"},
{"instance": "/foo/baz", "url": "fuchsia-boot:///#meta/baz.cm"}]}))
| (response
== json!({"instances": [{"instance": "/", "url": DEFAULT_ROOT_URL.to_string()},
{"instance": "/bar","url": "fuchsia-boot:///#meta/bar.cm"},
{"instance": "/foo", "url": "fuchsia-boot:///#meta/foo.cm"},
{"instance": "/foo/baz", "url": "fuchsia-boot:///#meta/baz.cm"}]}))
);
Ok(())
}
#[test]
fn test_capability_routing_all_results() -> Result<()> {
let model = two_instance_component_model()?;
let controller = CapabilityRouteController::default();
let response = controller.query(
model.clone(),
json!({ "capability_types": "directory protocol",
"response_level": "all"}),
)?;
let expected = json!({
"deps": ["v2_component_tree_dep"],
"results": [
{
"capability_type": "directory",
"results": {
"errors": [
{
"capability": "bad_dir",
"error": {
"error": {
"analyzer_model_error": {
"routing_error": {
"use_from_parent_not_found": {
"capability_id": "bad_dir",
"moniker": "/child",
}
}
}
},
"message": "`/child` tried to use `bad_dir` from its parent, but the parent does not offer that capability. Note, use clauses in CML default to using from parent."
},
"using_node": "/child",
},
],
"ok": [
{
"capability": "good_dir",
"using_node": "/child",
},
],
}
},
{
"capability_type": "protocol",
"results": {
"warnings": [
{
"capability": "protocol",
"warning": {
"error": {
"analyzer_model_error": {
"routing_error": {
"offer_from_child_instance_not_found": {
"capability_id": "protocol",
"child_moniker": "missing_child",
"moniker": "/",
}
}
}
},
"message": "`/` tried to offer `protocol` from `#missing_child`, but no such child was found."
},
"using_node": "/child",
},
],
}
}
]
});
assert_eq!(response, expected);
Ok(())
}
#[test]
fn test_capability_routing_verbose_results() -> Result<()> {
let model = two_instance_component_model()?;
let controller = CapabilityRouteController::default();
let response = controller.query(
model.clone(),
json!({ "capability_types": "directory protocol",
"response_level": "verbose"}),
)?;
let expected = json!({
"deps": ["v2_component_tree_dep"],
"results": [
{
"capability_type": "directory",
"results": {
"errors": [
{
"capability": "bad_dir",
"error": {
"error": {
"analyzer_model_error": {
"routing_error": {
"use_from_parent_not_found": {
"capability_id": "bad_dir",
"moniker": "/child",
}
}
}
},
"message": "`/child` tried to use `bad_dir` from its parent, but the parent does not offer that capability. Note, use clauses in CML default to using from parent."
},
"using_node": "/child",
},
],
"ok": [
{
"capability": "good_dir",
"route": [
{
"action": "use_by",
"capability": {
"availability": "required",
"dependency_type": "strong",
"rights": 1,
"source": "parent",
"source_name": "good_dir",
"subdir": null,
"target_path": "/",
"type": "directory"
},
"node_path": "/child"
},
{
"action": "offer_by",
"capability": {
"availability": "required",
"dependency_type": "strong",
"rights": 1,
"source": "self_",
"source_name": "good_dir",
"subdir": null,
"target": {
"child": {
"name": "child",
"collection": null,
}
},
"target_name": "good_dir",
"type": "directory"
},
"node_path": "/"
},
{
"action": "declare_by",
"capability": {
"name": "good_dir",
"rights": 1,
"source_path": null,
"type": "directory"
},
"node_path": "/"
}
],
"using_node": "/child"
}
]
}
},
{
"capability_type": "protocol",
"results": {
"warnings": [
{
"capability": "protocol",
"warning": {
"error": {
"analyzer_model_error": {
"routing_error": {
"offer_from_child_instance_not_found": {
"capability_id": "protocol",
"child_moniker": "missing_child",
"moniker": "/",
}
}
}
},
"message": "`/` tried to offer `protocol` from `#missing_child`, but no such child was found."
},
"using_node": "/child",
},
]
}
}
]
});
assert_eq!(response, expected);
Ok(())
}
#[test]
fn test_capability_routing_warn() -> Result<()> {
let model = two_instance_component_model()?;
let controller = CapabilityRouteController::default();
let response = controller.query(
model.clone(),
json!({ "capability_types": "directory protocol",
"response_level": "warn"}),
)?;
let expected = json!({
"deps": ["v2_component_tree_dep"],
"results": [
{
"capability_type": "directory",
"results": {
"errors": [
{
"capability": "bad_dir",
"error": {
"error": {
"analyzer_model_error": {
"routing_error": {
"use_from_parent_not_found": {
"capability_id": "bad_dir",
"moniker": "/child",
}
}
}
},
"message": "`/child` tried to use `bad_dir` from its parent, but the parent does not offer that capability. Note, use clauses in CML default to using from parent."
},
"using_node": "/child",
},
]
}
},
{
"capability_type": "protocol",
"results": {
"warnings": [
{
"capability": "protocol",
"using_node": "/child",
"warning": {
"error": {
"analyzer_model_error": {
"routing_error": {
"offer_from_child_instance_not_found": {
"capability_id": "protocol",
"child_moniker": "missing_child",
"moniker": "/",
}
}
}
},
"message": "`/` tried to offer `protocol` from `#missing_child`, but no such child was found."
}
}
]
}
}
]
});
assert_eq!(response, expected);
Ok(())
}
#[test]
fn test_capability_routing_errors_only() -> Result<()> {
let model = two_instance_component_model()?;
let controller = CapabilityRouteController::default();
let response = controller.query(
model.clone(),
json!({ "capability_types": "directory protocol",
"response_level": "error"}),
)?;
let expected = json!({
"deps": ["v2_component_tree_dep"],
"results": [
{
"capability_type": "directory",
"results": {
"errors": [
{
"capability": "bad_dir",
"error": {
"error": {
"analyzer_model_error": {
"routing_error": {
"use_from_parent_not_found": {
"capability_id": "bad_dir",
"moniker": "/child",
}
}
}
},
"message": "`/child` tried to use `bad_dir` from its parent, but the parent does not offer that capability. Note, use clauses in CML default to using from parent."
},
"using_node": "/child",
}
]
}
},
{
"capability_type": "protocol",
"results": {}
}
]
});
assert_eq!(response, expected);
Ok(())
}
}