diagnostics_hierarchy/
lib.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
5//! Diagnostics hierarchy
6//!
7//! This library provides a tree strcture used to store diagnostics data such as inspect and logs,
8//! as well as utilities for reading from it, serializing and deserializing it and testing it.
9
10use base64::display::Base64Display;
11use fidl_fuchsia_diagnostics::{
12    PropertySelector, Selector, StringSelector, StringSelectorUnknown, SubtreeSelector,
13    TreeSelector,
14};
15use num_derive::{FromPrimitive, ToPrimitive};
16use num_traits::bounds::Bounded;
17use selectors::ValidateExt;
18use serde::{Deserialize, Serialize};
19use std::borrow::{Borrow, Cow};
20use std::cmp::Ordering;
21use std::collections::BTreeMap;
22use std::fmt::{Display, Formatter, Result as FmtResult};
23use std::ops::{Add, AddAssign, MulAssign};
24use thiserror::Error;
25
26pub mod macros;
27pub mod serialization;
28
29/// Format in which the array will be read.
30///
31/// Histograms are formatted with its parameters inline with its buckets:
32///
33/// - ...N parameters...
34/// - underflow_bucket
35/// - ...M buckets...
36/// - overflow_bucket
37///
38/// Helper functions exist to make it easier to work with these formats:
39///
40/// - [`ArrayFormat::underflow_bucket_index`] returns the index of the underflow
41///   bucket, which also happens to be the first bucket.
42/// - [`ArrayFormat::extra_slots`] returns the number of additional slots
43///   necessary to store parameters and underflow buckets.
44///
45#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
46#[repr(u8)]
47pub enum ArrayFormat {
48    /// Regular array, it stores N values in N slots.
49    Default = 0,
50
51    /// The array is a linear histogram with N buckets and N+4 slots, which are:
52    /// - param_floor_value
53    /// - param_step_size
54    /// - underflow_bucket
55    /// - ...N buckets...
56    /// - overflow_bucket
57    LinearHistogram = 1,
58
59    /// The array is an exponential histogram with N buckets and N+5 slots, which are:
60    /// - param_floor_value
61    /// - param_initial_step
62    /// - param_step_multiplier
63    /// - underflow_bucket
64    /// - ...N buckets...
65    /// - overflow_bucket
66    ExponentialHistogram = 2,
67}
68
69impl ArrayFormat {
70    /// Return index of the underflow bucket for histograms, or 0 if not a
71    /// histogram.
72    #[inline(always)]
73    pub const fn underflow_bucket_index(self) -> usize {
74        match self {
75            ArrayFormat::Default => 0,
76            ArrayFormat::LinearHistogram => 2,
77            ArrayFormat::ExponentialHistogram => 3,
78        }
79    }
80
81    /// Return index of the overflow bucket for histograms, or 0 if not a
82    /// histogram. Depends on the number of buckets.
83    #[inline(always)]
84    pub const fn overflow_bucket_index(self, buckets: usize) -> usize {
85        match self {
86            ArrayFormat::Default => 0,
87            ArrayFormat::LinearHistogram => self.extra_slots() + buckets - 1,
88            ArrayFormat::ExponentialHistogram => self.extra_slots() + buckets - 1,
89        }
90    }
91
92    /// Return count of extra slots needed for storing parameters and
93    /// underflow/overflow slots.
94    #[inline(always)]
95    pub const fn extra_slots(self) -> usize {
96        match self {
97            // 0 parameters + 0 extra buckets
98            ArrayFormat::Default => 0,
99            // 2 parameters (floor, step size) + underflow bucket + overflow bucket
100            ArrayFormat::LinearHistogram => 4,
101            // 3 parameters (floor, initial step, step multiplier) + underflow bucket + overflow bucket
102            ArrayFormat::ExponentialHistogram => 5,
103        }
104    }
105}
106
107/// A hierarchy of nodes representing structured data, such as Inspect or
108/// structured log data.
109///
110/// Each hierarchy consists of properties, and a map of named child hierarchies.
111#[derive(Clone, Debug, PartialEq)]
112pub struct DiagnosticsHierarchy<Key = String> {
113    /// The name of this node.
114    pub name: String,
115
116    /// The properties for the node.
117    pub properties: Vec<Property<Key>>,
118
119    /// The children of this node.
120    pub children: Vec<DiagnosticsHierarchy<Key>>,
121
122    /// Values that were impossible to load.
123    pub missing: Vec<MissingValue>,
124}
125
126/// A value that couldn't be loaded in the hierarchy and the reason.
127#[derive(Clone, Debug, PartialEq)]
128pub struct MissingValue {
129    /// Specific reason why the value couldn't be loaded.
130    pub reason: MissingValueReason,
131
132    /// The name of the value.
133    pub name: String,
134}
135
136/// Reasons why the value couldn't be loaded.
137#[derive(Clone, Debug, PartialEq)]
138pub enum MissingValueReason {
139    /// A referenced hierarchy in the link was not found.
140    LinkNotFound,
141
142    /// A linked hierarchy couldn't be parsed.
143    LinkParseFailure,
144
145    /// There was no attempt to read the link.
146    LinkNeverExpanded,
147
148    /// There was a timeout while reading.
149    Timeout,
150}
151
152/// Compares the names of two properties or nodes. If both are unsigned integers, then it compares
153/// their numerical value.
154fn name_partial_cmp(a: &str, b: &str) -> Ordering {
155    match (a.parse::<u64>(), b.parse::<u64>()) {
156        (Ok(n), Ok(m)) => n.partial_cmp(&m).unwrap(),
157        _ => a.partial_cmp(b).unwrap(),
158    }
159}
160
161impl<Key> DiagnosticsHierarchy<Key>
162where
163    Key: AsRef<str>,
164{
165    /// Sorts the properties and children of the diagnostics hierarchy by name.
166    pub fn sort(&mut self) {
167        self.properties.sort_by(|p1, p2| name_partial_cmp(p1.name(), p2.name()));
168        self.children.sort_by(|c1, c2| name_partial_cmp(&c1.name, &c2.name));
169        for child in self.children.iter_mut() {
170            child.sort();
171        }
172    }
173
174    /// Creates a new empty diagnostics hierarchy with the root node named "root".
175    pub fn new_root() -> Self {
176        DiagnosticsHierarchy::new("root", vec![], vec![])
177    }
178
179    /// Creates a new diagnostics hierarchy with the given `name` for the root and the given
180    /// `properties` and `children` under that root.
181    pub fn new(
182        name: impl Into<String>,
183        properties: Vec<Property<Key>>,
184        children: Vec<DiagnosticsHierarchy<Key>>,
185    ) -> Self {
186        Self { name: name.into(), properties, children, missing: vec![] }
187    }
188
189    /// Either returns an existing child of `self` with name `name` or creates
190    /// a new child with name `name`.
191    pub fn get_or_add_child_mut<T>(&mut self, name: T) -> &mut DiagnosticsHierarchy<Key>
192    where
193        T: AsRef<str>,
194    {
195        // We have to use indices to iterate here because the borrow checker cannot
196        // deduce that there are no borrowed values in the else-branch.
197        // TODO(https://fxbug.dev/42122598): We could make this cleaner by changing the DiagnosticsHierarchy
198        // children to hashmaps.
199        match (0..self.children.len()).find(|&i| self.children[i].name == name.as_ref()) {
200            Some(matching_index) => &mut self.children[matching_index],
201            None => {
202                self.children.push(DiagnosticsHierarchy::new(name.as_ref(), vec![], vec![]));
203                self.children
204                    .last_mut()
205                    .expect("We just added an entry so we cannot get None here.")
206            }
207        }
208    }
209
210    /// Add a child to this DiagnosticsHierarchy.
211    ///
212    /// Note: It is possible to create multiple children with the same name using this method, but
213    /// readers may not support such a case.
214    pub fn add_child(&mut self, insert: DiagnosticsHierarchy<Key>) {
215        self.children.push(insert);
216    }
217
218    /// Creates and returns a new Node whose location in a hierarchy
219    /// rooted at `self` is defined by node_path.
220    ///
221    /// Requires: that node_path is not empty.
222    /// Requires: that node_path begin with the key fragment equal to the name of the node
223    ///           that add is being called on.
224    ///
225    /// NOTE: Inspect VMOs may allow multiple nodes of the same name. In this case,
226    ///        the first node found is returned.
227    pub fn get_or_add_node<T>(&mut self, node_path: &[T]) -> &mut DiagnosticsHierarchy<Key>
228    where
229        T: AsRef<str>,
230    {
231        assert!(!node_path.is_empty());
232        let mut iter = node_path.iter();
233        let first_path_string = iter.next().unwrap().as_ref();
234        // It is an invariant that the node path start with the key fragment equal to the
235        // name of the node that get_or_add_node is called on.
236        assert_eq!(first_path_string, &self.name);
237        let mut curr_node = self;
238        for node_path_entry in iter {
239            curr_node = curr_node.get_or_add_child_mut(node_path_entry);
240        }
241        curr_node
242    }
243
244    /// Inserts a new Property into this hierarchy.
245    pub fn add_property(&mut self, property: Property<Key>) {
246        self.properties.push(property);
247    }
248
249    /// Inserts a new Property into a Node whose location in a hierarchy
250    /// rooted at `self` is defined by node_path.
251    ///
252    /// Requires: that node_path is not empty.
253    /// Requires: that node_path begin with the key fragment equal to the name of the node
254    ///           that add is being called on.
255    ///
256    /// NOTE: Inspect VMOs may allow multiple nodes of the same name. In this case,
257    ///       the property is added to the first node found.
258    pub fn add_property_at_path<T>(&mut self, node_path: &[T], property: Property<Key>)
259    where
260        T: AsRef<str>,
261    {
262        self.get_or_add_node(node_path).properties.push(property);
263    }
264
265    /// Provides an iterator over the diagnostics hierarchy returning properties in pre-order.
266    pub fn property_iter(&self) -> DiagnosticsHierarchyIterator<'_, Key, PropertyIter> {
267        DiagnosticsHierarchyIterator::new(self)
268    }
269
270    pub fn error_iter(&self) -> DiagnosticsHierarchyIterator<'_, Key, ErrorIter> {
271        DiagnosticsHierarchyIterator::new(self)
272    }
273
274    /// Adds a value that couldn't be read. This can happen when loading a lazy child.
275    pub fn add_missing(&mut self, reason: MissingValueReason, name: String) {
276        self.missing.push(MissingValue { reason, name });
277    }
278    /// Returns the property of the given |name| if one exists.
279    pub fn get_property(&self, name: &str) -> Option<&Property<Key>> {
280        self.properties.iter().find(|prop| prop.name() == name)
281    }
282
283    /// Returns the child of the given |name| if one exists.
284    pub fn get_child(&self, name: &str) -> Option<&DiagnosticsHierarchy<Key>> {
285        self.children.iter().find(|node| node.name == name)
286    }
287
288    /// Returns a mutable reference to the child of the given |name| if one exists.
289    pub fn get_child_mut(&mut self, name: &str) -> Option<&mut DiagnosticsHierarchy<Key>> {
290        self.children.iter_mut().find(|node| node.name == name)
291    }
292
293    /// Returns the child of the given |path| if one exists.
294    pub fn get_child_by_path(&self, path: &[&str]) -> Option<&DiagnosticsHierarchy<Key>> {
295        let mut result = Some(self);
296        for name in path {
297            result = result.and_then(|node| node.get_child(name));
298        }
299        result
300    }
301
302    /// Returns a mutable reference to the child of the given |path| if one exists.
303    pub fn get_child_by_path_mut(
304        &mut self,
305        path: &[&str],
306    ) -> Option<&mut DiagnosticsHierarchy<Key>> {
307        let mut result = Some(self);
308        for name in path {
309            result = result.and_then(|node| node.get_child_mut(name));
310        }
311        result
312    }
313
314    /// Returns the property of the given |name| if one exists.
315    pub fn get_property_by_path(&self, path: &[&str]) -> Option<&Property<Key>> {
316        let node = self.get_child_by_path(&path[..path.len() - 1]);
317        node.and_then(|node| node.get_property(path[path.len() - 1]))
318    }
319}
320
321impl<Key> DiagnosticsHierarchy<Key>
322where
323    Key: PartialEq,
324{
325    /// Recursively merge another [`DiagnosticsHierarchy`] into this one.
326    pub fn merge(&mut self, other: DiagnosticsHierarchy<Key>) {
327        for other_property in other.properties {
328            if let Some(existing) =
329                self.properties.iter_mut().find(|p| p.key() == other_property.key())
330            {
331                *existing = other_property;
332            } else {
333                self.properties.push(other_property);
334            }
335        }
336
337        for other_child in other.children {
338            if let Some(existing_child) =
339                self.children.iter_mut().find(|c| c.name == other_child.name)
340            {
341                existing_child.merge(other_child);
342            } else {
343                // Remove missing errors for matching lazy nodes.
344                self.missing.retain(|m| m.name != other_child.name);
345
346                self.children.push(other_child);
347            }
348        }
349
350        for other_missing in other.missing {
351            if !self.missing.contains(&other_missing) {
352                self.missing.push(other_missing);
353            }
354        }
355    }
356}
357
358macro_rules! property_type_getters_ref {
359    ($([$variant:ident, $fn_name:ident, $type:ty]),*) => {
360        paste::item! {
361          impl<Key> Property<Key> {
362              $(
363                  #[doc = "Returns the " $variant " value or `None` if the property isn't of that type"]
364                  pub fn $fn_name(&self) -> Option<&$type> {
365                      match self {
366                          Property::$variant(_, value) => Some(value),
367                          _ => None,
368                      }
369                  }
370              )*
371          }
372        }
373    }
374}
375
376macro_rules! property_type_getters_copy {
377    ($([$variant:ident, $fn_name:ident, $type:ty]),*) => {
378        paste::item! {
379          impl<Key> Property<Key> {
380              $(
381                  #[doc = "Returns the " $variant " value or `None` if the property isn't of that type"]
382                  pub fn $fn_name(&self) -> Option<$type> {
383                      match self {
384                          Property::$variant(_, value) => Some(*value),
385                          _ => None,
386                      }
387                  }
388              )*
389          }
390        }
391    }
392}
393
394property_type_getters_copy!(
395    [Int, int, i64],
396    [Uint, uint, u64],
397    [Double, double, f64],
398    [Bool, boolean, bool]
399);
400
401property_type_getters_ref!(
402    [String, string, str],
403    [Bytes, bytes, [u8]],
404    [DoubleArray, double_array, ArrayContent<f64>],
405    [IntArray, int_array, ArrayContent<i64>],
406    [UintArray, uint_array, ArrayContent<u64>],
407    [StringList, string_list, [String]]
408);
409
410struct WorkStackEntry<'a, Key> {
411    node: &'a DiagnosticsHierarchy<Key>,
412    key: Vec<&'a str>,
413}
414
415pub struct PropertyIter;
416pub struct ErrorIter;
417
418pub struct DiagnosticsHierarchyIterator<'a, Key, PropOrIterMarker> {
419    work_stack: Vec<WorkStackEntry<'a, Key>>,
420    current_key: Vec<&'a str>,
421    current_node: Option<&'a DiagnosticsHierarchy<Key>>,
422    current_index: usize,
423    phantom: std::marker::PhantomData<PropOrIterMarker>,
424}
425
426enum EndOfTheLine<'a, T, Key> {
427    Yes(Option<T>),
428    No(&'a DiagnosticsHierarchy<Key>),
429}
430
431impl<'a, Key, Marker> DiagnosticsHierarchyIterator<'a, Key, Marker> {
432    /// Creates a new iterator for the given `hierarchy`.
433    fn new(hierarchy: &'a DiagnosticsHierarchy<Key>) -> Self {
434        DiagnosticsHierarchyIterator {
435            work_stack: vec![WorkStackEntry { node: hierarchy, key: vec![&hierarchy.name] }],
436            current_key: vec![],
437            current_node: None,
438            current_index: 0,
439            phantom: std::marker::PhantomData,
440        }
441    }
442
443    /// Get the next node. This abstracts stack management away from the type being iterated over.
444    fn get_node<T, U: 'a, F: FnOnce(&'a DiagnosticsHierarchy<Key>) -> &Vec<U>>(
445        &mut self,
446        iterable_node_data: F,
447    ) -> EndOfTheLine<'a, (Vec<&'a str>, Option<&'a T>), Key> {
448        match self.current_node {
449            // If we are going through a node's data, that node will be set here.
450            Some(node) => EndOfTheLine::No(node),
451            None => {
452                // If we don't have a node we are currently working with, then go to the next
453                // node in our stack.
454                let Some(WorkStackEntry { node, key }) = self.work_stack.pop() else {
455                    return EndOfTheLine::Yes(None);
456                };
457
458                // Push to the stack all children of the new node.
459                for child in node.children.iter() {
460                    let mut child_key = key.clone();
461                    child_key.push(&child.name);
462                    self.work_stack.push(WorkStackEntry { node: child, key: child_key })
463                }
464
465                // If this node doesn't have any data we care about, we still want to return that it
466                // exists, so we return with a None for data type we are examining.
467                if iterable_node_data(node).is_empty() {
468                    return EndOfTheLine::Yes(Some((key.clone(), None)));
469                }
470
471                self.current_index = 0;
472                self.current_key = key;
473
474                EndOfTheLine::No(node)
475            }
476        }
477    }
478
479    fn advance_index<T>(
480        &mut self,
481        data: &'a [T],
482        new_current: &'a DiagnosticsHierarchy<Key>,
483    ) -> &'a T {
484        let datum = &data[self.current_index];
485        self.current_index += 1;
486        self.current_node = Some(new_current);
487        datum
488    }
489}
490
491impl<'a, Key> Iterator for DiagnosticsHierarchyIterator<'a, Key, PropertyIter> {
492    /// Each item is a path to the node holding the resulting property.
493    /// If a node has no properties, a `None` will be returned for it.
494    /// If a node has properties a `Some` will be returned for each property and no `None` will be
495    /// returned.
496    type Item = (Vec<&'a str>, Option<&'a Property<Key>>);
497
498    fn next(&mut self) -> Option<Self::Item> {
499        loop {
500            let node = match self.get_node(|node| &node.properties) {
501                EndOfTheLine::Yes(r) => return r,
502                EndOfTheLine::No(n) => n,
503            };
504
505            // We were already done with this node. Try the next item in our stack.
506            if self.current_index == node.properties.len() {
507                self.current_node = None;
508                continue;
509            }
510
511            // Return the current property and advance our index to the next node we want to
512            // explore.
513            let property = self.advance_index(&node.properties, node);
514
515            return Some((self.current_key.clone(), Some(property)));
516        }
517    }
518}
519
520impl<'a, Key> Iterator for DiagnosticsHierarchyIterator<'a, Key, ErrorIter> {
521    /// Each item is a path to the node with a missing link.
522    /// If a node has no missing links, a `None` will be returned for it.
523    /// If a node has missing links a `Some` will be returned for each link and no `None` will be
524    /// returned.
525    type Item = (Vec<&'a str>, Option<&'a MissingValue>);
526
527    fn next(&mut self) -> Option<Self::Item> {
528        loop {
529            let node = match self.get_node(|node| &node.missing) {
530                EndOfTheLine::Yes(r) => return r,
531                EndOfTheLine::No(n) => n,
532            };
533
534            // We were already done with this node. Try the next item in our stack.
535            if self.current_index == node.missing.len() {
536                self.current_node = None;
537                continue;
538            }
539
540            // Return the current error and advance our index to the next node we want to
541            // explore.
542            let err = self.advance_index(&node.missing, node);
543            return Some((self.current_key.clone(), Some(err)));
544        }
545    }
546}
547
548/// A named property. Each of the fields consists of (name, value).
549///
550/// Key is the type of the property's name and is typically a string. In cases where
551/// there are well known, common property names, an alternative may be used to
552/// reduce copies of the name.
553#[derive(Debug, PartialEq, Clone)]
554pub enum Property<Key = String> {
555    /// The value is a string.
556    String(Key, String),
557
558    /// The value is a bytes vector.
559    Bytes(Key, Vec<u8>),
560
561    /// The value is an integer.
562    Int(Key, i64),
563
564    /// The value is an unsigned integer.
565    Uint(Key, u64),
566
567    /// The value is a double.
568    Double(Key, f64),
569
570    /// The value is a boolean.
571    Bool(Key, bool),
572
573    /// The value is a double array.
574    DoubleArray(Key, ArrayContent<f64>),
575
576    /// The value is an integer array.
577    IntArray(Key, ArrayContent<i64>),
578
579    /// The value is an unsigned integer array.
580    UintArray(Key, ArrayContent<u64>),
581
582    /// The value is a list of strings.
583    StringList(Key, Vec<String>),
584}
585
586impl<K> Property<K> {
587    /// Returns the key of a property
588    pub fn key(&self) -> &K {
589        match self {
590            Property::String(k, _) => k,
591            Property::Bytes(k, _) => k,
592            Property::Int(k, _) => k,
593            Property::Uint(k, _) => k,
594            Property::Double(k, _) => k,
595            Property::Bool(k, _) => k,
596            Property::DoubleArray(k, _) => k,
597            Property::IntArray(k, _) => k,
598            Property::UintArray(k, _) => k,
599            Property::StringList(k, _) => k,
600        }
601    }
602
603    /// Returns a string indicating which variant of property this is, useful for printing
604    /// debug values.
605    pub fn discriminant_name(&self) -> &'static str {
606        match self {
607            Property::String(_, _) => "String",
608            Property::Bytes(_, _) => "Bytes",
609            Property::Int(_, _) => "Int",
610            Property::IntArray(_, _) => "IntArray",
611            Property::Uint(_, _) => "Uint",
612            Property::UintArray(_, _) => "UintArray",
613            Property::Double(_, _) => "Double",
614            Property::DoubleArray(_, _) => "DoubleArray",
615            Property::Bool(_, _) => "Bool",
616            Property::StringList(_, _) => "StringList",
617        }
618    }
619
620    /// Return a a numeric property as a signed integer. Useful for having a single function to call
621    /// when a property has been passed through JSON, potentially losing its original signedness.
622    ///
623    /// Note: unsigned integers larger than `isize::MAX` will be returned as `None`. If you expect
624    /// values that high, consider calling `Property::int()` and `Property::uint()` directly.
625    pub fn number_as_int(&self) -> Option<i64> {
626        match self {
627            Property::Int(_, i) => Some(*i),
628            Property::Uint(_, u) => i64::try_from(*u).ok(),
629            Property::String(..)
630            | Property::Bytes(..)
631            | Property::Double(..)
632            | Property::Bool(..)
633            | Property::DoubleArray(..)
634            | Property::IntArray(..)
635            | Property::UintArray(..)
636            | Property::StringList(..) => None,
637        }
638    }
639}
640
641impl<K> Display for Property<K>
642where
643    K: AsRef<str>,
644{
645    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
646        macro_rules! pair {
647            ($fmt:literal, $val:expr) => {
648                write!(f, "{}={}", self.key().as_ref(), format_args!($fmt, $val))
649            };
650        }
651        match self {
652            Property::String(_, v) => pair!("{}", v),
653            Property::Bytes(_, v) => {
654                pair!("b64:{}", Base64Display::new(v, &base64::engine::general_purpose::STANDARD))
655            }
656            Property::Int(_, v) => pair!("{}", v),
657            Property::Uint(_, v) => pair!("{}", v),
658            Property::Double(_, v) => pair!("{}", v),
659            Property::Bool(_, v) => pair!("{}", v),
660            Property::DoubleArray(_, v) => pair!("{:?}", v),
661            Property::IntArray(_, v) => pair!("{:?}", v),
662            Property::UintArray(_, v) => pair!("{:?}", v),
663            Property::StringList(_, v) => pair!("{:?}", v),
664        }
665    }
666}
667
668/// Errors that can happen in this library.
669#[derive(Debug, Error)]
670pub enum Error {
671    #[error("Missing elements for {histogram_type:?} histogram. Expected {expected}, got {actual}")]
672    MissingHistogramElements { histogram_type: ArrayFormat, expected: usize, actual: usize },
673
674    #[error("TreeSelector only supports property and subtree selection.")]
675    InvalidTreeSelector,
676
677    #[error(transparent)]
678    Selectors(#[from] selectors::Error),
679
680    #[error(transparent)]
681    InvalidSelector(#[from] selectors::ValidationError),
682}
683
684impl Error {
685    fn missing_histogram_elements(histogram_type: ArrayFormat, actual: usize) -> Self {
686        Self::MissingHistogramElements {
687            histogram_type,
688            actual,
689            expected: histogram_type.extra_slots() + 1,
690        }
691    }
692}
693
694/// A linear histogram property.
695#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
696#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
697pub struct LinearHistogram<T> {
698    /// The number of buckets. If indexes is None this should equal counts.len().
699    pub size: usize,
700
701    /// The floor of the lowest bucket (not counting the negative-infinity bucket).
702    pub floor: T,
703
704    /// The increment for each bucket range.
705    pub step: T,
706
707    /// The number of items in each bucket.
708    pub counts: Vec<T>,
709
710    /// If Some<_>, the indexes of nonzero counts.
711    #[serde(skip_serializing_if = "Option::is_none")]
712    pub indexes: Option<Vec<usize>>,
713}
714
715/// An exponential histogram property.
716#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
717#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
718pub struct ExponentialHistogram<T> {
719    /// The number of buckets. If indexes is None this should equal counts.len().
720    pub size: usize,
721
722    /// The floor of the lowest bucket (not counting the negative-infinity bucket).
723    pub floor: T,
724
725    /// The increment for the second floor.
726    pub initial_step: T,
727
728    /// The multiplier for each successive floor.
729    pub step_multiplier: T,
730
731    /// The number of items in each bucket.
732    pub counts: Vec<T>,
733
734    /// If Some<_>, the indexes of nonzero counts.
735    #[serde(skip_serializing_if = "Option::is_none")]
736    pub indexes: Option<Vec<usize>>,
737}
738
739/// Represents the content of a DiagnosticsHierarchy array property: a regular array or a
740/// linear/exponential histogram.
741#[derive(Debug, PartialEq, Clone)]
742pub enum ArrayContent<T> {
743    /// The contents of an array.
744    Values(Vec<T>),
745
746    /// The data for a linear histogram.
747    LinearHistogram(LinearHistogram<T>),
748
749    // The data for an exponential histogram.
750    ExponentialHistogram(ExponentialHistogram<T>),
751}
752
753impl<T> ArrayContent<T>
754where
755    T: Add<Output = T> + num_traits::Zero + AddAssign + Copy + MulAssign + PartialEq + Bounded,
756{
757    /// Creates a new ArrayContent parsing the `values` based on the given `format`.
758    pub fn new(values: Vec<T>, format: ArrayFormat) -> Result<Self, Error> {
759        let (counts, indexes) = match format {
760            ArrayFormat::Default => return Ok(Self::Values(values)),
761            ArrayFormat::LinearHistogram | ArrayFormat::ExponentialHistogram => {
762                // Check that the minimum required values are available:
763                // floor, step size, underflow, bucket 0, overflow
764                if values.len() < format.extra_slots() + 1 {
765                    return Err(Error::missing_histogram_elements(format, values.len()));
766                }
767                let buckets = &values[format.underflow_bucket_index()..];
768
769                match serialization::maybe_condense_histogram(buckets, &None) {
770                    None => (buckets.to_vec(), None),
771                    Some((counts, indexes)) => (counts, Some(indexes)),
772                }
773            }
774        };
775
776        Ok(match format {
777            ArrayFormat::Default => unreachable!(),
778            ArrayFormat::LinearHistogram => Self::LinearHistogram(LinearHistogram {
779                floor: values[0],
780                step: values[1],
781                counts,
782                indexes,
783                size: values.len() - 2,
784            }),
785            ArrayFormat::ExponentialHistogram => Self::ExponentialHistogram(ExponentialHistogram {
786                floor: values[0],
787                initial_step: values[1],
788                step_multiplier: values[2],
789                counts,
790                indexes,
791                size: values.len() - 3,
792            }),
793        })
794    }
795
796    /// Returns the number of items in the array.
797    pub fn len(&self) -> usize {
798        match self {
799            Self::Values(vals) => vals.len(),
800            Self::LinearHistogram(LinearHistogram { size, .. })
801            | Self::ExponentialHistogram(ExponentialHistogram { size, .. }) => *size,
802        }
803    }
804
805    /// Returns whether the array is empty or not.
806    pub fn is_empty(&self) -> bool {
807        self.len() == 0
808    }
809
810    /// Returns the raw values of this Array content. In the case of a histogram, returns the
811    /// bucket counts.
812    pub fn raw_values(&self) -> Cow<'_, Vec<T>> {
813        match self {
814            Self::Values(values) => Cow::Borrowed(values),
815            Self::LinearHistogram(LinearHistogram { size, counts, indexes, .. })
816            | Self::ExponentialHistogram(ExponentialHistogram { size, counts, indexes, .. }) => {
817                if let Some(indexes) = indexes {
818                    let mut values = vec![T::zero(); *size];
819                    for (count, index) in counts.iter().zip(indexes.iter()) {
820                        if index <= size {
821                            values[*index] = *count;
822                        }
823                    }
824                    Cow::Owned(values)
825                } else {
826                    Cow::Borrowed(counts)
827                }
828            }
829        }
830    }
831}
832
833pub mod testing {
834    use crate::ArrayContent;
835    use num_traits::bounds::Bounded;
836    use std::ops::{Add, AddAssign, MulAssign};
837
838    // Require test code to import CondensableOnDemand to access the
839    // condense_histogram() associated function.
840    pub trait CondensableOnDemand {
841        fn condense_histogram(&mut self);
842    }
843
844    fn condense_counts<T: num_traits::Zero + Copy + PartialEq>(
845        counts: &[T],
846    ) -> (Vec<T>, Vec<usize>) {
847        let mut condensed_counts = vec![];
848        let mut indexes = vec![];
849        for (index, count) in counts.iter().enumerate() {
850            if *count != T::zero() {
851                condensed_counts.push(*count);
852                indexes.push(index);
853            }
854        }
855        (condensed_counts, indexes)
856    }
857
858    impl<T> CondensableOnDemand for ArrayContent<T>
859    where
860        T: Add<Output = T> + num_traits::Zero + AddAssign + Copy + MulAssign + PartialEq + Bounded,
861    {
862        fn condense_histogram(&mut self) {
863            match self {
864                Self::Values(_) => (),
865                Self::LinearHistogram(histogram) => {
866                    if histogram.indexes.is_some() {
867                        return;
868                    }
869                    let (counts, indexes) = condense_counts(&histogram.counts);
870                    histogram.counts = counts;
871                    histogram.indexes = Some(indexes);
872                }
873                Self::ExponentialHistogram(histogram) => {
874                    if histogram.indexes.is_some() {
875                        return;
876                    }
877                    let (counts, indexes) = condense_counts(&histogram.counts);
878                    histogram.counts = counts;
879                    histogram.indexes = Some(indexes);
880                }
881            }
882        }
883    }
884}
885
886impl<Key> Property<Key>
887where
888    Key: AsRef<str>,
889{
890    /// Returns the key of a property.
891    pub fn name(&self) -> &str {
892        match self {
893            Property::String(name, _)
894            | Property::Bytes(name, _)
895            | Property::Int(name, _)
896            | Property::IntArray(name, _)
897            | Property::Uint(name, _)
898            | Property::UintArray(name, _)
899            | Property::Double(name, _)
900            | Property::Bool(name, _)
901            | Property::DoubleArray(name, _)
902            | Property::StringList(name, _) => name.as_ref(),
903        }
904    }
905}
906
907impl<T: Borrow<Selector>> TryFrom<&[T]> for HierarchyMatcher {
908    type Error = Error;
909
910    fn try_from(selectors: &[T]) -> Result<Self, Self::Error> {
911        // TODO(https://fxbug.dev/42069126: remove cloning, the archivist can probably hold
912        // HierarchyMatcher<'static>
913        let mut matcher = HierarchyMatcher::default();
914        for selector in selectors {
915            let selector = selector.borrow();
916            selector.validate().map_err(|e| Error::Selectors(e.into()))?;
917
918            // Safe to unwrap since we already validated the selector.
919            // TODO(https://fxbug.dev/42069126): instead of doing this over Borrow<Selector> do it over
920            // Selector.
921            match selector.tree_selector.clone().unwrap() {
922                TreeSelector::SubtreeSelector(subtree_selector) => {
923                    matcher.insert_subtree(subtree_selector.clone());
924                }
925                TreeSelector::PropertySelector(property_selector) => {
926                    matcher.insert_property(property_selector.clone());
927                }
928                _ => return Err(Error::Selectors(selectors::Error::InvalidTreeSelector)),
929            }
930        }
931        Ok(matcher)
932    }
933}
934
935impl<T: Borrow<Selector>> TryFrom<Vec<T>> for HierarchyMatcher {
936    type Error = Error;
937
938    fn try_from(selectors: Vec<T>) -> Result<Self, Self::Error> {
939        selectors[..].try_into()
940    }
941}
942
943#[derive(Debug)]
944struct OrdStringSelector(StringSelector);
945
946impl From<StringSelector> for OrdStringSelector {
947    fn from(selector: StringSelector) -> Self {
948        Self(selector)
949    }
950}
951
952impl Ord for OrdStringSelector {
953    fn cmp(&self, other: &OrdStringSelector) -> Ordering {
954        match (&self.0, &other.0) {
955            (StringSelector::ExactMatch(s), StringSelector::ExactMatch(o)) => s.cmp(o),
956            (StringSelector::StringPattern(s), StringSelector::StringPattern(o)) => s.cmp(o),
957            (StringSelector::ExactMatch(_), StringSelector::StringPattern(_)) => Ordering::Less,
958            (StringSelector::StringPattern(_), StringSelector::ExactMatch(_)) => Ordering::Greater,
959            (StringSelectorUnknown!(), StringSelector::ExactMatch(_)) => Ordering::Less,
960            (StringSelectorUnknown!(), StringSelector::StringPattern(_)) => Ordering::Less,
961            (StringSelectorUnknown!(), StringSelectorUnknown!()) => Ordering::Equal,
962        }
963    }
964}
965
966impl PartialOrd for OrdStringSelector {
967    fn partial_cmp(&self, other: &OrdStringSelector) -> Option<Ordering> {
968        Some(self.cmp(other))
969    }
970}
971
972impl PartialEq for OrdStringSelector {
973    fn eq(&self, other: &OrdStringSelector) -> bool {
974        match (&self.0, &other.0) {
975            (StringSelector::ExactMatch(s), StringSelector::ExactMatch(o)) => s.eq(o),
976            (StringSelector::StringPattern(s), StringSelector::StringPattern(o)) => s.eq(o),
977            (StringSelector::ExactMatch(_), StringSelector::StringPattern(_)) => false,
978            (StringSelector::StringPattern(_), StringSelector::ExactMatch(_)) => false,
979            (StringSelectorUnknown!(), StringSelectorUnknown!()) => true,
980        }
981    }
982}
983
984impl Eq for OrdStringSelector {}
985
986#[derive(Default, Debug)]
987pub struct HierarchyMatcher {
988    nodes: BTreeMap<OrdStringSelector, HierarchyMatcher>,
989    properties: Vec<OrdStringSelector>,
990    subtree: bool,
991}
992
993impl HierarchyMatcher {
994    pub fn new<I>(selectors: I) -> Result<Self, Error>
995    where
996        I: Iterator<Item = Selector>,
997    {
998        let mut matcher = HierarchyMatcher::default();
999        for selector in selectors {
1000            selector.validate().map_err(|e| Error::Selectors(e.into()))?;
1001
1002            // Safe to unwrap since we already validated the selector.
1003            match selector.tree_selector.unwrap() {
1004                TreeSelector::SubtreeSelector(subtree_selector) => {
1005                    matcher.insert_subtree(subtree_selector);
1006                }
1007                TreeSelector::PropertySelector(property_selector) => {
1008                    matcher.insert_property(property_selector);
1009                }
1010                _ => return Err(Error::Selectors(selectors::Error::InvalidTreeSelector)),
1011            }
1012        }
1013        Ok(matcher)
1014    }
1015
1016    fn insert_subtree(&mut self, selector: SubtreeSelector) {
1017        self.insert(selector.node_path, None);
1018    }
1019
1020    fn insert_property(&mut self, selector: PropertySelector) {
1021        self.insert(selector.node_path, Some(selector.target_properties));
1022    }
1023
1024    fn insert(&mut self, node_path: Vec<StringSelector>, property: Option<StringSelector>) {
1025        // Note: this could have additional optimization so that branches are collapsed into a
1026        // single one (for example foo/bar is included by f*o/bar), however, in practice, we don't
1027        // hit that edge case.
1028        let mut matcher = self;
1029        for node in node_path {
1030            matcher = matcher.nodes.entry(node.into()).or_default();
1031        }
1032        match property {
1033            Some(property) => {
1034                matcher.properties.push(property.into());
1035            }
1036            None => matcher.subtree = true,
1037        }
1038    }
1039}
1040
1041#[derive(Debug, PartialEq)]
1042pub enum SelectResult<'a, Key: Clone> {
1043    Properties(Vec<Cow<'a, Property<Key>>>),
1044    Nodes(Vec<Cow<'a, DiagnosticsHierarchy<Key>>>),
1045}
1046
1047impl<'a, Key: Clone> SelectResult<'a, Key> {
1048    pub fn into_owned(self) -> SelectResult<'static, Key> {
1049        match self {
1050            Self::Properties(v) => SelectResult::Properties(
1051                v.into_iter().map(|v| Cow::Owned(v.into_owned())).collect(),
1052            ),
1053            Self::Nodes(v) => {
1054                SelectResult::Nodes(v.into_iter().map(|v| Cow::Owned(v.into_owned())).collect())
1055            }
1056        }
1057    }
1058
1059    /// Returns Err(()) if `self` is `Self::Nodes`. Otherwise, adds to property list.
1060    fn add_property(&mut self, prop: &'a Property<Key>) {
1061        let Self::Properties(v) = self else {
1062            panic!("must be Self::Properties to call add_property");
1063        };
1064        v.push(Cow::Borrowed(prop));
1065    }
1066
1067    /// Returns Err(()) if `self` is `Self::Properties`. Otherwise, adds to property list.
1068    fn add_node(&mut self, node: &'a DiagnosticsHierarchy<Key>) {
1069        let Self::Nodes(v) = self else {
1070            panic!("must be Self::Nodes to call add_node");
1071        };
1072        v.push(Cow::Borrowed(node));
1073    }
1074}
1075
1076pub fn select_from_hierarchy<'a, 'b, Key>(
1077    root_node: &'a DiagnosticsHierarchy<Key>,
1078    selector: &'b Selector,
1079) -> Result<SelectResult<'a, Key>, Error>
1080where
1081    Key: AsRef<str> + Clone,
1082    'a: 'b,
1083{
1084    selector.validate()?;
1085
1086    struct StackEntry<'a, Key> {
1087        node: &'a DiagnosticsHierarchy<Key>,
1088        node_path_index: usize,
1089        explored_path: Vec<&'a str>,
1090    }
1091
1092    // Safe to unwrap since we validated above.
1093    let (node_path, property_selector, stack_entry) = match selector.tree_selector.as_ref().unwrap()
1094    {
1095        TreeSelector::SubtreeSelector(subtree_selector) => (
1096            &subtree_selector.node_path,
1097            None,
1098            StackEntry { node: root_node, node_path_index: 0, explored_path: vec![] },
1099        ),
1100        TreeSelector::PropertySelector(property_selector) => (
1101            &property_selector.node_path,
1102            Some(&property_selector.target_properties),
1103            StackEntry { node: root_node, node_path_index: 0, explored_path: vec![] },
1104        ),
1105        _ => return Err(Error::InvalidTreeSelector),
1106    };
1107
1108    let mut stack = vec![stack_entry];
1109    let mut result = if property_selector.is_some() {
1110        SelectResult::Properties(vec![])
1111    } else {
1112        SelectResult::Nodes(vec![])
1113    };
1114
1115    while let Some(StackEntry { node, node_path_index, mut explored_path }) = stack.pop() {
1116        // Unwrap is safe since we validate is_empty right above.
1117        if !selectors::match_string(&node_path[node_path_index], &node.name) {
1118            continue;
1119        }
1120        explored_path.push(&node.name);
1121
1122        // If we are at the last node in the path, then we just need to explore the properties.
1123        // Otherwise, we explore the children of the current node and the properties.
1124        if node_path_index != node_path.len() - 1 {
1125            // If this node matches the next selector we are looking at, then explore its children.
1126            for child in node.children.iter() {
1127                stack.push(StackEntry {
1128                    node: child,
1129                    node_path_index: node_path_index + 1,
1130                    explored_path: explored_path.clone(),
1131                });
1132            }
1133        } else if let Some(s) = property_selector {
1134            // If we have a property selector, then add any properties matching it to our result.
1135            for property in &node.properties {
1136                if selectors::match_string(s, property.key()) {
1137                    result.add_property(property);
1138                }
1139            }
1140        } else {
1141            // If we don't have a property selector and we reached the end of the node path, then
1142            // we should add the current node to the result.
1143            result.add_node(node);
1144        }
1145    }
1146
1147    Ok(result)
1148}
1149
1150/// Filters a hierarchy given a tree selector.
1151pub fn filter_tree<'a, Key, I: IntoIterator<Item = &'a TreeSelector>>(
1152    root_node: DiagnosticsHierarchy<Key>,
1153    selectors: I,
1154) -> Option<DiagnosticsHierarchy<Key>>
1155where
1156    Key: AsRef<str>,
1157{
1158    let mut matcher = HierarchyMatcher::default();
1159    for selector in selectors.into_iter() {
1160        match selector {
1161            TreeSelector::SubtreeSelector(subtree_selector) => {
1162                matcher.insert_subtree(subtree_selector.clone());
1163            }
1164            TreeSelector::PropertySelector(property_selector) => {
1165                matcher.insert_property(property_selector.clone());
1166            }
1167            _ => {}
1168        }
1169    }
1170    filter_hierarchy(root_node, &matcher)
1171}
1172
1173/// Filters a diagnostics hierarchy using a set of path selectors and their associated property
1174/// selectors.
1175///
1176/// If the return type is None that implies that the filter encountered no errors AND the tree was
1177/// filtered to be empty at the end.
1178pub fn filter_hierarchy<Key>(
1179    mut root_node: DiagnosticsHierarchy<Key>,
1180    hierarchy_matcher: &HierarchyMatcher,
1181) -> Option<DiagnosticsHierarchy<Key>>
1182where
1183    Key: AsRef<str>,
1184{
1185    let starts_empty = root_node.children.is_empty() && root_node.properties.is_empty();
1186    if filter_hierarchy_helper(&mut root_node, &[hierarchy_matcher]) {
1187        if !starts_empty && root_node.children.is_empty() && root_node.properties.is_empty() {
1188            return None;
1189        }
1190        return Some(root_node);
1191    }
1192    None
1193}
1194
1195fn filter_hierarchy_helper<Key>(
1196    node: &mut DiagnosticsHierarchy<Key>,
1197    hierarchy_matchers: &[&HierarchyMatcher],
1198) -> bool
1199where
1200    Key: AsRef<str>,
1201{
1202    let child_matchers = eval_matchers_on_node_name(&node.name, hierarchy_matchers);
1203    if child_matchers.is_empty() {
1204        node.children.clear();
1205        node.properties.clear();
1206        return false;
1207    }
1208
1209    if child_matchers.iter().any(|m| m.subtree) {
1210        return true;
1211    }
1212
1213    node.children.retain_mut(|child| filter_hierarchy_helper(child, &child_matchers));
1214    node.properties.retain_mut(|prop| eval_matchers_on_property(prop.name(), &child_matchers));
1215
1216    !(node.children.is_empty() && node.properties.is_empty())
1217}
1218
1219fn eval_matchers_on_node_name<'a>(
1220    node_name: &'a str,
1221    matchers: &'a [&'a HierarchyMatcher],
1222) -> Vec<&'a HierarchyMatcher> {
1223    let mut result = vec![];
1224    for matcher in matchers {
1225        for (node_pattern, tree_matcher) in matcher.nodes.iter() {
1226            if selectors::match_string(&node_pattern.0, node_name) {
1227                result.push(tree_matcher);
1228            }
1229        }
1230    }
1231    result
1232}
1233
1234fn eval_matchers_on_property(property_name: &str, matchers: &[&HierarchyMatcher]) -> bool {
1235    matchers.iter().any(|matcher| {
1236        matcher
1237            .properties
1238            .iter()
1239            .any(|property_pattern| selectors::match_string(&property_pattern.0, property_name))
1240    })
1241}
1242
1243/// The parameters of an exponential histogram.
1244#[derive(Clone)]
1245pub struct ExponentialHistogramParams<T: Clone> {
1246    /// The floor of the exponential histogram.
1247    pub floor: T,
1248
1249    /// The initial step of the exponential histogram.
1250    pub initial_step: T,
1251
1252    /// The step multiplier of the exponential histogram.
1253    pub step_multiplier: T,
1254
1255    /// The number of buckets that the exponential histogram can have. This doesn't include the
1256    /// overflow and underflow buckets.
1257    pub buckets: usize,
1258}
1259
1260/// The parameters of a linear histogram.
1261#[derive(Clone)]
1262pub struct LinearHistogramParams<T: Clone> {
1263    /// The floor of the linear histogram.
1264    pub floor: T,
1265
1266    /// The step size of the linear histogram.
1267    pub step_size: T,
1268
1269    /// The number of buckets that the linear histogram can have. This doesn't include the overflow
1270    /// and underflow buckets.
1271    pub buckets: usize,
1272}
1273
1274/// A type which can function as a "view" into a diagnostics hierarchy, optionally allocating a new
1275/// instance to service a request.
1276pub trait DiagnosticsHierarchyGetter<K: Clone> {
1277    fn get_diagnostics_hierarchy<'a>(
1278        &'a self,
1279    ) -> impl std::future::Future<Output = Cow<'_, DiagnosticsHierarchy<K>>>
1280    where
1281        K: 'a;
1282}
1283
1284impl<K: Clone> DiagnosticsHierarchyGetter<K> for DiagnosticsHierarchy<K> {
1285    async fn get_diagnostics_hierarchy<'a>(&'a self) -> Cow<'_, DiagnosticsHierarchy<K>>
1286    where
1287        K: 'a,
1288    {
1289        Cow::Borrowed(self)
1290    }
1291}
1292
1293#[cfg(test)]
1294mod tests {
1295    use super::*;
1296    use crate::testing::CondensableOnDemand;
1297    use test_case::test_case;
1298
1299    use assert_matches::assert_matches;
1300    use selectors::VerboseError;
1301    use std::sync::Arc;
1302
1303    fn validate_hierarchy_iteration(
1304        mut results_vec: Vec<(Vec<String>, Option<Property>)>,
1305        test_hierarchy: DiagnosticsHierarchy,
1306    ) {
1307        let expected_num_entries = results_vec.len();
1308        let mut num_entries = 0;
1309        for (key, val) in test_hierarchy.property_iter() {
1310            num_entries += 1;
1311            let (expected_key, expected_property) = results_vec.pop().unwrap();
1312            assert_eq!(key.to_vec().join("/"), expected_key.to_vec().join("/"));
1313            assert_eq!(val, expected_property.as_ref());
1314        }
1315
1316        assert_eq!(num_entries, expected_num_entries);
1317    }
1318
1319    #[track_caller]
1320    fn validate_hierarchy_error_iteration(
1321        mut results_vec: Vec<(Vec<String>, Option<MissingValue>)>,
1322        test_hierarchy: DiagnosticsHierarchy,
1323    ) {
1324        let expected_num_entries = results_vec.len();
1325        let mut num_entries = 0;
1326        for (key, reason) in test_hierarchy.error_iter() {
1327            num_entries += 1;
1328            let (expected_key, expected_reason) = results_vec.pop().unwrap();
1329            assert_eq!(reason, expected_reason.as_ref());
1330            assert_eq!(key.to_vec().join("/"), expected_key.to_vec().join("/"));
1331        }
1332
1333        assert_eq!(num_entries, expected_num_entries);
1334    }
1335
1336    #[fuchsia::test]
1337    fn test_diagnostics_hierarchy_property_iteration() {
1338        let double_array_data = vec![-1.2, 2.3, 3.4, 4.5, -5.6];
1339        let chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
1340        let string_data = chars.iter().cycle().take(6000).collect::<String>();
1341        let bytes_data = (0u8..=9u8).cycle().take(5000).collect::<Vec<u8>>();
1342
1343        let test_hierarchy = DiagnosticsHierarchy::new(
1344            "root".to_string(),
1345            vec![
1346                Property::Int("int-root".to_string(), 3),
1347                Property::DoubleArray(
1348                    "property-double-array".to_string(),
1349                    ArrayContent::Values(double_array_data.clone()),
1350                ),
1351            ],
1352            vec![DiagnosticsHierarchy::new(
1353                "child-1".to_string(),
1354                vec![
1355                    Property::Uint("property-uint".to_string(), 10),
1356                    Property::Double("property-double".to_string(), -3.4),
1357                    Property::String("property-string".to_string(), string_data.clone()),
1358                    Property::IntArray(
1359                        "property-int-array".to_string(),
1360                        ArrayContent::new(vec![1, 2, 1, 1, 1, 1, 1], ArrayFormat::LinearHistogram)
1361                            .unwrap(),
1362                    ),
1363                ],
1364                vec![DiagnosticsHierarchy::new(
1365                    "child-1-1".to_string(),
1366                    vec![
1367                        Property::Int("property-int".to_string(), -9),
1368                        Property::Bytes("property-bytes".to_string(), bytes_data.clone()),
1369                        Property::UintArray(
1370                            "property-uint-array".to_string(),
1371                            ArrayContent::new(
1372                                vec![1, 1, 2, 0, 1, 1, 2, 0, 0],
1373                                ArrayFormat::ExponentialHistogram,
1374                            )
1375                            .unwrap(),
1376                        ),
1377                    ],
1378                    vec![],
1379                )],
1380            )],
1381        );
1382
1383        let results_vec = vec![
1384            (
1385                vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1386                Some(Property::UintArray(
1387                    "property-uint-array".to_string(),
1388                    ArrayContent::new(
1389                        vec![1, 1, 2, 0, 1, 1, 2, 0, 0],
1390                        ArrayFormat::ExponentialHistogram,
1391                    )
1392                    .unwrap(),
1393                )),
1394            ),
1395            (
1396                vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1397                Some(Property::Bytes("property-bytes".to_string(), bytes_data)),
1398            ),
1399            (
1400                vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1401                Some(Property::Int("property-int".to_string(), -9)),
1402            ),
1403            (
1404                vec!["root".to_string(), "child-1".to_string()],
1405                Some(Property::IntArray(
1406                    "property-int-array".to_string(),
1407                    ArrayContent::new(vec![1, 2, 1, 1, 1, 1, 1], ArrayFormat::LinearHistogram)
1408                        .unwrap(),
1409                )),
1410            ),
1411            (
1412                vec!["root".to_string(), "child-1".to_string()],
1413                Some(Property::String("property-string".to_string(), string_data)),
1414            ),
1415            (
1416                vec!["root".to_string(), "child-1".to_string()],
1417                Some(Property::Double("property-double".to_string(), -3.4)),
1418            ),
1419            (
1420                vec!["root".to_string(), "child-1".to_string()],
1421                Some(Property::Uint("property-uint".to_string(), 10)),
1422            ),
1423            (
1424                vec!["root".to_string()],
1425                Some(Property::DoubleArray(
1426                    "property-double-array".to_string(),
1427                    ArrayContent::Values(double_array_data),
1428                )),
1429            ),
1430            (vec!["root".to_string()], Some(Property::Int("int-root".to_string(), 3))),
1431        ];
1432
1433        validate_hierarchy_iteration(results_vec, test_hierarchy);
1434    }
1435
1436    #[fuchsia::test]
1437    fn test_diagnostics_hierarchy_error_iteration() {
1438        let mut test_hierarchy = DiagnosticsHierarchy::new(
1439            "root".to_string(),
1440            vec![],
1441            vec![
1442                DiagnosticsHierarchy::new(
1443                    "child-1".to_string(),
1444                    vec![],
1445                    vec![DiagnosticsHierarchy::new("child-1-1".to_string(), vec![], vec![])],
1446                ),
1447                DiagnosticsHierarchy::new("child-2".to_string(), vec![], vec![]),
1448            ],
1449        );
1450
1451        test_hierarchy.children[0]
1452            .add_missing(MissingValueReason::LinkNeverExpanded, "child-1".to_string());
1453        test_hierarchy.children[0].children[0]
1454            .add_missing(MissingValueReason::Timeout, "child-1-1".to_string());
1455
1456        let results_vec = vec![
1457            (
1458                vec!["root".to_string(), "child-1".to_string(), "child-1-1".to_string()],
1459                Some(MissingValue {
1460                    reason: MissingValueReason::Timeout,
1461                    name: "child-1-1".to_string(),
1462                }),
1463            ),
1464            (
1465                vec!["root".to_string(), "child-1".to_string()],
1466                Some(MissingValue {
1467                    reason: MissingValueReason::LinkNeverExpanded,
1468                    name: "child-1".to_string(),
1469                }),
1470            ),
1471            (vec!["root".to_string(), "child-2".to_string()], None),
1472            (vec!["root".to_string()], None),
1473        ];
1474
1475        validate_hierarchy_error_iteration(results_vec, test_hierarchy);
1476    }
1477
1478    #[fuchsia::test]
1479    fn test_getters() {
1480        let a_prop = Property::Int("a".to_string(), 1);
1481        let b_prop = Property::Uint("b".to_string(), 2);
1482        let child2 = DiagnosticsHierarchy::new("child2".to_string(), vec![], vec![]);
1483        let child = DiagnosticsHierarchy::new(
1484            "child".to_string(),
1485            vec![b_prop.clone()],
1486            vec![child2.clone()],
1487        );
1488        let mut hierarchy = DiagnosticsHierarchy::new(
1489            "root".to_string(),
1490            vec![a_prop.clone()],
1491            vec![child.clone()],
1492        );
1493        assert_matches!(hierarchy.get_child("child"), Some(node) if *node == child);
1494        assert_matches!(hierarchy.get_child_mut("child"), Some(node) if *node == child);
1495        assert_matches!(hierarchy.get_child_by_path(&["child", "child2"]),
1496                        Some(node) if *node == child2);
1497        assert_matches!(hierarchy.get_child_by_path_mut(&["child", "child2"]),
1498                        Some(node) if *node == child2);
1499        assert_matches!(hierarchy.get_property("a"), Some(prop) if *prop == a_prop);
1500        assert_matches!(hierarchy.get_property_by_path(&["child", "b"]),
1501                        Some(prop) if *prop == b_prop);
1502    }
1503
1504    #[fuchsia::test]
1505    fn test_edge_case_hierarchy_iteration() {
1506        let root_only_with_one_property_hierarchy = DiagnosticsHierarchy::new(
1507            "root".to_string(),
1508            vec![Property::Int("property-int".to_string(), -9)],
1509            vec![],
1510        );
1511
1512        let results_vec =
1513            vec![(vec!["root".to_string()], Some(Property::Int("property-int".to_string(), -9)))];
1514
1515        validate_hierarchy_iteration(results_vec, root_only_with_one_property_hierarchy);
1516
1517        let empty_hierarchy = DiagnosticsHierarchy::new("root".to_string(), vec![], vec![]);
1518
1519        let results_vec = vec![(vec!["root".to_string()], None)];
1520
1521        validate_hierarchy_iteration(results_vec, empty_hierarchy);
1522
1523        let empty_root_populated_child = DiagnosticsHierarchy::new(
1524            "root",
1525            vec![],
1526            vec![DiagnosticsHierarchy::new(
1527                "foo",
1528                vec![Property::Int("11".to_string(), -4)],
1529                vec![],
1530            )],
1531        );
1532
1533        let results_vec = vec![
1534            (
1535                vec!["root".to_string(), "foo".to_string()],
1536                Some(Property::Int("11".to_string(), -4)),
1537            ),
1538            (vec!["root".to_string()], None),
1539        ];
1540
1541        validate_hierarchy_iteration(results_vec, empty_root_populated_child);
1542
1543        let empty_root_empty_child = DiagnosticsHierarchy::new(
1544            "root",
1545            vec![],
1546            vec![DiagnosticsHierarchy::new("foo", vec![], vec![])],
1547        );
1548
1549        let results_vec = vec![
1550            (vec!["root".to_string(), "foo".to_string()], None),
1551            (vec!["root".to_string()], None),
1552        ];
1553
1554        validate_hierarchy_iteration(results_vec, empty_root_empty_child);
1555    }
1556
1557    #[fuchsia::test]
1558    fn array_value() {
1559        let values = vec![1, 2, 5, 7, 9, 11, 13];
1560        let array = ArrayContent::<u64>::new(values.clone(), ArrayFormat::Default);
1561        assert_matches!(array, Ok(ArrayContent::Values(vals)) if vals == values);
1562    }
1563
1564    #[fuchsia::test]
1565    fn linear_histogram_array_value() {
1566        let values = vec![1, 2, 5, 7, 9, 11, 13];
1567        let array = ArrayContent::<i64>::new(values, ArrayFormat::LinearHistogram);
1568        assert_matches!(array, Ok(ArrayContent::LinearHistogram(hist))
1569            if hist == LinearHistogram {
1570                floor: 1,
1571                step: 2,
1572                counts: vec![5, 7, 9, 11, 13],
1573                indexes: None,
1574                size: 5,
1575            }
1576        );
1577    }
1578
1579    #[fuchsia::test]
1580    fn exponential_histogram_array_value() {
1581        let values = vec![1.0, 2.0, 5.0, 7.0, 9.0, 11.0, 15.0];
1582        let array = ArrayContent::<f64>::new(values, ArrayFormat::ExponentialHistogram);
1583        assert_matches!(array, Ok(ArrayContent::ExponentialHistogram(hist))
1584            if hist == ExponentialHistogram {
1585                floor: 1.0,
1586                initial_step: 2.0,
1587                step_multiplier: 5.0,
1588                counts: vec![7.0, 9.0, 11.0, 15.0],
1589                indexes: None,
1590                size: 4,
1591            }
1592        );
1593    }
1594
1595    #[fuchsia::test]
1596    fn deserialize_linear_int_histogram() -> Result<(), serde_json::Error> {
1597        let json = r#"{
1598            "root": {
1599                "histogram": {
1600                    "floor": -2,
1601                    "step": 3,
1602                    "counts": [4, 5, 6],
1603                    "size": 3
1604                }
1605            }
1606        }"#;
1607        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1608        let expected = DiagnosticsHierarchy::new(
1609            "root".to_string(),
1610            vec![Property::IntArray(
1611                "histogram".to_string(),
1612                ArrayContent::new(vec![-2, 3, 4, 5, 6], ArrayFormat::LinearHistogram).unwrap(),
1613            )],
1614            vec![],
1615        );
1616        assert_eq!(parsed, expected);
1617        Ok(())
1618    }
1619
1620    #[fuchsia::test]
1621    fn deserialize_exponential_int_histogram() -> Result<(), serde_json::Error> {
1622        let json = r#"{
1623            "root": {
1624                "histogram": {
1625                    "floor": 1,
1626                    "initial_step": 3,
1627                    "step_multiplier": 2,
1628                    "counts": [4, 5, 6],
1629                    "size": 3
1630                }
1631            }
1632        }"#;
1633        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1634        let expected = DiagnosticsHierarchy::new(
1635            "root".to_string(),
1636            vec![Property::IntArray(
1637                "histogram".to_string(),
1638                ArrayContent::new(vec![1, 3, 2, 4, 5, 6], ArrayFormat::ExponentialHistogram)
1639                    .unwrap(),
1640            )],
1641            vec![],
1642        );
1643        assert_eq!(parsed, expected);
1644        Ok(())
1645    }
1646
1647    #[fuchsia::test]
1648    fn deserialize_linear_uint_histogram() -> Result<(), serde_json::Error> {
1649        let json = r#"{
1650            "root": {
1651                "histogram": {
1652                    "floor": 2,
1653                    "step": 3,
1654                    "counts": [4, 9223372036854775808, 6],
1655                    "size": 3
1656                }
1657            }
1658        }"#;
1659        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1660        let expected = DiagnosticsHierarchy::new(
1661            "root".to_string(),
1662            vec![Property::UintArray(
1663                "histogram".to_string(),
1664                ArrayContent::new(
1665                    vec![2, 3, 4, 9_223_372_036_854_775_808, 6],
1666                    ArrayFormat::LinearHistogram,
1667                )
1668                .unwrap(),
1669            )],
1670            vec![],
1671        );
1672        assert_eq!(parsed, expected);
1673        Ok(())
1674    }
1675
1676    #[fuchsia::test]
1677    fn deserialize_linear_double_histogram() -> Result<(), serde_json::Error> {
1678        let json = r#"{
1679            "root": {
1680                "histogram": {
1681                    "floor": 2.0,
1682                    "step": 3.0,
1683                    "counts": [4.0, 5.0, 6.0],
1684                    "size": 3
1685                }
1686            }
1687        }"#;
1688        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1689        let expected = DiagnosticsHierarchy::new(
1690            "root".to_string(),
1691            vec![Property::DoubleArray(
1692                "histogram".to_string(),
1693                ArrayContent::new(vec![2.0, 3.0, 4.0, 5.0, 6.0], ArrayFormat::LinearHistogram)
1694                    .unwrap(),
1695            )],
1696            vec![],
1697        );
1698        assert_eq!(parsed, expected);
1699        Ok(())
1700    }
1701
1702    #[fuchsia::test]
1703    fn deserialize_sparse_histogram() -> Result<(), serde_json::Error> {
1704        let json = r#"{
1705            "root": {
1706                "histogram": {
1707                    "floor": 2,
1708                    "step": 3,
1709                    "counts": [4, 5, 6],
1710                    "indexes": [1, 2, 4],
1711                    "size": 8
1712                }
1713            }
1714        }"#;
1715        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1716
1717        let mut histogram =
1718            ArrayContent::new(vec![2, 3, 0, 4, 5, 0, 6, 0, 0, 0], ArrayFormat::LinearHistogram)
1719                .unwrap();
1720        histogram.condense_histogram();
1721        let expected = DiagnosticsHierarchy::new(
1722            "root".to_string(),
1723            vec![Property::IntArray("histogram".to_string(), histogram)],
1724            vec![],
1725        );
1726        assert_eq!(parsed, expected);
1727        Ok(())
1728    }
1729
1730    // If a struct can't be parsed as a valid histogram, it will be created as a Node. So if
1731    // there's a node "histogram" (as opposed to a property "histogram") then it didn't parse
1732    // as a histogram.
1733
1734    #[fuchsia::test]
1735    fn reject_histogram_incompatible_values() -> Result<(), serde_json::Error> {
1736        let json = r#"{
1737            "root": {
1738                "histogram": {
1739                    "floor": -2,
1740                    "step": 3,
1741                    "counts": [4, 9223372036854775808, 6],
1742                    "size": 3
1743                }
1744            }
1745        }"#;
1746        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1747        assert_eq!(parsed.children.len(), 1);
1748        assert_eq!(&parsed.children[0].name, "histogram");
1749        Ok(())
1750    }
1751
1752    #[fuchsia::test]
1753    fn reject_histogram_bad_sparse_list() -> Result<(), serde_json::Error> {
1754        let json = r#"{
1755            "root": {
1756                "histogram": {
1757                    "floor": -2,
1758                    "step": 3,
1759                    "counts": [4, 5, 6],
1760                    "indexes": [0, 1, 2, 3],
1761                    "size": 8
1762                }
1763            }
1764        }"#;
1765        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1766        assert_eq!(parsed.children.len(), 1);
1767        assert_eq!(&parsed.children[0].name, "histogram");
1768        Ok(())
1769    }
1770
1771    #[fuchsia::test]
1772    fn reject_histogram_bad_index() -> Result<(), serde_json::Error> {
1773        let json = r#"{
1774            "root": {
1775                "histogram": {
1776                    "floor": -2,
1777                    "step": 3,
1778                    "counts": [4, 5, 6],
1779                    "indexes": [0, 1, 4],
1780                    "size": 4
1781                }
1782            }
1783        }"#;
1784        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1785        assert_eq!(parsed.children.len(), 1);
1786        assert_eq!(&parsed.children[0].name, "histogram");
1787        Ok(())
1788    }
1789
1790    #[fuchsia::test]
1791    fn reject_histogram_wrong_field() -> Result<(), serde_json::Error> {
1792        let json = r#"{
1793            "root": {
1794                "histogram": {
1795                    "floor": 2,
1796                    "step": 3,
1797                    "counts": [4, 5, 6],
1798                    "incorrect": [0, 1, 3],
1799                    "size": 4
1800                }
1801            }
1802        }"#;
1803        let parsed: DiagnosticsHierarchy = serde_json::from_str(json)?;
1804        assert_eq!(parsed.children.len(), 1);
1805        assert_eq!(&parsed.children[0].name, "histogram");
1806        Ok(())
1807    }
1808
1809    #[fuchsia::test]
1810    fn exponential_histogram() {
1811        let values = vec![0, 2, 4, 0, 1, 2, 3, 4, 5];
1812        let array = ArrayContent::new(values, ArrayFormat::ExponentialHistogram);
1813        assert_matches!(array, Ok(ArrayContent::ExponentialHistogram(hist))
1814            if hist == ExponentialHistogram {
1815                floor: 0,
1816                initial_step: 2,
1817                step_multiplier: 4,
1818                counts: vec![0, 1, 2, 3, 4, 5],
1819                indexes: None,
1820                size: 6,
1821            }
1822        );
1823    }
1824
1825    #[fuchsia::test]
1826    fn add_to_hierarchy() {
1827        let mut hierarchy = DiagnosticsHierarchy::new_root();
1828        let prop_1 = Property::String("x".to_string(), "foo".to_string());
1829        let path_1 = vec!["root", "one"];
1830        let prop_2 = Property::Uint("c".to_string(), 3);
1831        let path_2 = vec!["root", "two"];
1832        let prop_2_prime = Property::Int("z".to_string(), -4);
1833        hierarchy.add_property_at_path(&path_1, prop_1.clone());
1834        hierarchy.add_property_at_path(&path_2.clone(), prop_2.clone());
1835        hierarchy.add_property_at_path(&path_2, prop_2_prime.clone());
1836
1837        assert_eq!(
1838            hierarchy,
1839            DiagnosticsHierarchy {
1840                name: "root".to_string(),
1841                children: vec![
1842                    DiagnosticsHierarchy {
1843                        name: "one".to_string(),
1844                        properties: vec![prop_1],
1845                        children: vec![],
1846                        missing: vec![],
1847                    },
1848                    DiagnosticsHierarchy {
1849                        name: "two".to_string(),
1850                        properties: vec![prop_2, prop_2_prime],
1851                        children: vec![],
1852                        missing: vec![],
1853                    }
1854                ],
1855                properties: vec![],
1856                missing: vec![],
1857            }
1858        );
1859    }
1860
1861    #[fuchsia::test]
1862    fn string_lists() {
1863        let mut hierarchy = DiagnosticsHierarchy::new_root();
1864        let prop_1 =
1865            Property::StringList("x".to_string(), vec!["foo".to_string(), "bar".to_string()]);
1866        let path_1 = vec!["root", "one"];
1867        hierarchy.add_property_at_path(&path_1, prop_1.clone());
1868
1869        assert_eq!(
1870            hierarchy,
1871            DiagnosticsHierarchy {
1872                name: "root".to_string(),
1873                children: vec![DiagnosticsHierarchy {
1874                    name: "one".to_string(),
1875                    properties: vec![prop_1],
1876                    children: vec![],
1877                    missing: vec![],
1878                },],
1879                properties: vec![],
1880                missing: vec![],
1881            }
1882        );
1883    }
1884
1885    #[fuchsia::test]
1886    // TODO(https://fxbug.dev/42169733): delete the below
1887    #[cfg_attr(feature = "variant_asan", ignore)]
1888    #[cfg_attr(feature = "variant_hwasan", ignore)]
1889    #[should_panic]
1890    // Empty paths are meaningless on insertion and break the method invariant.
1891    fn no_empty_paths_allowed() {
1892        let mut hierarchy = DiagnosticsHierarchy::<String>::new_root();
1893        let path_1: Vec<&String> = vec![];
1894        hierarchy.get_or_add_node(&path_1);
1895    }
1896
1897    #[fuchsia::test]
1898    #[should_panic]
1899    // Paths provided to add must begin at the node we're calling
1900    // add() on.
1901    fn path_must_start_at_self() {
1902        let mut hierarchy = DiagnosticsHierarchy::<String>::new_root();
1903        let path_1 = vec!["not_root", "a"];
1904        hierarchy.get_or_add_node(&path_1);
1905    }
1906
1907    #[fuchsia::test]
1908    fn sort_hierarchy() {
1909        let mut hierarchy = DiagnosticsHierarchy::new(
1910            "root",
1911            vec![
1912                Property::String("x".to_string(), "foo".to_string()),
1913                Property::Uint("c".to_string(), 3),
1914                Property::Int("z".to_string(), -4),
1915            ],
1916            vec![
1917                DiagnosticsHierarchy::new(
1918                    "foo",
1919                    vec![
1920                        Property::Int("11".to_string(), -4),
1921                        Property::Bytes("123".to_string(), "foo".bytes().collect()),
1922                        Property::Double("0".to_string(), 8.1),
1923                    ],
1924                    vec![],
1925                ),
1926                DiagnosticsHierarchy::new("bar", vec![], vec![]),
1927            ],
1928        );
1929
1930        hierarchy.sort();
1931
1932        let sorted_hierarchy = DiagnosticsHierarchy::new(
1933            "root",
1934            vec![
1935                Property::Uint("c".to_string(), 3),
1936                Property::String("x".to_string(), "foo".to_string()),
1937                Property::Int("z".to_string(), -4),
1938            ],
1939            vec![
1940                DiagnosticsHierarchy::new("bar", vec![], vec![]),
1941                DiagnosticsHierarchy::new(
1942                    "foo",
1943                    vec![
1944                        Property::Double("0".to_string(), 8.1),
1945                        Property::Int("11".to_string(), -4),
1946                        Property::Bytes("123".to_string(), "foo".bytes().collect()),
1947                    ],
1948                    vec![],
1949                ),
1950            ],
1951        );
1952        assert_eq!(sorted_hierarchy, hierarchy);
1953    }
1954
1955    fn parse_selectors_and_filter_hierarchy(
1956        hierarchy: DiagnosticsHierarchy,
1957        test_selectors: Vec<&str>,
1958    ) -> Option<DiagnosticsHierarchy> {
1959        let parsed_test_selectors = test_selectors
1960            .into_iter()
1961            .map(|selector_string| {
1962                Arc::new(
1963                    selectors::parse_selector::<VerboseError>(selector_string)
1964                        .expect("All test selectors are valid and parsable."),
1965                )
1966            })
1967            .collect::<Vec<Arc<Selector>>>();
1968
1969        let hierarchy_matcher: HierarchyMatcher = parsed_test_selectors.try_into().unwrap();
1970
1971        filter_hierarchy(hierarchy, &hierarchy_matcher).map(|mut hierarchy| {
1972            hierarchy.sort();
1973            hierarchy
1974        })
1975    }
1976
1977    fn get_test_hierarchy() -> DiagnosticsHierarchy {
1978        DiagnosticsHierarchy::new(
1979            "root",
1980            vec![
1981                Property::String("x".to_string(), "foo".to_string()),
1982                Property::Uint("c".to_string(), 3),
1983                Property::Int("z".to_string(), -4),
1984            ],
1985            vec![
1986                make_foo(),
1987                DiagnosticsHierarchy::new(
1988                    "bar",
1989                    vec![Property::Int("12".to_string(), -4)],
1990                    vec![DiagnosticsHierarchy::new(
1991                        "zed",
1992                        vec![Property::Int("13/:".to_string(), -4)],
1993                        vec![],
1994                    )],
1995                ),
1996            ],
1997        )
1998    }
1999
2000    fn make_all_foo_props() -> Vec<Property> {
2001        vec![
2002            Property::Int("11".to_string(), -4),
2003            Property::Bytes("123".to_string(), b"foo".to_vec()),
2004            Property::Double("0".to_string(), 8.1),
2005        ]
2006    }
2007
2008    fn make_zed() -> Vec<DiagnosticsHierarchy> {
2009        vec![DiagnosticsHierarchy::new("zed", vec![Property::Int("13".to_string(), -4)], vec![])]
2010    }
2011
2012    fn make_foo() -> DiagnosticsHierarchy {
2013        DiagnosticsHierarchy::new("foo", make_all_foo_props(), make_zed())
2014    }
2015
2016    #[fuchsia::test]
2017    fn test_filter_hierarchy() {
2018        let test_selectors = vec!["*:root/foo:11", "*:root:z", r#"*:root/bar/zed:13\/\:"#];
2019
2020        assert_eq!(
2021            parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
2022            Some(DiagnosticsHierarchy::new(
2023                "root",
2024                vec![Property::Int("z".to_string(), -4),],
2025                vec![
2026                    DiagnosticsHierarchy::new(
2027                        "bar",
2028                        vec![],
2029                        vec![DiagnosticsHierarchy::new(
2030                            "zed",
2031                            vec![Property::Int("13/:".to_string(), -4)],
2032                            vec![],
2033                        )],
2034                    ),
2035                    DiagnosticsHierarchy::new(
2036                        "foo",
2037                        vec![Property::Int("11".to_string(), -4),],
2038                        vec![],
2039                    )
2040                ],
2041            ))
2042        );
2043
2044        let test_selectors = vec!["*:root"];
2045        let mut sorted_expected = get_test_hierarchy();
2046        sorted_expected.sort();
2047        assert_eq!(
2048            parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
2049            Some(sorted_expected)
2050        );
2051    }
2052
2053    #[fuchsia::test]
2054    fn test_filter_does_not_include_empty_node() {
2055        let test_selectors = vec!["*:root/foo:blorg"];
2056
2057        assert_eq!(
2058            parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
2059            None,
2060        );
2061    }
2062
2063    #[fuchsia::test]
2064    fn test_filter_empty_hierarchy() {
2065        let test_selectors = vec!["*:root"];
2066
2067        assert_eq!(
2068            parse_selectors_and_filter_hierarchy(
2069                DiagnosticsHierarchy::new("root", vec![], vec![]),
2070                test_selectors
2071            ),
2072            Some(DiagnosticsHierarchy::new("root", vec![], vec![])),
2073        );
2074    }
2075
2076    #[fuchsia::test]
2077    fn test_full_filtering() {
2078        // If we select a non-existent root, then we return a fully filtered hierarchy.
2079        let test_selectors = vec!["*:non-existent-root"];
2080        assert_eq!(
2081            parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
2082            None,
2083        );
2084
2085        // If we select a non-existent child of the root, then we return a fully filtered hierarchy.
2086        let test_selectors = vec!["*:root/i-dont-exist:foo"];
2087        assert_eq!(
2088            parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
2089            None,
2090        );
2091
2092        // Even if the root exists, but we don't include any property, we consider the hierarchy
2093        // fully filtered. This is aligned with the previous case.
2094        let test_selectors = vec!["*:root:i-dont-exist"];
2095        assert_eq!(
2096            parse_selectors_and_filter_hierarchy(get_test_hierarchy(), test_selectors),
2097            None,
2098        );
2099    }
2100
2101    #[fuchsia::test]
2102    fn test_subtree_selection_includes_empty_nodes() {
2103        let test_selectors = vec!["*:root"];
2104        let mut empty_hierarchy = DiagnosticsHierarchy::new(
2105            "root",
2106            vec![],
2107            vec![
2108                DiagnosticsHierarchy::new(
2109                    "foo",
2110                    vec![],
2111                    vec![DiagnosticsHierarchy::new("zed", vec![], vec![])],
2112                ),
2113                DiagnosticsHierarchy::new(
2114                    "bar",
2115                    vec![],
2116                    vec![DiagnosticsHierarchy::new("zed", vec![], vec![])],
2117                ),
2118            ],
2119        );
2120
2121        empty_hierarchy.sort();
2122
2123        assert_eq!(
2124            parse_selectors_and_filter_hierarchy(empty_hierarchy.clone(), test_selectors),
2125            Some(empty_hierarchy)
2126        );
2127    }
2128
2129    #[fuchsia::test]
2130    fn test_empty_tree_filtering() {
2131        // Subtree selection on the empty tree should produce the empty tree.
2132        let mut empty_hierarchy = DiagnosticsHierarchy::new("root", vec![], vec![]);
2133        empty_hierarchy.sort();
2134
2135        let subtree_selector = vec!["*:root"];
2136        assert_eq!(
2137            parse_selectors_and_filter_hierarchy(empty_hierarchy.clone(), subtree_selector),
2138            Some(empty_hierarchy.clone())
2139        );
2140
2141        // Selecting a property on the root, even if it doesn't exist, should produce nothing.
2142        let fake_property_selector = vec!["*:root:blorp"];
2143        assert_eq!(
2144            parse_selectors_and_filter_hierarchy(empty_hierarchy.clone(), fake_property_selector),
2145            None,
2146        );
2147    }
2148
2149    #[test_case(vec![Property::Int("11".to_string(), -4)], "root/foo:11" ; "specific_property")]
2150    #[test_case(make_all_foo_props(), "root/foo:*" ; "many_properties")]
2151    #[test_case(vec![], "root/foo:none" ; "property_not_there")]
2152    #[fuchsia::test]
2153    fn test_select_from_hierarchy_property_selectors(expected: Vec<Property>, tree_selector: &str) {
2154        let hierarchy = get_test_hierarchy();
2155        let parsed_selector =
2156            selectors::parse_selector::<VerboseError>(&format!("*:{tree_selector}"))
2157                .expect("All test selectors are valid and parsable.");
2158        let Ok(SelectResult::Properties(mut property_entry_vec)) =
2159            select_from_hierarchy(&hierarchy, &parsed_selector)
2160        else {
2161            panic!("must be properties");
2162        };
2163
2164        property_entry_vec.sort_by(|p1, p2| p1.name().cmp(p2.name()));
2165        let mut expected = expected.iter().map(Cow::Borrowed).collect::<Vec<_>>();
2166        expected.sort_by(|p1, p2| p1.name().cmp(p2.name()));
2167
2168        assert_eq!(property_entry_vec, expected);
2169    }
2170
2171    #[test_case(vec![], "root/none" ; "node_not_there")]
2172    #[test_case(make_zed(), "root/foo/zed" ; "properties_only")]
2173    #[test_case(vec![make_foo()], "root/foo" ; "nodes_and_properties")]
2174    #[test_case(vec![get_test_hierarchy()], "root" ; "select_root")]
2175    #[fuchsia::test]
2176    fn test_select_from_hierarchy_tree_selectors(
2177        expected: Vec<DiagnosticsHierarchy>,
2178        tree_selector: &str,
2179    ) {
2180        let hierarchy = get_test_hierarchy();
2181        let parsed_selector =
2182            selectors::parse_selector::<VerboseError>(&format!("*:{tree_selector}"))
2183                .expect("All test selectors are valid and parsable.");
2184        let Ok(SelectResult::Nodes(node_vec)) = select_from_hierarchy(&hierarchy, &parsed_selector)
2185        else {
2186            panic!("must be nodes");
2187        };
2188
2189        let expected = expected.iter().map(Cow::Borrowed).collect::<Vec<_>>();
2190
2191        assert_eq!(node_vec, expected);
2192    }
2193
2194    #[fuchsia::test]
2195    fn sort_numerical_value() {
2196        let mut diagnostics_hierarchy = DiagnosticsHierarchy::new(
2197            "root",
2198            vec![
2199                Property::Double("2".to_string(), 2.3),
2200                Property::Int("0".to_string(), -4),
2201                Property::Uint("10".to_string(), 3),
2202                Property::String("1".to_string(), "test".to_string()),
2203            ],
2204            vec![
2205                DiagnosticsHierarchy::new("123", vec![], vec![]),
2206                DiagnosticsHierarchy::new("34", vec![], vec![]),
2207                DiagnosticsHierarchy::new("4", vec![], vec![]),
2208                DiagnosticsHierarchy::new("023", vec![], vec![]),
2209                DiagnosticsHierarchy::new("12", vec![], vec![]),
2210                DiagnosticsHierarchy::new("1", vec![], vec![]),
2211            ],
2212        );
2213        diagnostics_hierarchy.sort();
2214        assert_eq!(
2215            diagnostics_hierarchy,
2216            DiagnosticsHierarchy::new(
2217                "root",
2218                vec![
2219                    Property::Int("0".to_string(), -4),
2220                    Property::String("1".to_string(), "test".to_string()),
2221                    Property::Double("2".to_string(), 2.3),
2222                    Property::Uint("10".to_string(), 3),
2223                ],
2224                vec![
2225                    DiagnosticsHierarchy::new("1", vec![], vec![]),
2226                    DiagnosticsHierarchy::new("4", vec![], vec![]),
2227                    DiagnosticsHierarchy::new("12", vec![], vec![]),
2228                    DiagnosticsHierarchy::new("023", vec![], vec![]),
2229                    DiagnosticsHierarchy::new("34", vec![], vec![]),
2230                    DiagnosticsHierarchy::new("123", vec![], vec![]),
2231                ]
2232            )
2233        );
2234    }
2235
2236    #[fuchsia::test]
2237    fn filter_hierarchy_doesnt_return_partial_matches() {
2238        let hierarchy = DiagnosticsHierarchy::new(
2239            "root",
2240            vec![],
2241            vec![DiagnosticsHierarchy::new("session_started_at", vec![], vec![])],
2242        );
2243        let test_selectors = vec!["*:root/session_started_at/0"];
2244        assert_eq!(parse_selectors_and_filter_hierarchy(hierarchy, test_selectors), None);
2245    }
2246
2247    #[fuchsia::test]
2248    fn test_filter_tree() {
2249        let test_selectors = vec!["root/foo:11", "root:z", r#"root/bar/zed:13\/\:"#];
2250        let parsed_test_selectors = test_selectors
2251            .into_iter()
2252            .map(|s| {
2253                selectors::parse_tree_selector::<VerboseError>(s)
2254                    .expect("All test selectors are valid and parsable.")
2255            })
2256            .collect::<Vec<_>>();
2257
2258        let result =
2259            filter_tree(get_test_hierarchy(), &parsed_test_selectors).map(|mut hierarchy| {
2260                hierarchy.sort();
2261                hierarchy
2262            });
2263        assert_eq!(
2264            result,
2265            Some(DiagnosticsHierarchy::new(
2266                "root",
2267                vec![Property::Int("z".to_string(), -4),],
2268                vec![
2269                    DiagnosticsHierarchy::new(
2270                        "bar",
2271                        vec![],
2272                        vec![DiagnosticsHierarchy::new(
2273                            "zed",
2274                            vec![Property::Int("13/:".to_string(), -4)],
2275                            vec![],
2276                        )],
2277                    ),
2278                    DiagnosticsHierarchy::new(
2279                        "foo",
2280                        vec![Property::Int("11".to_string(), -4),],
2281                        vec![],
2282                    )
2283                ],
2284            ))
2285        );
2286    }
2287
2288    #[fuchsia::test]
2289    fn test_matcher_from_iterator() {
2290        let matcher = HierarchyMatcher::new(
2291            ["*:root/foo:11", "*:root:z", r#"*:root/bar/zed:13\/\:"#].into_iter().map(|s| {
2292                selectors::parse_selector::<VerboseError>(s)
2293                    .expect("All test selectors are valid and parsable.")
2294            }),
2295        )
2296        .expect("create matcher from iterator of selectors");
2297        let result = filter_hierarchy(get_test_hierarchy(), &matcher).map(|mut hierarchy| {
2298            hierarchy.sort();
2299            hierarchy
2300        });
2301        assert_eq!(
2302            result,
2303            Some(DiagnosticsHierarchy::new(
2304                "root",
2305                vec![Property::Int("z".to_string(), -4),],
2306                vec![
2307                    DiagnosticsHierarchy::new(
2308                        "bar",
2309                        vec![],
2310                        vec![DiagnosticsHierarchy::new(
2311                            "zed",
2312                            vec![Property::Int("13/:".to_string(), -4)],
2313                            vec![],
2314                        )],
2315                    ),
2316                    DiagnosticsHierarchy::new(
2317                        "foo",
2318                        vec![Property::Int("11".to_string(), -4),],
2319                        vec![],
2320                    )
2321                ],
2322            ))
2323        );
2324    }
2325
2326    #[test_case(DiagnosticsHierarchy::new_root() ; "empty")]
2327    #[test_case(DiagnosticsHierarchy::new(
2328        "root",
2329        vec![Property::String("x".to_string(), "foo".to_string())],
2330        vec![],
2331    ) ; "properties")]
2332    #[test_case(DiagnosticsHierarchy::new(
2333        "root",
2334        vec![],
2335        vec![DiagnosticsHierarchy::new_root()],
2336    ) ; "children")]
2337    #[fuchsia::test]
2338    fn test_merge_hierarchy_empty(other: DiagnosticsHierarchy) {
2339        let mut hierarchy = DiagnosticsHierarchy::new_root();
2340        hierarchy.merge(other.clone());
2341        assert_eq!(hierarchy, other);
2342    }
2343
2344    #[fuchsia::test]
2345    fn test_merge_hierarchy_properties_and_children() {
2346        let mut a = DiagnosticsHierarchy::new(
2347            "root",
2348            vec![Property::Int("a".to_string(), 1)],
2349            vec![DiagnosticsHierarchy::new("child_a", vec![], vec![])],
2350        );
2351        let b = DiagnosticsHierarchy::new(
2352            "root",
2353            vec![Property::String("b".to_string(), "foo".to_string())],
2354            vec![DiagnosticsHierarchy::new("child_b", vec![], vec![])],
2355        );
2356        a.merge(b);
2357        let expected = DiagnosticsHierarchy::new(
2358            "root",
2359            vec![
2360                Property::Int("a".to_string(), 1),
2361                Property::String("b".to_string(), "foo".to_string()),
2362            ],
2363            vec![
2364                DiagnosticsHierarchy::new("child_a", vec![], vec![]),
2365                DiagnosticsHierarchy::new("child_b", vec![], vec![]),
2366            ],
2367        );
2368        assert_eq!(a, expected);
2369    }
2370
2371    #[fuchsia::test]
2372    fn test_merge_hierarchy_nested_children() {
2373        let mut a = DiagnosticsHierarchy::<String>::new(
2374            "root",
2375            vec![],
2376            vec![DiagnosticsHierarchy::new(
2377                "child",
2378                vec![],
2379                vec![DiagnosticsHierarchy::new("grandchild_a", vec![], vec![])],
2380            )],
2381        );
2382        let b = DiagnosticsHierarchy::new(
2383            "root",
2384            vec![],
2385            vec![DiagnosticsHierarchy::new(
2386                "child",
2387                vec![],
2388                vec![DiagnosticsHierarchy::new("grandchild_b", vec![], vec![])],
2389            )],
2390        );
2391        a.merge(b);
2392        let expected = DiagnosticsHierarchy::new(
2393            "root",
2394            vec![],
2395            vec![DiagnosticsHierarchy::new(
2396                "child",
2397                vec![],
2398                vec![
2399                    DiagnosticsHierarchy::new("grandchild_a", vec![], vec![]),
2400                    DiagnosticsHierarchy::new("grandchild_b", vec![], vec![]),
2401                ],
2402            )],
2403        );
2404        assert_eq!(a, expected);
2405    }
2406
2407    #[fuchsia::test]
2408    fn test_merge_hierarchy_missing() {
2409        let mut a: DiagnosticsHierarchy<String> = DiagnosticsHierarchy::new_root();
2410        a.add_missing(MissingValueReason::LinkNotFound, "a".to_string());
2411        let mut b = DiagnosticsHierarchy::new_root();
2412        b.add_missing(MissingValueReason::LinkParseFailure, "b".to_string());
2413        a.merge(b);
2414        let expected = DiagnosticsHierarchy {
2415            missing: vec![
2416                MissingValue { reason: MissingValueReason::LinkNotFound, name: "a".to_string() },
2417                MissingValue {
2418                    reason: MissingValueReason::LinkParseFailure,
2419                    name: "b".to_string(),
2420                },
2421            ],
2422            ..DiagnosticsHierarchy::new_root()
2423        };
2424        assert_eq!(a, expected);
2425    }
2426
2427    #[fuchsia::test]
2428    fn test_merge_hierarchy_missing_no_duplicates() {
2429        let mut a: DiagnosticsHierarchy<String> = DiagnosticsHierarchy::new_root();
2430        a.add_missing(MissingValueReason::LinkNotFound, "a".to_string());
2431        let mut b = DiagnosticsHierarchy::new_root();
2432        b.add_missing(MissingValueReason::LinkNotFound, "a".to_string());
2433        a.merge(b);
2434        let expected = DiagnosticsHierarchy {
2435            missing: vec![MissingValue {
2436                reason: MissingValueReason::LinkNotFound,
2437                name: "a".to_string(),
2438            }],
2439            ..DiagnosticsHierarchy::new_root()
2440        };
2441        assert_eq!(a, expected);
2442    }
2443
2444    #[fuchsia::test]
2445    fn test_merge_hierarchy_overwrite_property() {
2446        let mut a = DiagnosticsHierarchy::new(
2447            "root",
2448            vec![Property::String("x".to_string(), "foo".to_string())],
2449            vec![],
2450        );
2451        let b = DiagnosticsHierarchy::new(
2452            "root",
2453            vec![Property::String("x".to_string(), "bar".to_string())],
2454            vec![],
2455        );
2456        a.merge(b);
2457        let expected = DiagnosticsHierarchy::new(
2458            "root",
2459            vec![Property::String("x".to_string(), "bar".to_string())],
2460            vec![],
2461        );
2462        assert_eq!(a, expected);
2463    }
2464
2465    #[fuchsia::test]
2466    fn test_merge_hierarchy_recursive_children() {
2467        let mut a = DiagnosticsHierarchy::new(
2468            "root",
2469            vec![],
2470            vec![DiagnosticsHierarchy::new(
2471                "child",
2472                vec![Property::Int("a".to_string(), 1)],
2473                vec![],
2474            )],
2475        );
2476        let b = DiagnosticsHierarchy::new(
2477            "root",
2478            vec![],
2479            vec![DiagnosticsHierarchy::new(
2480                "child",
2481                vec![Property::Int("b".to_string(), 2)],
2482                vec![],
2483            )],
2484        );
2485        a.merge(b);
2486        let expected = DiagnosticsHierarchy::new(
2487            "root",
2488            vec![],
2489            vec![DiagnosticsHierarchy::new(
2490                "child",
2491                vec![Property::Int("a".to_string(), 1), Property::Int("b".to_string(), 2)],
2492                vec![],
2493            )],
2494        );
2495        assert_eq!(a, expected);
2496    }
2497}