Skip to main content

fuchsia_triage/metrics/
fetch.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::{MetricValue, missing, syntax_error};
6use crate::config::{DataFetcher, DiagnosticData, Source};
7use anyhow::{Context, Error, Result, anyhow, bail};
8use diagnostics_hierarchy::{DiagnosticsHierarchy, SelectResult};
9use fidl_fuchsia_diagnostics::Selector;
10use fidl_fuchsia_inspect::DEFAULT_TREE_NAME;
11use moniker::ExtendedMoniker;
12use regex_lite::Regex;
13use selectors::{SelectorExt, VerboseError};
14use serde::Serialize;
15use serde_derive::Deserialize;
16use serde_json::Value as JsonValue;
17use serde_json::map::Map as JsonMap;
18use std::collections::HashMap;
19use std::str::FromStr;
20use std::sync::LazyLock;
21
22/// [Fetcher] is a source of values to feed into the calculations. It may contain data either
23/// from snapshot.zip files (e.g. inspect.json data that can be accessed via "select" entries)
24/// or supplied in the specification of a trial.
25#[derive(Clone, Debug)]
26pub enum Fetcher<'a> {
27    FileData(FileDataFetcher<'a>),
28    TrialData(TrialDataFetcher<'a>),
29}
30
31/// [FileDataFetcher] contains fetchers for data in snapshot.zip files.
32#[derive(Clone, Debug)]
33pub struct FileDataFetcher<'a> {
34    pub inspect: &'a InspectFetcher,
35    pub syslog: &'a TextFetcher,
36    pub klog: &'a TextFetcher,
37    pub bootlog: &'a TextFetcher,
38    pub annotations: &'a KeyValueFetcher,
39}
40
41impl<'a> FileDataFetcher<'a> {
42    pub fn new(data: &'a [DiagnosticData]) -> FileDataFetcher<'a> {
43        let mut fetcher = FileDataFetcher {
44            inspect: InspectFetcher::ref_empty(),
45            syslog: TextFetcher::ref_empty(),
46            klog: TextFetcher::ref_empty(),
47            bootlog: TextFetcher::ref_empty(),
48            annotations: KeyValueFetcher::ref_empty(),
49        };
50        for DiagnosticData { source, data, .. } in data.iter() {
51            match source {
52                Source::Inspect => {
53                    if let DataFetcher::Inspect(data) = data {
54                        fetcher.inspect = data;
55                    }
56                }
57                Source::Syslog => {
58                    if let DataFetcher::Text(data) = data {
59                        fetcher.syslog = data;
60                    }
61                }
62                Source::Klog => {
63                    if let DataFetcher::Text(data) = data {
64                        fetcher.klog = data;
65                    }
66                }
67                Source::Bootlog => {
68                    if let DataFetcher::Text(data) = data {
69                        fetcher.bootlog = data;
70                    }
71                }
72                Source::Annotations => {
73                    if let DataFetcher::KeyValue(data) = data {
74                        fetcher.annotations = data;
75                    }
76                }
77            }
78        }
79        fetcher
80    }
81
82    pub(crate) fn fetch(&self, selector: &SelectorString) -> MetricValue {
83        match selector.selector_type {
84            // Selectors return a vector. Non-wildcarded Inspect selectors will usually return
85            // a vector with a single element, but when a component exposes multiple
86            // `fuchsia.inspect.Tree`s, a non-wildcarded selector may match multiple properties.
87            SelectorType::Inspect => MetricValue::Vector(self.inspect.fetch(selector)),
88        }
89    }
90
91    // Return a vector of errors encountered by contained fetchers.
92    pub fn errors(&self) -> Vec<String> {
93        self.inspect.component_errors.iter().map(|e| format!("{e}")).collect()
94    }
95}
96
97/// [TrialDataFetcher] stores the key-value lookup for metric names whose values are given as
98/// part of a trial (under the "test" section of the .triage files).
99#[derive(Clone, Debug)]
100pub struct TrialDataFetcher<'a> {
101    values: &'a HashMap<String, JsonValue>,
102    pub(crate) klog: &'a TextFetcher,
103    pub(crate) syslog: &'a TextFetcher,
104    pub(crate) bootlog: &'a TextFetcher,
105    pub(crate) annotations: &'a KeyValueFetcher,
106}
107
108static EMPTY_JSONVALUES: LazyLock<HashMap<String, JsonValue>> = LazyLock::new(HashMap::new);
109
110impl<'a> TrialDataFetcher<'a> {
111    pub fn new(values: &'a HashMap<String, JsonValue>) -> TrialDataFetcher<'a> {
112        TrialDataFetcher {
113            values,
114            klog: TextFetcher::ref_empty(),
115            syslog: TextFetcher::ref_empty(),
116            bootlog: TextFetcher::ref_empty(),
117            annotations: KeyValueFetcher::ref_empty(),
118        }
119    }
120
121    pub fn new_empty() -> TrialDataFetcher<'static> {
122        TrialDataFetcher {
123            values: &EMPTY_JSONVALUES,
124            klog: TextFetcher::ref_empty(),
125            syslog: TextFetcher::ref_empty(),
126            bootlog: TextFetcher::ref_empty(),
127            annotations: KeyValueFetcher::ref_empty(),
128        }
129    }
130
131    pub fn set_syslog(&mut self, fetcher: &'a TextFetcher) {
132        self.syslog = fetcher;
133    }
134
135    pub fn set_klog(&mut self, fetcher: &'a TextFetcher) {
136        self.klog = fetcher;
137    }
138
139    pub fn set_bootlog(&mut self, fetcher: &'a TextFetcher) {
140        self.bootlog = fetcher;
141    }
142
143    pub fn set_annotations(&mut self, fetcher: &'a KeyValueFetcher) {
144        self.annotations = fetcher;
145    }
146
147    pub(crate) fn fetch(&self, name: &str) -> MetricValue {
148        match self.values.get(name) {
149            Some(value) => MetricValue::from(value),
150            None => syntax_error(format!("Value {name} not overridden in test")),
151        }
152    }
153
154    pub(crate) fn has_entry(&self, name: &str) -> bool {
155        self.values.contains_key(name)
156    }
157}
158
159/// Selector type used to determine how to query target file.
160#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
161pub enum SelectorType {
162    /// Selector for Inspect Tree ("inspect.json" files).
163    Inspect,
164}
165
166impl FromStr for SelectorType {
167    type Err = anyhow::Error;
168    fn from_str(selector_type: &str) -> Result<Self, Self::Err> {
169        match selector_type {
170            "INSPECT" => Ok(SelectorType::Inspect),
171            incorrect => bail!("Invalid selector type '{}' - must be INSPECT", incorrect),
172        }
173    }
174}
175
176#[derive(Debug, Clone, PartialEq, Serialize)]
177pub struct SelectorString {
178    pub(crate) full_selector: String,
179    pub selector_type: SelectorType,
180    body: String,
181
182    #[serde(skip_serializing)]
183    parsed_selector: Selector,
184}
185
186impl SelectorString {
187    pub fn body(&self) -> &str {
188        &self.body
189    }
190}
191
192impl TryFrom<String> for SelectorString {
193    type Error = anyhow::Error;
194
195    fn try_from(full_selector: String) -> Result<Self, Self::Error> {
196        let mut string_parts = full_selector.splitn(2, ':');
197        let selector_type =
198            SelectorType::from_str(string_parts.next().ok_or_else(|| anyhow!("Empty selector"))?)?;
199        let body = string_parts.next().ok_or_else(|| anyhow!("Selector needs a :"))?.to_owned();
200        let parsed_selector = selectors::parse_selector::<VerboseError>(&body)?;
201        Ok(SelectorString { full_selector, selector_type, body, parsed_selector })
202    }
203}
204
205#[derive(Debug)]
206pub struct ComponentInspectInfo {
207    processed_data: DiagnosticsHierarchy,
208    moniker: ExtendedMoniker,
209    tree_name: String,
210}
211
212impl ComponentInspectInfo {
213    fn matches_selector(&self, selector: &Selector) -> bool {
214        self.moniker
215            .match_against_selectors_and_tree_name(&self.tree_name, Some(selector))
216            .next()
217            .is_some()
218    }
219}
220
221#[derive(Default, Debug)]
222pub struct KeyValueFetcher {
223    pub map: JsonMap<String, JsonValue>,
224}
225
226impl FromStr for KeyValueFetcher {
227    type Err = anyhow::Error;
228
229    fn from_str(json_text: &str) -> Result<Self, Self::Err> {
230        let raw_json =
231            json_text.parse::<JsonValue>().context("Couldn't parse KeyValue text as JSON.")?;
232        match raw_json {
233            JsonValue::Object(map) => Ok(KeyValueFetcher { map }),
234            _ => bail!("Bad json KeyValue data needs to be Object (map)."),
235        }
236    }
237}
238
239impl TryFrom<&str> for KeyValueFetcher {
240    type Error = anyhow::Error;
241
242    fn try_from(json_text: &str) -> Result<Self, Self::Error> {
243        json_text.parse()
244    }
245}
246
247impl TryFrom<&JsonMap<String, JsonValue>> for KeyValueFetcher {
248    type Error = anyhow::Error;
249
250    fn try_from(map: &JsonMap<String, JsonValue>) -> Result<Self, Self::Error> {
251        // This doesn't fail today, but that's an implementation detail; don't count on it.
252        Ok(KeyValueFetcher { map: map.clone() })
253    }
254}
255
256static EMPTY_KEY_VALUE_FETCHER: LazyLock<KeyValueFetcher> = LazyLock::new(KeyValueFetcher::default);
257
258impl KeyValueFetcher {
259    pub fn ref_empty() -> &'static Self {
260        &EMPTY_KEY_VALUE_FETCHER
261    }
262
263    pub fn len(&self) -> usize {
264        self.map.len()
265    }
266
267    pub fn fetch(&self, key: &str) -> MetricValue {
268        match self.map.get(key) {
269            Some(value) => MetricValue::from(value),
270            None => missing(format!("Key '{key}' not found in annotations")),
271        }
272    }
273}
274
275#[derive(Default, Debug)]
276pub struct TextFetcher {
277    pub lines: Vec<String>,
278}
279
280impl From<&str> for TextFetcher {
281    fn from(log_buffer: &str) -> Self {
282        TextFetcher { lines: log_buffer.split('\n').map(|s| s.to_string()).collect::<Vec<_>>() }
283    }
284}
285
286static EMPTY_TEXT_FETCHER: LazyLock<TextFetcher> = LazyLock::new(TextFetcher::default);
287
288impl TextFetcher {
289    pub fn ref_empty() -> &'static Self {
290        &EMPTY_TEXT_FETCHER
291    }
292
293    pub fn contains(&self, pattern: &str) -> bool {
294        let re = match Regex::new(pattern) {
295            Ok(re) => re,
296            _ => return false,
297        };
298        self.lines.iter().any(|s| re.is_match(s))
299    }
300}
301
302#[derive(Default, Debug)]
303pub struct InspectFetcher {
304    pub components: Vec<ComponentInspectInfo>,
305    pub component_errors: Vec<anyhow::Error>,
306}
307
308impl FromStr for InspectFetcher {
309    type Err = anyhow::Error;
310
311    fn from_str(json_text: &str) -> Result<Self, Self::Err> {
312        let raw_json =
313            json_text.parse::<JsonValue>().context("Couldn't parse Inspect text as JSON.")?;
314        match raw_json {
315            JsonValue::Array(list) => Self::try_from(list),
316            _ => bail!("Bad json inspect data needs to be array."),
317        }
318    }
319}
320
321impl TryFrom<&str> for InspectFetcher {
322    type Error = anyhow::Error;
323
324    fn try_from(json_text: &str) -> Result<Self, Self::Error> {
325        json_text.parse()
326    }
327}
328
329impl TryFrom<Vec<JsonValue>> for InspectFetcher {
330    type Error = anyhow::Error;
331
332    fn try_from(component_vec: Vec<JsonValue>) -> Result<Self, Self::Error> {
333        fn extract_json_value(component: &mut JsonValue, key: &'_ str) -> Result<JsonValue, Error> {
334            Ok(component
335                .get_mut(key)
336                .ok_or_else(|| anyhow!("'{}' not found in Inspect component", key))?
337                .take())
338        }
339
340        fn moniker_from(component: &mut JsonValue) -> Result<ExtendedMoniker, anyhow::Error> {
341            let value = extract_json_value(component, "moniker")
342                .or_else(|_| bail!("'moniker' not found in Inspect component"))?;
343            let moniker = ExtendedMoniker::parse_str(
344                value
345                    .as_str()
346                    .ok_or_else(|| anyhow!("Inspect component path wasn't a valid string"))?,
347            )?;
348            Ok(moniker)
349        }
350
351        let components = component_vec.into_iter().map(|mut raw_component| {
352            let moniker = moniker_from(&mut raw_component)?;
353            let tree_name = match extract_json_value(
354                &mut extract_json_value(&mut raw_component, "metadata")?,
355                "name",
356            ) {
357                Ok(n) => n.as_str().unwrap_or(DEFAULT_TREE_NAME).to_string(),
358                // the "name" field might be missing from older systems
359                Err(_) => DEFAULT_TREE_NAME.to_string(),
360            };
361            let raw_contents = extract_json_value(&mut raw_component, "payload").or_else(|_| {
362                extract_json_value(&mut raw_component, "contents").or_else(|_| {
363                    bail!("Neither 'payload' nor 'contents' found in Inspect component")
364                })
365            })?;
366            let processed_data: DiagnosticsHierarchy = match raw_contents {
367                v if v.is_null() => {
368                    // If the payload is null, leave the hierarchy empty.
369                    DiagnosticsHierarchy::new_root()
370                }
371                raw_contents => serde_json::from_value(raw_contents).with_context(|| {
372                    format!(
373                        "Unable to deserialize Inspect contents for {moniker} to node hierarchy",
374                    )
375                })?,
376            };
377            Ok(ComponentInspectInfo { moniker, processed_data, tree_name })
378        });
379
380        let mut component_errors = vec![];
381        let components = components
382            .filter_map(|v| match v {
383                Ok(component) => Some(component),
384                Err(e) => {
385                    component_errors.push(e);
386                    None
387                }
388            })
389            .collect::<Vec<_>>();
390        Ok(Self { components, component_errors })
391    }
392}
393
394static EMPTY_INSPECT_FETCHER: LazyLock<InspectFetcher> = LazyLock::new(InspectFetcher::default);
395
396impl InspectFetcher {
397    pub fn ref_empty() -> &'static Self {
398        &EMPTY_INSPECT_FETCHER
399    }
400
401    fn try_fetch(&self, selector_string: &SelectorString) -> Result<Vec<MetricValue>, Error> {
402        let mut intermediate_results = Vec::new();
403        let mut found_component = false;
404        for component in &self.components {
405            if !component.matches_selector(&selector_string.parsed_selector) {
406                continue;
407            }
408            found_component = true;
409            let selector = selector_string.parsed_selector.clone();
410            intermediate_results.push(diagnostics_hierarchy::select_from_hierarchy(
411                &component.processed_data,
412                &selector,
413            )?);
414        }
415
416        if !found_component {
417            return Ok(vec![missing(format!(
418                "No component found matching selector {}",
419                selector_string.body,
420            ))]);
421        }
422
423        let mut result = vec![];
424        for r in intermediate_results {
425            match r {
426                SelectResult::Properties(p) => {
427                    result.extend(p.into_iter().map(|p| MetricValue::from(p.into_owned())))
428                }
429                SelectResult::Nodes(n) => {
430                    for node in n {
431                        for _ in &node.children {
432                            result.push(MetricValue::Node);
433                        }
434
435                        for prop in &node.properties {
436                            result.push(MetricValue::from(prop.clone()));
437                        }
438                    }
439                }
440            }
441        }
442
443        Ok(result)
444    }
445
446    pub fn fetch(&self, selector: &SelectorString) -> Vec<MetricValue> {
447        match self.try_fetch(selector) {
448            Ok(v) => v,
449            Err(e) => vec![syntax_error(format!("Fetch {selector:?} -> {e}"))],
450        }
451    }
452
453    #[cfg(test)]
454    fn fetch_str(&self, selector_str: &str) -> Vec<MetricValue> {
455        match SelectorString::try_from(selector_str.to_owned()) {
456            Ok(selector) => self.fetch(&selector),
457            Err(e) => vec![syntax_error(format!("Bad selector {selector_str}: {e}"))],
458        }
459    }
460}
461
462#[cfg(test)]
463mod test {
464    use super::*;
465    use crate::metrics::variable::VariableName;
466    use crate::metrics::{Metric, MetricState, Problem, ValueSource};
467    use crate::{assert_problem, make_metrics};
468    use assert_matches::assert_matches;
469    use serde_json::Value as JsonValue;
470
471    static LOCAL_M: LazyLock<HashMap<String, JsonValue>> = LazyLock::new(|| {
472        let mut m = HashMap::new();
473        m.insert("foo".to_owned(), JsonValue::from(42));
474        m.insert("a::b".to_owned(), JsonValue::from(7));
475        m
476    });
477    static FOO_42_AB_7_TRIAL_FETCHER: LazyLock<TrialDataFetcher<'static>> =
478        LazyLock::new(|| TrialDataFetcher::new(&LOCAL_M));
479    static LOCAL_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
480        let s = r#"[
481            {
482                "data_source": "Inspect",
483                "moniker": "bar",
484                "metadata": {},
485                "payload": { "root": { "bar": 99 }}
486            },
487            {
488                "data_source": "Inspect",
489                "moniker": "bar2",
490                "metadata": {},
491                "payload": { "root": { "bar": 90 }}
492            }
493
494            ]"#;
495        vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
496    });
497    static BAR_99_FILE_FETCHER: LazyLock<FileDataFetcher<'static>> =
498        LazyLock::new(|| FileDataFetcher::new(&LOCAL_F));
499    static BAR_SELECTOR: LazyLock<SelectorString> =
500        LazyLock::new(|| SelectorString::try_from("INSPECT:bar:root:bar".to_owned()).unwrap());
501    static NEW_BAR_SELECTOR: LazyLock<SelectorString> =
502        LazyLock::new(|| SelectorString::try_from("INSPECT:bar2:root:bar".to_owned()).unwrap());
503    static BAD_COMPONENT_SELECTOR: LazyLock<SelectorString> = LazyLock::new(|| {
504        SelectorString::try_from("INSPECT:bad_component:root:bar".to_owned()).unwrap()
505    });
506    static WRONG_SELECTOR: LazyLock<SelectorString> =
507        LazyLock::new(|| SelectorString::try_from("INSPECT:bar:root:oops".to_owned()).unwrap());
508    static LOCAL_DUPLICATES_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
509        let s = r#"[
510                {
511                    "data_source": "Inspect",
512                    "moniker": "bootstrap/foo",
513                    "metadata": {},
514                    "payload": null
515                },
516                {
517                    "data_source": "Inspect",
518                    "moniker": "bootstrap/foo",
519                    "metadata": {},
520                    "payload": {"root": {"bar": 10}}
521                }
522            ]"#;
523        vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
524    });
525    static LOCAL_DUPLICATES_FETCHER: LazyLock<FileDataFetcher<'static>> =
526        LazyLock::new(|| FileDataFetcher::new(&LOCAL_DUPLICATES_F));
527    static DUPLICATE_SELECTOR: LazyLock<SelectorString> = LazyLock::new(|| {
528        SelectorString::try_from("INSPECT:bootstrap/foo:root:bar".to_owned()).unwrap()
529    });
530
531    macro_rules! variable {
532        ($name:expr) => {
533            &VariableName::new($name.to_string())
534        };
535    }
536
537    #[fuchsia::test]
538    fn test_file_fetch() {
539        assert_eq!(
540            BAR_99_FILE_FETCHER.fetch(&BAR_SELECTOR),
541            MetricValue::Vector(vec![MetricValue::Int(99)])
542        );
543        assert_eq!(BAR_99_FILE_FETCHER.fetch(&WRONG_SELECTOR), MetricValue::Vector(vec![]),);
544    }
545
546    #[fuchsia::test]
547    fn test_duplicate_file_fetch() {
548        assert_eq!(
549            LOCAL_DUPLICATES_FETCHER.fetch(&DUPLICATE_SELECTOR),
550            MetricValue::Vector(vec![MetricValue::Int(10)])
551        );
552    }
553
554    #[fuchsia::test]
555    fn test_trial_fetch() {
556        assert!(FOO_42_AB_7_TRIAL_FETCHER.has_entry("foo"));
557        assert!(FOO_42_AB_7_TRIAL_FETCHER.has_entry("a::b"));
558        assert!(!FOO_42_AB_7_TRIAL_FETCHER.has_entry("a:b"));
559        assert!(!FOO_42_AB_7_TRIAL_FETCHER.has_entry("oops"));
560        assert_eq!(FOO_42_AB_7_TRIAL_FETCHER.fetch("foo"), MetricValue::Int(42));
561        assert_problem!(
562            FOO_42_AB_7_TRIAL_FETCHER.fetch("oops"),
563            "SyntaxError: Value oops not overridden in test"
564        );
565    }
566
567    #[fuchsia::test]
568    fn test_eval_with_file() {
569        let metrics = make_metrics!({
570            "bar_file":{
571                eval: {
572                    "bar_plus_one": "bar + 1",
573                    "oops_plus_one": "oops + 1"
574                }
575                select: {
576                    "bar": [BAR_SELECTOR],
577                    "wrong_or_bar": [WRONG_SELECTOR, BAR_SELECTOR],
578                    "wrong_or_wrong":  [WRONG_SELECTOR, WRONG_SELECTOR],
579                    "wrong_or_new_bar_or_bar": [WRONG_SELECTOR, NEW_BAR_SELECTOR, BAR_SELECTOR],
580                    "bad_component_or_bar": [BAD_COMPONENT_SELECTOR, BAR_SELECTOR]
581                }
582            },
583            "other_file":{
584                eval: {
585                    "bar": "42"
586                }
587            }
588        });
589
590        let file_state =
591            MetricState::new(&metrics, Fetcher::FileData(BAR_99_FILE_FETCHER.clone()), None);
592        assert_eq!(
593            file_state.evaluate_variable("bar_file", variable!("bar_plus_one")),
594            MetricValue::Int(100)
595        );
596        assert_problem!(
597            file_state.evaluate_variable("bar_file", variable!("oops_plus_one")),
598            "SyntaxError: Metric 'oops' Not Found in 'bar_file'"
599        );
600        assert_eq!(
601            file_state.evaluate_variable("bar_file", variable!("bar")),
602            MetricValue::Vector(vec![MetricValue::Int(99)])
603        );
604        assert_eq!(
605            file_state.evaluate_variable("other_file", variable!("bar")),
606            MetricValue::Int(42)
607        );
608        assert_eq!(
609            file_state.evaluate_variable("other_file", variable!("other_file::bar")),
610            MetricValue::Int(42)
611        );
612        assert_eq!(
613            file_state.evaluate_variable("other_file", variable!("bar_file::bar")),
614            MetricValue::Vector(vec![MetricValue::Int(99)])
615        );
616        assert_eq!(
617            file_state.evaluate_variable("bar_file", variable!("bar")),
618            file_state.evaluate_variable("bar_file", variable!("wrong_or_bar")),
619        );
620        assert_eq!(
621            file_state.evaluate_variable("bar_file", variable!("wrong_or_wrong")),
622            MetricValue::Vector(vec![]),
623        );
624        assert_eq!(
625            file_state.evaluate_variable("bar_file", variable!("wrong_or_new_bar_or_bar")),
626            MetricValue::Vector(vec![MetricValue::Int(90)])
627        );
628        assert_eq!(
629            file_state.evaluate_variable("bar_file", variable!("bad_component_or_bar")),
630            MetricValue::Vector(vec![MetricValue::Int(99)])
631        );
632        assert_problem!(
633            file_state.evaluate_variable("other_file", variable!("bar_plus_one")),
634            "SyntaxError: Metric 'bar_plus_one' Not Found in 'other_file'"
635        );
636        assert_problem!(
637            file_state.evaluate_variable("missing_file", variable!("bar_plus_one")),
638            "SyntaxError: Bad namespace 'missing_file'"
639        );
640        assert_problem!(
641            file_state.evaluate_variable("bar_file", variable!("other_file::bar_plus_one")),
642            "SyntaxError: Metric 'bar_plus_one' Not Found in 'other_file'"
643        );
644    }
645
646    #[fuchsia::test]
647    fn test_eval_with_trial() {
648        // The (broken) "foo" selector should be ignored in favor of the "foo" fetched value.
649        // The file "a" should be completely ignored when testing foo_file.
650        let metrics = make_metrics!({
651            "a":{
652                eval: {
653                "b": "2",
654                "c": "3",
655                "foo": "4",
656                }
657            },
658            "foo_file":{
659                eval: {
660                    "foo_plus_one": "foo + 1",
661                    "oops_plus_one": "oops + 1",
662                    "ab_plus_one": "a::b + 1",
663                    "ac_plus_one": "a::c + 1"
664                }
665                select: {
666                "foo": [BAR_SELECTOR]
667                }
668            }
669        });
670
671        let trial_state =
672            MetricState::new(&metrics, Fetcher::TrialData(FOO_42_AB_7_TRIAL_FETCHER.clone()), None);
673
674        // foo from values shadows foo selector.
675        assert_eq!(
676            trial_state.evaluate_variable("foo_file", variable!("foo")),
677            MetricValue::Int(42)
678        );
679        // Value shadowing also works in expressions.
680        assert_eq!(
681            trial_state.evaluate_variable("foo_file", variable!("foo_plus_one")),
682            MetricValue::Int(43)
683        );
684        // foo can shadow eval as well as selector.
685        assert_eq!(trial_state.evaluate_variable("a", variable!("foo")), MetricValue::Int(42));
686        // A value that's not there should be "SyntaxError" (e.g. not crash)
687        assert_problem!(
688            trial_state.evaluate_variable("foo_file", variable!("oops_plus_one")),
689            "SyntaxError: Metric 'oops' Not Found in 'foo_file'"
690        );
691        // a::b ignores the "b" in file "a" and uses "a::b" from values.
692        assert_eq!(
693            trial_state.evaluate_variable("foo_file", variable!("ab_plus_one")),
694            MetricValue::Int(8)
695        );
696        // a::c should return Missing, not look up c in file a.
697        assert_problem!(
698            trial_state.evaluate_variable("foo_file", variable!("ac_plus_one")),
699            "SyntaxError: Name a::c not in test values and refers outside the file"
700        );
701    }
702
703    #[fuchsia::test]
704    fn inspect_fetcher_new_works() -> Result<(), Error> {
705        assert!(InspectFetcher::try_from("foo").is_err(), "'foo' isn't valid JSON");
706        assert!(InspectFetcher::try_from(r#"{"a":5}"#).is_err(), "Needed an array");
707        assert!(InspectFetcher::try_from("[]").is_ok(), "A JSON array should have worked");
708        Ok(())
709    }
710
711    #[fuchsia::test]
712    fn test_fetch_with_tree_names() {
713        let cases = &[
714            (
715                "INSPECT:core/*:[name=root]root:foo",
716                vec![MetricValue::String("bar".to_string())],
717                r#"[
718  {
719    "data_source": "Inspect",
720    "metadata": {
721      "name": "root",
722      "component_url": "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm",
723      "timestamp": 6532507441581
724    },
725    "moniker": "core/foo",
726    "payload": {
727      "root": {
728        "foo": "bar"
729      }
730    }
731  },
732  {
733    "data_source": "Inspect",
734    "metadata": {
735      "name": "root",
736      "component_url": "fuchsia-pkg://fuchsia.com/baz#meta/baz.cm",
737      "timestamp": 6532507441581
738    },
739    "moniker": "core/baz",
740    "payload": {
741      "root": {
742        "baz": ""
743      }
744    }
745  }
746]
747"#,
748            ),
749            (
750                "INSPECT:core/*:[name=foo-is-bar]root:foo",
751                vec![MetricValue::String("bar".to_string())],
752                r#"[
753  {
754    "data_source": "Inspect",
755    "metadata": {
756      "name": "foo-is-bar",
757      "component_url": "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm",
758      "timestamp": 6532507441581
759    },
760    "moniker": "core/foo",
761    "payload": {
762      "root": {
763        "foo": "bar"
764      }
765    }
766  },
767  {
768    "data_source": "Inspect",
769    "metadata": {
770      "name": "foo-is-qux",
771      "component_url": "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm",
772      "timestamp": 6532507441581
773    },
774    "moniker": "core/foo",
775    "payload": {
776      "root": {
777        "foo": "qux"
778      }
779    }
780  },
781  {
782    "data_source": "Inspect",
783    "metadata": {
784      "name": "root",
785      "component_url": "fuchsia-pkg://fuchsia.com/baz#meta/baz.cm",
786      "timestamp": 6532507441581
787    },
788    "moniker": "core/baz",
789    "payload": {
790      "root": {
791        "baz": ""
792      }
793    }
794  }
795]
796"#,
797            ),
798        ];
799
800        for (selector, expected, json) in cases {
801            let fetcher = InspectFetcher::try_from(*json).unwrap();
802            let metric = fetcher.fetch_str(selector);
803            assert_eq!(expected, &metric, "component list: {:#?}", fetcher.components);
804        }
805    }
806
807    #[fuchsia::test]
808    fn test_fetch() -> Result<(), Error> {
809        // This tests both the moniker/payload and path/content (old-style) Inspect formats.
810        let json_options = vec![
811            r#"[
812        {"moniker":"asdf/foo/qwer", "metadata": {},
813         "payload":{"root":{"dataInt":5, "child":{"dataFloat":2.3}}}},
814        {"moniker":"zxcv/bar/hjkl", "metadata": {},
815         "payload":{"base":{"dataInt":42, "array":[2,3,4], "yes": true}}},
816        {"moniker":"fail_component", "metadata": {},
817         "payload": ["a", "b"]},
818        {"moniker":"missing_component", "metadata": {},
819         "payload": null}
820        ]"#,
821            r#"[
822        {"moniker":"asdf/foo/qwer", "metadata": {},
823         "payload":{"root":{"dataInt":5, "child":{"dataFloat":2.3}}}},
824        {"moniker":"zxcv/bar/hjkl", "metadata": {},
825         "contents":{"base":{"dataInt":42, "array":[2,3,4], "yes": true}}},
826        {"moniker":"fail_component", "metadata": {},
827         "payload": ["a", "b"]},
828        {"moniker":"missing_component", "metadata": {},
829         "payload": null}
830        ]"#,
831        ];
832
833        for json in json_options.into_iter() {
834            let inspect = InspectFetcher::try_from(json)?;
835            assert_eq!(
836                vec!["Unable to deserialize Inspect contents for fail_component to node hierarchy"],
837                inspect.component_errors.iter().map(|e| format!("{e}")).collect::<Vec<_>>()
838            );
839            macro_rules! assert_wrong {
840                ($selector:expr, $error:expr) => {
841                    let error = inspect.fetch_str($selector);
842                    assert_eq!(error.len(), 1);
843                    assert_problem!(&error[0], $error);
844                };
845            }
846            assert_wrong!(
847                "INSPET:*/foo/*:root:dataInt",
848                "SyntaxError: Bad selector INSPET:*/foo/*:root:dataInt: Invalid selector type \'INSPET\' - must be INSPECT"
849            );
850            assert_eq!(
851                inspect.fetch_str("INSPECT:*/foo/*:root:dataInt"),
852                vec![MetricValue::Int(5)]
853            );
854            assert_eq!(
855                inspect.fetch_str("INSPECT:*/foo/*:root/child:dataFloat"),
856                vec![MetricValue::Float(2.3)]
857            );
858            assert_eq!(
859                inspect.fetch_str("INSPECT:zxcv/*/hjk*:base:yes"),
860                vec![MetricValue::Bool(true)]
861            );
862            assert_eq!(inspect.fetch_str("INSPECT:*/foo/*:root.dataInt"), vec![]);
863            assert_wrong!(
864                "INSPECT:*/fo/*:root.dataInt",
865                "Missing: No component found matching selector */fo/*:root.dataInt"
866            );
867
868            assert_eq!(inspect.fetch_str("INSPECT:*/foo/*:root/kid:dataInt"), vec![]);
869            assert_eq!(inspect.fetch_str("INSPECT:*/bar/*:base/array:dataInt"), vec![]);
870            assert_eq!(
871                inspect.fetch_str("INSPECT:*/bar/*:base:array"),
872                vec![MetricValue::Vector(vec![
873                    MetricValue::Int(2),
874                    MetricValue::Int(3),
875                    MetricValue::Int(4)
876                ])]
877            );
878        }
879        Ok(())
880    }
881
882    #[fuchsia::test]
883    fn inspect_ref_empty() -> Result<(), Error> {
884        // Make sure it doesn't crash, can be called multiple times and they both work right.
885        let fetcher1 = InspectFetcher::ref_empty();
886        let fetcher2 = InspectFetcher::ref_empty();
887
888        match fetcher1.try_fetch(&SelectorString::try_from("INSPECT:a:b:c".to_string())?).unwrap()
889            [0]
890        {
891            MetricValue::Problem(Problem::Missing(_)) => {}
892            _ => bail!("Should have Missing'd a valid selector"),
893        }
894        match fetcher2.try_fetch(&SelectorString::try_from("INSPECT:a:b:c".to_string())?).unwrap()
895            [0]
896        {
897            MetricValue::Problem(Problem::Missing(_)) => {}
898            _ => bail!("Should have Missing'd a valid selector"),
899        }
900        Ok(())
901    }
902
903    #[fuchsia::test]
904    fn text_fetcher_works() {
905        let fetcher = TextFetcher::from("abcfoo\ndefgfoo");
906        assert!(fetcher.contains("d*g"));
907        assert!(fetcher.contains("foo"));
908        assert!(!fetcher.contains("food"));
909        // Make sure ref_empty() doesn't crash and can be used multiple times.
910        let fetcher1 = TextFetcher::ref_empty();
911        let fetcher2 = TextFetcher::ref_empty();
912        assert!(!fetcher1.contains("a"));
913        assert!(!fetcher2.contains("a"));
914    }
915
916    #[fuchsia::test]
917    fn test_selector_string_parse() -> Result<(), Error> {
918        // Test correct shape of SelectorString and verify no errors on parse for valid selector.
919        let full_selector = "INSPECT:bad_component:root:bar".to_string();
920        let selector_type = SelectorType::Inspect;
921        let body = "bad_component:root:bar".to_string();
922        let parsed_selector = selectors::parse_selector::<VerboseError>(&body)?;
923
924        assert_eq!(
925            SelectorString::try_from("INSPECT:bad_component:root:bar".to_string())?,
926            SelectorString { full_selector, selector_type, body, parsed_selector }
927        );
928
929        // Test that a selector that does not follow the correct syntax results in parse error.
930        assert_matches!(SelectorString::try_from("INSPECT:not a selector".to_string()), Err(_));
931
932        // Test that an invalid selector results in a parse error.
933        assert_matches!(
934            SelectorString::try_from("INSPECT:*/foo/*:root:data:Int".to_string()),
935            Err(_)
936        );
937
938        Ok(())
939    }
940
941    // KeyValueFetcher is tested in metrics::test::annotations_work()
942}