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