| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| pub(crate) mod util; |
| |
| pub mod error; |
| |
| use { |
| crate::{error::*, util::*}, |
| directed_graph::DirectedGraph, |
| fidl_fuchsia_component_config as fconfig, fidl_fuchsia_component_decl as fdecl, |
| itertools::Itertools, |
| std::{ |
| collections::{HashMap, HashSet}, |
| fmt, |
| path::Path, |
| }, |
| }; |
| |
| /// Validates Configuration Value Spec. |
| /// |
| /// For now, this simply verifies that all semantically required fields are present. |
| pub fn validate_value_spec(spec: &fconfig::ValueSpec) -> Result<(), ErrorList> { |
| let mut errors = vec![]; |
| if let Some(value) = &spec.value { |
| match value { |
| fconfig::Value::Single(s) => match s { |
| fconfig::SingleValue::Bool(_) |
| | fconfig::SingleValue::Uint8(_) |
| | fconfig::SingleValue::Uint16(_) |
| | fconfig::SingleValue::Uint32(_) |
| | fconfig::SingleValue::Uint64(_) |
| | fconfig::SingleValue::Int8(_) |
| | fconfig::SingleValue::Int16(_) |
| | fconfig::SingleValue::Int32(_) |
| | fconfig::SingleValue::Int64(_) |
| | fconfig::SingleValue::String(_) => {} |
| fconfig::SingleValueUnknown!() => { |
| errors.push(Error::invalid_field("ValueSpec", "value")); |
| } |
| }, |
| fconfig::Value::Vector(l) => match l { |
| fconfig::VectorValue::BoolVector(_) |
| | fconfig::VectorValue::Uint8Vector(_) |
| | fconfig::VectorValue::Uint16Vector(_) |
| | fconfig::VectorValue::Uint32Vector(_) |
| | fconfig::VectorValue::Uint64Vector(_) |
| | fconfig::VectorValue::Int8Vector(_) |
| | fconfig::VectorValue::Int16Vector(_) |
| | fconfig::VectorValue::Int32Vector(_) |
| | fconfig::VectorValue::Int64Vector(_) |
| | fconfig::VectorValue::StringVector(_) => {} |
| fconfig::VectorValueUnknown!() => { |
| errors.push(Error::invalid_field("ValueSpec", "value")); |
| } |
| }, |
| fconfig::ValueUnknown!() => { |
| errors.push(Error::invalid_field("ValueSpec", "value")); |
| } |
| } |
| } else { |
| errors.push(Error::missing_field("ValueSpec", "value")); |
| } |
| |
| if errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(ErrorList::new(errors)) |
| } |
| } |
| |
| /// Validates Configuration Values Data. |
| /// |
| /// The Value Data may ultimately originate from a CVF file, or be directly constructed by the |
| /// caller. Either way, Value Data should always be validated before it's used. For now, this |
| /// simply verifies that all semantically required fields are present. |
| /// |
| /// This method does not validate value data against a configuration schema. |
| pub fn validate_values_data(data: &fconfig::ValuesData) -> Result<(), ErrorList> { |
| let mut errors = vec![]; |
| if let Some(values) = &data.values { |
| for spec in values { |
| if let Err(mut e) = validate_value_spec(spec) { |
| errors.append(&mut e.errs); |
| } |
| } |
| } else { |
| errors.push(Error::missing_field("ValuesData", "values")); |
| } |
| |
| if let Some(checksum) = &data.checksum { |
| match checksum { |
| fdecl::ConfigChecksum::Sha256(_) => {} |
| fdecl::ConfigChecksumUnknown!() => { |
| errors.push(Error::invalid_field("ValuesData", "checksum")); |
| } |
| } |
| } else { |
| errors.push(Error::missing_field("ValuesData", "checksum")); |
| } |
| |
| if errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(ErrorList::new(errors)) |
| } |
| } |
| |
| /// Validates a Component. |
| /// |
| /// The Component may ultimately originate from a CM file, or be directly constructed by the |
| /// caller. Either way, a Component should always be validated before it's used. Examples |
| /// of what is validated (which may evolve in the future): |
| /// |
| /// - That all semantically required fields are present |
| /// - That a child_name referenced in a source actually exists in the list of children |
| /// - That there are no duplicate target paths. |
| /// - That a cap is not offered back to the child that exposed it. |
| /// |
| /// All checks are local to this Component. |
| pub fn validate(decl: &fdecl::Component) -> Result<(), ErrorList> { |
| let ctx = ValidationContext::default(); |
| ctx.validate(decl).map_err(|errs| ErrorList::new(errs)) |
| } |
| |
| /// Validates a list of Capabilitys independently. |
| pub fn validate_capabilities( |
| capabilities: &Vec<fdecl::Capability>, |
| as_builtin: bool, |
| ) -> Result<(), ErrorList> { |
| let mut ctx = ValidationContext::default(); |
| for capability in capabilities { |
| ctx.validate_capability_decl(capability, as_builtin); |
| } |
| if ctx.errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(ErrorList::new(ctx.errors)) |
| } |
| } |
| |
| /// An interface to call into either `check_dynamic_name()` or `check_name()`, depending on the context |
| /// of the caller. |
| type CheckChildNameFn = fn(Option<&String>, &str, &str, &mut Vec<Error>) -> bool; |
| |
| pub fn validate_dynamic_child(child: &fdecl::Child) -> Result<(), ErrorList> { |
| validate_child(child, check_dynamic_name) |
| } |
| |
| /// Validates an independent Child. Performs the same validation on it as `validate`. A |
| /// `check_name_fn` is passed into specify the function used to validate the child name. |
| fn validate_child( |
| child: &fdecl::Child, |
| check_child_name: CheckChildNameFn, |
| ) -> Result<(), ErrorList> { |
| let mut errors = vec![]; |
| check_child_name(child.name.as_ref(), "Child", "name", &mut errors); |
| check_url(child.url.as_ref(), "Child", "url", &mut errors); |
| if child.startup.is_none() { |
| errors.push(Error::missing_field("Child", "startup")); |
| } |
| // Allow `on_terminate` to be unset since the default is almost always desired. |
| if child.environment.is_some() { |
| check_name(child.environment.as_ref(), "Child", "environment", &mut errors); |
| } |
| if errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(ErrorList { errs: errors }) |
| } |
| } |
| |
| /// Validates a collection of dynamic offers. Dynamic offers differ from static |
| /// offers, in that |
| /// |
| /// 1. a dynamic offer's `target` field must be omitted; |
| /// 2. a dynamic offer's `source` _may_ be a dynamic child; |
| /// 3. since this crate isn't really designed to handle dynamic children, we |
| /// disable the checks that ensure that the source/target exist, and that the |
| /// offers don't introduce any cycles. |
| pub fn validate_dynamic_offers(offers: &Vec<fdecl::Offer>) -> Result<(), ErrorList> { |
| let mut ctx = ValidationContext::default(); |
| for offer in offers { |
| ctx.validate_offers_decl(offer, OfferType::Dynamic) |
| } |
| ctx.validate_offer_group(offers); |
| if ctx.errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(ErrorList::new(ctx.errors)) |
| } |
| } |
| |
| fn check_offer_name( |
| prop: Option<&String>, |
| decl: &str, |
| keyword: &str, |
| errors: &mut Vec<Error>, |
| offer_type: OfferType, |
| ) -> bool { |
| match offer_type { |
| OfferType::Static => check_name(prop, decl, keyword, errors), |
| OfferType::Dynamic => check_dynamic_name(prop, decl, keyword, errors), |
| } |
| } |
| |
| #[derive(Default)] |
| struct ValidationContext<'a> { |
| all_children: HashMap<&'a str, &'a fdecl::Child>, |
| all_collections: HashSet<&'a str>, |
| all_capability_ids: HashSet<&'a str>, |
| all_storage_and_sources: HashMap<&'a str, Option<&'a fdecl::Ref>>, |
| all_services: HashSet<&'a str>, |
| all_protocols: HashSet<&'a str>, |
| all_directories: HashSet<&'a str>, |
| all_runners: HashSet<&'a str>, |
| all_resolvers: HashSet<&'a str>, |
| all_environment_names: HashSet<&'a str>, |
| all_events: HashSet<&'a str>, |
| all_event_streams: HashSet<&'a str>, |
| strong_dependencies: DirectedGraph<DependencyNode<'a>>, |
| target_ids: IdMap<'a>, |
| errors: Vec<Error>, |
| } |
| |
| /// A node in the DependencyGraph. The first string describes the type of node and the second |
| /// string is the name of the node. |
| #[derive(Copy, Clone, Hash, Ord, Debug, PartialOrd, PartialEq, Eq)] |
| enum DependencyNode<'a> { |
| Self_, |
| Child(&'a str), |
| Collection(&'a str), |
| Environment(&'a str), |
| /// This variant is automatically translated to the source backing the capability by |
| /// `add_strong_dep`, it does not appear in the dependency graph. |
| Capability(&'a str), |
| } |
| |
| impl<'a> DependencyNode<'a> { |
| fn try_from_ref(ref_: Option<&'a fdecl::Ref>) -> Option<DependencyNode<'a>> { |
| if ref_.is_none() { |
| return None; |
| } |
| match ref_.unwrap() { |
| fdecl::Ref::Child(fdecl::ChildRef { name, .. }) => { |
| Some(DependencyNode::Child(name.as_str())) |
| } |
| fdecl::Ref::Collection(fdecl::CollectionRef { name, .. }) => { |
| Some(DependencyNode::Collection(name.as_str())) |
| } |
| fdecl::Ref::Capability(fdecl::CapabilityRef { name, .. }) => { |
| Some(DependencyNode::Capability(name.as_str())) |
| } |
| fdecl::Ref::Self_(_) => Some(DependencyNode::Self_), |
| fdecl::Ref::Parent(_) => { |
| // We don't care about dependency cycles with the parent, as any potential issues |
| // with that are resolved by cycle detection in the parent's manifest. |
| None |
| } |
| fdecl::Ref::Framework(_) => { |
| // We don't care about dependency cycles with the framework, as the framework |
| // always outlives the component. |
| None |
| } |
| fdecl::Ref::Debug(_) => { |
| // We don't care about dependency cycles with any debug capabilities from the |
| // environment, as those are put there by our parent, and any potential cycles with |
| // our parent are handled by cycle detection in the parent's manifest. |
| None |
| } |
| _ => { |
| // We were unable to understand this FIDL value |
| None |
| } |
| } |
| } |
| } |
| |
| impl<'a> fmt::Display for DependencyNode<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| DependencyNode::Self_ => write!(f, "self"), |
| DependencyNode::Child(name) => write!(f, "child {}", name), |
| DependencyNode::Collection(name) => write!(f, "collection {}", name), |
| DependencyNode::Environment(name) => write!(f, "environment {}", name), |
| DependencyNode::Capability(name) => write!(f, "capability {}", name), |
| } |
| } |
| } |
| |
| impl<'a> ValidationContext<'a> { |
| fn validate(mut self, decl: &'a fdecl::Component) -> Result<(), Vec<Error>> { |
| // Collect all environment names first, so that references to them can be checked. |
| if let Some(envs) = &decl.environments { |
| self.collect_environment_names(&envs); |
| } |
| |
| // Validate "program". |
| if let Some(program) = decl.program.as_ref() { |
| self.validate_program(program); |
| } |
| |
| // Validate "children" and build the set of all children. |
| if let Some(children) = decl.children.as_ref() { |
| for child in children { |
| self.validate_child_decl(&child); |
| } |
| } |
| |
| // Validate "collections" and build the set of all collections. |
| if let Some(collections) = decl.collections.as_ref() { |
| for collection in collections { |
| self.validate_collection_decl(&collection); |
| } |
| } |
| |
| // Validate "capabilities" and build the set of all capabilities. |
| if let Some(capabilities) = decl.capabilities.as_ref() { |
| for capability in capabilities { |
| self.validate_capability_decl(capability, false); |
| } |
| } |
| |
| // Validate "uses". |
| if let Some(uses) = decl.uses.as_ref() { |
| self.validate_use_decls(uses); |
| } |
| |
| // Validate "exposes". |
| if let Some(exposes) = decl.exposes.as_ref() { |
| let mut target_ids = HashMap::new(); |
| for expose in exposes.iter() { |
| self.validate_expose_decl(&expose, &mut target_ids); |
| } |
| } |
| |
| // Validate "offers". |
| if let Some(offers) = decl.offers.as_ref() { |
| for offer in offers.iter() { |
| self.validate_offers_decl(&offer, OfferType::Static); |
| } |
| self.validate_offer_group(offers); |
| } |
| |
| // Validate "environments" after all other declarations are processed. |
| if let Some(environment) = decl.environments.as_ref() { |
| for environment in environment { |
| self.validate_environment_decl(&environment); |
| } |
| } |
| |
| // Validate "config" |
| if let Some(config) = decl.config.as_ref() { |
| self.validate_config(&config); |
| } |
| |
| // Check that there are no strong cyclical dependencies |
| if let Err(e) = self.strong_dependencies.topological_sort() { |
| self.errors.push(Error::dependency_cycle(e.format_cycle())); |
| } |
| |
| if self.errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(self.errors) |
| } |
| } |
| |
| /// Collects all the environment names, watching for duplicates. |
| fn collect_environment_names(&mut self, envs: &'a [fdecl::Environment]) { |
| for env in envs { |
| if let Some(name) = env.name.as_ref() { |
| if !self.all_environment_names.insert(name) { |
| self.errors.push(Error::duplicate_field("Environment", "name", name)); |
| } |
| } |
| } |
| } |
| |
| // Validates a config schema. Checks that each field's layout matches the expected constraints |
| // and properties. |
| fn validate_config(&mut self, config: &fdecl::ConfigSchema) { |
| if let Some(fields) = &config.fields { |
| for field in fields { |
| if field.key.is_none() { |
| self.errors.push(Error::missing_field("ConfigField", "key")); |
| } |
| if let Some(type_) = &field.type_ { |
| self.validate_config_type(type_, true); |
| } else { |
| self.errors.push(Error::missing_field("ConfigField", "value_type")); |
| } |
| } |
| } else { |
| self.errors.push(Error::missing_field("ConfigSchema", "fields")); |
| } |
| |
| if let Some(checksum) = &config.checksum { |
| match checksum { |
| fdecl::ConfigChecksum::Sha256(_) => {} |
| fdecl::ConfigChecksumUnknown!() => { |
| self.errors.push(Error::invalid_field("ConfigSchema", "checksum")); |
| } |
| } |
| } else { |
| self.errors.push(Error::missing_field("ConfigSchema", "checksum")); |
| } |
| |
| if config.value_source.is_none() { |
| self.errors.push(Error::missing_field("ConfigSchema", "value_source")); |
| } |
| } |
| |
| fn validate_config_type(&mut self, type_: &fdecl::ConfigType, accept_vectors: bool) { |
| match &type_.layout { |
| fdecl::ConfigTypeLayout::Bool |
| | fdecl::ConfigTypeLayout::Uint8 |
| | fdecl::ConfigTypeLayout::Uint16 |
| | fdecl::ConfigTypeLayout::Uint32 |
| | fdecl::ConfigTypeLayout::Uint64 |
| | fdecl::ConfigTypeLayout::Int8 |
| | fdecl::ConfigTypeLayout::Int16 |
| | fdecl::ConfigTypeLayout::Int32 |
| | fdecl::ConfigTypeLayout::Int64 => { |
| // These layouts have no parameters or constraints |
| if let Some(parameters) = &type_.parameters { |
| if !parameters.is_empty() { |
| self.errors.push(Error::extraneous_field("ConfigType", "parameters")); |
| } |
| } else { |
| self.errors.push(Error::missing_field("ConfigType", "parameters")); |
| } |
| |
| if !type_.constraints.is_empty() { |
| self.errors.push(Error::extraneous_field("ConfigType", "constraints")); |
| } |
| } |
| fdecl::ConfigTypeLayout::String => { |
| // String has exactly one constraint and no parameter |
| if let Some(parameters) = &type_.parameters { |
| if !parameters.is_empty() { |
| self.errors.push(Error::extraneous_field("ConfigType", "parameters")); |
| } |
| } else { |
| self.errors.push(Error::missing_field("ConfigType", "parameters")); |
| } |
| |
| if type_.constraints.is_empty() { |
| self.errors.push(Error::missing_field("ConfigType", "constraints")); |
| } else if type_.constraints.len() > 1 { |
| self.errors.push(Error::extraneous_field("ConfigType", "constraints")); |
| } else if let fdecl::LayoutConstraint::MaxSize(_) = &type_.constraints[0] { |
| } else { |
| self.errors.push(Error::invalid_field("ConfigType", "constraints")); |
| } |
| } |
| fdecl::ConfigTypeLayout::Vector => { |
| if accept_vectors { |
| // Vector has exactly one constraint and one parameter |
| if let Some(parameters) = &type_.parameters { |
| if parameters.is_empty() { |
| self.errors.push(Error::missing_field("ConfigType", "parameters")); |
| } else if parameters.len() > 1 { |
| self.errors.push(Error::extraneous_field("ConfigType", "parameters")); |
| } else if let fdecl::LayoutParameter::NestedType(nested_type) = |
| ¶meters[0] |
| { |
| self.validate_config_type(nested_type, false); |
| } else { |
| self.errors.push(Error::invalid_field("ConfigType", "parameters")); |
| } |
| } else { |
| self.errors.push(Error::missing_field("ConfigType", "parameters")) |
| } |
| |
| if type_.constraints.is_empty() { |
| self.errors.push(Error::missing_field("ConfigType", "constraints")); |
| } else if type_.constraints.len() > 1 { |
| self.errors.push(Error::extraneous_field("ConfigType", "constraints")); |
| } else if let fdecl::LayoutConstraint::MaxSize(_) = &type_.constraints[0] { |
| } else { |
| self.errors.push(Error::invalid_field("ConfigType", "constraints")); |
| } |
| } else { |
| self.errors.push(Error::nested_vector()); |
| } |
| } |
| _ => self.errors.push(Error::invalid_field("ConfigType", "layout")), |
| } |
| } |
| |
| /// Validates an individual capability declaration as either a built-in capability or (if |
| /// `as_builtin = false`) as a component or namespace capability. |
| // Storage capabilities are not currently allowed as built-ins, but there's no deep reason for this. |
| // Update this method to allow built-in storage capabilities as needed. |
| fn validate_capability_decl(&mut self, capability: &'a fdecl::Capability, as_builtin: bool) { |
| match capability { |
| fdecl::Capability::Service(service) => self.validate_service_decl(&service, as_builtin), |
| fdecl::Capability::Protocol(protocol) => { |
| self.validate_protocol_decl(&protocol, as_builtin) |
| } |
| fdecl::Capability::Directory(directory) => { |
| self.validate_directory_decl(&directory, as_builtin) |
| } |
| fdecl::Capability::Storage(storage) => { |
| if as_builtin { |
| self.errors.push(Error::invalid_capability_type( |
| "RuntimeConfig", |
| "capability", |
| "storage", |
| )) |
| } else { |
| self.validate_storage_decl(&storage) |
| } |
| } |
| fdecl::Capability::Runner(runner) => self.validate_runner_decl(&runner, as_builtin), |
| fdecl::Capability::Resolver(resolver) => { |
| self.validate_resolver_decl(&resolver, as_builtin) |
| } |
| fdecl::Capability::Event(event) => { |
| if as_builtin { |
| self.validate_event_decl(&event) |
| } else { |
| self.errors.push(Error::invalid_capability_type( |
| "Component", |
| "capability", |
| "event", |
| )) |
| } |
| } |
| _ => { |
| if as_builtin { |
| self.errors.push(Error::invalid_capability_type( |
| "RuntimeConfig", |
| "capability", |
| "unknown", |
| )); |
| } else { |
| self.errors.push(Error::invalid_capability_type( |
| "Component", |
| "capability", |
| "unknown", |
| )); |
| } |
| } |
| } |
| } |
| |
| fn validate_use_decls(&mut self, uses: &'a [fdecl::Use]) { |
| // Validate all events first so that we keep track of them for validation of event_streams. |
| for use_ in uses.iter() { |
| match use_ { |
| fdecl::Use::Event(e) => self.validate_event(e), |
| _ => {} |
| } |
| } |
| |
| // Validate individual fields. |
| for use_ in uses.iter() { |
| self.validate_use_decl(&use_); |
| } |
| |
| self.validate_use_paths(&uses); |
| } |
| |
| fn validate_use_decl(&mut self, use_: &'a fdecl::Use) { |
| match use_ { |
| fdecl::Use::Service(u) => { |
| self.validate_use_source( |
| u.source.as_ref(), |
| u.source_name.as_ref(), |
| u.dependency_type.as_ref(), |
| u.availability.as_ref(), |
| "UseService", |
| "source", |
| ); |
| check_name(u.source_name.as_ref(), "UseService", "source_name", &mut self.errors); |
| check_path(u.target_path.as_ref(), "UseService", "target_path", &mut self.errors); |
| } |
| fdecl::Use::Protocol(u) => { |
| self.validate_use_source( |
| u.source.as_ref(), |
| u.source_name.as_ref(), |
| u.dependency_type.as_ref(), |
| u.availability.as_ref(), |
| "UseProtocol", |
| "source", |
| ); |
| check_name(u.source_name.as_ref(), "UseProtocol", "source_name", &mut self.errors); |
| check_path(u.target_path.as_ref(), "UseProtocol", "target_path", &mut self.errors); |
| } |
| fdecl::Use::Directory(u) => { |
| self.validate_use_source( |
| u.source.as_ref(), |
| u.source_name.as_ref(), |
| u.dependency_type.as_ref(), |
| u.availability.as_ref(), |
| "UseDirectory", |
| "source", |
| ); |
| check_name(u.source_name.as_ref(), "UseDirectory", "source_name", &mut self.errors); |
| check_path(u.target_path.as_ref(), "UseDirectory", "target_path", &mut self.errors); |
| if u.rights.is_none() { |
| self.errors.push(Error::missing_field("UseDirectory", "rights")); |
| } |
| if let Some(subdir) = u.subdir.as_ref() { |
| check_relative_path(Some(subdir), "UseDirectory", "subdir", &mut self.errors); |
| } |
| } |
| fdecl::Use::Storage(u) => { |
| check_name(u.source_name.as_ref(), "UseStorage", "source_name", &mut self.errors); |
| check_path(u.target_path.as_ref(), "UseStorage", "target_path", &mut self.errors); |
| check_use_availability("UseStorage", u.availability.as_ref(), &mut self.errors); |
| } |
| fdecl::Use::EventStreamDeprecated(e) => { |
| self.validate_event_stream_deprecated(e); |
| check_use_availability( |
| "UseEventStreamDeprecated", |
| e.availability.as_ref(), |
| &mut self.errors, |
| ); |
| } |
| fdecl::Use::Event(_) => { |
| // Skip events. We must have already validated by this point. |
| // See `validate_use_decls`. |
| } |
| fdecl::Use::EventStream(u) => { |
| self.validate_event_stream(u); |
| check_use_availability("UseEventStream", u.availability.as_ref(), &mut self.errors); |
| } |
| _ => { |
| self.errors.push(Error::invalid_field("Component", "use")); |
| } |
| } |
| } |
| |
| fn validate_event_stream(&mut self, u: &fdecl::UseEventStream) { |
| match u.source { |
| Some(fdecl::Ref::Child(_)) | Some(fdecl::Ref::Parent(_)) => {} |
| Some(fdecl::Ref::Framework(_)) => match &u.scope { |
| Some(value) if value.is_empty() => { |
| self.errors.push(Error::invalid_field("UseEventStream", "scope")); |
| } |
| Some(_) => {} |
| None => { |
| self.errors.push(Error::missing_field("UseEventStream", "scope")); |
| } |
| }, |
| _ => { |
| self.errors.push(Error::invalid_field("UseEventStream", "source")); |
| } |
| } |
| if let Some(scope) = &u.scope { |
| for reference in scope { |
| if !matches!(reference, fdecl::Ref::Child(_) | fdecl::Ref::Collection(_)) { |
| self.errors.push(Error::invalid_field("UseEventStream", "scope")); |
| } |
| } |
| } |
| check_path(u.target_path.as_ref(), "UseEventStream", "target_path", &mut self.errors); |
| if let Some(name) = &u.source_name { |
| check_name(Some(name), "UseEventStream", "source_name", &mut self.errors); |
| } else { |
| self.errors.push(Error::missing_field("UseEventStream", "source_name")); |
| } |
| } |
| |
| /// Validates the "program" declaration. This does not check runner-specific properties |
| /// since those are checked by the runner. |
| fn validate_program(&mut self, program: &fdecl::Program) { |
| if program.runner.is_none() { |
| self.errors.push(Error::missing_field("Program", "runner")); |
| } |
| } |
| |
| /// Validates that paths-based capabilities (service, directory, protocol) |
| /// are different and not prefixes of each other. |
| fn validate_use_paths(&mut self, uses: &[fdecl::Use]) { |
| #[derive(Debug, PartialEq, Clone, Copy)] |
| struct PathCapability<'a> { |
| decl: &'a str, |
| dir: &'a Path, |
| use_: &'a fdecl::Use, |
| } |
| let mut used_paths = HashMap::new(); |
| for use_ in uses.iter() { |
| match use_ { |
| fdecl::Use::Service(fdecl::UseService { target_path: Some(path), .. }) |
| | fdecl::Use::Protocol(fdecl::UseProtocol { target_path: Some(path), .. }) |
| | fdecl::Use::Directory(fdecl::UseDirectory { target_path: Some(path), .. }) |
| | fdecl::Use::Storage(fdecl::UseStorage { target_path: Some(path), .. }) => { |
| let capability = match use_ { |
| fdecl::Use::Service(_) => { |
| let dir = match Path::new(path).parent() { |
| Some(p) => p, |
| None => continue, // Invalid path, validated elsewhere |
| }; |
| PathCapability { decl: "UseService", dir, use_ } |
| } |
| fdecl::Use::Protocol(_) => { |
| let dir = match Path::new(path).parent() { |
| Some(p) => p, |
| None => continue, // Invalid path, validated elsewhere |
| }; |
| PathCapability { decl: "UseProtocol", dir, use_ } |
| } |
| fdecl::Use::Directory(_) => { |
| PathCapability { decl: "UseDirectory", dir: Path::new(path), use_ } |
| } |
| fdecl::Use::Storage(_) => { |
| PathCapability { decl: "UseStorage", dir: Path::new(path), use_ } |
| } |
| _ => unreachable!(), |
| }; |
| if used_paths.insert(path, capability).is_some() { |
| // Disallow multiple capabilities for the same path. |
| self.errors.push(Error::duplicate_field(capability.decl, "path", path)); |
| } |
| } |
| _ => {} |
| } |
| } |
| for ((&path_a, capability_a), (&path_b, capability_b)) in |
| used_paths.iter().tuple_combinations() |
| { |
| if match (capability_a.use_, capability_b.use_) { |
| // Directories and storage can't be the same or partially overlap. |
| (fdecl::Use::Directory(_), fdecl::Use::Directory(_)) |
| | (fdecl::Use::Storage(_), fdecl::Use::Directory(_)) |
| | (fdecl::Use::Directory(_), fdecl::Use::Storage(_)) |
| | (fdecl::Use::Storage(_), fdecl::Use::Storage(_)) => { |
| capability_b.dir == capability_a.dir |
| || capability_b.dir.starts_with(capability_a.dir) |
| || capability_a.dir.starts_with(capability_b.dir) |
| } |
| |
| // Protocols and Services can't overlap with Directories. |
| (_, fdecl::Use::Directory(_)) | (fdecl::Use::Directory(_), _) => { |
| capability_b.dir == capability_a.dir |
| || capability_b.dir.starts_with(capability_a.dir) |
| || capability_a.dir.starts_with(capability_b.dir) |
| } |
| |
| // Protocols and Services containing directories may be same, but |
| // partial overlap is disallowed. |
| (_, _) => { |
| capability_b.dir != capability_a.dir |
| && (capability_b.dir.starts_with(capability_a.dir) |
| || capability_a.dir.starts_with(capability_b.dir)) |
| } |
| } { |
| self.errors.push(Error::invalid_path_overlap( |
| capability_a.decl, |
| path_a, |
| capability_b.decl, |
| path_b, |
| )); |
| } |
| } |
| } |
| |
| fn validate_event(&mut self, event: &'a fdecl::UseEvent) { |
| self.validate_use_source( |
| event.source.as_ref(), |
| event.source_name.as_ref(), |
| event.dependency_type.as_ref(), |
| event.availability.as_ref(), |
| "UseEvent", |
| "source", |
| ); |
| check_use_availability("UseEvent", event.availability.as_ref(), &mut self.errors); |
| if let Some(fdecl::Ref::Self_(_)) = event.source { |
| self.errors.push(Error::invalid_field("UseEvent", "source")); |
| } |
| check_name(event.source_name.as_ref(), "UseEvent", "source_name", &mut self.errors); |
| check_name(event.target_name.as_ref(), "UseEvent", "target_name", &mut self.errors); |
| if let Some(target_name) = event.target_name.as_ref() { |
| if !self.all_events.insert(target_name) { |
| self.errors.push(Error::duplicate_field("UseEvent", "target_name", target_name)); |
| } |
| } |
| } |
| |
| fn validate_event_stream_deprecated( |
| &mut self, |
| event_stream: &'a fdecl::UseEventStreamDeprecated, |
| ) { |
| check_name(event_stream.name.as_ref(), "UseEventStream", "name", &mut self.errors); |
| if let Some(name) = event_stream.name.as_ref() { |
| if !self.all_event_streams.insert(name) { |
| self.errors.push(Error::duplicate_field("UseEventStream", "name", name)); |
| } |
| } |
| match event_stream.subscriptions.as_ref() { |
| None => { |
| self.errors.push(Error::missing_field("UseEventStream", "subscriptions")); |
| } |
| Some(subscriptions) if subscriptions.is_empty() => { |
| self.errors.push(Error::empty_field("UseEventStream", "subscriptions")); |
| } |
| Some(subscriptions) => { |
| for subscription in subscriptions { |
| check_name( |
| subscription.event_name.as_ref(), |
| "UseEventStream", |
| "event_name", |
| &mut self.errors, |
| ); |
| let event_name = subscription.event_name.clone().unwrap_or_default(); |
| if !self.all_events.contains(event_name.as_str()) { |
| self.errors.push(Error::event_stream_event_not_found( |
| "UseEventStream", |
| "events", |
| event_name, |
| )); |
| } |
| } |
| } |
| } |
| } |
| |
| // disallow (use from #child dependency=strong) && (offer to #child from self) |
| // - err: `use` must have dependency=weak to prevent cycle |
| // add strong dependencies to dependency graph, so we can check for cycles |
| fn validate_use_source( |
| &mut self, |
| source: Option<&'a fdecl::Ref>, |
| source_name: Option<&'a String>, |
| dependency_type: Option<&fdecl::DependencyType>, |
| availability: Option<&'a fdecl::Availability>, |
| decl: &str, |
| field: &str, |
| ) { |
| match source { |
| Some(fdecl::Ref::Parent(_)) => {} |
| Some(fdecl::Ref::Framework(_)) => {} |
| Some(fdecl::Ref::Debug(_)) => {} |
| Some(fdecl::Ref::Self_(_)) => {} |
| Some(fdecl::Ref::Capability(capability)) => { |
| if !self.all_capability_ids.contains(capability.name.as_str()) { |
| self.errors.push(Error::invalid_capability(decl, field, &capability.name)); |
| } else if dependency_type == Some(&fdecl::DependencyType::Strong) { |
| self.add_strong_dep( |
| source_name, |
| DependencyNode::try_from_ref(source), |
| Some(DependencyNode::Self_), |
| ); |
| } |
| } |
| Some(fdecl::Ref::Child(child)) => { |
| if !self.all_children.contains_key(&child.name as &str) { |
| self.errors.push(Error::invalid_child(decl, field, &child.name)); |
| } else if dependency_type == Some(&fdecl::DependencyType::Strong) { |
| self.add_strong_dep( |
| source_name, |
| DependencyNode::try_from_ref(source), |
| Some(DependencyNode::Self_), |
| ); |
| } |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl, field)); |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl, field)); |
| } |
| }; |
| check_use_availability(decl, availability, &mut self.errors); |
| |
| match (source, availability) { |
| // Using from a parent can have any availability |
| (Some(&fdecl::Ref::Parent(_)), _) => (), |
| // In all other cases the availability must be required |
| (_, Some(&fdecl::Availability::Required)) => (), |
| // TODO(dgonyeo): we need to handle the availability being unset until we've soft |
| // migrated all manifests |
| (_, None) => (), |
| _ => self.errors.push(Error::availability_must_be_required( |
| decl, |
| "availability", |
| source_name, |
| )), |
| } |
| |
| let is_use_from_child = match source { |
| Some(fdecl::Ref::Child(_)) => true, |
| _ => false, |
| }; |
| match (is_use_from_child, dependency_type) { |
| ( |
| false, |
| Some(fdecl::DependencyType::Weak) | Some(fdecl::DependencyType::WeakForMigration), |
| ) => { |
| self.errors.push(Error::invalid_field(decl, "dependency_type")); |
| } |
| _ => {} |
| } |
| } |
| |
| fn validate_child_decl(&mut self, child: &'a fdecl::Child) { |
| if let Err(mut e) = validate_child(child, check_name) { |
| self.errors.append(&mut e.errs); |
| } |
| if let Some(name) = child.name.as_ref() { |
| let name: &str = name; |
| if self.all_children.insert(name, child).is_some() { |
| self.errors.push(Error::duplicate_field("Child", "name", name)); |
| } |
| if let Some(env) = child.environment.as_ref() { |
| let source = DependencyNode::Environment(env.as_str()); |
| let target = DependencyNode::Child(name); |
| self.add_strong_dep(None, Some(source), Some(target)); |
| } |
| } |
| if let Some(environment) = child.environment.as_ref() { |
| if !self.all_environment_names.contains(environment.as_str()) { |
| self.errors.push(Error::invalid_environment("Child", "environment", environment)); |
| } |
| } |
| } |
| |
| fn validate_collection_decl(&mut self, collection: &'a fdecl::Collection) { |
| let name = collection.name.as_ref(); |
| if check_name(name, "Collection", "name", &mut self.errors) { |
| let name: &str = name.unwrap(); |
| if !self.all_collections.insert(name) { |
| self.errors.push(Error::duplicate_field("Collection", "name", name)); |
| } |
| } |
| if collection.durability.is_none() { |
| self.errors.push(Error::missing_field("Collection", "durability")); |
| } |
| if let Some(environment) = collection.environment.as_ref() { |
| if !self.all_environment_names.contains(environment.as_str()) { |
| self.errors.push(Error::invalid_environment( |
| "Collection", |
| "environment", |
| environment, |
| )); |
| } |
| if let Some(name) = collection.name.as_ref() { |
| let source = DependencyNode::Environment(environment.as_str()); |
| let target = DependencyNode::Collection(name.as_str()); |
| self.add_strong_dep(None, Some(source), Some(target)); |
| } |
| } |
| // Allow `allowed_offers` & `allow_long_names` to be unset/unvalidated, for backwards compatibility. |
| } |
| |
| fn validate_environment_decl(&mut self, environment: &'a fdecl::Environment) { |
| let name = environment.name.as_ref(); |
| check_name(name, "Environment", "name", &mut self.errors); |
| if environment.extends.is_none() { |
| self.errors.push(Error::missing_field("Environment", "extends")); |
| } |
| if let Some(runners) = environment.runners.as_ref() { |
| let mut registered_runners = HashSet::new(); |
| for runner in runners { |
| self.validate_runner_registration(runner, name.clone(), &mut registered_runners); |
| } |
| } |
| if let Some(resolvers) = environment.resolvers.as_ref() { |
| let mut registered_schemes = HashSet::new(); |
| for resolver in resolvers { |
| self.validate_resolver_registration( |
| resolver, |
| name.clone(), |
| &mut registered_schemes, |
| ); |
| } |
| } |
| |
| match environment.extends.as_ref() { |
| Some(fdecl::EnvironmentExtends::None) => { |
| if environment.stop_timeout_ms.is_none() { |
| self.errors.push(Error::missing_field("Environment", "stop_timeout_ms")); |
| } |
| } |
| None | Some(fdecl::EnvironmentExtends::Realm) => {} |
| } |
| |
| if let Some(debugs) = environment.debug_capabilities.as_ref() { |
| for debug in debugs { |
| self.validate_environment_debug_registration(debug, name.clone()); |
| } |
| } |
| } |
| |
| fn validate_runner_registration( |
| &mut self, |
| runner_registration: &'a fdecl::RunnerRegistration, |
| environment_name: Option<&'a String>, |
| runner_names: &mut HashSet<&'a str>, |
| ) { |
| check_name( |
| runner_registration.source_name.as_ref(), |
| "RunnerRegistration", |
| "source_name", |
| &mut self.errors, |
| ); |
| self.validate_registration_source( |
| environment_name, |
| runner_registration.source.as_ref(), |
| "RunnerRegistration", |
| ); |
| // If the source is `self`, ensure we have a corresponding Runner. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = |
| (&runner_registration.source, &runner_registration.source_name) |
| { |
| if !self.all_runners.contains(name as &str) { |
| self.errors.push(Error::invalid_runner("RunnerRegistration", "source_name", name)); |
| } |
| } |
| |
| check_name( |
| runner_registration.target_name.as_ref(), |
| "RunnerRegistration", |
| "target_name", |
| &mut self.errors, |
| ); |
| if let Some(name) = runner_registration.target_name.as_ref() { |
| if !runner_names.insert(name.as_str()) { |
| self.errors.push(Error::duplicate_field("RunnerRegistration", "target_name", name)); |
| } |
| } |
| } |
| |
| fn validate_resolver_registration( |
| &mut self, |
| resolver_registration: &'a fdecl::ResolverRegistration, |
| environment_name: Option<&'a String>, |
| schemes: &mut HashSet<&'a str>, |
| ) { |
| check_name( |
| resolver_registration.resolver.as_ref(), |
| "ResolverRegistration", |
| "resolver", |
| &mut self.errors, |
| ); |
| self.validate_registration_source( |
| environment_name, |
| resolver_registration.source.as_ref(), |
| "ResolverRegistration", |
| ); |
| check_url_scheme( |
| resolver_registration.scheme.as_ref(), |
| "ResolverRegistration", |
| "scheme", |
| &mut self.errors, |
| ); |
| if let Some(scheme) = resolver_registration.scheme.as_ref() { |
| if !schemes.insert(scheme.as_str()) { |
| self.errors.push(Error::duplicate_field("ResolverRegistration", "scheme", scheme)); |
| } |
| } |
| } |
| |
| fn validate_registration_source( |
| &mut self, |
| environment_name: Option<&'a String>, |
| source: Option<&'a fdecl::Ref>, |
| ty: &str, |
| ) { |
| match source { |
| Some(fdecl::Ref::Parent(_)) => {} |
| Some(fdecl::Ref::Self_(_)) => {} |
| Some(fdecl::Ref::Child(child_ref)) => { |
| // Make sure the child is valid. |
| self.validate_child_ref(ty, "source", &child_ref); |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(ty, "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(ty, "source")); |
| } |
| } |
| |
| let source = DependencyNode::try_from_ref(source); |
| if let Some(source) = source { |
| if let Some(env_name) = &environment_name { |
| let target = DependencyNode::Environment(env_name); |
| self.strong_dependencies.add_edge(source, target); |
| } |
| } |
| } |
| |
| fn validate_service_decl(&mut self, service: &'a fdecl::Service, as_builtin: bool) { |
| if check_name(service.name.as_ref(), "Service", "name", &mut self.errors) { |
| let name = service.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Service", "name", name.as_str())); |
| } |
| self.all_services.insert(name); |
| } |
| match as_builtin { |
| true => { |
| if let Some(path) = service.source_path.as_ref() { |
| self.errors.push(Error::extraneous_source_path("Service", path)) |
| } |
| } |
| false => { |
| check_path( |
| service.source_path.as_ref(), |
| "Service", |
| "source_path", |
| &mut self.errors, |
| ); |
| } |
| } |
| } |
| |
| fn validate_protocol_decl(&mut self, protocol: &'a fdecl::Protocol, as_builtin: bool) { |
| if check_name(protocol.name.as_ref(), "Protocol", "name", &mut self.errors) { |
| let name = protocol.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Protocol", "name", name.as_str())); |
| } |
| self.all_protocols.insert(name); |
| } |
| match as_builtin { |
| true => { |
| if let Some(path) = protocol.source_path.as_ref() { |
| self.errors.push(Error::extraneous_source_path("Protocol", path)) |
| } |
| } |
| false => { |
| check_path( |
| protocol.source_path.as_ref(), |
| "Protocol", |
| "source_path", |
| &mut self.errors, |
| ); |
| } |
| } |
| } |
| |
| fn validate_directory_decl(&mut self, directory: &'a fdecl::Directory, as_builtin: bool) { |
| if check_name(directory.name.as_ref(), "Directory", "name", &mut self.errors) { |
| let name = directory.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Directory", "name", name.as_str())); |
| } |
| self.all_directories.insert(name); |
| } |
| match as_builtin { |
| true => { |
| if let Some(path) = directory.source_path.as_ref() { |
| self.errors.push(Error::extraneous_source_path("Directory", path)) |
| } |
| } |
| false => { |
| check_path( |
| directory.source_path.as_ref(), |
| "Directory", |
| "source_path", |
| &mut self.errors, |
| ); |
| } |
| } |
| if directory.rights.is_none() { |
| self.errors.push(Error::missing_field("Directory", "rights")); |
| } |
| } |
| |
| fn validate_storage_decl(&mut self, storage: &'a fdecl::Storage) { |
| match storage.source.as_ref() { |
| Some(fdecl::Ref::Parent(_)) => {} |
| Some(fdecl::Ref::Self_(_)) => {} |
| Some(fdecl::Ref::Child(child)) => { |
| self.validate_source_child(child, "Storage", OfferType::Static); |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field("Storage", "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field("Storage", "source")); |
| } |
| }; |
| if check_name(storage.name.as_ref(), "Storage", "name", &mut self.errors) { |
| let name = storage.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Storage", "name", name.as_str())); |
| } |
| self.all_storage_and_sources.insert(name, storage.source.as_ref()); |
| } |
| if storage.storage_id.is_none() { |
| self.errors.push(Error::missing_field("Storage", "storage_id")); |
| } |
| check_name(storage.backing_dir.as_ref(), "Storage", "backing_dir", &mut self.errors); |
| } |
| |
| fn validate_runner_decl(&mut self, runner: &'a fdecl::Runner, as_builtin: bool) { |
| if check_name(runner.name.as_ref(), "Runner", "name", &mut self.errors) { |
| let name = runner.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Runner", "name", name.as_str())); |
| } |
| self.all_runners.insert(name); |
| } |
| match as_builtin { |
| true => { |
| if let Some(path) = runner.source_path.as_ref() { |
| self.errors.push(Error::extraneous_source_path("Runner", path)) |
| } |
| } |
| false => { |
| check_path(runner.source_path.as_ref(), "Runner", "source_path", &mut self.errors); |
| } |
| } |
| } |
| |
| fn validate_resolver_decl(&mut self, resolver: &'a fdecl::Resolver, as_builtin: bool) { |
| if check_name(resolver.name.as_ref(), "Resolver", "name", &mut self.errors) { |
| let name = resolver.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Resolver", "name", name.as_str())); |
| } |
| self.all_resolvers.insert(name); |
| } |
| match as_builtin { |
| true => { |
| if let Some(path) = resolver.source_path.as_ref() { |
| self.errors.push(Error::extraneous_source_path("Resolver", path)) |
| } |
| } |
| false => { |
| check_path( |
| resolver.source_path.as_ref(), |
| "Resolver", |
| "source_path", |
| &mut self.errors, |
| ); |
| } |
| } |
| } |
| |
| fn validate_environment_debug_registration( |
| &mut self, |
| debug: &'a fdecl::DebugRegistration, |
| environment_name: Option<&'a String>, |
| ) { |
| match debug { |
| fdecl::DebugRegistration::Protocol(o) => { |
| let decl = "DebugProtocolRegistration"; |
| self.validate_environment_debug_fields( |
| decl, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_protocols.contains(&name as &str) { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| } |
| |
| if let Some(env_name) = &environment_name { |
| let source = DependencyNode::try_from_ref(o.source.as_ref()); |
| let target = Some(DependencyNode::Environment(env_name)); |
| self.add_strong_dep(None, source, target); |
| } |
| } |
| _ => { |
| self.errors.push(Error::invalid_field("Environment", "debug")); |
| } |
| } |
| } |
| |
| fn validate_environment_debug_fields( |
| &mut self, |
| decl: &str, |
| source: Option<&fdecl::Ref>, |
| source_name: Option<&String>, |
| target_name: Option<&'a String>, |
| ) { |
| // We don't support "source" from "capability" for now. |
| match source { |
| Some(fdecl::Ref::Parent(_)) => {} |
| Some(fdecl::Ref::Self_(_)) => {} |
| Some(fdecl::Ref::Framework(_)) => {} |
| Some(fdecl::Ref::Child(child)) => { |
| self.validate_source_child(child, decl, OfferType::Static) |
| } |
| Some(_) => self.errors.push(Error::invalid_field(decl, "source")), |
| None => self.errors.push(Error::missing_field(decl, "source")), |
| } |
| check_name(source_name, decl, "source_name", &mut self.errors); |
| check_name(target_name, decl, "target_name", &mut self.errors); |
| } |
| |
| fn validate_event_decl(&mut self, event: &'a fdecl::Event) { |
| if check_name(event.name.as_ref(), "Event", "name", &mut self.errors) { |
| let name = event.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("Event", "name", name.as_str())); |
| } |
| } |
| } |
| |
| fn validate_source_child( |
| &mut self, |
| child: &fdecl::ChildRef, |
| decl_type: &str, |
| offer_type: OfferType, |
| ) { |
| let mut valid = true; |
| valid &= check_offer_name( |
| Some(&child.name), |
| decl_type, |
| "source.child.name", |
| &mut self.errors, |
| offer_type, |
| ); |
| match offer_type { |
| OfferType::Static => { |
| valid &= if child.collection.is_some() { |
| self.errors.push(Error::extraneous_field(decl_type, "source.child.collection")); |
| false |
| } else { |
| true |
| }; |
| if !valid { |
| return; |
| } |
| if !self.all_children.contains_key(&child.name as &str) { |
| self.errors.push(Error::invalid_child( |
| decl_type, |
| "source", |
| &child.name as &str, |
| )); |
| } |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| |
| fn validate_source_collection(&mut self, collection: &fdecl::CollectionRef, decl_type: &str) { |
| if !check_name( |
| Some(&collection.name), |
| decl_type, |
| "source.collection.name", |
| &mut self.errors, |
| ) { |
| return; |
| } |
| if !self.all_collections.contains(&collection.name as &str) { |
| self.errors.push(Error::invalid_collection( |
| decl_type, |
| "source", |
| &collection.name as &str, |
| )); |
| } |
| } |
| |
| fn validate_filtered_service_fields( |
| &mut self, |
| decl_type: &str, |
| source_instance_filter: Option<&Vec<String>>, |
| renamed_instances: Option<&Vec<fdecl::NameMapping>>, |
| ) { |
| if let Some(source_instance_filter) = source_instance_filter { |
| if source_instance_filter.is_empty() { |
| // if the source_instance_filter is empty the offered service will have 0 instances, |
| // which means the offer shouldn't have been created at all. |
| self.errors.push(Error::invalid_field(decl_type, "source_instance_filter")); |
| } |
| } |
| if let Some(renamed_instances) = renamed_instances { |
| // Multiple sources shouldn't map to the same target name |
| let mut seen_target_names = HashSet::<String>::new(); |
| for mapping in renamed_instances { |
| if !seen_target_names.insert(mapping.target_name.clone()) { |
| self.errors.push(Error::invalid_field(decl_type, "renamed_instances")); |
| break; |
| } |
| } |
| } |
| } |
| |
| fn validate_source_capability( |
| &mut self, |
| capability: &fdecl::CapabilityRef, |
| decl_type: &str, |
| field: &str, |
| ) { |
| if !self.all_capability_ids.contains(capability.name.as_str()) { |
| self.errors.push(Error::invalid_capability(decl_type, field, &capability.name)); |
| } |
| } |
| |
| fn validate_storage_source(&mut self, source_name: &String, decl_type: &str) { |
| if check_name(Some(source_name), decl_type, "source.storage.name", &mut self.errors) { |
| if !self.all_storage_and_sources.contains_key(source_name.as_str()) { |
| self.errors.push(Error::invalid_storage(decl_type, "source", source_name)); |
| } |
| } |
| } |
| |
| fn validate_expose_decl( |
| &mut self, |
| expose: &'a fdecl::Expose, |
| prev_target_ids: &mut HashMap<&'a str, AllowableIds>, |
| ) { |
| match expose { |
| fdecl::Expose::Service(e) => { |
| let decl = "ExposeService"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::Many, |
| CollectionSource::Allow, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding Service. |
| // TODO: Consider bringing this bit into validate_expose_fields. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_services.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fdecl::Expose::Protocol(e) => { |
| let decl = "ExposeProtocol"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding Protocol. |
| // TODO: Consider bringing this bit into validate_expose_fields. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_protocols.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fdecl::Expose::Directory(e) => { |
| let decl = "ExposeDirectory"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding Directory. |
| // TODO: Consider bringing this bit into validate_expose_fields. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_directories.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| if name.starts_with('/') && e.rights.is_none() { |
| self.errors.push(Error::missing_field(decl, "rights")); |
| } |
| } |
| |
| // Subdir makes sense when routing, but when exposing to framework the subdirectory |
| // can be exposed directly. |
| match e.target.as_ref() { |
| Some(fdecl::Ref::Framework(_)) => { |
| if e.subdir.is_some() { |
| self.errors.push(Error::invalid_field(decl, "subdir")); |
| } |
| } |
| _ => {} |
| } |
| |
| if let Some(subdir) = e.subdir.as_ref() { |
| check_relative_path(Some(subdir), decl, "subdir", &mut self.errors); |
| } |
| } |
| fdecl::Expose::Runner(e) => { |
| let decl = "ExposeRunner"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding Runner. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_runners.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fdecl::Expose::Resolver(e) => { |
| let decl = "ExposeResolver"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding Resolver. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_resolvers.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fdecl::Expose::EventStream(e) => { |
| let decl = "ExposeEventStream"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // Exposing to framework from framework should never be valid. |
| if e.target == Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})) { |
| self.errors.push(Error::invalid_field("ExposeEventStream", "target")); |
| } |
| if let Some(scope) = &e.scope { |
| if scope.is_empty() { |
| self.errors.push(Error::invalid_field(decl, "scope")); |
| } |
| for value in scope { |
| match value { |
| fdecl::Ref::Child(child) => { |
| self.validate_child_ref("ExposeEventStream", "scope", &child); |
| } |
| fdecl::Ref::Collection(child) => { |
| self.validate_collection_ref("ExposeEventStream", "scope", &child); |
| } |
| _ => { |
| self.errors |
| .push(Error::invalid_field("ExposeEventStream", "scope")); |
| } |
| } |
| } |
| } |
| if e.source == Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})) |
| && e.scope == None |
| { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| } |
| _ => { |
| self.errors.push(Error::invalid_field("Component", "expose")); |
| } |
| } |
| } |
| |
| fn validate_expose_fields( |
| &mut self, |
| decl: &str, |
| allowable_ids: AllowableIds, |
| collection_source: CollectionSource, |
| source: Option<&fdecl::Ref>, |
| source_name: Option<&String>, |
| target_name: Option<&'a String>, |
| target: Option<&fdecl::Ref>, |
| prev_child_target_ids: &mut HashMap<&'a str, AllowableIds>, |
| ) { |
| match source { |
| Some(r) => match r { |
| fdecl::Ref::Self_(_) => {} |
| fdecl::Ref::Framework(_) => {} |
| fdecl::Ref::Child(child) => { |
| self.validate_source_child(child, decl, OfferType::Static); |
| } |
| fdecl::Ref::Capability(c) => { |
| self.validate_source_capability(c, decl, "source"); |
| } |
| fdecl::Ref::Collection(c) if collection_source == CollectionSource::Allow => { |
| self.validate_source_collection(c, decl); |
| } |
| _ => { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| }, |
| None => { |
| self.errors.push(Error::missing_field(decl, "source")); |
| } |
| } |
| match target { |
| Some(r) => match r { |
| fdecl::Ref::Parent(_) => {} |
| fdecl::Ref::Framework(_) => { |
| if source != Some(&fdecl::Ref::Self_(fdecl::SelfRef {})) { |
| self.errors.push(Error::invalid_field(decl, "target")); |
| } |
| } |
| _ => { |
| self.errors.push(Error::invalid_field(decl, "target")); |
| } |
| }, |
| None => { |
| self.errors.push(Error::missing_field(decl, "target")); |
| } |
| } |
| check_name(source_name, decl, "source_name", &mut self.errors); |
| if check_name(target_name, decl, "target_name", &mut self.errors) { |
| // TODO: This logic needs to pair the target name with the target before concluding |
| // there's a duplicate. |
| let target_name = target_name.unwrap(); |
| if let Some(prev_state) = prev_child_target_ids.insert(target_name, allowable_ids) { |
| if prev_state == AllowableIds::One || prev_state != allowable_ids { |
| self.errors.push(Error::duplicate_field(decl, "target_name", target_name)); |
| } |
| } |
| } |
| } |
| |
| /// Adds a strong dependency between two nodes in the dependency graph between `source` and |
| /// `target`. |
| /// |
| /// `source_name` is the name of the capability being routed (if applicable). The function is |
| /// a no-op if `source` or `target` is `None`; this behavior is a convenience so that the |
| /// caller can directly pass the result of `DependencyNode::try_from_ref`. |
| fn add_strong_dep( |
| &mut self, |
| source_name: Option<&'a String>, |
| source: Option<DependencyNode<'a>>, |
| target: Option<DependencyNode<'a>>, |
| ) { |
| if source.is_none() || target.is_none() { |
| return; |
| } |
| let source = source.unwrap(); |
| let target = target.unwrap(); |
| |
| let source = { |
| // A dependency on a storage capability from `self` is really a dependency on the |
| // backing dir. Perform that translation here. |
| let possible_storage_name = match (source, source_name) { |
| (DependencyNode::Capability(name), _) => Some(name), |
| (DependencyNode::Self_, Some(name)) => Some(name.as_str()), |
| _ => None, |
| }; |
| let possible_storage_source = |
| possible_storage_name.map(|name| self.all_storage_and_sources.get(name)).flatten(); |
| let source = possible_storage_source |
| .map(|r| DependencyNode::try_from_ref(*r)) |
| .unwrap_or(Some(source)); |
| if source.is_none() { |
| return; |
| } |
| source.unwrap() |
| }; |
| |
| if source == target { |
| // This is already its own error, or is a valid `use from self`, don't report this as a |
| // cycle. |
| } else { |
| self.strong_dependencies.add_edge(source, target); |
| } |
| } |
| |
| // Checks a group of offer decls to confirm that any duplicate offers are |
| // valid aggregate offer declarations. |
| fn validate_offer_group(&mut self, offers: &Vec<fdecl::Offer>) { |
| let mut offer_groups: HashMap<String, Vec<fdecl::OfferService>> = HashMap::new(); |
| let service_offers = offers.into_iter().filter_map(|o| { |
| if let fdecl::Offer::Service(s) = o { |
| Some(s) |
| } else { |
| None |
| } |
| }); |
| for offer in service_offers { |
| let offer_group_key = format!("{:?}_{:?}", offer.target_name, offer.target); |
| offer_groups.entry(offer_group_key).or_insert_with(|| vec![]).push(offer.clone()); |
| } |
| for (_, offer_group) in offer_groups { |
| if offer_group.len() == 1 { |
| // If there is not multiple offers for a target_name, target pair then there are no |
| // aggregation conditions to check. |
| continue; |
| } |
| let mut source_instance_filter_entries: HashSet<String> = HashSet::new(); |
| let mut service_source_names: HashSet<String> = HashSet::new(); |
| for o in offer_group { |
| // Currently only service capabilities can be aggregated |
| match o.source_instance_filter { |
| None => { |
| self.errors.push(Error::invalid_aggregate_offer( |
| "source_instance_filter must be set for all aggregate service offers", |
| )); |
| } |
| Some(source_instance_filter) => { |
| for instance_name in source_instance_filter { |
| if !source_instance_filter_entries.insert(instance_name.clone()) { |
| // If the source instance in the filter has been seen before this means there is a conflicting |
| // aggregate service offer. |
| self.errors.push(Error::invalid_aggregate_offer(format!("Conflicting source_instance_filter in aggregate service offer, instance_name '{}' seen in filter lists multiple times", instance_name))); |
| } |
| } |
| } |
| } |
| service_source_names.insert( |
| o.source_name |
| .expect("Offer Service declarations must always contain source_name"), |
| ); |
| } |
| if service_source_names.len() > 1 { |
| self.errors.push(Error::invalid_aggregate_offer(format!( |
| "All aggregate service offers must have the same source_name, saw {}. Use renamed_instances to rename instance names to avoid conflict.", |
| service_source_names.into_iter().sorted().collect::<Vec<String>>().join(", ") |
| ))); |
| } |
| } |
| } |
| fn validate_offers_decl(&mut self, offer: &'a fdecl::Offer, offer_type: OfferType) { |
| match offer { |
| fdecl::Offer::Service(o) => { |
| let decl = "OfferService"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::Many, |
| CollectionSource::Allow, |
| offer_type, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| o.availability.as_ref(), |
| ); |
| self.validate_filtered_service_fields( |
| decl, |
| o.source_instance_filter.as_ref(), |
| o.renamed_instances.as_ref(), |
| ); |
| match offer_type { |
| OfferType::Static => { |
| // If the offer source is `self`, ensure we have a corresponding Service. |
| // TODO: Consider bringing this bit into validate_offer_fields |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = |
| (&o.source, &o.source_name) |
| { |
| if !self.all_services.contains(&name as &str) { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| } |
| self.add_strong_dep( |
| o.source_name.as_ref(), |
| DependencyNode::try_from_ref(o.source.as_ref()), |
| DependencyNode::try_from_ref(o.target.as_ref()), |
| ); |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| fdecl::Offer::Protocol(o) => { |
| let decl = "OfferProtocol"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| offer_type, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| o.availability.as_ref(), |
| ); |
| if o.dependency_type.is_none() { |
| self.errors.push(Error::missing_field(decl, "dependency_type")); |
| } else if o.dependency_type == Some(fdecl::DependencyType::Strong) { |
| match offer_type { |
| OfferType::Static => { |
| self.add_strong_dep( |
| o.source_name.as_ref(), |
| DependencyNode::try_from_ref(o.source.as_ref()), |
| DependencyNode::try_from_ref(o.target.as_ref()), |
| ); |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| match offer_type { |
| OfferType::Static => { |
| // If the offer source is `self`, ensure we have a |
| // corresponding Protocol. |
| // TODO: Consider bringing this bit into validate_offer_fields. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = |
| (&o.source, &o.source_name) |
| { |
| if !self.all_protocols.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| fdecl::Offer::Directory(o) => { |
| let decl = "OfferDirectory"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| offer_type, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| o.availability.as_ref(), |
| ); |
| if o.dependency_type.is_none() { |
| self.errors.push(Error::missing_field(decl, "dependency_type")); |
| } else if o.dependency_type == Some(fdecl::DependencyType::Strong) { |
| match offer_type { |
| OfferType::Static => { |
| self.add_strong_dep( |
| o.source_name.as_ref(), |
| DependencyNode::try_from_ref(o.source.as_ref()), |
| DependencyNode::try_from_ref(o.target.as_ref()), |
| ); |
| // If the offer source is `self`, ensure we have a corresponding |
| // Directory. |
| // |
| // TODO: Consider bringing this bit into validate_offer_fields. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = |
| (&o.source, &o.source_name) |
| { |
| if !self.all_directories.contains(&name as &str) { |
| self.errors |
| .push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| |
| if let Some(subdir) = o.subdir.as_ref() { |
| check_relative_path(Some(subdir), "OfferDirectory", "subdir", &mut self.errors); |
| } |
| } |
| fdecl::Offer::Storage(o) => { |
| self.validate_storage_offer_fields( |
| "OfferStorage", |
| offer_type, |
| o.source_name.as_ref(), |
| o.source.as_ref(), |
| o.target.as_ref(), |
| o.availability.as_ref(), |
| ); |
| |
| match offer_type { |
| OfferType::Static => { |
| // Storage capabilities with a source of `Ref::Self_` |
| // don't interact with the component's runtime in any |
| // way, they're actually synthesized by the framework |
| // out of a pre-existing directory capability. Thus, its |
| // actual source is the backing directory capability. |
| match (o.source.as_ref(), o.source_name.as_ref()) { |
| (Some(fdecl::Ref::Self_ { .. }), Some(source_name)) => { |
| if let Some(source) = DependencyNode::try_from_ref( |
| *self |
| .all_storage_and_sources |
| .get(source_name.as_str()) |
| .unwrap_or(&None), |
| ) { |
| if let Some(target) = |
| DependencyNode::try_from_ref(o.target.as_ref()) |
| { |
| self.strong_dependencies.add_edge(source, target); |
| } |
| } |
| } |
| _ => self.add_strong_dep( |
| o.source_name.as_ref(), |
| DependencyNode::try_from_ref(o.source.as_ref()), |
| DependencyNode::try_from_ref(o.target.as_ref()), |
| ), |
| } |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| fdecl::Offer::Runner(o) => { |
| let decl = "OfferRunner"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| offer_type, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| Some(&fdecl::Availability::Required), |
| ); |
| match offer_type { |
| OfferType::Static => { |
| // If the offer source is `self`, ensure we have a corresponding Runner. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = |
| (&o.source, &o.source_name) |
| { |
| if !self.all_runners.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| self.add_strong_dep( |
| o.source_name.as_ref(), |
| DependencyNode::try_from_ref(o.source.as_ref()), |
| DependencyNode::try_from_ref(o.target.as_ref()), |
| ); |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| fdecl::Offer::Resolver(o) => { |
| let decl = "OfferResolver"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| CollectionSource::Deny, |
| offer_type, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| Some(&fdecl::Availability::Required), |
| ); |
| |
| match offer_type { |
| OfferType::Static => { |
| // If the offer source is `self`, ensure we have a |
| // corresponding Resolver. |
| if let (Some(fdecl::Ref::Self_(_)), Some(ref name)) = |
| (&o.source, &o.source_name) |
| { |
| if !self.all_resolvers.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| self.add_strong_dep( |
| o.source_name.as_ref(), |
| DependencyNode::try_from_ref(o.source.as_ref()), |
| DependencyNode::try_from_ref(o.target.as_ref()), |
| ); |
| } |
| OfferType::Dynamic => {} |
| } |
| } |
| fdecl::Offer::Event(e) => { |
| self.validate_event_offer_fields(e, offer_type); |
| } |
| fdecl::Offer::EventStream(e) => { |
| self.validate_event_stream_offer_fields(e, offer_type); |
| } |
| _ => { |
| self.errors.push(Error::invalid_field("Component", "offer")); |
| } |
| } |
| } |
| |
| /// Validates that the offer target is to a valid child or collection. |
| fn validate_offer_target( |
| &mut self, |
| target: &'a Option<fdecl::Ref>, |
| decl_type: &str, |
| field_name: &str, |
| ) -> Option<TargetId<'a>> { |
| match target.as_ref() { |
| Some(fdecl::Ref::Child(child)) => { |
| if self.validate_child_ref(decl_type, field_name, &child) { |
| Some(TargetId::Component(&child.name)) |
| } else { |
| None |
| } |
| } |
| Some(fdecl::Ref::Collection(collection)) => { |
| if self.validate_collection_ref(decl_type, field_name, &collection) { |
| Some(TargetId::Collection(&collection.name)) |
| } else { |
| None |
| } |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl_type, field_name)); |
| None |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl_type, field_name)); |
| None |
| } |
| } |
| } |
| |
| fn validate_offer_fields( |
| &mut self, |
| decl: &str, |
| allowable_names: AllowableIds, |
| collection_source: CollectionSource, |
| offer_type: OfferType, |
| source: Option<&fdecl::Ref>, |
| source_name: Option<&String>, |
| target: Option<&'a fdecl::Ref>, |
| target_name: Option<&'a String>, |
| availability: Option<&fdecl::Availability>, |
| ) { |
| match source { |
| Some(fdecl::Ref::Parent(_)) |
| | Some(fdecl::Ref::Self_(_)) |
| | Some(fdecl::Ref::VoidType(_)) |
| | Some(fdecl::Ref::Framework(_)) => {} |
| Some(fdecl::Ref::Child(child)) => self.validate_source_child(child, decl, offer_type), |
| Some(fdecl::Ref::Capability(c)) => self.validate_source_capability(c, decl, "source"), |
| Some(fdecl::Ref::Collection(c)) if collection_source == CollectionSource::Allow => { |
| self.validate_source_collection(c, decl) |
| } |
| Some(_) => self.errors.push(Error::invalid_field(decl, "source")), |
| None => self.errors.push(Error::missing_field(decl, "source")), |
| } |
| check_offer_availability(decl, availability, source, source_name, &mut self.errors); |
| check_offer_name(source_name, decl, "source_name", &mut self.errors, offer_type); |
| match (offer_type, target) { |
| (OfferType::Static, Some(fdecl::Ref::Child(c))) => { |
| self.validate_target_child(decl, allowable_names, c, source, target_name); |
| } |
| (OfferType::Static, Some(fdecl::Ref::Collection(c))) => { |
| self.validate_target_collection(decl, allowable_names, c, target_name); |
| } |
| (OfferType::Static, Some(_)) => { |
| self.errors.push(Error::invalid_field(decl, "target")); |
| } |
| (OfferType::Static, None) => { |
| self.errors.push(Error::missing_field(decl, "target")); |
| } |
| |
| (OfferType::Dynamic, Some(_)) => { |
| self.errors.push(Error::extraneous_field(decl, "target")); |
| } |
| (OfferType::Dynamic, None) => {} |
| } |
| check_offer_name(target_name, decl, "target_name", &mut self.errors, offer_type); |
| } |
| |
| fn validate_storage_offer_fields( |
| &mut self, |
| decl: &str, |
| offer_type: OfferType, |
| source_name: Option<&'a String>, |
| source: Option<&'a fdecl::Ref>, |
| target: Option<&'a fdecl::Ref>, |
| availability: Option<&fdecl::Availability>, |
| ) { |
| if source_name.is_none() { |
| self.errors.push(Error::missing_field(decl, "source_name")); |
| } |
| match source { |
| Some(fdecl::Ref::Parent(_) | fdecl::Ref::VoidType(_)) => (), |
| Some(fdecl::Ref::Self_(_)) => { |
| self.validate_storage_source(source_name.unwrap(), decl); |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl, "source")); |
| } |
| } |
| check_offer_availability(decl, availability, source, source_name, &mut self.errors); |
| match offer_type { |
| OfferType::Static => { |
| self.validate_storage_target(decl, target); |
| } |
| OfferType::Dynamic => { |
| if target.is_some() { |
| self.errors.push(Error::extraneous_field(decl, "target")); |
| } |
| } |
| } |
| } |
| |
| fn validate_event_stream_offer_fields( |
| &mut self, |
| event_stream: &'a fdecl::OfferEventStream, |
| offer_type: OfferType, |
| ) { |
| let decl = "OfferEventStream"; |
| check_name(event_stream.source_name.as_ref(), decl, "source_name", &mut self.errors); |
| if event_stream.target == Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})) { |
| // Expose to framework from framework is never valid. |
| self.errors.push(Error::invalid_field("OfferEventStream", "target")); |
| } |
| let source_name = |
| event_stream.source_name.as_ref().map(|value| value.as_str()).unwrap_or(""); |
| match (&event_stream.filter, source_name, &event_stream.source) { |
| (Some(_), "capability_requested", _) | (Some(_), "directory_ready", _) => {} |
| (Some(_), _, Some(fdecl::Ref::Framework(_))) => { |
| self.errors.push(Error::invalid_field(decl, "filter")); |
| } |
| _ => {} |
| } |
| if let Some(scope) = &event_stream.scope { |
| if scope.is_empty() { |
| self.errors.push(Error::invalid_field(decl, "scope")); |
| } |
| for value in scope { |
| match value { |
| fdecl::Ref::Child(child) => { |
| self.validate_child_ref("OfferEventStream", "scope", &child); |
| } |
| fdecl::Ref::Collection(collection) => { |
| self.validate_collection_ref("OfferEventStream", "scope", &collection); |
| } |
| _ => { |
| self.errors.push(Error::invalid_field("OfferEventStream", "scope")); |
| } |
| } |
| } |
| } |
| // Only parent, framework, child, and void are valid. |
| match event_stream.source { |
| Some( |
| fdecl::Ref::Parent(_) |
| | fdecl::Ref::Framework(_) |
| | fdecl::Ref::Child(_) |
| | fdecl::Ref::VoidType(_), |
| ) => {} |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl, "source")); |
| } |
| }; |
| |
| check_offer_availability( |
| decl, |
| event_stream.availability.as_ref(), |
| event_stream.source.as_ref(), |
| event_stream.source_name.as_ref(), |
| &mut self.errors, |
| ); |
| |
| match offer_type { |
| OfferType::Static => { |
| let target_id = self.validate_offer_target(&event_stream.target, decl, "target"); |
| if let (Some(target_id), Some(target_name)) = |
| (target_id, event_stream.target_name.as_ref()) |
| { |
| // Assuming the target_name is valid, ensure the target_name isn't already used. |
| if let Some(_) = self |
| .target_ids |
| .entry(target_id) |
| .or_insert(HashMap::new()) |
| .insert(target_name, AllowableIds::One) |
| { |
| self.errors.push(Error::duplicate_field( |
| decl, |
| "target_name", |
| target_name as &str, |
| )); |
| } |
| } |
| } |
| OfferType::Dynamic => { |
| if event_stream.target.is_some() { |
| self.errors.push(Error::extraneous_field(decl, "target")); |
| } |
| } |
| } |
| check_name(event_stream.target_name.as_ref(), decl, "target_name", &mut self.errors); |
| } |
| |
| fn validate_event_offer_fields(&mut self, event: &'a fdecl::OfferEvent, offer_type: OfferType) { |
| let decl = "OfferEvent"; |
| check_offer_name( |
| event.source_name.as_ref(), |
| decl, |
| "source_name", |
| &mut self.errors, |
| offer_type, |
| ); |
| |
| // Only parent, framework, and void are valid. |
| match event.source { |
| Some(fdecl::Ref::Parent(_) | fdecl::Ref::Framework(_) | fdecl::Ref::VoidType(_)) => {} |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl, "source")); |
| } |
| }; |
| |
| check_offer_availability( |
| decl, |
| event.availability.as_ref(), |
| event.source.as_ref(), |
| event.source_name.as_ref(), |
| &mut self.errors, |
| ); |
| |
| match offer_type { |
| OfferType::Static => { |
| let target_id = self.validate_offer_target(&event.target, decl, "target"); |
| if let (Some(target_id), Some(target_name)) = |
| (target_id, event.target_name.as_ref()) |
| { |
| // Assuming the target_name is valid, ensure the target_name isn't already used. |
| if let Some(_) = self |
| .target_ids |
| .entry(target_id) |
| .or_insert(HashMap::new()) |
| .insert(target_name, AllowableIds::One) |
| { |
| self.errors.push(Error::duplicate_field( |
| decl, |
| "target_name", |
| target_name as &str, |
| )); |
| } |
| } |
| } |
| OfferType::Dynamic => { |
| if event.target.is_some() { |
| self.errors.push(Error::extraneous_field(decl, "target")); |
| } |
| } |
| } |
| check_offer_name( |
| event.target_name.as_ref(), |
| decl, |
| "target_name", |
| &mut self.errors, |
| offer_type, |
| ); |
| } |
| |
| /// Check a `ChildRef` contains a valid child that exists. |
| /// |
| /// We ensure the target child is statically defined (i.e., not a dynamic child inside |
| /// a collection). |
| fn validate_child_ref( |
| &mut self, |
| decl: &str, |
| field_name: &str, |
| child: &fdecl::ChildRef, |
| ) -> bool { |
| // Ensure the name is valid, and the reference refers to a static child. |
| // |
| // We attempt to list all errors if possible. |
| let mut valid = true; |
| if !check_name( |
| Some(&child.name), |
| decl, |
| &format!("{}.child.name", field_name), |
| &mut self.errors, |
| ) { |
| valid = false; |
| } |
| if child.collection.is_some() { |
| self.errors |
| .push(Error::extraneous_field(decl, format!("{}.child.collection", field_name))); |
| valid = false; |
| } |
| if !valid { |
| return false; |
| } |
| |
| // Ensure the child exists. |
| let name: &str = &child.name; |
| if !self.all_children.contains_key(name) { |
| self.errors.push(Error::invalid_child(decl, field_name, name)); |
| return false; |
| } |
| |
| true |
| } |
| |
| /// Check a `CollectionRef` is valid and refers to an existing collection. |
| fn validate_collection_ref( |
| &mut self, |
| decl: &str, |
| field_name: &str, |
| collection: &fdecl::CollectionRef, |
| ) -> bool { |
| // Ensure the name is valid. |
| if !check_name( |
| Some(&collection.name), |
| decl, |
| &format!("{}.collection.name", field_name), |
| &mut self.errors, |
| ) { |
| return false; |
| } |
| |
| // Ensure the collection exists. |
| if !self.all_collections.contains(&collection.name as &str) { |
| self.errors.push(Error::invalid_collection(decl, field_name, &collection.name as &str)); |
| return false; |
| } |
| |
| true |
| } |
| |
| fn validate_target_child( |
| &mut self, |
| decl: &str, |
| allowable_names: AllowableIds, |
| child: &'a fdecl::ChildRef, |
| source: Option<&fdecl::Ref>, |
| target_name: Option<&'a String>, |
| ) { |
| if !self.validate_child_ref(decl, "target", child) { |
| return; |
| } |
| if let Some(target_name) = target_name { |
| let names_for_target = |
| self.target_ids.entry(TargetId::Component(&child.name)).or_insert(HashMap::new()); |
| if let Some(prev_state) = names_for_target.insert(target_name, allowable_names) { |
| if prev_state == AllowableIds::One || prev_state != allowable_names { |
| self.errors.push(Error::duplicate_field( |
| decl, |
| "target_name", |
| target_name as &str, |
| )); |
| } |
| } |
| if let Some(source) = source { |
| if let fdecl::Ref::Child(source_child) = source { |
| if source_child.name == child.name { |
| self.errors |
| .push(Error::offer_target_equals_source(decl, &child.name as &str)); |
| } |
| } |
| } |
| } |
| } |
| |
| fn validate_target_collection( |
| &mut self, |
| decl: &str, |
| allowable_names: AllowableIds, |
| collection: &'a fdecl::CollectionRef, |
| target_name: Option<&'a String>, |
| ) { |
| if !self.validate_collection_ref(decl, "target", &collection) { |
| return; |
| } |
| if let Some(target_name) = target_name { |
| let names_for_target = self |
| .target_ids |
| .entry(TargetId::Collection(&collection.name)) |
| .or_insert(HashMap::new()); |
| if let Some(prev_state) = names_for_target.insert(target_name, allowable_names) { |
| if prev_state == AllowableIds::One || prev_state != allowable_names { |
| self.errors.push(Error::duplicate_field( |
| decl, |
| "target_name", |
| target_name as &str, |
| )); |
| } |
| } |
| } |
| } |
| |
| fn validate_storage_target(&mut self, decl: &str, target: Option<&'a fdecl::Ref>) { |
| match target { |
| Some(fdecl::Ref::Child(c)) => { |
| self.validate_child_ref(decl, "target", &c); |
| } |
| Some(fdecl::Ref::Collection(c)) => { |
| self.validate_collection_ref(decl, "target", &c); |
| } |
| Some(_) => self.errors.push(Error::invalid_field(decl, "target")), |
| None => self.errors.push(Error::missing_field(decl, "target")), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::error::{Error, ErrorList}, |
| fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, |
| test_case::test_case, |
| }; |
| |
| macro_rules! test_validate { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_test($input, $result); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_validate_any_result { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| results = $results:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_test_any_result($input, $results); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_validate_values_data { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_values_data_test($input, $result); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_validate_capabilities { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| as_builtin = $as_builtin:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_capabilities_test($input, $as_builtin, $result); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_dependency { |
| ( |
| $( |
| ($test_name:ident) => { |
| ty = $ty:expr, |
| offer_decl = $offer_decl:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| let mut decl = new_component_decl(); |
| let dependencies = vec![ |
| ("a", "b"), |
| ("b", "a"), |
| ]; |
| let offers = dependencies.into_iter().map(|(from,to)| { |
| let mut offer_decl = $offer_decl; |
| offer_decl.source = Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: from.to_string(), collection: None }, |
| )); |
| offer_decl.target = Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: to.to_string(), collection: None }, |
| )); |
| $ty(offer_decl) |
| }).collect(); |
| let children = ["a", "b"].iter().map(|name| { |
| fdecl::Child { |
| name: Some(name.to_string()), |
| url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| }).collect(); |
| decl.offers = Some(offers); |
| decl.children = Some(children); |
| let result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child a", "child b", "child a"]].iter().cloned().collect()).format_cycle()), |
| ])); |
| validate_test(decl, result); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_weak_dependency { |
| ( |
| $( |
| ($test_name:ident) => { |
| ty = $ty:expr, |
| offer_decl = $offer_decl:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test_case(fdecl::DependencyType::Weak)] |
| #[test_case(fdecl::DependencyType::WeakForMigration)] |
| fn $test_name(weak_dep: fdecl::DependencyType) { |
| let mut decl = new_component_decl(); |
| let offers = vec![ |
| { |
| let mut offer_decl = $offer_decl; |
| offer_decl.source = Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: "a".to_string(), collection: None }, |
| )); |
| offer_decl.target = Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: "b".to_string(), collection: None }, |
| )); |
| offer_decl.dependency_type = Some(fdecl::DependencyType::Strong); |
| $ty(offer_decl) |
| }, |
| { |
| let mut offer_decl = $offer_decl; |
| offer_decl.source = Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: "b".to_string(), collection: None }, |
| )); |
| offer_decl.target = Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: "a".to_string(), collection: None }, |
| )); |
| offer_decl.dependency_type = Some(weak_dep); |
| $ty(offer_decl) |
| }, |
| ]; |
| let children = ["a", "b"].iter().map(|name| { |
| fdecl::Child { |
| name: Some(name.to_string()), |
| url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| }).collect(); |
| decl.offers = Some(offers); |
| decl.children = Some(children); |
| let result = Ok(()); |
| validate_test(decl, result); |
| } |
| )+ |
| } |
| } |
| |
| fn validate_test(input: fdecl::Component, expected_res: Result<(), ErrorList>) { |
| let res = validate(&input); |
| assert_eq!(res, expected_res); |
| } |
| |
| fn validate_test_any_result(input: fdecl::Component, expected_res: Vec<Result<(), ErrorList>>) { |
| let res = format!("{:?}", validate(&input)); |
| let expected_res_debug = format!("{:?}", expected_res); |
| |
| let matched_exp = |
| expected_res.into_iter().find(|expected| res == format!("{:?}", expected)); |
| |
| assert!( |
| matched_exp.is_some(), |
| "assertion failed: Expected one of:\n{:?}\nActual:\n{:?}", |
| expected_res_debug, |
| res |
| ); |
| } |
| |
| fn validate_values_data_test(input: fconfig::ValuesData, expected_res: Result<(), ErrorList>) { |
| let res = validate_values_data(&input); |
| assert_eq!(res, expected_res); |
| } |
| |
| fn validate_capabilities_test( |
| input: Vec<fdecl::Capability>, |
| as_builtin: bool, |
| expected_res: Result<(), ErrorList>, |
| ) { |
| let res = validate_capabilities(&input, as_builtin); |
| assert_eq!(res, expected_res); |
| } |
| |
| fn new_component_decl() -> fdecl::Component { |
| fdecl::Component { |
| program: None, |
| uses: None, |
| exposes: None, |
| offers: None, |
| facets: None, |
| capabilities: None, |
| children: None, |
| collections: None, |
| environments: None, |
| ..fdecl::Component::EMPTY |
| } |
| } |
| |
| test_validate_any_result! { |
| test_validate_use_disallows_nested_dirs => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar/baz".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectory", "/foo/bar/baz", "UseDirectory", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectory", "/foo/bar", "UseDirectory", "/foo/bar/baz"), |
| ])), |
| ], |
| }, |
| test_validate_use_disallows_nested_dirs_storage => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar/baz".to_string()), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseStorage", "/foo/bar/baz", "UseStorage", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseStorage", "/foo/bar", "UseStorage", "/foo/bar/baz"), |
| ])), |
| ], |
| }, |
| test_validate_use_disallows_nested_dirs_directory_and_storage => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar/baz".to_string()), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseStorage", "/foo/bar/baz", "UseDirectory", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectory", "/foo/bar", "UseStorage", "/foo/bar/baz"), |
| ])), |
| ], |
| }, |
| test_validate_use_disallows_common_prefixes_protocol => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("crow".to_string()), |
| target_path: Some("/foo/bar/fuchsia.2".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseProtocol", "/foo/bar/fuchsia.2", "UseDirectory", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectory", "/foo/bar", "UseProtocol", "/foo/bar/fuchsia.2"), |
| ])), |
| ], |
| }, |
| test_validate_use_disallows_common_prefixes_service => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("space".to_string()), |
| target_path: Some("/foo/bar/baz/fuchsia.logger.Log".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseService", "/foo/bar/baz/fuchsia.logger.Log", "UseDirectory", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectory", "/foo/bar", "UseService", "/foo/bar/baz/fuchsia.logger.Log"), |
| ])), |
| ], |
| }, |
| } |
| |
| test_validate_values_data! { |
| test_values_data_ok => { |
| input = fconfig::ValuesData { |
| values: Some(vec![ |
| fconfig::ValueSpec { |
| value: Some(fconfig::Value::Single(fconfig::SingleValue::Bool(true))), |
| ..fconfig::ValueSpec::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Ok(()), |
| }, |
| test_values_data_no_checksum => { |
| input = fconfig::ValuesData { |
| values: Some(vec![]), |
| checksum: None, |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ValuesData", "checksum") |
| ])), |
| }, |
| test_values_data_unknown_checksum => { |
| input = fconfig::ValuesData { |
| values: Some(vec![]), |
| checksum: Some(fdecl::ConfigChecksum::unknown(0, vec![])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ValuesData", "checksum") |
| ])), |
| }, |
| test_values_data_no_values => { |
| input = fconfig::ValuesData { |
| values: None, |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ValuesData", "values") |
| ])), |
| }, |
| test_values_data_no_inner_value => { |
| input = fconfig::ValuesData { |
| values: Some(vec![ |
| fconfig::ValueSpec::EMPTY |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ValueSpec", "value") |
| ])), |
| }, |
| test_values_data_unknown_inner_value => { |
| input = fconfig::ValuesData { |
| values: Some(vec![ |
| fconfig::ValueSpec { |
| value: Some(fconfig::Value::unknown(0, vec![])), |
| ..fconfig::ValueSpec::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ValueSpec", "value") |
| ])), |
| }, |
| test_values_data_unknown_single_value => { |
| input = fconfig::ValuesData { |
| values: Some(vec![ |
| fconfig::ValueSpec { |
| value: Some(fconfig::Value::Single(fconfig::SingleValue::unknown(0, vec![]))), |
| ..fconfig::ValueSpec::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ValueSpec", "value") |
| ])), |
| }, |
| test_values_data_unknown_list_value => { |
| input = fconfig::ValuesData { |
| values: Some(vec![ |
| fconfig::ValueSpec { |
| value: Some(fconfig::Value::Vector(fconfig::VectorValue::unknown(0, vec![]))), |
| ..fconfig::ValueSpec::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| ..fconfig::ValuesData::EMPTY |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ValueSpec", "value") |
| ])), |
| }, |
| } |
| |
| test_validate! { |
| // uses |
| test_validate_uses_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.program = Some(fdecl::Program { |
| runner: Some("elf".to_string()), |
| info: Some(fdata::Dictionary { |
| entries: None, |
| ..fdata::Dictionary::EMPTY |
| }), |
| ..fdecl::Program::EMPTY |
| }); |
| decl.uses = Some(vec![ |
| fdecl::Use::Service(fdecl::UseService { |
| source: None, |
| source_name: None, |
| target_path: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: None, |
| source_name: None, |
| target_path: None, |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: None, |
| source_name: None, |
| target_path: None, |
| rights: None, |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: None, |
| target_path: None, |
| ..fdecl::UseStorage::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("cache".to_string()), |
| target_path: None, |
| ..fdecl::UseStorage::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: None, |
| source_name: None, |
| target_name: None, |
| filter: None, |
| ..fdecl::UseEvent::EMPTY |
| }), |
| fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated { |
| name: None, |
| subscriptions: None, |
| ..fdecl::UseEventStreamDeprecated::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("UseEvent", "source"), |
| Error::missing_field("UseEvent", "source_name"), |
| Error::missing_field("UseEvent", "target_name"), |
| Error::missing_field("UseService", "source"), |
| Error::missing_field("UseService", "source_name"), |
| Error::missing_field("UseService", "target_path"), |
| Error::missing_field("UseProtocol", "source"), |
| Error::missing_field("UseProtocol", "source_name"), |
| Error::missing_field("UseProtocol", "target_path"), |
| Error::missing_field("UseDirectory", "source"), |
| Error::missing_field("UseDirectory", "source_name"), |
| Error::missing_field("UseDirectory", "target_path"), |
| Error::missing_field("UseDirectory", "rights"), |
| Error::missing_field("UseStorage", "source_name"), |
| Error::missing_field("UseStorage", "target_path"), |
| Error::missing_field("UseStorage", "target_path"), |
| Error::missing_field("UseEventStream", "name"), |
| Error::missing_field("UseEventStream", "subscriptions"), |
| ])), |
| }, |
| test_validate_uses_invalid_identifiers_service => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("foo/".to_string()), |
| target_path: Some("/".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseService", "source_name"), |
| Error::invalid_field("UseService", "target_path"), |
| ])), |
| }, |
| test_validate_uses_invalid_identifiers_protocol => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("foo/".to_string()), |
| target_path: Some("/".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseProtocol", "source_name"), |
| Error::invalid_field("UseProtocol", "target_path"), |
| ])), |
| }, |
| test_validate_uses_invalid_identifiers_directory => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("foo/".to_string()), |
| target_path: Some("/".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: Some("/foo".to_string()), |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseDirectory", "source_name"), |
| Error::invalid_field("UseDirectory", "target_path"), |
| Error::invalid_field("UseDirectory", "subdir"), |
| ])), |
| }, |
| test_validate_uses_invalid_identifiers_storage => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("/cache".to_string()), |
| target_path: Some("/".to_string()), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("temp".to_string()), |
| target_path: Some("tmp".to_string()), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseStorage", "source_name"), |
| Error::invalid_field("UseStorage", "target_path"), |
| Error::invalid_field("UseStorage", "target_path"), |
| ])), |
| }, |
| test_validate_uses_invalid_identifiers_event => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("/foo".to_string()), |
| target_name: Some("/foo".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| source_name: Some("started".to_string()), |
| target_name: Some("started".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated { |
| name: Some("bar".to_string()), |
| subscriptions: Some(vec!["a".to_string(), "b".to_string()].into_iter().map(|name| fdecl::EventSubscription { |
| event_name: Some(name), |
| ..fdecl::EventSubscription::EMPTY |
| }).collect()), |
| ..fdecl::UseEventStreamDeprecated::EMPTY |
| }), |
| fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated { |
| name: Some("bleep".to_string()), |
| subscriptions: Some(vec![fdecl::EventSubscription { |
| event_name: Some("started".to_string()), |
| ..fdecl::EventSubscription::EMPTY |
| }]), |
| ..fdecl::UseEventStreamDeprecated::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseEvent", "source"), |
| Error::invalid_field("UseEvent", "source_name"), |
| Error::invalid_field("UseEvent", "target_name"), |
| Error::event_stream_event_not_found("UseEventStream", "events", "a".to_string()), |
| Error::event_stream_event_not_found("UseEventStream", "events", "b".to_string()), |
| ])), |
| }, |
| test_validate_uses_missing_source => { |
| input = { |
| fdecl::Component { |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { |
| name: "this-storage-doesnt-exist".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }) |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("UseProtocol", "source", "this-storage-doesnt-exist"), |
| ])), |
| }, |
| test_validate_uses_invalid_child => { |
| input = { |
| fdecl::Component { |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})), |
| source_name: Some("service_name".to_string()), |
| target_path: Some("/svc/service_name".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})), |
| source_name: Some("DirectoryName".to_string()), |
| target_path: Some("/data/DirectoryName".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "no-such-child".to_string(), collection: None})), |
| source_name: Some("abc".to_string()), |
| target_name: Some("abc".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("UseEvent", "source", "no-such-child"), |
| Error::invalid_child("UseProtocol", "source", "no-such-child"), |
| Error::invalid_child("UseService", "source", "no-such-child"), |
| Error::invalid_child("UseDirectory", "source", "no-such-child"), |
| ])), |
| }, |
| test_validate_use_from_child_offer_to_child_strong_cycle => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("a".to_string()), |
| source_path: Some("/a".to_string()), |
| ..fdecl::Service::EMPTY |
| })]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("service_name".to_string()), |
| target_path: Some("/svc/service_name".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("DirectoryName".to_string()), |
| target_path: Some("/data/DirectoryName".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("abc".to_string()), |
| target_name: Some("abc".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }) |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{self -> child child -> self}}".to_string()), |
| ])), |
| }, |
| test_validate_use_from_child_storage_no_cycle => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("cdata".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None } )), |
| backing_dir: Some("minfs".to_string()), |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("pdata".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| backing_dir: Some("minfs".to_string()), |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| ]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child1".to_string(), collection: None})), |
| source_name: Some("a".to_string()), |
| target_path: Some("/svc/a".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("cdata".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("cdata".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("pdata".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("pdata".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child1".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Ok(()), |
| }, |
| test_validate_use_from_child_storage_cycle => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| backing_dir: Some("minfs".to_string()), |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| ]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("a".to_string()), |
| target_path: Some("/svc/a".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("data".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{self -> child child -> self}}".to_string()), |
| ])), |
| }, |
| test_validate_storage_strong_cycle_between_children => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None } )), |
| backing_dir: Some("minfs".to_string()), |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }) |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("data".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child1".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{child child1 -> child child2 -> child child1}}".to_string()), |
| ])), |
| }, |
| test_validate_strong_cycle_between_children_through_environment_debug => { |
| input = { |
| fdecl::Component { |
| environments: Some(vec![ |
| fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::Realm), |
| debug_capabilities: Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| source_name: Some("fuchsia.foo.Bar".to_string()), |
| target_name: Some("fuchsia.foo.Bar".to_string()), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }, |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child1".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: Some("env".to_string()), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{child child1 -> environment env -> child child2 -> child child1}}".to_string()), |
| ])), |
| }, |
| test_validate_strong_cycle_between_children_through_environment_runner => { |
| input = { |
| fdecl::Component { |
| environments: Some(vec![ |
| fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::Realm), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| source_name: Some("coff".to_string()), |
| target_name: Some("coff".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| } |
| ]), |
| ..fdecl::Environment::EMPTY |
| }, |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child1".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: Some("env".to_string()), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{child child1 -> environment env -> child child2 -> child child1}}".to_string()), |
| ])), |
| }, |
| test_validate_strong_cycle_between_children_through_environment_resolver => { |
| input = { |
| fdecl::Component { |
| environments: Some(vec![ |
| fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::Realm), |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("gopher".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| scheme: Some("gopher".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| } |
| ]), |
| ..fdecl::Environment::EMPTY |
| }, |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child1".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: Some("env".to_string()), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{child child1 -> environment env -> child child2 -> child child1}}".to_string()), |
| ])), |
| }, |
| test_validate_strong_cycle_between_self_and_two_children => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("fuchsia.foo.Bar".to_string()), |
| source_path: Some("/svc/fuchsia.foo.Bar".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }) |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("fuchsia.foo.Bar".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| target_name: Some("fuchsia.foo.Bar".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child1".to_string(), collection: None })), |
| source_name: Some("fuchsia.bar.Baz".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child2".to_string(), collection: None })), |
| target_name: Some("fuchsia.bar.Baz".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| ]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child2".to_string(), collection: None})), |
| source_name: Some("fuchsia.baz.Foo".to_string()), |
| target_path: Some("/svc/fuchsia.baz.Foo".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child1".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo2".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{self -> child child1 -> child child2 -> self}}".to_string()), |
| ])), |
| }, |
| test_validate_strong_cycle_with_self_storage => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| backing_dir: Some("minfs".to_string()), |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("minfs".to_string()), |
| source_path: Some("/minfs".to_string()), |
| rights: Some(fio::RW_STAR_DIR), |
| ..fdecl::Directory::EMPTY |
| }), |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("data".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| ]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("fuchsia.foo.Bar".to_string()), |
| target_path: Some("/svc/fuchsia.foo.Bar".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| ..fdecl::Child::EMPTY |
| }, |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{self -> child child -> self}}".to_string()), |
| ])), |
| }, |
| test_validate_strong_cycle_with_self_storage_admin_protocol => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| backing_dir: Some("minfs".to_string()), |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("minfs".to_string()), |
| source_path: Some("/minfs".to_string()), |
| rights: Some(fio::RW_STAR_DIR), |
| ..fdecl::Directory::EMPTY |
| }), |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { name: "data".to_string() })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| ]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("fuchsia.foo.Bar".to_string()), |
| target_path: Some("/svc/fuchsia.foo.Bar".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| ..fdecl::Child::EMPTY |
| }, |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{self -> child child -> self}}".to_string()), |
| ])), |
| }, |
| test_validate_use_from_child_offer_to_child_weak_cycle => { |
| input = { |
| fdecl::Component { |
| capabilities: Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("a".to_string()), |
| source_path: Some("/a".to_string()), |
| ..fdecl::Service::EMPTY |
| })]), |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("service_name".to_string()), |
| target_path: Some("/svc/service_name".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::WeakForMigration), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("DirectoryName".to_string()), |
| target_path: Some("/data/DirectoryName".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::WeakForMigration), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ name: "child".to_string(), collection: None})), |
| source_name: Some("abc".to_string()), |
| target_name: Some("abc".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }) |
| ]), |
| offers: Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Ok(()), |
| }, |
| test_validate_use_from_not_child_weak => { |
| input = { |
| fdecl::Component { |
| uses: Some(vec![ |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseProtocol", "dependency_type"), |
| ])), |
| }, |
| test_validate_has_events_in_event_stream => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated { |
| name: Some("bar".to_string()), |
| subscriptions: None, |
| ..fdecl::UseEventStreamDeprecated::EMPTY |
| }), |
| fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated { |
| name: Some("barbar".to_string()), |
| subscriptions: Some(vec![]), |
| ..fdecl::UseEventStreamDeprecated::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("UseEventStream", "subscriptions"), |
| Error::empty_field("UseEventStream", "subscriptions"), |
| ])), |
| }, |
| test_validate_event_stream_offer_valid_decls => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_event_stream_offer_to_framework_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "OfferEventStream".to_string(), field: "target".to_string() }), |
| Error::InvalidField(DeclField { decl: "OfferEventStream".to_string(), field: "target".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_offer_to_scope_zero_length_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| scope: Some(vec![]), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "OfferEventStream".to_string(), field: "scope".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_offer_to_scope_framework_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| scope: Some(vec![fdecl::Ref::Framework(fdecl::FrameworkRef{})]), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "OfferEventStream".to_string(), field: "scope".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_offer_to_scope_valid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_event_stream_offer_to_scope_with_capability_requested => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("capability_requested".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("directory_ready".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_event_stream_offer_to_scope_with_invalid_capability_name => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("some_invalid_capability".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("directory_ready".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "OfferEventStream".to_string(), field: "filter".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_offer_with_no_source_name_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: None, |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| scope: Some(vec![fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})]), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::MissingField(DeclField { decl: "OfferEventStream".to_string(), field: "source_name".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_offer_duplicate_target => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::DuplicateField(DeclField { decl: "OfferEventStream".to_string(), field: "target_name".to_string() }, "started".to_string()), |
| ])), |
| }, |
| test_validate_event_stream_offer_invalid_source => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| source: Some(fdecl::Ref::Debug(fdecl::DebugRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "OfferEventStream".to_string(), field: "source".to_string() }), |
| ])), |
| }, |
| |
| test_validate_event_stream_offer_missing_source => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("started".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| fdecl::Offer::EventStream(fdecl::OfferEventStream { |
| source_name: Some("diagnostics_ready".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test2".to_string(), collection: None})), |
| target_name: Some("diagnostics_ready".to_string()), |
| ..fdecl::OfferEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child{ |
| name: Some("test2".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/fake_component#meta/fake_component.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::MissingField(DeclField { decl: "OfferEventStream".to_string(), field: "source".to_string() }), |
| ])), |
| }, |
| |
| test_validate_event_stream_expose_to_framework_from_framework_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::EventStream(fdecl::ExposeEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::ExposeEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "target".to_string() }), |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "target".to_string() }), |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "source".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_expose_to_framework_from_other_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::EventStream(fdecl::ExposeEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::ExposeEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "source".to_string() }), |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "target".to_string() }), |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "target".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_scope_must_be_non_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::EventStream(fdecl::ExposeEventStream { |
| source_name: Some("stopped".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "test".to_string(), collection: None})), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| scope: Some(vec![]), |
| target_name: Some("stopped".to_string()), |
| ..fdecl::ExposeEventStream::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("test".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "ExposeEventStream".to_string(), field: "scope".to_string() }), |
| ])), |
| }, |
| test_validate_event_stream_must_have_target_path => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStream(fdecl::UseEventStream { |
| source_name: Some("bar".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| ..fdecl::UseEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::MissingField(DeclField { decl: "UseEventStream".to_string(), field: "target_path".to_string() }) |
| ])), |
| }, |
| test_validate_event_stream_must_have_source_names => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStream(fdecl::UseEventStream { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target_path: Some("/svc/something".to_string()), |
| ..fdecl::UseEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::MissingField(DeclField { decl: "UseEventStream".to_string(), field: "source_name".to_string() }) |
| ])), |
| }, |
| test_validate_event_stream_scope_must_be_child_or_collection => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStream(fdecl::UseEventStream { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target_path: Some("/svc/something".to_string()), |
| source_name: Some("some_source".to_string()), |
| scope: Some(vec![fdecl::Ref::Framework(fdecl::FrameworkRef{})]), |
| ..fdecl::UseEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "UseEventStream".to_string(), field: "scope".to_string() }) |
| ])), |
| }, |
| test_validate_event_stream_source_must_be_parent_framework_or_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStream(fdecl::UseEventStream { |
| source: Some(fdecl::Ref::Debug(fdecl::DebugRef{})), |
| target_path: Some("/svc/something".to_string()), |
| source_name: Some("some_source".to_string()), |
| scope: Some(vec![]), |
| ..fdecl::UseEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "UseEventStream".to_string(), field: "source".to_string() }) |
| ])), |
| }, |
| test_validate_event_stream_source_framework_must_have_nonempty_scope => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStream(fdecl::UseEventStream { |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target_path: Some("/svc/something".to_string()), |
| source_name: Some("some_source".to_string()), |
| scope: Some(vec![]), |
| ..fdecl::UseEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::InvalidField(DeclField { decl: "UseEventStream".to_string(), field: "scope".to_string() }) |
| ])), |
| }, |
| test_validate_event_stream_source_framework_must_specify_scope => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::EventStream(fdecl::UseEventStream { |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target_path: Some("/svc/something".to_string()), |
| source_name: Some("some_source".to_string()), |
| ..fdecl::UseEventStream::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::MissingField(DeclField { decl: "UseEventStream".to_string(), field: "scope".to_string() }) |
| ])), |
| }, |
| test_validate_uses_no_runner => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.program = Some(fdecl::Program { |
| runner: None, |
| info: Some(fdata::Dictionary { |
| entries: None, |
| ..fdata::Dictionary::EMPTY |
| }), |
| ..fdecl::Program::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Program", "runner"), |
| ])), |
| }, |
| test_validate_uses_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.program = Some(fdecl::Program { |
| runner: Some("elf".to_string()), |
| info: Some(fdata::Dictionary { |
| entries: None, |
| ..fdata::Dictionary::EMPTY |
| }), |
| ..fdecl::Program::EMPTY |
| }); |
| decl.uses = Some(vec![ |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_path: Some(format!("/s/{}", "b".repeat(1024))), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_path: Some(format!("/p/{}", "c".repeat(1024))), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_path: Some(format!("/d/{}", "d".repeat(1024))), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("cache".to_string()), |
| target_path: Some(format!("/{}", "e".repeat(1024))), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "a".repeat(101))), |
| filter: None, |
| ..fdecl::UseEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long_with_max("UseEvent", "source_name", 100), |
| Error::field_too_long_with_max("UseEvent", "target_name", 100), |
| Error::field_too_long_with_max("UseService", "source_name", 100), |
| Error::field_too_long_with_max("UseService", "target_path", 1024), |
| Error::field_too_long_with_max("UseProtocol", "source_name", 100), |
| Error::field_too_long_with_max("UseProtocol", "target_path", 1024), |
| Error::field_too_long_with_max("UseDirectory", "source_name", 100), |
| Error::field_too_long_with_max("UseDirectory", "target_path", 1024), |
| Error::field_too_long_with_max("UseStorage", "target_path", 1024), |
| ])), |
| }, |
| test_validate_conflicting_paths => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("foo".to_string()), |
| target_path: Some("/bar".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("space".to_string()), |
| target_path: Some("/bar".to_string()), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("crow".to_string()), |
| target_path: Some("/bar".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::duplicate_field("UseProtocol", "path", "/bar"), |
| Error::duplicate_field("UseDirectory", "path", "/bar"), |
| ])), |
| }, |
| test_validate_events_can_come_before_or_after_event_stream => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| source_name: Some("started".to_string()), |
| target_name: Some("started".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| fdecl::Use::EventStreamDeprecated(fdecl::UseEventStreamDeprecated { |
| name: Some("bar".to_string()), |
| subscriptions: Some( |
| vec!["started".to_string(), "stopped".to_string()] |
| .into_iter() |
| .map(|name| fdecl::EventSubscription { |
| event_name: Some(name), |
| ..fdecl::EventSubscription::EMPTY |
| }) |
| .collect() |
| ), |
| ..fdecl::UseEventStreamDeprecated::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| source_name: Some("stopped".to_string()), |
| target_name: Some("stopped".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_uses_invalid_self_source => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| fdecl::Use::Event(fdecl::UseEvent { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("started".to_string()), |
| target_name: Some("foo_started".to_string()), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseEvent", "source"), |
| ])), |
| }, |
| test_validate_uses_invalid_source_for_optional => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("minfs".to_string()), |
| source_path: Some("/minfs".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("source".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/source#meta/source.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.uses = Some(vec![ |
| // These uses are fine, uses with a source of parent can be optional. |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("fuchsia.examples.EchoService".to_string()), |
| target_path: Some("/svc/fuchsia.examples.EchoService".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("fuchsia.examples.Echo".to_string()), |
| target_path: Some("/svc/fuchsia.examples.Echo".to_string()), |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("minfs".to_string()), |
| target_path: Some("/minfs".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Storage(fdecl::UseStorage { |
| source_name: Some("data".to_string()), |
| target_path: Some("/data".to_string()), |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseStorage::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("start".to_string()), |
| target_name: Some("start".to_string()), |
| filter: None, |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| // These uses are not fine, uses with a source other than parent must be |
| // required. |
| fdecl::Use::Service(fdecl::UseService { |
| source: Some(fdecl::Ref::Debug(fdecl::DebugRef {})), |
| source_name: Some("fuchsia.examples.EchoService".to_string()), |
| target_path: Some("/svc/fuchsia.examples.EchoService1".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseService::EMPTY |
| }), |
| fdecl::Use::Protocol(fdecl::UseProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| source_name: Some("fuchsia.examples.Echo".to_string()), |
| target_path: Some("/svc/fuchsia.examples.Echo1".to_string()), |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseProtocol::EMPTY |
| }), |
| fdecl::Use::Directory(fdecl::UseDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("minfs".to_string()), |
| target_path: Some("/minfs1".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseDirectory::EMPTY |
| }), |
| fdecl::Use::Event(fdecl::UseEvent { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "source".to_string(), |
| collection: None, |
| })), |
| source_name: Some("start".to_string()), |
| target_name: Some("start1".to_string()), |
| filter: None, |
| availability: Some(fdecl::Availability::Optional), |
| ..fdecl::UseEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::availability_must_be_required( |
| "UseEvent", |
| "availability", |
| Some(&"start".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "UseService", |
| "availability", |
| Some(&"fuchsia.examples.EchoService".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "UseProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "UseDirectory", |
| "availability", |
| Some(&"minfs".to_string()), |
| ), |
| ])), |
| }, |
| // exposes |
| test_validate_exposes_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: None, |
| source_name: None, |
| target_name: None, |
| target: None, |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: None, |
| source_name: None, |
| target_name: None, |
| target: None, |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: None, |
| source_name: None, |
| target_name: None, |
| target: None, |
| rights: None, |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ExposeService", "source"), |
| Error::missing_field("ExposeService", "target"), |
| Error::missing_field("ExposeService", "source_name"), |
| Error::missing_field("ExposeService", "target_name"), |
| Error::missing_field("ExposeProtocol", "source"), |
| Error::missing_field("ExposeProtocol", "target"), |
| Error::missing_field("ExposeProtocol", "source_name"), |
| Error::missing_field("ExposeProtocol", "target_name"), |
| Error::missing_field("ExposeDirectory", "source"), |
| Error::missing_field("ExposeDirectory", "target"), |
| Error::missing_field("ExposeDirectory", "source_name"), |
| Error::missing_field("ExposeDirectory", "target_name"), |
| Error::missing_field("ExposeRunner", "source"), |
| Error::missing_field("ExposeRunner", "target"), |
| Error::missing_field("ExposeRunner", "source_name"), |
| Error::missing_field("ExposeRunner", "target_name"), |
| Error::missing_field("ExposeResolver", "source"), |
| Error::missing_field("ExposeResolver", "target"), |
| Error::missing_field("ExposeResolver", "source_name"), |
| Error::missing_field("ExposeResolver", "target_name"), |
| ])), |
| }, |
| test_validate_exposes_extraneous => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("logger".to_string()), |
| target_name: Some("logger".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("legacy_logger".to_string()), |
| target_name: Some("legacy_logger".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("data".to_string()), |
| target_name: Some("data".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("ExposeService", "source.child.collection"), |
| Error::extraneous_field("ExposeProtocol", "source.child.collection"), |
| Error::extraneous_field("ExposeDirectory", "source.child.collection"), |
| Error::extraneous_field("ExposeRunner", "source.child.collection"), |
| Error::extraneous_field("ExposeResolver", "source.child.collection"), |
| ])), |
| }, |
| test_validate_exposes_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: Some("/foo".to_string()), |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("/path".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("elf!".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("/path".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("pkg!".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ExposeService", "source.child.name"), |
| Error::invalid_field("ExposeService", "source_name"), |
| Error::invalid_field("ExposeService", "target_name"), |
| Error::invalid_field("ExposeProtocol", "source.child.name"), |
| Error::invalid_field("ExposeProtocol", "source_name"), |
| Error::invalid_field("ExposeProtocol", "target_name"), |
| Error::invalid_field("ExposeDirectory", "source.child.name"), |
| Error::invalid_field("ExposeDirectory", "source_name"), |
| Error::invalid_field("ExposeDirectory", "target_name"), |
| Error::invalid_field("ExposeDirectory", "subdir"), |
| Error::invalid_field("ExposeRunner", "source.child.name"), |
| Error::invalid_field("ExposeRunner", "source_name"), |
| Error::invalid_field("ExposeRunner", "target_name"), |
| Error::invalid_field("ExposeResolver", "source.child.name"), |
| Error::invalid_field("ExposeResolver", "source_name"), |
| Error::invalid_field("ExposeResolver", "target_name"), |
| ])), |
| }, |
| test_validate_exposes_invalid_source_target => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("logger".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }]); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: None, |
| source_name: Some("a".to_string()), |
| target_name: Some("b".to_string()), |
| target: None, |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("c".to_string()), |
| target_name: Some("d".to_string()), |
| target: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "z".to_string()})), |
| source_name: Some("e".to_string()), |
| target_name: Some("f".to_string()), |
| target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "z".to_string()})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("g".to_string()), |
| target_name: Some("h".to_string()), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("i".to_string()), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| target_name: Some("j".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("k".to_string()), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| target_name: Some("l".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("m".to_string()), |
| target_name: Some("n".to_string()), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ExposeService", "source"), |
| Error::missing_field("ExposeService", "target"), |
| Error::invalid_field("ExposeProtocol", "source"), |
| Error::invalid_field("ExposeProtocol", "target"), |
| Error::invalid_field("ExposeDirectory", "source"), |
| Error::invalid_field("ExposeDirectory", "target"), |
| Error::invalid_field("ExposeDirectory", "source"), |
| Error::invalid_field("ExposeDirectory", "target"), |
| Error::invalid_field("ExposeRunner", "source"), |
| Error::invalid_field("ExposeRunner", "target"), |
| Error::invalid_field("ExposeResolver", "source"), |
| Error::invalid_field("ExposeResolver", "target"), |
| Error::invalid_field("ExposeDirectory", "target"), |
| ])), |
| }, |
| test_validate_exposes_invalid_source_collection => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![fdecl::Collection{ |
| name: Some("col".to_string()), |
| durability: Some(fdecl::Durability::Transient), |
| allowed_offers: None, |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }]); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("a".to_string()), |
| target_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})), |
| source_name: Some("b".to_string()), |
| target_name: Some("b".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})), |
| source_name: Some("c".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("c".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef {name: "col".to_string()})), |
| source_name: Some("d".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("d".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ExposeProtocol", "source"), |
| Error::invalid_field("ExposeDirectory", "source"), |
| Error::invalid_field("ExposeRunner", "source"), |
| Error::invalid_field("ExposeResolver", "source"), |
| ])), |
| }, |
| test_validate_exposes_sources_collection => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![ |
| fdecl::Collection { |
| name: Some("col".to_string()), |
| durability: Some(fdecl::Durability::Transient), |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| } |
| ]); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("a".to_string()), |
| ..fdecl::ExposeService::EMPTY |
| }) |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_exposes_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(1025))), |
| target_name: Some(format!("{}", "b".repeat(1025))), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some("a".repeat(101)), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("b".repeat(101)), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some("a".repeat(101)), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("b".repeat(101)), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("ExposeService", "source.child.name"), |
| Error::field_too_long("ExposeService", "source_name"), |
| Error::field_too_long("ExposeService", "target_name"), |
| Error::field_too_long("ExposeProtocol", "source.child.name"), |
| Error::field_too_long("ExposeProtocol", "source_name"), |
| Error::field_too_long("ExposeProtocol", "target_name"), |
| Error::field_too_long("ExposeDirectory", "source.child.name"), |
| Error::field_too_long("ExposeDirectory", "source_name"), |
| Error::field_too_long("ExposeDirectory", "target_name"), |
| Error::field_too_long("ExposeRunner", "source.child.name"), |
| Error::field_too_long("ExposeRunner", "source_name"), |
| Error::field_too_long("ExposeRunner", "target_name"), |
| Error::field_too_long("ExposeResolver", "source.child.name"), |
| Error::field_too_long("ExposeResolver", "source_name"), |
| Error::field_too_long("ExposeResolver", "target_name"), |
| ])), |
| }, |
| test_validate_exposes_invalid_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("data".to_string()), |
| target_name: Some("data".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("ExposeService", "source", "netstack"), |
| Error::invalid_child("ExposeProtocol", "source", "netstack"), |
| Error::invalid_child("ExposeDirectory", "source", "netstack"), |
| Error::invalid_child("ExposeRunner", "source", "netstack"), |
| Error::invalid_child("ExposeResolver", "source", "netstack"), |
| ])), |
| }, |
| test_validate_exposes_invalid_source_capability => { |
| input = { |
| fdecl::Component { |
| exposes: Some(vec![ |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { |
| name: "this-storage-doesnt-exist".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("ExposeProtocol", "source", "this-storage-doesnt-exist"), |
| ])), |
| }, |
| test_validate_exposes_duplicate_target => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("netstack".to_string()), |
| target_name: Some("fuchsia.net.Stack".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("netstack2".to_string()), |
| target_name: Some("fuchsia.net.Stack".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("fonts".to_string()), |
| target_name: Some("fuchsia.fonts.Provider".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("fonts2".to_string()), |
| target_name: Some("fuchsia.fonts.Provider".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("assets".to_string()), |
| target_name: Some("stuff".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: None, |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("assets2".to_string()), |
| target_name: Some("stuff".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| rights: None, |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("source_elf".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("source_elf".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("source_pkg".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("source_pkg".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("netstack".to_string()), |
| source_path: Some("/path".to_string()), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("netstack2".to_string()), |
| source_path: Some("/path".to_string()), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("fonts".to_string()), |
| source_path: Some("/path".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("fonts2".to_string()), |
| source_path: Some("/path".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("assets".to_string()), |
| source_path: Some("/path".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("assets2".to_string()), |
| source_path: Some("/path".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: Some("source_elf".to_string()), |
| source_path: Some("/path".to_string()), |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: Some("source_pkg".to_string()), |
| source_path: Some("/path".to_string()), |
| ..fdecl::Resolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| // Duplicate services are allowed. |
| Error::duplicate_field("ExposeProtocol", "target_name", |
| "fuchsia.fonts.Provider"), |
| Error::duplicate_field("ExposeDirectory", "target_name", |
| "stuff"), |
| Error::duplicate_field("ExposeRunner", "target_name", |
| "elf"), |
| Error::duplicate_field("ExposeResolver", "target_name", "pkg"), |
| ])), |
| }, |
| // TODO: Add analogous test for offer |
| test_validate_exposes_invalid_capability_from_self => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| fdecl::Expose::Service(fdecl::ExposeService { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("fuchsia.netstack.Netstack".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("foo".to_string()), |
| ..fdecl::ExposeService::EMPTY |
| }), |
| fdecl::Expose::Protocol(fdecl::ExposeProtocol { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("fuchsia.netstack.Netstack".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("bar".to_string()), |
| ..fdecl::ExposeProtocol::EMPTY |
| }), |
| fdecl::Expose::Directory(fdecl::ExposeDirectory { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("dir".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("assets".to_string()), |
| rights: None, |
| subdir: None, |
| ..fdecl::ExposeDirectory::EMPTY |
| }), |
| fdecl::Expose::Runner(fdecl::ExposeRunner { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("source_elf".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..fdecl::ExposeRunner::EMPTY |
| }), |
| fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| source_name: Some("source_pkg".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("ExposeService", "source", "fuchsia.netstack.Netstack"), |
| Error::invalid_capability("ExposeProtocol", "source", "fuchsia.netstack.Netstack"), |
| Error::invalid_capability("ExposeDirectory", "source", "dir"), |
| Error::invalid_capability("ExposeRunner", "source", "source_elf"), |
| Error::invalid_capability("ExposeResolver", "source", "source_pkg"), |
| ])), |
| }, |
| |
| // offers |
| test_validate_offers_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| dependency_type: None, |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| rights: None, |
| subdir: None, |
| dependency_type: None, |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: None, |
| source: None, |
| target: None, |
| target_name: None, |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| filter: None, |
| ..fdecl::OfferEvent::EMPTY |
| }) |
| ]); |
| decl |
| }, |
| // TODO(dgonyeo): we need to handle the availability being unset until we've soft |
| // migrated all manifests |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("OfferService", "source"), |
| Error::missing_field("OfferService", "source_name"), |
| Error::missing_field("OfferService", "target"), |
| Error::missing_field("OfferService", "target_name"), |
| //Error::missing_field("OfferService", "availability"), |
| Error::missing_field("OfferProtocol", "source"), |
| Error::missing_field("OfferProtocol", "source_name"), |
| Error::missing_field("OfferProtocol", "target"), |
| Error::missing_field("OfferProtocol", "target_name"), |
| Error::missing_field("OfferProtocol", "dependency_type"), |
| //Error::missing_field("OfferProtocol", "availability"), |
| Error::missing_field("OfferDirectory", "source"), |
| Error::missing_field("OfferDirectory", "source_name"), |
| Error::missing_field("OfferDirectory", "target"), |
| Error::missing_field("OfferDirectory", "target_name"), |
| Error::missing_field("OfferDirectory", "dependency_type"), |
| //Error::missing_field("OfferDirectory", "availability"), |
| Error::missing_field("OfferStorage", "source_name"), |
| Error::missing_field("OfferStorage", "source"), |
| Error::missing_field("OfferStorage", "target"), |
| //Error::missing_field("OfferStorage", "availability"), |
| Error::missing_field("OfferRunner", "source"), |
| Error::missing_field("OfferRunner", "source_name"), |
| Error::missing_field("OfferRunner", "target"), |
| Error::missing_field("OfferRunner", "target_name"), |
| //Error::missing_field("OfferRunner", "availability"), |
| Error::missing_field("OfferEvent", "source_name"), |
| Error::missing_field("OfferEvent", "source"), |
| Error::missing_field("OfferEvent", "target"), |
| Error::missing_field("OfferEvent", "target_name"), |
| //Error::missing_field("OfferEvent", "availability"), |
| ])), |
| }, |
| test_validate_offers_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { |
| name: "b".repeat(101), |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { |
| name: "b".repeat(101), |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { |
| name: "b".repeat(101), |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "b".repeat(101) } |
| )), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some("b".repeat(101)), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { |
| name: "c".repeat(101), |
| } |
| )), |
| target_name: Some("d".repeat(101)), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some("b".repeat(101)), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { |
| name: "c".repeat(101), |
| } |
| )), |
| target_name: Some("d".repeat(101)), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None |
| })), |
| target_name: Some(format!("{}", "a".repeat(101))), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("OfferService", "source.child.name"), |
| Error::field_too_long("OfferService", "source_name"), |
| Error::field_too_long("OfferService", "target.child.name"), |
| Error::field_too_long("OfferService", "target_name"), |
| Error::field_too_long("OfferService", "target.collection.name"), |
| Error::field_too_long("OfferService", "target_name"), |
| Error::field_too_long("OfferProtocol", "source.child.name"), |
| Error::field_too_long("OfferProtocol", "source_name"), |
| Error::field_too_long("OfferProtocol", "target.child.name"), |
| Error::field_too_long("OfferProtocol", "target_name"), |
| Error::field_too_long("OfferProtocol", "target.collection.name"), |
| Error::field_too_long("OfferProtocol", "target_name"), |
| Error::field_too_long("OfferDirectory", "source.child.name"), |
| Error::field_too_long("OfferDirectory", "source_name"), |
| Error::field_too_long("OfferDirectory", "target.child.name"), |
| Error::field_too_long("OfferDirectory", "target_name"), |
| Error::field_too_long("OfferDirectory", "target.collection.name"), |
| Error::field_too_long("OfferDirectory", "target_name"), |
| Error::field_too_long("OfferStorage", "target.child.name"), |
| Error::field_too_long("OfferStorage", "target.collection.name"), |
| Error::field_too_long("OfferRunner", "source.child.name"), |
| Error::field_too_long("OfferRunner", "source_name"), |
| Error::field_too_long("OfferRunner", "target.collection.name"), |
| Error::field_too_long("OfferRunner", "target_name"), |
| Error::field_too_long("OfferResolver", "source.child.name"), |
| Error::field_too_long("OfferResolver", "source_name"), |
| Error::field_too_long("OfferResolver", "target.collection.name"), |
| Error::field_too_long("OfferResolver", "target_name"), |
| Error::field_too_long("OfferEvent", "source_name"), |
| Error::field_too_long("OfferEvent", "target.child.name"), |
| Error::field_too_long("OfferEvent", "target_name"), |
| ])), |
| }, |
| test_validate_offers_extraneous => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{ })), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("elf".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| ]); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("fuchsia.logger.Log".to_string()), |
| source_path: Some("/svc/logger".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("assets".to_string()), |
| source_path: Some("/data/assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("OfferService", "source.child.collection"), |
| Error::extraneous_field("OfferService", "target.child.collection"), |
| Error::extraneous_field("OfferProtocol", "source.child.collection"), |
| Error::extraneous_field("OfferProtocol", "target.child.collection"), |
| Error::extraneous_field("OfferDirectory", "source.child.collection"), |
| Error::extraneous_field("OfferDirectory", "target.child.collection"), |
| Error::extraneous_field("OfferStorage", "target.child.collection"), |
| Error::extraneous_field("OfferRunner", "source.child.collection"), |
| Error::extraneous_field("OfferRunner", "target.child.collection"), |
| Error::extraneous_field("OfferResolver", "source.child.collection"), |
| Error::extraneous_field("OfferResolver", "target.child.collection"), |
| ])), |
| }, |
| test_validate_offers_invalid_filtered_service_fields => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: Some(vec![]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log1".to_string()), |
| renamed_instances: Some(vec![fdecl::NameMapping{source_name: "a".to_string(), target_name: "b".to_string()}, fdecl::NameMapping{source_name: "c".to_string(), target_name: "b".to_string()}]), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("logger".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("OfferService", "source_instance_filter"), |
| Error::invalid_field("OfferService", "renamed_instances"), |
| ])), |
| }, |
| test_validate_offers_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: Some("/foo".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("/path".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("elf!".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("/path".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("pkg!".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("/path".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/path".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::OfferEvent::EMPTY |
| }) |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("OfferService", "source.child.name"), |
| Error::invalid_field("OfferService", "source_name"), |
| Error::invalid_field("OfferService", "target.child.name"), |
| Error::invalid_field("OfferService", "target_name"), |
| Error::invalid_field("OfferProtocol", "source.child.name"), |
| Error::invalid_field("OfferProtocol", "source_name"), |
| Error::invalid_field("OfferProtocol", "target.child.name"), |
| Error::invalid_field("OfferProtocol", "target_name"), |
| Error::invalid_field("OfferDirectory", "source.child.name"), |
| Error::invalid_field("OfferDirectory", "source_name"), |
| Error::invalid_field("OfferDirectory", "target.child.name"), |
| Error::invalid_field("OfferDirectory", "target_name"), |
| Error::invalid_field("OfferDirectory", "subdir"), |
| Error::invalid_field("OfferRunner", "source.child.name"), |
| Error::invalid_field("OfferRunner", "source_name"), |
| Error::invalid_field("OfferRunner", "target.child.name"), |
| Error::invalid_field("OfferRunner", "target_name"), |
| Error::invalid_field("OfferResolver", "source.child.name"), |
| Error::invalid_field("OfferResolver", "source_name"), |
| Error::invalid_field("OfferResolver", "target.child.name"), |
| Error::invalid_field("OfferResolver", "target_name"), |
| Error::invalid_field("OfferEvent", "source_name"), |
| Error::invalid_field("OfferEvent", "target.child.name"), |
| Error::invalid_field("OfferEvent", "target_name"), |
| ])), |
| }, |
| test_validate_offers_target_equals_source => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("logger".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("logger".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("legacy_logger".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("legacy_logger".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("web".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("web".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("logger".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::offer_target_equals_source("OfferService", "logger"), |
| Error::offer_target_equals_source("OfferProtocol", "logger"), |
| Error::offer_target_equals_source("OfferDirectory", "logger"), |
| Error::offer_target_equals_source("OfferRunner", "logger"), |
| Error::offer_target_equals_source("OfferResolver", "logger"), |
| ])), |
| }, |
| test_validate_offers_storage_target_equals_source => { |
| input = fdecl::Component { |
| offers: Some(vec![ |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef { })), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }) |
| ]), |
| capabilities: Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("data".to_string()), |
| backing_dir: Some("minfs".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| ]), |
| children: Some(vec![ |
| fdecl::Child { |
| name: Some("logger".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]), |
| ..new_component_decl() |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle("{{child logger -> child logger}}".to_string()), |
| ])), |
| }, |
| test_validate_offers_invalid_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| ]); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("memfs".to_string()), |
| backing_dir: Some("memfs".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("netstack".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.collections = Some(vec![ |
| fdecl::Collection { |
| name: Some("modular".to_string()), |
| durability: Some(fdecl::Durability::Persistent), |
| environment: None, |
| allowed_offers: Some(fdecl::AllowedOffers::StaticAndDynamic), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("Storage", "source", "logger"), |
| Error::invalid_child("OfferService", "source", "logger"), |
| Error::invalid_child("OfferProtocol", "source", "logger"), |
| Error::invalid_child("OfferDirectory", "source", "logger"), |
| ])), |
| }, |
| test_validate_offers_invalid_source_capability => { |
| input = { |
| fdecl::Component { |
| offers: Some(vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { |
| name: "this-storage-doesnt-exist".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("OfferProtocol", "source", "this-storage-doesnt-exist"), |
| Error::invalid_child("OfferProtocol", "target", "netstack"), |
| ])), |
| }, |
| test_validate_offers_target => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: Some(vec!["instance_0".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: Some(vec!["instance_1".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("duplicated".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("duplicated".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("duplicated".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("stopped".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| target_name: Some("started".to_string()), |
| filter: None, |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("started_on_x".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| target_name: Some("started".to_string()), |
| filter: None, |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child{ |
| name: Some("netstack".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Eager), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.collections = Some(vec![ |
| fdecl::Collection{ |
| name: Some("modular".to_string()), |
| durability: Some(fdecl::Durability::Persistent), |
| environment: None, |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| // Duplicate services are allowed. |
| Error::duplicate_field("OfferProtocol", "target_name", "fuchsia.logger.LegacyLog"), |
| Error::duplicate_field("OfferDirectory", "target_name", "assets"), |
| Error::duplicate_field("OfferRunner", "target_name", "duplicated"), |
| Error::duplicate_field("OfferResolver", "target_name", "duplicated"), |
| Error::duplicate_field("OfferEvent", "target_name", "started"), |
| ])), |
| }, |
| test_validate_offers_target_invalid => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("legacy_logger".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("legacy_logger".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("data".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source_name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("data".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("elf".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("elf".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| source_name: Some("pkg".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("pkg".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("started".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| filter: None, |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source_name: Some("started".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("started".to_string()), |
| target: Some(fdecl::Ref::Collection( |
| fdecl::CollectionRef { name: "modular".to_string(), } |
| )), |
| filter: None, |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("OfferService", "target", "netstack"), |
| Error::invalid_collection("OfferService", "target", "modular"), |
| Error::invalid_child("OfferProtocol", "target", "netstack"), |
| Error::invalid_collection("OfferProtocol", "target", "modular"), |
| Error::invalid_child("OfferDirectory", "target", "netstack"), |
| Error::invalid_collection("OfferDirectory", "target", "modular"), |
| Error::invalid_child("OfferStorage", "target", "netstack"), |
| Error::invalid_collection("OfferStorage", "target", "modular"), |
| Error::invalid_child("OfferRunner", "target", "netstack"), |
| Error::invalid_collection("OfferRunner", "target", "modular"), |
| Error::invalid_child("OfferResolver", "target", "netstack"), |
| Error::invalid_collection("OfferResolver", "target", "modular"), |
| Error::invalid_child("OfferEvent", "target", "netstack"), |
| Error::invalid_collection("OfferEvent", "target", "modular"), |
| ])), |
| }, |
| test_validate_offers_invalid_source_collection => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![ |
| fdecl::Collection { |
| name: Some("col".to_string()), |
| durability: Some(fdecl::Durability::Transient), |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| } |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("b".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("b".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("c".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("c".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("d".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("d".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("e".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("e".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("f".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("f".to_string()), |
| filter: None, |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("OfferProtocol", "source"), |
| Error::invalid_field("OfferDirectory", "source"), |
| Error::invalid_field("OfferStorage", "source"), |
| Error::invalid_field("OfferRunner", "source"), |
| Error::invalid_field("OfferResolver", "source"), |
| Error::invalid_field("OfferEvent", "source"), |
| ])), |
| }, |
| test_validate_offers_source_collection => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![ |
| fdecl::Collection { |
| name: Some("col".to_string()), |
| durability: Some(fdecl::Durability::Transient), |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| } |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| ..fdecl::Child::EMPTY |
| } |
| ]); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name: "col".to_string() })), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_offers_event_from_realm => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some( |
| vec![ |
| fdecl::Ref::Self_(fdecl::SelfRef {}), |
| fdecl::Ref::Child(fdecl::ChildRef {name: "netstack".to_string(), collection: None }), |
| fdecl::Ref::Collection(fdecl::CollectionRef {name: "modular".to_string() }), |
| ] |
| .into_iter() |
| .enumerate() |
| .map(|(i, source)| { |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(source), |
| source_name: Some("started".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| target_name: Some(format!("started_{}", i)), |
| |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| ..fdecl::OfferEvent::EMPTY |
| }) |
| }) |
| .collect()); |
| decl.children = Some(vec![ |
| fdecl::Child{ |
| name: Some("netstack".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Eager), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.collections = Some(vec![ |
| fdecl::Collection { |
| name: Some("modular".to_string()), |
| durability: Some(fdecl::Durability::Persistent), |
| environment: None, |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("OfferEvent", "source"), |
| Error::invalid_field("OfferEvent", "source"), |
| Error::invalid_field("OfferEvent", "source"), |
| ])), |
| }, |
| test_validate_offers_long_dependency_cycle => { |
| input = { |
| let mut decl = new_component_decl(); |
| let dependencies = vec![ |
| ("d", "b"), |
| ("a", "b"), |
| ("b", "c"), |
| ("b", "d"), |
| ("c", "a"), |
| ]; |
| let offers = dependencies.into_iter().map(|(from,to)| |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: from.to_string(), collection: None }, |
| )), |
| source_name: Some(format!("thing_{}", from)), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { name: to.to_string(), collection: None }, |
| )), |
| target_name: Some(format!("thing_{}", from)), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| })).collect(); |
| let children = ["a", "b", "c", "d"].iter().map(|name| { |
| fdecl::Child { |
| name: Some(name.to_string()), |
| url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| } |
| }).collect(); |
| decl.offers = Some(offers); |
| decl.children = Some(children); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle(directed_graph::Error::CyclesDetected([vec!["child a", "child b", "child c", "child a"], vec!["child b", "child d", "child b"]].iter().cloned().collect()).format_cycle()), |
| ])), |
| }, |
| test_validate_offers_not_required_invalid_source_service => { |
| input = { |
| let mut decl = generate_offer_invalid_source_and_availability_decl( |
| |source, availability, target_name| |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(source), |
| source_name: Some("fuchsia.examples.Echo".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "sink".to_string(), |
| collection: None, |
| })), |
| target_name: Some(target_name.into()), |
| availability: Some(availability), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("fuchsia.examples.Echo".to_string()), |
| source_path: Some("/svc/fuchsia.examples.Echo".to_string()), |
| ..fdecl::Service::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = { |
| Err(ErrorList::new(vec![ |
| Error::availability_must_be_required( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferService", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| ])) |
| }, |
| }, |
| test_validate_offers_not_required_invalid_source_protocol => { |
| input = { |
| let mut decl = generate_offer_invalid_source_and_availability_decl( |
| |source, availability, target_name| |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(source), |
| source_name: Some("fuchsia.examples.Echo".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "sink".to_string(), |
| collection: None, |
| })), |
| target_name: Some(target_name.into()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| availability: Some(availability), |
| ..fdecl::OfferProtocol::EMPTY |
| }) |
| ); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("fuchsia.examples.Echo".to_string()), |
| source_path: Some("/svc/fuchsia.examples.Echo".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = { |
| Err(ErrorList::new(vec![ |
| Error::availability_must_be_required( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferProtocol", |
| "availability", |
| Some(&"fuchsia.examples.Echo".to_string()), |
| ), |
| ])) |
| }, |
| }, |
| test_validate_offers_not_required_invalid_source_directory => { |
| input = { |
| let mut decl = generate_offer_invalid_source_and_availability_decl( |
| |source, availability, target_name| |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| source: Some(source), |
| source_name: Some("assets".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "sink".to_string(), |
| collection: None, |
| })), |
| target_name: Some(target_name.into()), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Weak), |
| availability: Some(availability), |
| ..fdecl::OfferDirectory::EMPTY |
| }) |
| ); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("assets".to_string()), |
| source_path: Some("/assets".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = { |
| Err(ErrorList::new(vec![ |
| Error::availability_must_be_required( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferDirectory", |
| "availability", |
| Some(&"assets".to_string()), |
| ), |
| ])) |
| }, |
| }, |
| test_validate_offers_not_required_invalid_source_storage => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("sink".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/sink#meta/sink.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("data".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| backing_dir: Some("minfs".to_string()), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| ]); |
| let new_offer = |source: fdecl::Ref, availability: fdecl::Availability, |
| target_name: &str| |
| { |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(source), |
| source_name: Some("data".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "sink".to_string(), |
| collection: None, |
| })), |
| target_name: Some(target_name.into()), |
| availability: Some(availability), |
| ..fdecl::OfferStorage::EMPTY |
| }) |
| }; |
| decl.offers = Some(vec![ |
| // These offers are fine, offers with a source of parent or void can be |
| // optional. |
| new_offer( |
| fdecl::Ref::Parent(fdecl::ParentRef {}), |
| fdecl::Availability::Required, |
| "data0", |
| ), |
| new_offer( |
| fdecl::Ref::Parent(fdecl::ParentRef {}), |
| fdecl::Availability::Optional, |
| "data1", |
| ), |
| new_offer( |
| fdecl::Ref::Parent(fdecl::ParentRef {}), |
| fdecl::Availability::SameAsTarget, |
| "data2", |
| ), |
| new_offer( |
| fdecl::Ref::VoidType(fdecl::VoidRef {}), |
| fdecl::Availability::Optional, |
| "data3", |
| ), |
| // These offers are not fine, offers with a source other than parent or void |
| // must be required. |
| new_offer( |
| fdecl::Ref::Self_(fdecl::SelfRef {}), |
| fdecl::Availability::Optional, |
| "data4", |
| ), |
| new_offer( |
| fdecl::Ref::Self_(fdecl::SelfRef {}), |
| fdecl::Availability::SameAsTarget, |
| "data5", |
| ), |
| // These offers are also not fine, offers with a source of void must be optional |
| new_offer( |
| fdecl::Ref::VoidType(fdecl::VoidRef {}), |
| fdecl::Availability::Required, |
| "data6", |
| ), |
| new_offer( |
| fdecl::Ref::VoidType(fdecl::VoidRef {}), |
| fdecl::Availability::SameAsTarget, |
| "data7", |
| ), |
| ]); |
| decl |
| }, |
| result = { |
| Err(ErrorList::new(vec![ |
| Error::availability_must_be_required( |
| "OfferStorage", |
| "availability", |
| Some(&"data".to_string()), |
| ), |
| Error::availability_must_be_required( |
| "OfferStorage", |
| "availability", |
| Some(&"data".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferStorage", |
| "availability", |
| Some(&"data".to_string()), |
| ), |
| Error::availability_must_be_optional( |
| "OfferStorage", |
| "availability", |
| Some(&"data".to_string()), |
| ), |
| ])) |
| }, |
| }, |
| |
| test_validate_offers_valid_service_aggregation => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_a".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: Some(vec!["default".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_b".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: Some(vec!["a_different_default".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("child_a".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_b".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_c".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| |
| // environments |
| test_validate_environment_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: None, |
| extends: None, |
| runners: None, |
| resolvers: None, |
| stop_timeout_ms: None, |
| debug_capabilities: None, |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Environment", "name"), |
| Error::missing_field("Environment", "extends"), |
| ])), |
| }, |
| |
| test_validate_environment_no_stop_timeout => { |
| input = { let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: None, |
| resolvers: None, |
| stop_timeout_ms: None, |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![Error::missing_field("Environment", "stop_timeout_ms")])), |
| }, |
| |
| test_validate_environment_extends_stop_timeout => { |
| input = { let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::Realm), |
| runners: None, |
| resolvers: None, |
| stop_timeout_ms: None, |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_environment_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("a".repeat(101)), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: Some("a".repeat(101)), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target_name: Some("a".repeat(101)), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("a".repeat(101)), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| scheme: Some("a".repeat(101)), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("Environment", "name"), |
| Error::field_too_long("RunnerRegistration", "source_name"), |
| Error::field_too_long("RunnerRegistration", "target_name"), |
| Error::field_too_long("ResolverRegistration", "resolver"), |
| Error::field_too_long("ResolverRegistration", "scheme"), |
| ])), |
| }, |
| test_validate_environment_empty_runner_resolver_fields => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: None, |
| source: None, |
| target_name: None, |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: None, |
| source: None, |
| scheme: None, |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("RunnerRegistration", "source_name"), |
| Error::missing_field("RunnerRegistration", "source"), |
| Error::missing_field("RunnerRegistration", "target_name"), |
| Error::missing_field("ResolverRegistration", "resolver"), |
| Error::missing_field("ResolverRegistration", "source"), |
| Error::missing_field("ResolverRegistration", "scheme"), |
| ])), |
| }, |
| test_validate_environment_invalid_fields => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: Some("^a".to_string()), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| target_name: Some("%a".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("^a".to_string()), |
| source: Some(fdecl::Ref::Framework(fdecl::FrameworkRef{})), |
| scheme: Some("9scheme".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("RunnerRegistration", "source_name"), |
| Error::invalid_field("RunnerRegistration", "source"), |
| Error::invalid_field("RunnerRegistration", "target_name"), |
| Error::invalid_field("ResolverRegistration", "resolver"), |
| Error::invalid_field("ResolverRegistration", "source"), |
| Error::invalid_field("ResolverRegistration", "scheme"), |
| ])), |
| }, |
| test_validate_environment_missing_runner => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: Some("dart".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| target_name: Some("dart".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: None, |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_runner("RunnerRegistration", "source_name", "dart"), |
| ])), |
| }, |
| test_validate_environment_duplicate_registrations => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: Some("dart".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target_name: Some("dart".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| fdecl::RunnerRegistration { |
| source_name: Some("other-dart".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| target_name: Some("dart".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("pkg_resolver".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| fdecl::ResolverRegistration { |
| resolver: Some("base_resolver".to_string()), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef{})), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::duplicate_field("RunnerRegistration", "target_name", "dart"), |
| Error::duplicate_field("ResolverRegistration", "scheme", "fuchsia-pkg"), |
| ])), |
| }, |
| test_validate_environment_from_missing_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: Some("elf".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ |
| name: "missing".to_string(), |
| collection: None, |
| })), |
| target_name: Some("elf".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("pkg_resolver".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ |
| name: "missing".to_string(), |
| collection: None, |
| })), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("RunnerRegistration", "source", "missing"), |
| Error::invalid_child("ResolverRegistration", "source", "missing"), |
| ])), |
| }, |
| test_validate_environment_runner_child_cycle => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: Some(vec![ |
| fdecl::RunnerRegistration { |
| source_name: Some("elf".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ |
| name: "child".to_string(), |
| collection: None, |
| })), |
| target_name: Some("elf".to_string()), |
| ..fdecl::RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: None, |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl.children = Some(vec![fdecl::Child { |
| name: Some("child".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| url: Some("fuchsia-pkg://child".to_string()), |
| environment: Some("env".to_string()), |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child child", "environment env", "child child"]].iter().cloned().collect()).format_cycle() |
| ), |
| ])), |
| }, |
| test_validate_environment_resolver_child_cycle => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: None, |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("pkg_resolver".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ |
| name: "child".to_string(), |
| collection: None, |
| })), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl.children = Some(vec![fdecl::Child { |
| name: Some("child".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| url: Some("fuchsia-pkg://child".to_string()), |
| environment: Some("env".to_string()), |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child child", "environment env", "child child"]].iter().cloned().collect()).format_cycle() |
| ), |
| ])), |
| }, |
| test_validate_environment_resolver_multiple_children_cycle => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![fdecl::Environment { |
| name: Some("env".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| runners: None, |
| resolvers: Some(vec![ |
| fdecl::ResolverRegistration { |
| resolver: Some("pkg_resolver".to_string()), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{ |
| name: "a".to_string(), |
| collection: None, |
| })), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..fdecl::ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("a".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| url: Some("fuchsia-pkg://child-a".to_string()), |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| url: Some("fuchsia-pkg://child-b".to_string()), |
| environment: Some("env".to_string()), |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.offers = Some(vec![fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".to_string(), |
| collection: None, |
| })), |
| source_name: Some("thing".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("thing".to_string()), |
| ..fdecl::OfferService::EMPTY |
| })]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child a", "environment env", "child b", "child a"]].iter().cloned().collect()).format_cycle() |
| ), |
| ])), |
| }, |
| test_validate_environment_debug_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: None, |
| source_name: None, |
| target_name: None, |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("DebugProtocolRegistration", "source"), |
| Error::missing_field("DebugProtocolRegistration", "source_name"), |
| Error::missing_field("DebugProtocolRegistration", "target_name"), |
| ])), |
| }, |
| test_validate_environment_debug_log_identifier => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("a".to_string()), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("DebugProtocolRegistration", "source.child.name"), |
| Error::field_too_long("DebugProtocolRegistration", "source_name"), |
| Error::field_too_long("DebugProtocolRegistration", "target_name"), |
| Error::field_too_long("DebugProtocolRegistration", "target_name"), |
| ])), |
| }, |
| test_validate_environment_debug_log_extraneous => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("DebugProtocolRegistration", "source.child.collection"), |
| ])), |
| }, |
| test_validate_environment_debug_log_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("DebugProtocolRegistration", "source.child.name"), |
| Error::invalid_field("DebugProtocolRegistration", "source_name"), |
| Error::invalid_field("DebugProtocolRegistration", "target_name"), |
| ])), |
| }, |
| test_validate_environment_debug_log_invalid_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("netstack".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("DebugProtocolRegistration", "source", "logger"), |
| |
| ])), |
| }, |
| test_validate_environment_debug_source_capability => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| fdecl::Environment { |
| name: Some("a".to_string()), |
| extends: Some(fdecl::EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| fdecl::DebugRegistration::Protocol(fdecl::DebugProtocolRegistration { |
| source: Some(fdecl::Ref::Capability(fdecl::CapabilityRef { |
| name: "storage".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| ..fdecl::DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..fdecl::Environment::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("DebugProtocolRegistration", "source"), |
| ])), |
| }, |
| |
| // children |
| test_validate_children_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![fdecl::Child{ |
| name: None, |
| url: None, |
| startup: None, |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Child", "name"), |
| Error::missing_field("Child", "url"), |
| Error::missing_field("Child", "startup"), |
| // `on_terminate` is allowed to be None |
| ])), |
| }, |
| test_validate_children_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("^bad".to_string()), |
| url: Some("bad-scheme&://blah".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("Child", "name"), |
| Error::invalid_field("Child", "url"), |
| ])), |
| }, |
| test_validate_children_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("a".repeat(1025)), |
| url: Some(format!("fuchsia-pkg://{}", "a".repeat(4083))), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: Some("a".repeat(1025)), |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long_with_max("Child", "name", 100), |
| Error::field_too_long_with_max("Child", "url", 4096), |
| Error::field_too_long_with_max("Child", "environment", 100), |
| Error::invalid_environment("Child", "environment", "a".repeat(1025)), |
| ])), |
| }, |
| test_validate_child_references_unknown_env => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![fdecl::Child{ |
| name: Some("foo".to_string()), |
| url: Some("fuchsia-pkg://foo".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: Some("test_env".to_string()), |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_environment("Child", "environment", "test_env"), |
| ])), |
| }, |
| |
| // collections |
| test_validate_collections_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![fdecl::Collection{ |
| name: None, |
| durability: None, |
| environment: None, |
| allowed_offers: None, |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Collection", "name"), |
| Error::missing_field("Collection", "durability"), |
| ])), |
| }, |
| test_validate_collections_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![fdecl::Collection{ |
| name: Some("^bad".to_string()), |
| durability: Some(fdecl::Durability::Persistent), |
| environment: None, |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("Collection", "name"), |
| ])), |
| }, |
| test_validate_collections_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![fdecl::Collection{ |
| name: Some("a".repeat(1025)), |
| durability: Some(fdecl::Durability::Transient), |
| environment: None, |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("Collection", "name"), |
| ])), |
| }, |
| test_validate_collection_references_unknown_env => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![fdecl::Collection { |
| name: Some("foo".to_string()), |
| durability: Some(fdecl::Durability::Transient), |
| environment: Some("test_env".to_string()), |
| allowed_offers: Some(fdecl::AllowedOffers::StaticOnly), |
| allow_long_names: None, |
| ..fdecl::Collection::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_environment("Collection", "environment", "test_env"), |
| ])), |
| }, |
| |
| // capabilities |
| test_validate_capabilities_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: None, |
| source_path: None, |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: None, |
| source_path: None, |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: None, |
| source_path: None, |
| rights: None, |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: None, |
| source: None, |
| backing_dir: None, |
| subdir: None, |
| storage_id: None, |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: None, |
| source_path: None, |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: None, |
| source_path: None, |
| ..fdecl::Resolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Service", "name"), |
| Error::missing_field("Service", "source_path"), |
| Error::missing_field("Protocol", "name"), |
| Error::missing_field("Protocol", "source_path"), |
| Error::missing_field("Directory", "name"), |
| Error::missing_field("Directory", "source_path"), |
| Error::missing_field("Directory", "rights"), |
| Error::missing_field("Storage", "source"), |
| Error::missing_field("Storage", "name"), |
| Error::missing_field("Storage", "storage_id"), |
| Error::missing_field("Storage", "backing_dir"), |
| Error::missing_field("Runner", "name"), |
| Error::missing_field("Runner", "source_path"), |
| Error::missing_field("Resolver", "name"), |
| Error::missing_field("Resolver", "source_path"), |
| ])), |
| }, |
| test_validate_capabilities_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("^bad".to_string()), |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { |
| name: "/bad".to_string() |
| })), |
| backing_dir: Some("&bad".to_string()), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..fdecl::Resolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("Service", "name"), |
| Error::invalid_field("Service", "source_path"), |
| Error::invalid_field("Protocol", "name"), |
| Error::invalid_field("Protocol", "source_path"), |
| Error::invalid_field("Directory", "name"), |
| Error::invalid_field("Directory", "source_path"), |
| Error::invalid_field("Storage", "source"), |
| Error::invalid_field("Storage", "name"), |
| Error::invalid_field("Storage", "backing_dir"), |
| Error::invalid_field("Runner", "name"), |
| Error::invalid_field("Runner", "source_path"), |
| Error::invalid_field("Resolver", "name"), |
| Error::invalid_field("Resolver", "source_path"), |
| ])), |
| }, |
| test_validate_capabilities_invalid_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("foo".to_string()), |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { |
| name: "invalid".to_string(), |
| })), |
| backing_dir: Some("foo".to_string()), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("Storage", "source"), |
| ])), |
| }, |
| test_validate_capabilities_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("a".repeat(101)), |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| backing_dir: Some(format!("{}", "c".repeat(101))), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "b".repeat(1024))), |
| ..fdecl::Resolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long_with_max("Service", "name", 100), |
| Error::field_too_long_with_max("Service", "source_path", 1024), |
| Error::field_too_long_with_max("Protocol", "name", 100), |
| Error::field_too_long_with_max("Protocol", "source_path", 1024), |
| Error::field_too_long_with_max("Directory", "name", 100), |
| Error::field_too_long_with_max("Directory", "source_path", 1024), |
| Error::field_too_long_with_max("Storage", "source.child.name", 100), |
| Error::field_too_long_with_max("Storage", "name", 100), |
| Error::field_too_long_with_max("Storage", "backing_dir", 100), |
| Error::field_too_long_with_max("Runner", "name", 100), |
| Error::field_too_long_with_max("Runner", "source_path", 1024), |
| Error::field_too_long_with_max("Resolver", "name", 100), |
| Error::field_too_long_with_max("Resolver", "source_path", 1024), |
| ])), |
| }, |
| test_validate_capabilities_duplicate_name => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("service".to_string()), |
| source_path: Some("/service".to_string()), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("service".to_string()), |
| source_path: Some("/service".to_string()), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("protocol".to_string()), |
| source_path: Some("/protocol".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("protocol".to_string()), |
| source_path: Some("/protocol".to_string()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("directory".to_string()), |
| source_path: Some("/directory".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("directory".to_string()), |
| source_path: Some("/directory".to_string()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("storage".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| backing_dir: Some("directory".to_string()), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: Some("storage".to_string()), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})), |
| backing_dir: Some("directory".to_string()), |
| subdir: None, |
| storage_id: Some(fdecl::StorageId::StaticInstanceIdOrMoniker), |
| ..fdecl::Storage::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: Some("runner".to_string()), |
| source_path: Some("/runner".to_string()), |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: Some("runner".to_string()), |
| source_path: Some("/runner".to_string()), |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: Some("resolver".to_string()), |
| source_path: Some("/resolver".to_string()), |
| ..fdecl::Resolver::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: Some("resolver".to_string()), |
| source_path: Some("/resolver".to_string()), |
| ..fdecl::Resolver::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::duplicate_field("Service", "name", "service"), |
| Error::duplicate_field("Protocol", "name", "protocol"), |
| Error::duplicate_field("Directory", "name", "directory"), |
| Error::duplicate_field("Storage", "name", "storage"), |
| Error::duplicate_field("Runner", "name", "runner"), |
| Error::duplicate_field("Resolver", "name", "resolver"), |
| ])), |
| }, |
| |
| test_validate_invalid_service_aggregation_missing_filter => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_a".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: Some(vec!["default".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_b".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| source_instance_filter: None, |
| ..fdecl::OfferService::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("child_a".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_b".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_c".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_aggregate_offer("source_instance_filter must be set for all aggregate service offers"), |
| ])), |
| }, |
| |
| test_validate_invalid_service_aggregation_conflicting_filter => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_a".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log1".to_string()), |
| source_instance_filter: Some(vec!["default".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_b".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log1".to_string()), |
| source_instance_filter: Some(vec!["default".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("child_a".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_b".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_c".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_aggregate_offer("Conflicting source_instance_filter in aggregate service offer, instance_name 'default' seen in filter lists multiple times"), |
| ])), |
| }, |
| |
| test_validate_invalid_service_aggregation_conflicting_source_name => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_a".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log2".to_string()), |
| source_instance_filter: Some(vec!["default2".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Child(fdecl::ChildRef{name: "child_b".to_string(), collection: None})), |
| source_name: Some("fuchsia.logger.LogAlt".to_string()), |
| target: Some(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "child_c".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log2".to_string()), |
| source_instance_filter: Some(vec!["default".to_string()]), |
| ..fdecl::OfferService::EMPTY |
| }) |
| ]); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("child_a".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_b".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("child_c".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_aggregate_offer("All aggregate service offers must have the same source_name, saw fuchsia.logger.Log, fuchsia.logger.LogAlt. Use renamed_instances to rename instance names to avoid conflict."), |
| ])), |
| }, |
| |
| test_validate_resolvers_missing_from_offer => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { name: "child".to_string(), collection: None })), |
| target_name: Some("a".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| })]); |
| decl.children = Some(vec![fdecl::Child { |
| name: Some("child".to_string()), |
| url: Some("test:///child".to_string()), |
| startup: Some(fdecl::StartupMode::Eager), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("OfferResolver", "source", "a"), |
| ])), |
| }, |
| test_validate_resolvers_missing_from_expose => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![fdecl::Expose::Resolver(fdecl::ExposeResolver { |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| target_name: Some("a".to_string()), |
| ..fdecl::ExposeResolver::EMPTY |
| })]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("ExposeResolver", "source", "a"), |
| ])), |
| }, |
| |
| test_validate_config_missing_config => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: None, |
| checksum: None, |
| value_source: None, |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ConfigSchema", "fields"), |
| Error::missing_field("ConfigSchema", "checksum"), |
| Error::missing_field("ConfigSchema", "value_source"), |
| ])), |
| }, |
| |
| test_validate_config_missing_config_field => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: None, |
| type_: None, |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ConfigField", "key"), |
| Error::missing_field("ConfigField", "value_type"), |
| ])), |
| }, |
| |
| test_validate_config_bool => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Bool, |
| parameters: Some(vec![]), |
| constraints: vec![] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| |
| test_validate_config_bool_extra_constraint => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Bool, |
| parameters: Some(vec![]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("ConfigType", "constraints") |
| ])), |
| }, |
| |
| test_validate_config_bool_missing_parameters => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Bool, |
| parameters: None, |
| constraints: vec![] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ConfigType", "parameters") |
| ])), |
| }, |
| |
| test_validate_config_string => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::String, |
| parameters: Some(vec![]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| |
| test_validate_config_string_missing_parameter => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::String, |
| parameters: None, |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ConfigType", "parameters") |
| ])), |
| }, |
| |
| test_validate_config_string_missing_constraint => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::String, |
| parameters: Some(vec![]), |
| constraints: vec![] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ConfigType", "constraints") |
| ])), |
| }, |
| |
| test_validate_config_string_extra_constraint => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::String, |
| parameters: Some(vec![]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10), fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("ConfigType", "constraints") |
| ])), |
| }, |
| |
| test_validate_config_vector_bool => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Vector, |
| parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Bool, |
| parameters: Some(vec![]), |
| constraints: vec![], |
| })]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| |
| test_validate_config_vector_extra_parameter => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Vector, |
| parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Bool, |
| parameters: Some(vec![]), |
| constraints: vec![], |
| }), fdecl::LayoutParameter::NestedType(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Uint8, |
| parameters: Some(vec![]), |
| constraints: vec![], |
| })]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("ConfigType", "parameters") |
| ])), |
| }, |
| |
| test_validate_config_vector_missing_parameter => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Vector, |
| parameters: Some(vec![]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ConfigType", "parameters") |
| ])), |
| }, |
| |
| test_validate_config_vector_string => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Vector, |
| parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::String, |
| parameters: Some(vec![]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| })]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| |
| test_validate_config_vector_vector => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.config = Some(fdecl::ConfigSchema{ |
| fields: Some(vec![ |
| fdecl::ConfigField { |
| key: Some("test".to_string()), |
| type_: Some(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Vector, |
| parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::Vector, |
| parameters: Some(vec![fdecl::LayoutParameter::NestedType(fdecl::ConfigType { |
| layout: fdecl::ConfigTypeLayout::String, |
| parameters: Some(vec![]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| })]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| })]), |
| constraints: vec![fdecl::LayoutConstraint::MaxSize(10)] |
| }), |
| ..fdecl::ConfigField::EMPTY |
| } |
| ]), |
| checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])), |
| value_source: Some(fdecl::ConfigValueSource::PackagePath("config/test.cvf".to_string())), |
| ..fdecl::ConfigSchema::EMPTY |
| }); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::nested_vector() |
| ])), |
| }, |
| } |
| |
| test_validate_capabilities! { |
| test_validate_capabilities_individually_ok => { |
| input = vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("foo_svc".into()), |
| source_path: Some("/svc/foo".into()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("foo_dir".into()), |
| source_path: Some("/foo".into()), |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| ], |
| as_builtin = false, |
| result = Ok(()), |
| }, |
| test_validate_capabilities_individually_err => { |
| input = vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: None, |
| source_path: None, |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: None, |
| source_path: None, |
| rights: None, |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Event(fdecl::Event { |
| name: None, |
| ..fdecl::Event::EMPTY |
| }), |
| ], |
| as_builtin = false, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Protocol", "name"), |
| Error::missing_field("Protocol", "source_path"), |
| Error::missing_field("Directory", "name"), |
| Error::missing_field("Directory", "source_path"), |
| Error::missing_field("Directory", "rights"), |
| Error::invalid_capability_type("Component", "capability", "event") |
| ])), |
| }, |
| test_validate_builtin_capabilities_individually_ok => { |
| input = vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: Some("foo_protocol".into()), |
| source_path: None, |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: Some("foo_dir".into()), |
| source_path: None, |
| rights: Some(fio::Operations::CONNECT), |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Service(fdecl::Service { |
| name: Some("foo_svc".into()), |
| source_path: None, |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: Some("foo_runner".into()), |
| source_path: None, |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: Some("foo_resolver".into()), |
| source_path: None, |
| ..fdecl::Resolver::EMPTY |
| }), |
| fdecl::Capability::Event(fdecl::Event { |
| name: Some("foo_event".into()), |
| ..fdecl::Event::EMPTY |
| }), |
| ], |
| as_builtin = true, |
| result = Ok(()), |
| }, |
| test_validate_builtin_capabilities_individually_err => { |
| input = vec![ |
| fdecl::Capability::Protocol(fdecl::Protocol { |
| name: None, |
| source_path: Some("/svc/foo".into()), |
| ..fdecl::Protocol::EMPTY |
| }), |
| fdecl::Capability::Directory(fdecl::Directory { |
| name: None, |
| source_path: Some("/foo".into()), |
| rights: None, |
| ..fdecl::Directory::EMPTY |
| }), |
| fdecl::Capability::Service(fdecl::Service { |
| name: None, |
| source_path: Some("/svc/foo".into()), |
| ..fdecl::Service::EMPTY |
| }), |
| fdecl::Capability::Runner(fdecl::Runner { |
| name: None, |
| source_path: Some("/foo".into()), |
| ..fdecl::Runner::EMPTY |
| }), |
| fdecl::Capability::Resolver(fdecl::Resolver { |
| name: None, |
| source_path: Some("/foo".into()), |
| ..fdecl::Resolver::EMPTY |
| }), |
| fdecl::Capability::Event(fdecl::Event { |
| name: None, |
| ..fdecl::Event::EMPTY |
| }), |
| fdecl::Capability::Storage(fdecl::Storage { |
| name: None, |
| ..fdecl::Storage::EMPTY |
| }), |
| ], |
| as_builtin = true, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("Protocol", "name"), |
| Error::extraneous_source_path("Protocol", "/svc/foo"), |
| Error::missing_field("Directory", "name"), |
| Error::extraneous_source_path("Directory", "/foo"), |
| Error::missing_field("Directory", "rights"), |
| Error::missing_field("Service", "name"), |
| Error::extraneous_source_path("Service", "/svc/foo"), |
| Error::missing_field("Runner", "name"), |
| Error::extraneous_source_path("Runner", "/foo"), |
| Error::missing_field("Resolver", "name"), |
| Error::extraneous_source_path("Resolver", "/foo"), |
| Error::missing_field("Event", "name"), |
| Error::invalid_capability_type("RuntimeConfig", "capability", "storage"), |
| ])), |
| }, |
| } |
| |
| fn generate_offer_invalid_source_and_availability_decl( |
| new_offer: impl Fn(fdecl::Ref, fdecl::Availability, &str) -> fdecl::Offer, |
| ) -> fdecl::Component { |
| let mut decl = new_component_decl(); |
| decl.children = Some(vec![ |
| fdecl::Child { |
| name: Some("source".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/source#meta/source.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| fdecl::Child { |
| name: Some("sink".to_string()), |
| url: Some("fuchsia-pkg://fuchsia.com/sink#meta/sink.cm".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }, |
| ]); |
| decl.offers = Some(vec![ |
| // These offers are fine, offers with a source of parent or void can be |
| // optional. |
| new_offer( |
| fdecl::Ref::Parent(fdecl::ParentRef {}), |
| fdecl::Availability::Required, |
| "fuchsia.examples.Echo0", |
| ), |
| new_offer( |
| fdecl::Ref::Parent(fdecl::ParentRef {}), |
| fdecl::Availability::Optional, |
| "fuchsia.examples.Echo1", |
| ), |
| new_offer( |
| fdecl::Ref::Parent(fdecl::ParentRef {}), |
| fdecl::Availability::SameAsTarget, |
| "fuchsia.examples.Echo2", |
| ), |
| new_offer( |
| fdecl::Ref::VoidType(fdecl::VoidRef {}), |
| fdecl::Availability::Optional, |
| "fuchsia.examples.Echo3", |
| ), |
| // These offers are not fine, offers with a source other than parent or void |
| // must be required. |
| new_offer( |
| fdecl::Ref::Self_(fdecl::SelfRef {}), |
| fdecl::Availability::Optional, |
| "fuchsia.examples.Echo4", |
| ), |
| new_offer( |
| fdecl::Ref::Self_(fdecl::SelfRef {}), |
| fdecl::Availability::SameAsTarget, |
| "fuchsia.examples.Echo5", |
| ), |
| new_offer( |
| fdecl::Ref::Framework(fdecl::FrameworkRef {}), |
| fdecl::Availability::Optional, |
| "fuchsia.examples.Echo6", |
| ), |
| new_offer( |
| fdecl::Ref::Framework(fdecl::FrameworkRef {}), |
| fdecl::Availability::SameAsTarget, |
| "fuchsia.examples.Echo7", |
| ), |
| new_offer( |
| fdecl::Ref::Child(fdecl::ChildRef { name: "source".to_string(), collection: None }), |
| fdecl::Availability::Optional, |
| "fuchsia.examples.Echo8", |
| ), |
| new_offer( |
| fdecl::Ref::Child(fdecl::ChildRef { name: "source".to_string(), collection: None }), |
| fdecl::Availability::SameAsTarget, |
| "fuchsia.examples.Echo9", |
| ), |
| // These offers are also not fine, offers with a source of void must be optional |
| new_offer( |
| fdecl::Ref::VoidType(fdecl::VoidRef {}), |
| fdecl::Availability::Required, |
| "fuchsia.examples.Echo10", |
| ), |
| new_offer( |
| fdecl::Ref::VoidType(fdecl::VoidRef {}), |
| fdecl::Availability::SameAsTarget, |
| "fuchsia.examples.Echo11", |
| ), |
| ]); |
| decl |
| } |
| |
| #[test] |
| fn test_validate_dynamic_offers_empty() { |
| assert_eq!(validate_dynamic_offers(&vec![]), Ok(())); |
| } |
| |
| #[test] |
| fn test_validate_dynamic_offers_okay() { |
| assert_eq!( |
| validate_dynamic_offers(&vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef)), |
| source_name: Some("thing".repeat(26)), |
| target_name: Some("thing".repeat(26)), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thang".repeat(26)), |
| target_name: Some("thang".repeat(26)), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thung1".repeat(26)), |
| target_name: Some("thung1".repeat(26)), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thung2".repeat(26)), |
| target_name: Some("thung2".repeat(26)), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thung3".repeat(26)), |
| target_name: Some("thung3".repeat(26)), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thung4".repeat(26)), |
| target_name: Some("thung4".repeat(26)), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thung5".repeat(26)), |
| target_name: Some("thung5".repeat(26)), |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| ]), |
| Ok(()) |
| ); |
| } |
| |
| #[test] |
| fn test_validate_dynamic_offers_specify_target() { |
| assert_eq!( |
| validate_dynamic_offers(&vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Self_(fdecl::SelfRef)), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "foo".to_string(), |
| collection: None |
| })), |
| source_name: Some("thing".to_string()), |
| target_name: Some("thing".to_string()), |
| ..fdecl::OfferProtocol::EMPTY |
| }), |
| fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "bar".to_string(), |
| collection: Some("baz".to_string()) |
| })), |
| source_name: Some("thang".to_string()), |
| target_name: Some("thang".to_string()), |
| ..fdecl::OfferService::EMPTY |
| }), |
| fdecl::Offer::Directory(fdecl::OfferDirectory { |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| target: Some(fdecl::Ref::Framework(fdecl::FrameworkRef)), |
| source_name: Some("thung1".to_string()), |
| target_name: Some("thung1".to_string()), |
| ..fdecl::OfferDirectory::EMPTY |
| }), |
| fdecl::Offer::Storage(fdecl::OfferStorage { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "bar".to_string(), |
| collection: Some("baz".to_string()) |
| })), |
| source_name: Some("thung2".to_string()), |
| target_name: Some("thung2".to_string()), |
| ..fdecl::OfferStorage::EMPTY |
| }), |
| fdecl::Offer::Runner(fdecl::OfferRunner { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "bar".to_string(), |
| collection: Some("baz".to_string()) |
| })), |
| source_name: Some("thung3".to_string()), |
| target_name: Some("thung3".to_string()), |
| ..fdecl::OfferRunner::EMPTY |
| }), |
| fdecl::Offer::Resolver(fdecl::OfferResolver { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "bar".to_string(), |
| collection: Some("baz".to_string()) |
| })), |
| source_name: Some("thung4".to_string()), |
| target_name: Some("thung4".to_string()), |
| ..fdecl::OfferResolver::EMPTY |
| }), |
| fdecl::Offer::Event(fdecl::OfferEvent { |
| target: Some(fdecl::Ref::Child(fdecl::ChildRef { |
| name: "bar".to_string(), |
| collection: Some("baz".to_string()) |
| })), |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef)), |
| source_name: Some("thung5".to_string()), |
| target_name: Some("thung5".to_string()), |
| ..fdecl::OfferEvent::EMPTY |
| }), |
| ]), |
| Err(ErrorList::new(vec![ |
| Error::extraneous_field("OfferProtocol", "target"), |
| Error::extraneous_field("OfferService", "target"), |
| Error::extraneous_field("OfferDirectory", "target"), |
| Error::extraneous_field("OfferStorage", "target"), |
| Error::extraneous_field("OfferRunner", "target"), |
| Error::extraneous_field("OfferResolver", "target"), |
| Error::extraneous_field("OfferEvent", "target"), |
| ])) |
| ); |
| } |
| |
| #[test] |
| fn test_validate_dynamic_child() { |
| assert_eq!( |
| Ok(()), |
| validate_dynamic_child(&fdecl::Child { |
| name: Some("a".repeat(MAX_DYNAMIC_NAME_LENGTH).to_string()), |
| url: Some("test:///child".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| on_terminate: None, |
| environment: None, |
| ..fdecl::Child::EMPTY |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_validate_dynamic_offers_missing_stuff() { |
| assert_eq!( |
| validate_dynamic_offers(&vec![ |
| fdecl::Offer::Protocol(fdecl::OfferProtocol::EMPTY), |
| fdecl::Offer::Service(fdecl::OfferService::EMPTY), |
| fdecl::Offer::Directory(fdecl::OfferDirectory::EMPTY), |
| fdecl::Offer::Storage(fdecl::OfferStorage::EMPTY), |
| fdecl::Offer::Runner(fdecl::OfferRunner::EMPTY), |
| fdecl::Offer::Resolver(fdecl::OfferResolver::EMPTY), |
| fdecl::Offer::Event(fdecl::OfferEvent::EMPTY), |
| ]), |
| Err(ErrorList::new(vec![ |
| Error::missing_field("OfferProtocol", "source"), |
| Error::missing_field("OfferProtocol", "source_name"), |
| Error::missing_field("OfferProtocol", "target_name"), |
| Error::missing_field("OfferProtocol", "dependency_type"), |
| Error::missing_field("OfferService", "source"), |
| Error::missing_field("OfferService", "source_name"), |
| Error::missing_field("OfferService", "target_name"), |
| Error::missing_field("OfferDirectory", "source"), |
| Error::missing_field("OfferDirectory", "source_name"), |
| Error::missing_field("OfferDirectory", "target_name"), |
| Error::missing_field("OfferDirectory", "dependency_type"), |
| Error::missing_field("OfferStorage", "source_name"), |
| Error::missing_field("OfferStorage", "source"), |
| Error::missing_field("OfferRunner", "source"), |
| Error::missing_field("OfferRunner", "source_name"), |
| Error::missing_field("OfferRunner", "target_name"), |
| Error::missing_field("OfferResolver", "source"), |
| Error::missing_field("OfferResolver", "source_name"), |
| Error::missing_field("OfferResolver", "target_name"), |
| Error::missing_field("OfferEvent", "source_name"), |
| Error::missing_field("OfferEvent", "source"), |
| Error::missing_field("OfferEvent", "target_name"), |
| ])) |
| ); |
| } |
| |
| test_dependency! { |
| (test_validate_offers_protocol_dependency_cycle) => { |
| ty = fdecl::Offer::Protocol, |
| offer_decl = fdecl::OfferProtocol { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferProtocol::EMPTY |
| }, |
| }, |
| (test_validate_offers_directory_dependency_cycle) => { |
| ty = fdecl::Offer::Directory, |
| offer_decl = fdecl::OfferDirectory { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..fdecl::OfferDirectory::EMPTY |
| }, |
| }, |
| (test_validate_offers_service_dependency_cycle) => { |
| ty = fdecl::Offer::Service, |
| offer_decl = fdecl::OfferService { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| ..fdecl::OfferService::EMPTY |
| }, |
| }, |
| (test_validate_offers_runner_dependency_cycle) => { |
| ty = fdecl::Offer::Runner, |
| offer_decl = fdecl::OfferRunner { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| ..fdecl::OfferRunner::EMPTY |
| }, |
| }, |
| (test_validate_offers_resolver_dependency_cycle) => { |
| ty = fdecl::Offer::Resolver, |
| offer_decl = fdecl::OfferResolver { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| ..fdecl::OfferResolver::EMPTY |
| }, |
| }, |
| } |
| test_weak_dependency! { |
| (test_validate_offers_protocol_weak_dependency_cycle) => { |
| ty = fdecl::Offer::Protocol, |
| offer_decl = fdecl::OfferProtocol { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| dependency_type: None, // Filled by macro |
| ..fdecl::OfferProtocol::EMPTY |
| }, |
| }, |
| (test_validate_offers_directory_weak_dependency_cycle) => { |
| ty = fdecl::Offer::Directory, |
| offer_decl = fdecl::OfferDirectory { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| rights: Some(fio::Operations::CONNECT), |
| subdir: None, |
| dependency_type: None, // Filled by macro |
| ..fdecl::OfferDirectory::EMPTY |
| }, |
| }, |
| } |
| } |