1use 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#[derive(Clone, Debug)]
26pub enum Fetcher<'a> {
27 FileData(FileDataFetcher<'a>),
28 TrialData(TrialDataFetcher<'a>),
29}
30
31#[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 SelectorType::Inspect => MetricValue::Vector(self.inspect.fetch(selector)),
88 }
89 }
90
91 pub fn errors(&self) -> Vec<String> {
93 self.inspect.component_errors.iter().map(|e| format!("{e}")).collect()
94 }
95}
96
97#[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#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
161pub enum SelectorType {
162 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 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 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 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 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 assert_eq!(
676 trial_state.evaluate_variable("foo_file", variable!("foo")),
677 MetricValue::Int(42)
678 );
679 assert_eq!(
681 trial_state.evaluate_variable("foo_file", variable!("foo_plus_one")),
682 MetricValue::Int(43)
683 );
684 assert_eq!(trial_state.evaluate_variable("a", variable!("foo")), MetricValue::Int(42));
686 assert_problem!(
688 trial_state.evaluate_variable("foo_file", variable!("oops_plus_one")),
689 "SyntaxError: Metric 'oops' Not Found in 'foo_file'"
690 );
691 assert_eq!(
693 trial_state.evaluate_variable("foo_file", variable!("ab_plus_one")),
694 MetricValue::Int(8)
695 );
696 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 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 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 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 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 assert_matches!(SelectorString::try_from("INSPECT:not a selector".to_string()), Err(_));
931
932 assert_matches!(
934 SelectorString::try_from("INSPECT:*/foo/*:root:data:Int".to_string()),
935 Err(_)
936 );
937
938 Ok(())
939 }
940
941 }