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