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