blob: 28223f4f2993dc5cc8b1ad92e0f9aac1be01b363 [file] [log] [blame]
// 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.
use crate::error::*;
use crate::parser::{self, ParsingError, VerboseError};
use crate::validate::*;
use anyhow::format_err;
use fidl_fuchsia_diagnostics::{
ComponentSelector, Interest, LogInterestSelector, PropertySelector, Selector, SelectorArgument,
Severity, StringSelector, StringSelectorUnknown, SubtreeSelector, TreeSelector,
};
use moniker::{ChildName, ExtendedMoniker, Moniker, EXTENDED_MONIKER_COMPONENT_MANAGER_STR};
use std::borrow::{Borrow, Cow};
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
// Character used to delimit the different sections of an inspect selector,
// the component selector, the tree selector, and the property selector.
pub static SELECTOR_DELIMITER: char = ':';
// Character used to delimit nodes within a component hierarchy path.
static PATH_NODE_DELIMITER: char = '/';
// Character used to escape interperetation of this parser's "special
// characers"; *, /, :, and \.
static ESCAPE_CHARACTER: char = '\\';
static TAB_CHAR: char = '\t';
static SPACE_CHAR: char = ' ';
// Pattern used to encode wildcard.
static WILDCARD_SYMBOL_CHAR: char = '*';
static RECURSIVE_WILDCARD_SYMBOL_STR: &str = "**";
/// Returns true iff a component selector uses the recursive glob.
/// Assumes the selector has already been validated.
pub fn contains_recursive_glob(component_selector: &ComponentSelector) -> bool {
// Unwrap as a valid selector must contain these fields.
let last_segment = component_selector.moniker_segments.as_ref().unwrap().last().unwrap();
string_selector_contains_recursive_glob(last_segment)
}
fn string_selector_contains_recursive_glob(selector: &StringSelector) -> bool {
match selector {
StringSelector::StringPattern(pattern) if pattern == RECURSIVE_WILDCARD_SYMBOL_STR => true,
StringSelector::StringPattern(_)
| StringSelector::ExactMatch(_)
| StringSelectorUnknown!() => false,
}
}
/// Extracts and validates or parses a selector from a `SelectorArgument`.
pub fn take_from_argument<E>(arg: SelectorArgument) -> Result<Selector, Error>
where
E: for<'a> ParsingError<'a>,
{
match arg {
SelectorArgument::StructuredSelector(s) => {
s.validate()?;
Ok(s)
}
SelectorArgument::RawSelector(r) => parse_selector::<VerboseError>(&r),
_ => Err(Error::InvalidSelectorArgument),
}
}
/// Increments the CharIndices iterator and updates the token builder
/// in order to avoid processing characters being escaped by the selector.
fn handle_escaped_char(
token_builder: &mut String,
selection_iter: &mut std::str::CharIndices<'_>,
) -> Result<(), anyhow::Error> {
token_builder.push(ESCAPE_CHARACTER);
let escaped_char_option: Option<(usize, char)> = selection_iter.next();
match escaped_char_option {
Some((_, escaped_char)) => token_builder.push(escaped_char),
None => {
return Err(format_err!(
"Selecter fails verification due to unmatched escape character",
));
}
}
Ok(())
}
/// Converts a string into a vector of string tokens representing the unparsed
/// string delimited by the provided delimiter, excluded escaped delimiters.
pub fn tokenize_string(
untokenized_selector: &str,
delimiter: char,
) -> Result<Vec<String>, anyhow::Error> {
let mut token_aggregator = Vec::new();
let mut curr_token_builder: String = String::new();
let mut unparsed_selector_iter = untokenized_selector.char_indices();
while let Some((_, selector_char)) = unparsed_selector_iter.next() {
match selector_char {
escape if escape == ESCAPE_CHARACTER => {
handle_escaped_char(&mut curr_token_builder, &mut unparsed_selector_iter)?;
}
selector_delimiter if selector_delimiter == delimiter => {
if curr_token_builder.is_empty() {
return Err(format_err!(
"Cannot have empty strings delimited by {}",
delimiter
));
}
token_aggregator.push(curr_token_builder);
curr_token_builder = String::new();
}
_ => curr_token_builder.push(selector_char),
}
}
// Push the last section of the selector into the aggregator since we don't delimit the
// end of the selector.
if curr_token_builder.is_empty() {
return Err(format_err!(
"Cannot have empty strings delimited by {}: {}",
delimiter,
untokenized_selector
));
}
token_aggregator.push(curr_token_builder);
return Ok(token_aggregator);
}
/// Converts an unparsed tree selector string into a TreeSelector.
pub fn parse_tree_selector<'a, E>(
unparsed_tree_selector: &'a str,
) -> Result<TreeSelector, ParseError>
where
E: ParsingError<'a>,
{
let result = parser::consuming_tree_selector::<E>(&unparsed_tree_selector)?;
Ok(result.into())
}
/// Converts an unparsed component selector string into a ComponentSelector.
pub fn parse_component_selector<'a, E>(
unparsed_component_selector: &'a str,
) -> Result<ComponentSelector, ParseError>
where
E: ParsingError<'a>,
{
let result = parser::consuming_component_selector::<E>(&unparsed_component_selector)?;
Ok(result.into())
}
/// Parses a log severity selector of the form `component_selector#SEVERITY`. For example:
/// core/foo#DEBUG.
pub fn parse_log_interest_selector(selector: &str) -> Result<LogInterestSelector, anyhow::Error> {
let default_invalid_selector_err = format_err!(
"Invalid component interest selector: '{}'. Expecting: '/some/moniker/selector#<log-level>'.",
selector
);
let mut parts = selector.split('#');
// Split each arg into sub string vectors containing strings
// for component [0] and interest [1] respectively.
let Some(component) = parts.next() else {
return Err(default_invalid_selector_err);
};
let Some(interest) = parts.next() else {
return Err(format_err!(
concat!(
"Missing <log-level> in selector. Expecting: '{}#<log-level>', ",
"such as #DEBUG or #INFO."
),
selector
));
};
if parts.next().is_some() {
return Err(default_invalid_selector_err);
}
let parsed_selector = match parse_component_selector::<VerboseError>(component) {
Ok(s) => s,
Err(e) => {
return Err(format_err!(
"Invalid component interest selector: '{}'. Error: {}",
selector,
e
))
}
};
let Some(min_severity) = parse_severity(interest.to_uppercase().as_ref()) else {
return Err(format_err!(
concat!(
"Invalid <log-level> in selector '{}'. Expecting: a min log level ",
"such as #DEBUG or #INFO."
),
selector
));
};
Ok(LogInterestSelector {
selector: parsed_selector,
interest: Interest { min_severity: Some(min_severity), ..Default::default() },
})
}
/// Parses a log severity selector of the form `component_selector#SEVERITY` or just `SEVERITY`.
/// For example: `core/foo#DEBUG` or `INFO`.
pub fn parse_log_interest_selector_or_severity(
selector: &str,
) -> Result<LogInterestSelector, anyhow::Error> {
if let Some(min_severity) = parse_severity(selector.to_uppercase().as_ref()) {
return Ok(LogInterestSelector {
selector: ComponentSelector {
moniker_segments: Some(vec![StringSelector::StringPattern("**".into())]),
..Default::default()
},
interest: Interest { min_severity: Some(min_severity), ..Default::default() },
});
}
parse_log_interest_selector(selector)
}
fn parse_severity(severity: &str) -> Option<Severity> {
match severity {
"TRACE" => Some(Severity::Trace),
"DEBUG" => Some(Severity::Debug),
"INFO" => Some(Severity::Info),
"WARN" => Some(Severity::Warn),
"ERROR" => Some(Severity::Error),
"FATAL" => Some(Severity::Fatal),
_ => None,
}
}
/// Converts an unparsed Inspect selector into a ComponentSelector and TreeSelector.
pub fn parse_selector<'a, E>(unparsed_selector: &'a str) -> Result<Selector, Error>
where
E: ParsingError<'a>,
{
let result = parser::selector::<E>(&unparsed_selector)?;
Ok(result.into())
}
/// Remove any comments process a quoted line.
pub fn parse_selector_file<E>(selector_file: &Path) -> Result<Vec<Selector>, Error>
where
E: for<'a> ParsingError<'a>,
{
let selector_file = fs::File::open(selector_file)?;
let mut result = Vec::new();
let reader = BufReader::new(selector_file);
for line in reader.lines() {
let line = line?;
if line.is_empty() {
continue;
}
if let Some(selector) = parser::selector_or_comment::<E>(&line)? {
result.push(selector.into());
}
}
Ok(result)
}
/// Loads all the selectors in the given directory.
pub fn parse_selectors<E>(directory: &Path) -> Result<Vec<Selector>, Error>
where
E: for<'a> ParsingError<'a>,
{
let path: PathBuf = directory.to_path_buf();
let mut selector_vec: Vec<Selector> = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if entry.path().is_dir() {
return Err(Error::NonFlatDirectory);
} else {
selector_vec.append(&mut parse_selector_file::<E>(&entry.path())?);
}
}
Ok(selector_vec)
}
/// Helper method for converting ExactMatch StringSelectors to regex. We must
/// escape all special characters on the behalf of the selector author when converting
/// exact matches to regex.
fn is_special_character(character: char) -> bool {
character == ESCAPE_CHARACTER
|| character == PATH_NODE_DELIMITER
|| character == SELECTOR_DELIMITER
|| character == WILDCARD_SYMBOL_CHAR
|| character == SPACE_CHAR
|| character == TAB_CHAR
}
/// Sanitizes raw strings from the system such that they align with the
/// special-character and escaping semantics of the Selector format.
///
/// Sanitization escapes the known special characters in the selector language.
pub fn sanitize_string_for_selectors(node: &str) -> Cow<'_, str> {
if node.is_empty() {
return Cow::Borrowed(node);
}
let mut token_builder = TokenBuilder::new(node);
for (index, node_char) in node.char_indices() {
token_builder.maybe_init(index);
if is_special_character(node_char) {
token_builder.into_string();
token_builder.push(ESCAPE_CHARACTER, index);
}
token_builder.push(node_char, index);
}
token_builder.take()
}
/// Sanitizes a moniker raw string such that it can be used in a selector.
/// Monikers have a restricted set of characters `a-z`, `0-9`, `_`, `.`, `-`.
/// Each moniker segment is separated by a `\`. Segments for collections also contain `:`.
/// That `:` will be escaped.
pub fn sanitize_moniker_for_selectors(moniker: &str) -> String {
moniker.replace(":", "\\:")
}
pub fn match_moniker_against_component_selector<I, S>(
mut moniker_segments: I,
component_selector: &ComponentSelector,
) -> Result<bool, anyhow::Error>
where
I: Iterator<Item = S>,
S: AsRef<str>,
{
let selector_segments = match &component_selector.moniker_segments {
Some(ref path_vec) => path_vec,
None => return Err(format_err!("Component selectors require moniker segments.")),
};
for (i, selector_segment) in selector_segments.iter().enumerate() {
// If the selector is longer than the moniker, then there's no match.
let Some(moniker_segment) = moniker_segments.next() else {
return Ok(false);
};
// If we are in the last segment and we find a recursive glob, then it's a match.
if i == selector_segments.len() - 1
&& string_selector_contains_recursive_glob(selector_segment)
{
return Ok(true);
}
if !match_string(selector_segment, moniker_segment.as_ref()) {
return Ok(false);
}
}
// We must have consumed all moniker segments.
if moniker_segments.next().is_some() {
return Ok(false);
}
Ok(true)
}
/// Evaluates a component moniker against a single selector, returning
/// True if the selector matches the component, else false.
///
/// Requires: hierarchy_path is not empty.
/// selectors contains valid Selectors.
pub fn match_component_moniker_against_selector<T>(
moniker: &[T],
selector: &Selector,
) -> Result<bool, anyhow::Error>
where
T: AsRef<str> + std::string::ToString,
{
selector.validate()?;
if moniker.is_empty() {
return Err(format_err!(
"Cannot have empty monikers, at least the component name is required."
));
}
// Unwrap is safe because the validator ensures there is a component selector.
let component_selector = selector.component_selector.as_ref().unwrap();
match_moniker_against_component_selector(moniker.iter(), component_selector)
}
/// Evaluates a component moniker against a list of selectors, returning
/// all of the selectors which are matches for that moniker.
///
/// Requires: hierarchy_path is not empty.
/// selectors contains valid Selectors.
pub fn match_component_moniker_against_selectors<'a, T, S>(
moniker: &[T],
selectors: &'a [S],
) -> Result<Vec<&'a Selector>, anyhow::Error>
where
T: AsRef<str> + std::string::ToString,
S: Borrow<Selector>,
{
if moniker.is_empty() {
return Err(format_err!(
"Cannot have empty monikers, at least the component name is required."
));
}
let selectors = selectors
.iter()
.map(|selector| {
let component_selector = selector.borrow();
component_selector.validate()?;
Ok(component_selector)
})
.collect::<Result<Vec<&Selector>, anyhow::Error>>();
selectors?
.iter()
.filter_map(|selector| {
match_component_moniker_against_selector(moniker, selector)
.map(|is_match| if is_match { Some(*selector) } else { None })
.transpose()
})
.collect::<Result<Vec<&Selector>, anyhow::Error>>()
}
/// Evaluates a component moniker against a list of component selectors, returning
/// all of the component selectors which are matches for that moniker.
///
/// Requires: moniker is not empty.
/// component_selectors contains valid ComponentSelectors.
pub fn match_moniker_against_component_selectors<'a, S, T>(
moniker: &[T],
selectors: &'a [S],
) -> Result<Vec<&'a ComponentSelector>, anyhow::Error>
where
S: Borrow<ComponentSelector> + 'a,
T: AsRef<str> + std::string::ToString,
{
if moniker.is_empty() {
return Err(format_err!(
"Cannot have empty monikers, at least the component name is required."
));
}
let component_selectors = selectors
.iter()
.map(|selector| {
let component_selector = selector.borrow();
component_selector.validate()?;
Ok(component_selector)
})
.collect::<Result<Vec<&ComponentSelector>, anyhow::Error>>();
component_selectors?
.iter()
.filter_map(|selector| {
match_moniker_against_component_selector(moniker.iter(), selector)
.map(|is_match| if is_match { Some(*selector) } else { None })
.transpose()
})
.collect::<Result<Vec<&ComponentSelector>, anyhow::Error>>()
}
/// Format a |Selector| as a string.
///
/// Returns the formatted |Selector|, or an error if the |Selector| is invalid.
///
/// Note that the output will always include both a component and tree selector. If your input is
/// simply "moniker" you will likely see "moniker:root" as many clients implicitly append "root" if
/// it is not present (e.g. iquery).
pub fn selector_to_string(selector: Selector) -> Result<String, anyhow::Error> {
selector.validate()?;
let component_selector =
selector.component_selector.ok_or_else(|| format_err!("component selector missing"))?;
let (node_path, maybe_property_selector) = match selector
.tree_selector
.ok_or_else(|| format_err!("tree selector missing"))?
{
TreeSelector::SubtreeSelector(SubtreeSelector { node_path, .. }) => (node_path, None),
TreeSelector::PropertySelector(PropertySelector {
node_path, target_properties, ..
}) => (node_path, Some(target_properties)),
_ => return Err(format_err!("unknown tree selector type")),
};
let mut segments = vec![];
let escape_special_chars = |val: &str| {
let mut ret = String::with_capacity(val.len());
for c in val.chars() {
if is_special_character(c) {
ret.push('\\');
}
ret.push(c);
}
ret
};
let process_string_selector_vector = |v: Vec<StringSelector>| -> Result<String, anyhow::Error> {
Ok(v.into_iter()
.map(|segment| match segment {
StringSelector::StringPattern(s) => Ok(s),
StringSelector::ExactMatch(s) => Ok(escape_special_chars(&s)),
_ => {
return Err(format_err!("Unknown string selector type"));
}
})
.collect::<Result<Vec<_>, anyhow::Error>>()?
.join("/"))
};
// Create the component moniker
segments.push(process_string_selector_vector(
component_selector
.moniker_segments
.ok_or_else(|| format_err!("component selector missing moniker"))?,
)?);
// Create the node selector
segments.push(process_string_selector_vector(node_path)?);
if let Some(property_selector) = maybe_property_selector {
segments.push(process_string_selector_vector(vec![property_selector])?);
}
Ok(segments.join(":"))
}
/// Match a selector against a target string.
pub fn match_string(selector: &StringSelector, target: impl AsRef<str>) -> bool {
match selector {
StringSelector::ExactMatch(s) => s == target.as_ref(),
StringSelector::StringPattern(pattern) => match_pattern(&pattern, &target.as_ref()),
_ => false,
}
}
fn match_pattern(pattern: &str, target: &str) -> bool {
// Tokenize the string. From: "a*bc*d" to "a, bc, d".
let mut pattern_tokens = vec![];
let mut token = TokenBuilder::new(pattern);
let mut chars = pattern.char_indices();
while let Some((index, curr_char)) = chars.next() {
token.maybe_init(index);
// If we find a backslash then push the next character directly to our new string.
match curr_char {
'\\' => {
match chars.next() {
Some((i, c)) => {
token.into_string();
token.push(c, i);
}
// We found a backslash without a character to its right. Return false as this
// isn't valid.
None => return false,
}
}
'*' => {
if !token.is_empty() {
pattern_tokens.push(token.take());
}
token = TokenBuilder::new(pattern);
}
c => {
token.push(c, index);
}
}
}
// Push the remaining token if there's any.
if !token.is_empty() {
pattern_tokens.push(token.take());
}
// Exit early. We only have *'s.
if pattern_tokens.is_empty() && !pattern.is_empty() {
return true;
}
// If the pattern doesn't begin with a * and the target string doesn't start with the first
// pattern token, we can exit.
if pattern.chars().nth(0) != Some('*') && !target.starts_with(pattern_tokens[0].as_ref()) {
return false;
}
// If the last character of the pattern is not an unescaped * and the target string doesn't end
// with the last token in the pattern, then we can exit.
if pattern.chars().rev().nth(0) != Some('*')
&& pattern.chars().rev().nth(1) != Some('\\')
&& !target.ends_with(pattern_tokens[pattern_tokens.len() - 1].as_ref())
{
return false;
}
// We must find all pattern tokens in the target string in order. If we don't find one then we
// fail.
let mut cur_string = target;
for pattern in pattern_tokens.iter() {
match cur_string.find(pattern.as_ref()) {
Some(i) => {
cur_string = &cur_string[i + pattern.len()..];
}
None => {
return false;
}
}
}
true
}
// Utility to allow matching the string cloning only when necessary, this is when we run into a
// escaped character.
#[derive(Debug)]
enum TokenBuilder<'a> {
Init(&'a str),
Slice { string: &'a str, start: usize, end: Option<usize> },
String(String),
}
impl<'a> TokenBuilder<'a> {
fn new(string: &'a str) -> Self {
Self::Init(string)
}
fn maybe_init(&mut self, start_index: usize) {
match self {
Self::Init(s) => *self = Self::Slice { string: s, start: start_index, end: None },
_ => {}
}
}
fn into_string(&mut self) {
if let Self::Slice { string, start, end: Some(end) } = self {
*self = Self::String(string[*start..=*end].to_string())
}
}
fn push(&mut self, c: char, index: usize) {
match self {
Self::Slice { end, .. } => {
*end = Some(index);
}
Self::String(s) => s.push(c),
Self::Init(_) => unreachable!(),
}
}
fn take(self) -> Cow<'a, str> {
match self {
Self::Slice { string, start, end: Some(end) } => Cow::Borrowed(&string[start..=end]),
Self::Slice { string, start, end: None } => Cow::Borrowed(&string[start..start]),
Self::String(s) => Cow::Owned(s),
Self::Init(_) => unreachable!(),
}
}
fn is_empty(&self) -> bool {
match self {
Self::Slice { start, end: Some(end), .. } => start > end,
Self::Slice { end: None, .. } => true,
Self::String(s) => s.is_empty(),
Self::Init(_) => true,
}
}
}
pub trait SelectorExt {
fn match_against_selectors<'a, S>(
&self,
selectors: &'a [S],
) -> Result<Vec<&'a Selector>, anyhow::Error>
where
S: Borrow<Selector>;
fn match_against_component_selectors<'a, S>(
&self,
selectors: &'a [S],
) -> Result<Vec<&'a ComponentSelector>, anyhow::Error>
where
S: Borrow<ComponentSelector>;
fn into_component_selector(self) -> ComponentSelector;
fn matches_selector(&self, selector: &Selector) -> Result<bool, anyhow::Error>;
fn matches_component_selector(
&self,
selector: &ComponentSelector,
) -> Result<bool, anyhow::Error>;
fn sanitized(&self) -> String;
}
impl SelectorExt for ExtendedMoniker {
fn match_against_selectors<'a, S>(
&self,
selectors: &'a [S],
) -> Result<Vec<&'a Selector>, anyhow::Error>
where
S: Borrow<Selector>,
{
match self {
ExtendedMoniker::ComponentManager => match_component_moniker_against_selectors(
&[EXTENDED_MONIKER_COMPONENT_MANAGER_STR],
selectors,
),
ExtendedMoniker::ComponentInstance(moniker) => {
moniker.match_against_selectors(selectors)
}
}
}
fn match_against_component_selectors<'a, S>(
&self,
selectors: &'a [S],
) -> Result<Vec<&'a ComponentSelector>, anyhow::Error>
where
S: Borrow<ComponentSelector>,
{
match self {
ExtendedMoniker::ComponentManager => match_moniker_against_component_selectors(
&[EXTENDED_MONIKER_COMPONENT_MANAGER_STR],
selectors,
),
ExtendedMoniker::ComponentInstance(moniker) => {
moniker.match_against_component_selectors(selectors)
}
}
}
fn matches_selector(&self, selector: &Selector) -> Result<bool, anyhow::Error> {
match self {
ExtendedMoniker::ComponentManager => match_component_moniker_against_selector(
&[EXTENDED_MONIKER_COMPONENT_MANAGER_STR],
selector,
),
ExtendedMoniker::ComponentInstance(moniker) => moniker.matches_selector(selector),
}
}
fn matches_component_selector(
&self,
selector: &ComponentSelector,
) -> Result<bool, anyhow::Error> {
match self {
ExtendedMoniker::ComponentManager => match_moniker_against_component_selector(
[EXTENDED_MONIKER_COMPONENT_MANAGER_STR].into_iter(),
selector,
),
ExtendedMoniker::ComponentInstance(moniker) => {
moniker.matches_component_selector(selector)
}
}
}
fn sanitized(&self) -> String {
match self {
ExtendedMoniker::ComponentManager => EXTENDED_MONIKER_COMPONENT_MANAGER_STR.to_string(),
ExtendedMoniker::ComponentInstance(moniker) => moniker.sanitized(),
}
}
fn into_component_selector(self) -> ComponentSelector {
ComponentSelector {
moniker_segments: Some(
match self {
ExtendedMoniker::ComponentManager => {
vec![EXTENDED_MONIKER_COMPONENT_MANAGER_STR.into()]
}
ExtendedMoniker::ComponentInstance(moniker) => {
moniker.path().iter().map(|value| value.to_string()).collect()
}
}
.into_iter()
.map(|value| StringSelector::ExactMatch(value))
.collect(),
),
..Default::default()
}
}
}
impl SelectorExt for Moniker {
fn match_against_selectors<'a, S>(
&self,
selectors: &'a [S],
) -> Result<Vec<&'a Selector>, anyhow::Error>
where
S: Borrow<Selector>,
{
let s = SegmentIterator::from(self).collect::<Vec<_>>();
match_component_moniker_against_selectors(&s, selectors)
}
fn match_against_component_selectors<'a, S>(
&self,
selectors: &'a [S],
) -> Result<Vec<&'a ComponentSelector>, anyhow::Error>
where
S: Borrow<ComponentSelector>,
{
let s = SegmentIterator::from(self).collect::<Vec<_>>();
match_moniker_against_component_selectors(&s, selectors)
}
fn matches_selector(&self, selector: &Selector) -> Result<bool, anyhow::Error> {
let s = SegmentIterator::from(self).collect::<Vec<_>>();
match_component_moniker_against_selector(&s, selector)
}
fn matches_component_selector(
&self,
selector: &ComponentSelector,
) -> Result<bool, anyhow::Error> {
let s = SegmentIterator::from(self).collect::<Vec<_>>();
match_moniker_against_component_selector(s.into_iter(), selector)
}
fn sanitized(&self) -> String {
SegmentIterator::from(self)
.map(|s| sanitize_string_for_selectors(&s).into_owned())
.collect::<Vec<String>>()
.join("/")
}
fn into_component_selector(self) -> ComponentSelector {
ComponentSelector {
moniker_segments: Some(
self.path()
.into_iter()
.map(|value| StringSelector::ExactMatch(value.to_string()))
.collect(),
),
..Default::default()
}
}
}
enum SegmentIterator<'a> {
Iter { path: &'a [ChildName], current_index: usize },
Root(bool),
}
impl<'a> From<&'a Moniker> for SegmentIterator<'a> {
fn from(moniker: &'a Moniker) -> Self {
let path = moniker.path();
if path.len() == 0 {
return SegmentIterator::Root(false);
}
SegmentIterator::Iter { path: path.as_slice(), current_index: 0 }
}
}
impl Iterator for SegmentIterator<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Iter { path, current_index } => {
let Some(segment) = path.get(*current_index) else {
return None;
};
let result = segment.to_string();
*self = Self::Iter { path, current_index: *current_index + 1 };
Some(result)
}
Self::Root(true) => None,
Self::Root(done) => {
*done = true;
Some("<root>".to_string())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::prelude::*;
use std::str::FromStr;
use tempfile::TempDir;
#[fuchsia::test]
fn successful_selector_parsing() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(
b"a:b:c
",
)
.expect("writing test file");
File::create(tempdir.path().join("b.txt"))
.expect("create file")
.write_all(b"a*/b:c/d/*:*")
.expect("writing test file");
File::create(tempdir.path().join("c.txt"))
.expect("create file")
.write_all(
b"// this is a comment
a:b:c
",
)
.expect("writing test file");
assert!(parse_selectors::<VerboseError>(tempdir.path()).is_ok());
}
#[fuchsia::test]
fn unsuccessful_selector_parsing_bad_selector() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(b"a:b:c")
.expect("writing test file");
File::create(tempdir.path().join("b.txt"))
.expect("create file")
.write_all(b"**:**:**")
.expect("writing test file");
assert!(parse_selectors::<VerboseError>(tempdir.path()).is_err());
}
#[fuchsia::test]
fn unsuccessful_selector_parsing_nonflat_dir() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(b"a:b:c")
.expect("writing test file");
File::create(tempdir.path().join("b.txt"))
.expect("create file")
.write_all(b"**:**:**")
.expect("writing test file");
std::fs::create_dir_all(tempdir.path().join("nested")).expect("make nested");
File::create(tempdir.path().join("nested/c.txt"))
.expect("create file")
.write_all(b"**:**:**")
.expect("writing test file");
assert!(parse_selectors::<VerboseError>(tempdir.path()).is_err());
}
#[fuchsia::test]
fn component_selector_match_test() {
// Note: We provide the full selector syntax but this test is only validating it
// against the provided moniker
let passing_test_cases = vec![
(r#"echo:*:*"#, vec!["echo"]),
(r#"*/echo:*:*"#, vec!["abc", "echo"]),
(r#"ab*/echo:*:*"#, vec!["abc", "echo"]),
(r#"ab*/echo:*:*"#, vec!["abcde", "echo"]),
(r#"*/ab*/echo:*:*"#, vec!["123", "abcde", "echo"]),
(r#"echo*:*:*"#, vec!["echo"]),
(r#"a/echo*:*:*"#, vec!["a", "echo1"]),
(r#"a/echo*:*:*"#, vec!["a", "echo"]),
(r#"ab*/echo:*:*"#, vec!["ab", "echo"]),
(r#"a/**:*:*"#, vec!["a", "echo"]),
(r#"a/**:*:*"#, vec!["a", "b", "echo"]),
];
for (selector, moniker) in passing_test_cases {
let parsed_selector = parse_selector::<VerboseError>(selector).unwrap();
assert!(
match_component_moniker_against_selector(&moniker, &parsed_selector).unwrap(),
"Selector {:?} failed to match {:?}",
selector,
moniker
);
}
// Note: We provide the full selector syntax but this test is only validating it
// against the provided moniker
let failing_test_cases = vec![
(r#"*:*:*"#, vec!["a", "echo"]),
(r#"*/echo:*:*"#, vec!["123", "abc", "echo"]),
(r#"a/**:*:*"#, vec!["b", "echo"]),
(r#"e/**:*:*"#, vec!["echo"]),
];
for (selector, moniker) in failing_test_cases {
let parsed_selector = parse_selector::<VerboseError>(selector).unwrap();
assert!(
!match_component_moniker_against_selector(&moniker, &parsed_selector).unwrap(),
"Selector {:?} matched {:?}, but was expected to fail",
selector,
moniker
);
}
}
#[fuchsia::test]
fn multiple_component_selectors_match_test() {
let selectors = vec![r#"*/echo"#, r#"ab*/echo"#, r#"abc/m*"#];
let moniker = vec!["abc".to_string(), "echo".to_string()];
let component_selectors = selectors
.into_iter()
.map(|selector| {
parse_component_selector::<VerboseError>(&selector.to_string()).unwrap()
})
.collect::<Vec<_>>();
let match_res =
match_moniker_against_component_selectors(moniker.as_slice(), &component_selectors[..]);
assert!(match_res.is_ok());
assert_eq!(match_res.unwrap().len(), 2);
}
#[fuchsia::test]
fn selector_to_string_test() {
// Check that parsing and formatting these selectors results in output identical to the
// original selector.
let cases = vec![
r#"moniker:root"#,
r#"my/component:root"#,
r#"my/component:root:a"#,
r#"a/b/c*ff:root:a"#,
r#"a/child*:root:a"#,
r#"a/child:root/a/b/c"#,
r#"a/child:root/a/b/c:d"#,
r#"a/child:root/a/b/c*/d"#,
r#"a/child:root/a/b/c\*/d"#,
r#"a/child:root/a/b/c:d*"#,
r#"a/child:root/a/b/c:*d*"#,
r#"a/child:root/a/b/c:\*d*"#,
r#"a/child:root/a/b/c:\*d\:\*\\"#,
];
for input in cases {
let selector = parse_selector::<VerboseError>(input)
.unwrap_or_else(|e| panic!("Failed to parse '{}': {}", input, e));
let output = selector_to_string(selector).unwrap_or_else(|e| {
panic!("Failed to format parsed selector for '{}': {}", input, e)
});
assert_eq!(output, input);
}
}
#[fuchsia::test]
fn exact_match_selector_to_string() {
let selector = Selector {
component_selector: Some(ComponentSelector {
moniker_segments: Some(vec![StringSelector::ExactMatch("a".to_string())]),
..Default::default()
}),
tree_selector: Some(TreeSelector::SubtreeSelector(SubtreeSelector {
node_path: vec![StringSelector::ExactMatch("a*:".to_string())],
})),
..Default::default()
};
// Check we generate the expected string with escaping.
let selector_string = selector_to_string(selector).unwrap();
assert_eq!(r#"a:a\*\:"#, selector_string);
// Parse the resultant selector, and check that it matches a moniker it is supposed to.
let parsed = parse_selector::<VerboseError>(&selector_string).unwrap();
assert!(match_moniker_against_component_selector(
["a"].into_iter(),
parsed.component_selector.as_ref().unwrap()
)
.unwrap());
}
#[fuchsia::test]
fn sanitize_moniker_for_selectors_result_is_usable() {
let selector = parse_selector::<VerboseError>(&format!(
"{}:root",
sanitize_moniker_for_selectors("foo/coll:bar/baz")
))
.unwrap();
let component_selector = selector.component_selector.as_ref().unwrap();
let moniker = vec!["foo".to_string(), "coll:bar".to_string(), "baz".to_string()];
assert!(
match_moniker_against_component_selector(moniker.iter(), &component_selector).unwrap()
);
}
#[fuchsia::test]
fn escaped_spaces() {
let selector_str = "foo:bar\\ baz/a*\\ b:quux";
let selector = parse_selector::<VerboseError>(selector_str).unwrap();
assert_eq!(
selector,
Selector {
component_selector: Some(ComponentSelector {
moniker_segments: Some(vec![StringSelector::ExactMatch("foo".into()),]),
..Default::default()
}),
tree_selector: Some(TreeSelector::PropertySelector(PropertySelector {
node_path: vec![
StringSelector::ExactMatch("bar baz".into()),
StringSelector::StringPattern("a*\\ b".into()),
],
target_properties: StringSelector::ExactMatch("quux".into())
})),
..Default::default()
}
);
}
#[fuchsia::test]
fn match_string_test() {
// Exact match.
assert!(match_string(&StringSelector::ExactMatch("foo".into()), "foo"));
// Valid pattern matches.
assert!(match_string(&StringSelector::StringPattern("*foo*".into()), "hellofoobye"));
assert!(match_string(&StringSelector::StringPattern("bar*foo".into()), "barxfoo"));
assert!(match_string(&StringSelector::StringPattern("bar*foo".into()), "barfoo"));
assert!(match_string(&StringSelector::StringPattern("bar*foo".into()), "barxfoo"));
assert!(match_string(&StringSelector::StringPattern("foo*".into()), "foobar"));
assert!(match_string(&StringSelector::StringPattern("*".into()), "foo"));
assert!(match_string(&StringSelector::StringPattern("bar*baz*foo".into()), "barxzybazfoo"));
assert!(match_string(&StringSelector::StringPattern("foo*bar*baz".into()), "foobazbarbaz"));
// Escaped char.
assert!(match_string(&StringSelector::StringPattern("foo\\*".into()), "foo*"));
// Invalid cases.
assert!(!match_string(&StringSelector::StringPattern("foo\\".into()), "foo\\"));
assert!(!match_string(&StringSelector::StringPattern("bar*foo".into()), "barxfoox"));
assert!(!match_string(&StringSelector::StringPattern("m*".into()), "echo.csx"));
assert!(!match_string(&StringSelector::StringPattern("*foo*".into()), "xbary"));
assert!(!match_string(
&StringSelector::StringPattern("foo*bar*baz*qux".into()),
"foobarbaazqux"
));
}
#[fuchsia::test]
fn test_log_interest_selector() {
assert_eq!(
parse_log_interest_selector("core/network#FATAL").unwrap(),
LogInterestSelector {
selector: parse_component_selector::<VerboseError>("core/network").unwrap(),
interest: Interest { min_severity: Some(Severity::Fatal), ..Default::default() }
}
);
assert_eq!(
parse_log_interest_selector("any/component#INFO").unwrap(),
LogInterestSelector {
selector: parse_component_selector::<VerboseError>("any/component").unwrap(),
interest: Interest { min_severity: Some(Severity::Info), ..Default::default() }
}
);
}
#[test]
fn test_log_interest_selector_error() {
assert!(parse_log_interest_selector("anything////#FATAL").is_err());
assert!(parse_log_interest_selector("core/network").is_err());
assert!(parse_log_interest_selector("core/network#FAKE").is_err());
}
#[test]
fn test_moniker_to_selector() {
assert_eq!(
Moniker::from_str("a/b/c").unwrap().into_component_selector(),
parse_component_selector::<VerboseError>("a/b/c").unwrap()
);
assert_eq!(
ExtendedMoniker::ComponentManager.into_component_selector(),
parse_component_selector::<VerboseError>("<component_manager>").unwrap()
);
assert_eq!(
ExtendedMoniker::ComponentInstance(Moniker::from_str("a/b/c").unwrap())
.into_component_selector(),
parse_component_selector::<VerboseError>("a/b/c").unwrap()
);
assert_eq!(
ExtendedMoniker::ComponentInstance(Moniker::from_str("a/coll:id/c").unwrap())
.into_component_selector(),
parse_component_selector::<VerboseError>("a/coll\\:id/c").unwrap()
);
}
#[test]
fn test_parse_log_interest_or_severity() {
for (severity_str, severity) in [
("TRACE", Severity::Trace),
("DEBUG", Severity::Debug),
("INFO", Severity::Info),
("WARN", Severity::Warn),
("ERROR", Severity::Error),
("FATAL", Severity::Fatal),
] {
assert_eq!(
parse_log_interest_selector_or_severity(severity_str).unwrap(),
LogInterestSelector {
selector: parse_component_selector::<VerboseError>("**").unwrap(),
interest: Interest { min_severity: Some(severity), ..Default::default() }
}
);
}
assert_eq!(
parse_log_interest_selector_or_severity("foo/bar#DEBUG").unwrap(),
LogInterestSelector {
selector: parse_component_selector::<VerboseError>("foo/bar").unwrap(),
interest: Interest { min_severity: Some(Severity::Debug), ..Default::default() }
}
);
assert!(parse_log_interest_selector_or_severity("RANDOM").is_err());
assert!(parse_log_interest_selector_or_severity("core/foo#NO#YES").is_err());
}
#[test]
fn test_parse_tree_selector() {
let selector = parse_tree_selector::<VerboseError>("root/node*/nested:prop").unwrap();
assert_eq!(
selector,
TreeSelector::PropertySelector(PropertySelector {
node_path: vec![
StringSelector::ExactMatch("root".into()),
StringSelector::StringPattern("node*".into()),
StringSelector::ExactMatch("nested".into()),
],
target_properties: StringSelector::ExactMatch("prop".into())
}),
);
}
}