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