blob: 859664b003b077d92df596164dfa6abaaec89648 [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 super::{missing, syntax_error, MetricValue};
use crate::config::{DataFetcher, DiagnosticData, Source};
use anyhow::{anyhow, bail, Context, Error, Result};
use diagnostics_hierarchy::DiagnosticsHierarchy;
use fidl_fuchsia_diagnostics::Selector;
use lazy_static::lazy_static;
use regex::Regex;
use selectors::VerboseError;
use serde::Serialize;
use serde_derive::Deserialize;
use serde_json::map::Map as JsonMap;
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
/// [Fetcher] is a source of values to feed into the calculations. It may contain data either
/// from snapshot.zip files (e.g. inspect.json data that can be accessed via "select" entries)
/// or supplied in the specification of a trial.
#[derive(Clone)]
pub enum Fetcher<'a> {
FileData(FileDataFetcher<'a>),
TrialData(TrialDataFetcher<'a>),
}
/// [FileDataFetcher] contains fetchers for data in snapshot.zip files.
#[derive(Clone)]
pub struct FileDataFetcher<'a> {
pub inspect: &'a InspectFetcher,
pub syslog: &'a TextFetcher,
pub klog: &'a TextFetcher,
pub bootlog: &'a TextFetcher,
pub annotations: &'a KeyValueFetcher,
}
impl<'a> FileDataFetcher<'a> {
pub fn new(data: &'a [DiagnosticData]) -> FileDataFetcher<'a> {
let mut fetcher = FileDataFetcher {
inspect: InspectFetcher::ref_empty(),
syslog: TextFetcher::ref_empty(),
klog: TextFetcher::ref_empty(),
bootlog: TextFetcher::ref_empty(),
annotations: KeyValueFetcher::ref_empty(),
};
for DiagnosticData { source, data, .. } in data.iter() {
match source {
Source::Inspect => {
if let DataFetcher::Inspect(data) = data {
fetcher.inspect = data;
}
}
Source::Syslog => {
if let DataFetcher::Text(data) = data {
fetcher.syslog = data;
}
}
Source::Klog => {
if let DataFetcher::Text(data) = data {
fetcher.klog = data;
}
}
Source::Bootlog => {
if let DataFetcher::Text(data) = data {
fetcher.bootlog = data;
}
}
Source::Annotations => {
if let DataFetcher::KeyValue(data) = data {
fetcher.annotations = data;
}
}
}
}
fetcher
}
pub(crate) fn fetch(&self, selector: &SelectorString) -> MetricValue {
match selector.selector_type {
// Selectors return a vector. Non-wildcarded Inspect selectors will return a
// single element, except in the case of multiple components with the same
// entry in the "moniker" field, where multiple matches are possible.
SelectorType::Inspect => MetricValue::Vector(self.inspect.fetch(&selector)),
}
}
// Return a vector of errors encountered by contained fetchers.
pub fn errors(&self) -> Vec<String> {
self.inspect.component_errors.iter().map(|e| format!("{}", e)).collect()
}
}
/// [TrialDataFetcher] stores the key-value lookup for metric names whose values are given as
/// part of a trial (under the "test" section of the .triage files).
#[derive(Clone)]
pub struct TrialDataFetcher<'a> {
values: &'a HashMap<String, JsonValue>,
pub(crate) klog: &'a TextFetcher,
pub(crate) syslog: &'a TextFetcher,
pub(crate) bootlog: &'a TextFetcher,
pub(crate) annotations: &'a KeyValueFetcher,
}
lazy_static! {
static ref EMPTY_JSONVALUES: HashMap<String, JsonValue> = HashMap::new();
}
impl<'a> TrialDataFetcher<'a> {
pub fn new(values: &'a HashMap<String, JsonValue>) -> TrialDataFetcher<'a> {
TrialDataFetcher {
values,
klog: TextFetcher::ref_empty(),
syslog: TextFetcher::ref_empty(),
bootlog: TextFetcher::ref_empty(),
annotations: KeyValueFetcher::ref_empty(),
}
}
pub fn new_empty() -> TrialDataFetcher<'static> {
TrialDataFetcher {
values: &EMPTY_JSONVALUES,
klog: TextFetcher::ref_empty(),
syslog: TextFetcher::ref_empty(),
bootlog: TextFetcher::ref_empty(),
annotations: KeyValueFetcher::ref_empty(),
}
}
pub fn set_syslog(&mut self, fetcher: &'a TextFetcher) {
self.syslog = fetcher;
}
pub fn set_klog(&mut self, fetcher: &'a TextFetcher) {
self.klog = fetcher;
}
pub fn set_bootlog(&mut self, fetcher: &'a TextFetcher) {
self.bootlog = fetcher;
}
pub fn set_annotations(&mut self, fetcher: &'a KeyValueFetcher) {
self.annotations = fetcher;
}
pub(crate) fn fetch(&self, name: &str) -> MetricValue {
match self.values.get(name) {
Some(value) => MetricValue::from(value),
None => syntax_error(format!("Value {} not overridden in test", name)),
}
}
pub(crate) fn has_entry(&self, name: &str) -> bool {
self.values.contains_key(name)
}
}
/// Selector type used to determine how to query target file.
#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
pub enum SelectorType {
/// Selector for Inspect Tree ("inspect.json" files).
Inspect,
}
impl FromStr for SelectorType {
type Err = anyhow::Error;
fn from_str(selector_type: &str) -> Result<Self, Self::Err> {
match selector_type {
"INSPECT" => Ok(SelectorType::Inspect),
incorrect => bail!("Invalid selector type '{}' - must be INSPECT", incorrect),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct SelectorString {
pub(crate) full_selector: String,
pub selector_type: SelectorType,
body: String,
#[serde(skip_serializing)]
parsed_selector: Selector,
}
impl SelectorString {
pub fn body(&self) -> &str {
&self.body
}
}
impl TryFrom<String> for SelectorString {
type Error = anyhow::Error;
fn try_from(full_selector: String) -> Result<Self, Self::Error> {
let mut string_parts = full_selector.splitn(2, ':');
let selector_type =
SelectorType::from_str(string_parts.next().ok_or(anyhow!("Empty selector"))?)?;
let body = string_parts.next().ok_or(anyhow!("Selector needs a :"))?.to_owned();
let parsed_selector = selectors::parse_selector::<VerboseError>(&body)?;
Ok(SelectorString { full_selector, selector_type, body, parsed_selector })
}
}
pub struct ComponentInspectInfo {
processed_data: DiagnosticsHierarchy,
moniker: Vec<String>,
}
pub struct KeyValueFetcher {
pub map: JsonMap<String, JsonValue>,
}
impl TryFrom<&str> for KeyValueFetcher {
type Error = anyhow::Error;
fn try_from(json_text: &str) -> Result<Self, Self::Error> {
let raw_json =
json_text.parse::<JsonValue>().context("Couldn't parse KeyValue text as JSON.")?;
match raw_json {
JsonValue::Object(map) => Ok(KeyValueFetcher { map }),
_ => bail!("Bad json KeyValue data needs to be Object (map)."),
}
}
}
impl TryFrom<&JsonMap<String, JsonValue>> for KeyValueFetcher {
type Error = anyhow::Error;
fn try_from(map: &JsonMap<String, JsonValue>) -> Result<Self, Self::Error> {
// This doesn't fail today, but that's an implementation detail; don't count on it.
Ok(KeyValueFetcher { map: map.clone() })
}
}
lazy_static! {
static ref EMPTY_KEY_VALUE_FETCHER: KeyValueFetcher = KeyValueFetcher { map: JsonMap::new() };
}
impl KeyValueFetcher {
pub fn ref_empty() -> &'static Self {
&EMPTY_KEY_VALUE_FETCHER
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn fetch(&self, key: &str) -> MetricValue {
match self.map.get(key) {
Some(value) => MetricValue::from(value),
None => missing(format!("Key '{}' not found in annotations", key)),
}
}
}
pub struct TextFetcher {
pub lines: Vec<String>,
}
impl From<&str> for TextFetcher {
fn from(log_buffer: &str) -> Self {
TextFetcher { lines: log_buffer.split('\n').map(|s| s.to_string()).collect::<Vec<_>>() }
}
}
lazy_static! {
static ref EMPTY_TEXT_FETCHER: TextFetcher = TextFetcher { lines: Vec::new() };
}
impl TextFetcher {
pub fn ref_empty() -> &'static Self {
&EMPTY_TEXT_FETCHER
}
pub fn contains(&self, pattern: &str) -> bool {
let re = match Regex::new(pattern) {
Ok(re) => re,
_ => return false,
};
self.lines.iter().any(|s| re.is_match(s))
}
}
pub struct InspectFetcher {
pub components: Vec<ComponentInspectInfo>,
pub component_errors: Vec<anyhow::Error>,
}
impl TryFrom<&str> for InspectFetcher {
type Error = anyhow::Error;
fn try_from(json_text: &str) -> Result<Self, Self::Error> {
let raw_json =
json_text.parse::<JsonValue>().context("Couldn't parse Inspect text as JSON.")?;
match raw_json {
JsonValue::Array(list) => Self::try_from(list),
_ => bail!("Bad json inspect data needs to be array."),
}
}
}
impl TryFrom<Vec<JsonValue>> for InspectFetcher {
type Error = anyhow::Error;
fn try_from(component_vec: Vec<JsonValue>) -> Result<Self, Self::Error> {
fn extract_json_value<'a>(
component: &'a JsonValue,
key: &'_ str,
) -> Result<&'a JsonValue, Error> {
component.get(key).ok_or_else(|| anyhow!("'{}' not found in Inspect component", key))
}
fn moniker_from(component: &JsonValue) -> Result<Vec<String>, Error> {
let value = extract_json_value(component, "moniker")
.or_else(|_| bail!("'moniker' not found in Inspect component"))?;
Ok(value
.as_str()
.ok_or_else(|| anyhow!("Inspect component path wasn't a valid string"))?
.split("/")
.map(|s| s.to_owned())
.collect())
}
let components: Vec<_> = component_vec
.iter()
.map(|raw_component| {
let moniker = moniker_from(raw_component)?;
let raw_contents = extract_json_value(raw_component, "payload").or_else(|_| {
extract_json_value(raw_component, "contents").or_else(|_| {
bail!("Neither 'payload' nor 'contents' found in Inspect component")
})
})?;
let processed_data: DiagnosticsHierarchy = match raw_contents {
v if v.is_null() => {
// If the payload is null, leave the hierarchy empty.
DiagnosticsHierarchy::new_root()
}
raw_contents => {
serde_json::from_value(raw_contents.clone()).with_context(|| {
format!(
"Unable to deserialize Inspect contents for {} to node hierarchy",
moniker.join("/")
)
})?
}
};
Ok(ComponentInspectInfo { moniker, processed_data })
})
.collect::<Vec<_>>();
let mut component_errors = vec![];
let components = components
.into_iter()
.filter_map(|v| match v {
Ok(component) => Some(component),
Err(e) => {
component_errors.push(e);
None
}
})
.collect::<Vec<_>>();
Ok(Self { components, component_errors })
}
}
lazy_static! {
static ref EMPTY_INSPECT_FETCHER: InspectFetcher =
InspectFetcher { components: Vec::new(), component_errors: Vec::new() };
}
impl InspectFetcher {
pub fn ref_empty() -> &'static Self {
&EMPTY_INSPECT_FETCHER
}
fn try_fetch(&self, selector_string: &SelectorString) -> Result<Vec<MetricValue>, Error> {
let arc_selector = Arc::new(selector_string.parsed_selector.clone());
let mut properties = Vec::new();
let mut found_component = false;
for component in self.components.iter() {
if !selectors::match_component_moniker_against_selector(
&component.moniker,
&arc_selector,
)? {
continue;
}
found_component = true;
let selector = selector_string.parsed_selector.clone();
for property in
diagnostics_hierarchy::select_from_hierarchy(&component.processed_data, &selector)?
.into_iter()
{
properties.push(property.clone())
}
}
if !found_component {
return Ok(vec![missing(format!(
"No component found matching selector {}",
selector_string.body.to_string()
))]);
}
Ok(properties.into_iter().map(|property| MetricValue::from(property)).collect())
}
pub fn fetch(&self, selector: &SelectorString) -> Vec<MetricValue> {
match self.try_fetch(selector) {
Ok(v) => v,
Err(e) => vec![syntax_error(format!("Fetch {:?} -> {}", selector, e))],
}
}
#[cfg(test)]
fn fetch_str(&self, selector_str: &str) -> Vec<MetricValue> {
match SelectorString::try_from(selector_str.to_owned()) {
Ok(selector) => self.fetch(&selector),
Err(e) => vec![syntax_error(format!("Bad selector {}: {}", selector_str, e))],
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::metrics::variable::VariableName;
use crate::metrics::{Metric, MetricState, Problem, ValueSource};
use crate::{assert_problem, make_metrics};
use serde_json::Value as JsonValue;
lazy_static! {
static ref LOCAL_M: HashMap<String, JsonValue> = {
let mut m = HashMap::new();
m.insert("foo".to_owned(), JsonValue::try_from(42).unwrap());
m.insert("a::b".to_owned(), JsonValue::try_from(7).unwrap());
m
};
static ref FOO_42_AB_7_TRIAL_FETCHER: TrialDataFetcher<'static> =
TrialDataFetcher::new(&LOCAL_M);
static ref LOCAL_F: Vec<DiagnosticData> = {
let s = r#"[
{
"data_source": "Inspect",
"moniker": "bar",
"payload": { "root": { "bar": 99 }}
},
{
"data_source": "Inspect",
"moniker": "bar2",
"payload": { "root": { "bar": 90 }}
}
]"#;
vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
};
static ref BAR_99_FILE_FETCHER: FileDataFetcher<'static> = FileDataFetcher::new(&LOCAL_F);
static ref BAR_SELECTOR: SelectorString =
SelectorString::try_from("INSPECT:bar:root:bar".to_owned()).unwrap();
static ref NEW_BAR_SELECTOR: SelectorString =
SelectorString::try_from("INSPECT:bar2:root:bar".to_owned()).unwrap();
static ref BAD_COMPONENT_SELECTOR: SelectorString =
SelectorString::try_from("INSPECT:bad_component:root:bar".to_owned()).unwrap();
static ref WRONG_SELECTOR: SelectorString =
SelectorString::try_from("INSPECT:bar:root:oops".to_owned()).unwrap();
static ref LOCAL_DUPLICATES_F: Vec<DiagnosticData> = {
let s = r#"[
{
"data_source": "Inspect",
"moniker": "bootstrap/foo",
"payload": null
},
{
"data_source": "Inspect",
"moniker": "bootstrap/foo",
"payload": {"root": {"bar": 10}}
}
]"#;
vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
};
static ref LOCAL_DUPLICATES_FETCHER: FileDataFetcher<'static> =
FileDataFetcher::new(&LOCAL_DUPLICATES_F);
static ref DUPLICATE_SELECTOR: SelectorString =
SelectorString::try_from("INSPECT:bootstrap/foo:root:bar".to_owned()).unwrap();
}
macro_rules! variable {
($name:expr) => {
&VariableName::new($name.to_string())
};
}
#[fuchsia::test]
fn test_file_fetch() {
assert_eq!(
BAR_99_FILE_FETCHER.fetch(&BAR_SELECTOR),
MetricValue::Vector(vec![MetricValue::Int(99)])
);
assert_eq!(BAR_99_FILE_FETCHER.fetch(&WRONG_SELECTOR), MetricValue::Vector(vec![]),);
}
#[fuchsia::test]
fn test_duplicate_file_fetch() {
assert_eq!(
LOCAL_DUPLICATES_FETCHER.fetch(&DUPLICATE_SELECTOR),
MetricValue::Vector(vec![MetricValue::Int(10)])
);
}
#[fuchsia::test]
fn test_trial_fetch() {
assert!(FOO_42_AB_7_TRIAL_FETCHER.has_entry("foo"));
assert!(FOO_42_AB_7_TRIAL_FETCHER.has_entry("a::b"));
assert!(!FOO_42_AB_7_TRIAL_FETCHER.has_entry("a:b"));
assert!(!FOO_42_AB_7_TRIAL_FETCHER.has_entry("oops"));
assert_eq!(FOO_42_AB_7_TRIAL_FETCHER.fetch("foo"), MetricValue::Int(42));
assert_problem!(
FOO_42_AB_7_TRIAL_FETCHER.fetch("oops"),
"SyntaxError: Value oops not overridden in test"
);
}
#[fuchsia::test]
fn test_eval_with_file() {
let metrics = make_metrics!({
"bar_file":{
eval: {
"bar_plus_one": "bar + 1",
"oops_plus_one": "oops + 1"
}
select: {
"bar": [BAR_SELECTOR],
"wrong_or_bar": [WRONG_SELECTOR, BAR_SELECTOR],
"wrong_or_wrong": [WRONG_SELECTOR, WRONG_SELECTOR],
"wrong_or_new_bar_or_bar": [WRONG_SELECTOR, NEW_BAR_SELECTOR, BAR_SELECTOR],
"bad_component_or_bar": [BAD_COMPONENT_SELECTOR, BAR_SELECTOR]
}
},
"other_file":{
eval: {
"bar": "42"
}
}
});
let file_state =
MetricState::new(&metrics, Fetcher::FileData(BAR_99_FILE_FETCHER.clone()), None);
assert_eq!(
file_state.evaluate_variable("bar_file", variable!("bar_plus_one")),
MetricValue::Int(100)
);
assert_problem!(
file_state.evaluate_variable("bar_file", variable!("oops_plus_one")),
"SyntaxError: Metric 'oops' Not Found in 'bar_file'"
);
assert_eq!(
file_state.evaluate_variable("bar_file", variable!("bar")),
MetricValue::Vector(vec![MetricValue::Int(99)])
);
assert_eq!(
file_state.evaluate_variable("other_file", variable!("bar")),
MetricValue::Int(42)
);
assert_eq!(
file_state.evaluate_variable("other_file", variable!("other_file::bar")),
MetricValue::Int(42)
);
assert_eq!(
file_state.evaluate_variable("other_file", variable!("bar_file::bar")),
MetricValue::Vector(vec![MetricValue::Int(99)])
);
assert_eq!(
file_state.evaluate_variable("bar_file", variable!("bar")),
file_state.evaluate_variable("bar_file", variable!("wrong_or_bar")),
);
assert_eq!(
file_state.evaluate_variable("bar_file", variable!("wrong_or_wrong")),
MetricValue::Vector(vec![]),
);
assert_eq!(
file_state.evaluate_variable("bar_file", variable!("wrong_or_new_bar_or_bar")),
MetricValue::Vector(vec![MetricValue::Int(90)])
);
assert_eq!(
file_state.evaluate_variable("bar_file", variable!("bad_component_or_bar")),
MetricValue::Vector(vec![MetricValue::Int(99)])
);
assert_problem!(
file_state.evaluate_variable("other_file", variable!("bar_plus_one")),
"SyntaxError: Metric 'bar_plus_one' Not Found in 'other_file'"
);
assert_problem!(
file_state.evaluate_variable("missing_file", variable!("bar_plus_one")),
"SyntaxError: Bad namespace 'missing_file'"
);
assert_problem!(
file_state.evaluate_variable("bar_file", variable!("other_file::bar_plus_one")),
"SyntaxError: Metric 'bar_plus_one' Not Found in 'other_file'"
);
}
#[fuchsia::test]
fn test_eval_with_trial() {
// The (broken) "foo" selector should be ignored in favor of the "foo" fetched value.
// The file "a" should be completely ignored when testing foo_file.
let metrics = make_metrics!({
"a":{
eval: {
"b": "2",
"c": "3",
"foo": "4",
}
},
"foo_file":{
eval: {
"foo_plus_one": "foo + 1",
"oops_plus_one": "oops + 1",
"ab_plus_one": "a::b + 1",
"ac_plus_one": "a::c + 1"
}
select: {
"foo": [BAR_SELECTOR]
}
}
});
let trial_state =
MetricState::new(&metrics, Fetcher::TrialData(FOO_42_AB_7_TRIAL_FETCHER.clone()), None);
// foo from values shadows foo selector.
assert_eq!(
trial_state.evaluate_variable("foo_file", variable!("foo")),
MetricValue::Int(42)
);
// Value shadowing also works in expressions.
assert_eq!(
trial_state.evaluate_variable("foo_file", variable!("foo_plus_one")),
MetricValue::Int(43)
);
// foo can shadow eval as well as selector.
assert_eq!(trial_state.evaluate_variable("a", variable!("foo")), MetricValue::Int(42));
// A value that's not there should be "SyntaxError" (e.g. not crash)
assert_problem!(
trial_state.evaluate_variable("foo_file", variable!("oops_plus_one")),
"SyntaxError: Metric 'oops' Not Found in 'foo_file'"
);
// a::b ignores the "b" in file "a" and uses "a::b" from values.
assert_eq!(
trial_state.evaluate_variable("foo_file", variable!("ab_plus_one")),
MetricValue::Int(8)
);
// a::c should return Missing, not look up c in file a.
assert_problem!(
trial_state.evaluate_variable("foo_file", variable!("ac_plus_one")),
"SyntaxError: Name a::c not in test values and refers outside the file"
);
}
#[fuchsia::test]
fn inspect_fetcher_new_works() -> Result<(), Error> {
assert!(InspectFetcher::try_from("foo").is_err(), "'foo' isn't valid JSON");
assert!(InspectFetcher::try_from(r#"{"a":5}"#).is_err(), "Needed an array");
assert!(InspectFetcher::try_from("[]").is_ok(), "A JSON array should have worked");
Ok(())
}
#[fuchsia::test]
fn test_fetch() -> Result<(), Error> {
// This tests both the moniker/payload and path/content (old-style) Inspect formats.
let json_options = vec![
r#"[
{"moniker":"asdf/foo/qwer",
"payload":{"root":{"dataInt":5, "child":{"dataFloat":2.3}}}},
{"moniker":"zxcv/bar/hjkl",
"payload":{"base":{"dataInt":42, "array":[2,3,4], "yes": true}}},
{"moniker":"fail_component",
"payload": ["a", "b"]},
{"moniker":"missing_component",
"payload": null}
]"#,
r#"[
{"moniker":"asdf/foo/qwer",
"payload":{"root":{"dataInt":5, "child":{"dataFloat":2.3}}}},
{"moniker":"zxcv/bar/hjkl",
"contents":{"base":{"dataInt":42, "array":[2,3,4], "yes": true}}},
{"moniker":"fail_component",
"payload": ["a", "b"]},
{"moniker":"missing_component",
"payload": null}
]"#,
];
for json in json_options.into_iter() {
let inspect = InspectFetcher::try_from(json)?;
assert_eq!(
vec!["Unable to deserialize Inspect contents for fail_component to node hierarchy"],
inspect.component_errors.iter().map(|e| format!("{}", e)).collect::<Vec<_>>()
);
macro_rules! assert_wrong {
($selector:expr, $error:expr) => {
let error = inspect.fetch_str($selector);
assert_eq!(error.len(), 1);
assert_problem!(&error[0], $error);
};
}
assert_wrong!("INSPET:*/foo/*:root:dataInt",
"SyntaxError: Bad selector INSPET:*/foo/*:root:dataInt: Invalid selector type \'INSPET\' - must be INSPECT");
assert_eq!(
inspect.fetch_str("INSPECT:*/foo/*:root:dataInt"),
vec![MetricValue::Int(5)]
);
assert_eq!(
inspect.fetch_str("INSPECT:*/foo/*:root/child:dataFloat"),
vec![MetricValue::Float(2.3)]
);
assert_eq!(
inspect.fetch_str("INSPECT:zxcv/*/hjk*:base:yes"),
vec![MetricValue::Bool(true)]
);
assert_eq!(inspect.fetch_str("INSPECT:*/foo/*:root.dataInt"), vec![]);
assert_wrong!(
"INSPECT:*/fo/*:root.dataInt",
"Missing: No component found matching selector */fo/*:root.dataInt"
);
assert_eq!(inspect.fetch_str("INSPECT:*/foo/*:root/kid:dataInt"), vec![]);
assert_eq!(inspect.fetch_str("INSPECT:*/bar/*:base/array:dataInt"), vec![]);
assert_eq!(
inspect.fetch_str("INSPECT:*/bar/*:base:array"),
vec![MetricValue::Vector(vec![
MetricValue::Int(2),
MetricValue::Int(3),
MetricValue::Int(4)
])]
);
}
Ok(())
}
#[fuchsia::test]
fn inspect_ref_empty() -> Result<(), Error> {
// Make sure it doesn't crash, can be called multiple times and they both work right.
let fetcher1 = InspectFetcher::ref_empty();
let fetcher2 = InspectFetcher::ref_empty();
match fetcher1.try_fetch(&SelectorString::try_from("INSPECT:a:b:c".to_string())?).unwrap()
[0]
{
MetricValue::Problem(Problem::Missing(_)) => {}
_ => bail!("Should have Missing'd a valid selector"),
}
match fetcher2.try_fetch(&SelectorString::try_from("INSPECT:a:b:c".to_string())?).unwrap()
[0]
{
MetricValue::Problem(Problem::Missing(_)) => {}
_ => bail!("Should have Missing'd a valid selector"),
}
Ok(())
}
#[fuchsia::test]
fn text_fetcher_works() {
let fetcher = TextFetcher::from("abcfoo\ndefgfoo");
assert!(fetcher.contains("d*g"));
assert!(fetcher.contains("foo"));
assert!(!fetcher.contains("food"));
// Make sure ref_empty() doesn't crash and can be used multiple times.
let fetcher1 = TextFetcher::ref_empty();
let fetcher2 = TextFetcher::ref_empty();
assert!(!fetcher1.contains("a"));
assert!(!fetcher2.contains("a"));
}
#[fuchsia::test]
fn test_selector_string_parse() -> Result<(), Error> {
// Test correct shape of SelectorString and verify no errors on parse for valid selector.
let full_selector = "INSPECT:bad_component:root:bar".to_string();
let selector_type = SelectorType::Inspect;
let body = "bad_component:root:bar".to_string();
let parsed_selector = selectors::parse_selector::<VerboseError>(&body)?;
assert_eq!(
SelectorString::try_from("INSPECT:bad_component:root:bar".to_string())?,
SelectorString { full_selector, selector_type, body, parsed_selector }
);
// Test that a selector that does not follow the correct syntax results in parse error.
assert_eq!(
format!(
"{:?}",
SelectorString::try_from("INSPECT:not a selector".to_string()).err().unwrap()
),
"Failed to parse the input. Error: 0: at line 0, in Tag:\nnot a selector\n ^\n\n"
);
// Test that an invalid selector results in a parse error.
assert_eq!(
format!(
"{:?}",
SelectorString::try_from("INSPECT:*/foo/*:root:data:Int".to_string()).err().unwrap()
),
"Failed to parse the input. Error: 0: at line 0, in Eof:\n*/foo/*:root:data:Int\n ^\n\n"
);
Ok(())
}
// KeyValueFetcher is tested in metrics::test::annotations_work()
}