diagnostics_assertions/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Utilities to assert the structure of a DiagnosticsHierarchy.
6//!
7//! Pretty much the useful [`assert_data_tree`][assert_data_tree] macro
8//! plus some utilities for it.
9
10use anyhow::{bail, format_err, Error};
11use diagnostics_hierarchy::{
12    ArrayContent, ArrayFormat, ExponentialHistogram, ExponentialHistogramParams, LinearHistogram,
13    LinearHistogramParams, Property, EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS,
14    LINEAR_HISTOGRAM_EXTRA_SLOTS,
15};
16use difference::{Changeset, Difference};
17use num_traits::One;
18use regex::Regex;
19use std::collections::BTreeSet;
20use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
21use std::ops::{Add, AddAssign, MulAssign};
22
23pub use diagnostics_hierarchy::{hierarchy, DiagnosticsHierarchy, DiagnosticsHierarchyGetter};
24pub use std::sync::Arc;
25
26pub trait JsonGetter<K: Clone + AsRef<str>>: DiagnosticsHierarchyGetter<K> {
27    fn get_pretty_json(&self) -> impl std::future::Future<Output = String> {
28        async {
29            let mut tree = self.get_diagnostics_hierarchy().await;
30            tree.to_mut().sort();
31            serde_json::to_string_pretty(&tree).expect("pretty json string")
32        }
33    }
34
35    fn get_json(&self) -> impl std::future::Future<Output = String> {
36        async {
37            let mut tree = self.get_diagnostics_hierarchy().await;
38            tree.to_mut().sort();
39            serde_json::to_string(&tree).expect("json string")
40        }
41    }
42}
43
44impl<K: Clone + AsRef<str>, T: DiagnosticsHierarchyGetter<K>> JsonGetter<K> for T {}
45
46/// Macro to simplify creating `TreeAssertion`s. Commonly used indirectly through the second
47/// parameter of `assert_data_tree!`. See `assert_data_tree!` for more usage examples.
48///
49/// Each leaf value must be a type that implements either `PropertyAssertion` or `TreeAssertion`.
50///
51/// Example:
52/// ```
53/// // Manual creation of `TreeAssertion`.
54/// let mut root = TreeAssertion::new("root", true);
55/// root.add_property_assertion("a-string-property", Arc::new("expected-string-value"));
56/// let mut child = TreeAssertion::new("child", true);
57/// child.add_property_assertion("any-property", Arc::new(AnyProperty));
58/// root.add_child_assertion(child);
59/// root.add_child_assertion(opaque_child);
60///
61/// // Creation with `tree_assertion!`.
62/// let root = tree_assertion!(
63///     root: {
64///         "a-string-property": "expected-string-value",
65///         child: {
66///             "any-property": AnyProperty,
67///         },
68///         opaque_child, // required trailing comma for `TreeAssertion` expressions
69///     }
70/// );
71/// ```
72///
73/// Note that `TreeAssertion`s given directly to the macro must always be followed by `,`.
74#[macro_export]
75macro_rules! tree_assertion {
76    (@build $tree_assertion:expr,) => {};
77
78    // Exact match of tree
79    (@build $tree_assertion:expr, var $key:ident: { $($sub:tt)* }) => {{
80        #[allow(unused_mut)]
81        let mut child_tree_assertion = TreeAssertion::new($key, true);
82        $crate::tree_assertion!(@build child_tree_assertion, $($sub)*);
83        $tree_assertion.add_child_assertion(child_tree_assertion);
84    }};
85    (@build $tree_assertion:expr, var $key:ident: { $($sub:tt)* }, $($rest:tt)*) => {{
86        $crate::tree_assertion!(@build $tree_assertion, var $key: { $($sub)* });
87        $crate::tree_assertion!(@build $tree_assertion, $($rest)*);
88    }};
89
90    // Partial match of tree
91    (@build $tree_assertion:expr, var $key:ident: contains { $($sub:tt)* }) => {{
92        #[allow(unused_mut)]
93        let mut child_tree_assertion = TreeAssertion::new($key, false);
94        $crate::tree_assertion!(@build child_tree_assertion, $($sub)*);
95        $tree_assertion.add_child_assertion(child_tree_assertion);
96    }};
97    (@build $tree_assertion:expr, var $key:ident: contains { $($sub:tt)* }, $($rest:tt)*) => {{
98        $crate::tree_assertion!(@build $tree_assertion, var $key: contains { $($sub)* });
99        $crate::tree_assertion!(@build $tree_assertion, $($rest)*);
100    }};
101
102    // Matching properties of a tree
103    (@build $tree_assertion:expr, var $key:ident: $assertion:expr) => {{
104        $tree_assertion.add_property_assertion($key, $crate::Arc::new($assertion))
105    }};
106    (@build $tree_assertion:expr, var $key:ident: $assertion:expr, $($rest:tt)*) => {{
107        $crate::tree_assertion!(@build $tree_assertion, var $key: $assertion);
108        $crate::tree_assertion!(@build $tree_assertion, $($rest)*);
109    }};
110
111    // Key identifier format
112    // Unquoted, e.g.: `foo: ... `
113    (@build $tree_assertion:expr, $key:ident: $($rest:tt)+) => {{
114        let key = stringify!($key);
115        $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+);
116    }};
117    // Key identifier as a constant.
118    // e.g.: `ref some::crate::CONSTANT: ...`
119    //
120    // Note, this works ONLY if it's a full path. For unquoted literals,
121    // such as `foo`, see the production above.
122    (@build $tree_assertion:expr, ref $key:path: $($rest:tt)+) => {{
123        let key: &str = $key.as_ref(); // Accept only AsRef<str>
124        $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+);
125    }};
126    // Allows string literal for key
127    (@build $tree_assertion:expr, $key:tt: $($rest:tt)+) => {{
128        let key: &'static str = $key;
129        $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+);
130    }};
131    // Allows an expression that resolves into a String for key
132    (@build $tree_assertion:expr, $key:expr => $($rest:tt)+) => {{
133        let key_string : String = $key;
134        let key = &key_string;
135        $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+);
136    }};
137    // Allows an expression that resolves into a TreeAssertion
138    (@build $tree_assertion:expr, $child_assertion:expr, $($rest:tt)*) => {{
139        $tree_assertion.add_child_assertion($child_assertion);
140        $crate::tree_assertion!(@build $tree_assertion, $($rest)*);
141    }};
142
143    // Entry points
144    (var $key:ident: { $($sub:tt)* }) => {{
145        use $crate::TreeAssertion;
146        #[allow(unused_mut)]
147        let mut tree_assertion = TreeAssertion::new($key, true);
148        $crate::tree_assertion!(@build tree_assertion, $($sub)*);
149        tree_assertion
150    }};
151    (var $key:ident: contains { $($sub:tt)* }) => {{
152        use $crate::TreeAssertion;
153        #[allow(unused_mut)]
154        let mut tree_assertion = TreeAssertion::new($key, false);
155        $crate::tree_assertion!(@build tree_assertion, $($sub)*);
156        tree_assertion
157    }};
158    ($key:ident: $($rest:tt)+) => {{
159        let key = stringify!($key);
160        $crate::tree_assertion!(var key: $($rest)+)
161    }};
162    ($key:tt: $($rest:tt)+) => {{
163        let key: &'static str = $key;
164        $crate::tree_assertion!(var key: $($rest)+)
165    }};
166}
167
168/// Macro to simplify tree matching in tests. The first argument is the actual tree passed as a
169/// `DiagnosticsHierarchyGetter` (e.g. a `DiagnosticsHierarchy` or an `Inspector`). The second argument is given
170/// to `tree_assertion!` which creates a `TreeAssertion` to validate the tree.
171///
172/// Each leaf value must be a type that implements either `PropertyAssertion` or `TreeAssertion`.
173///
174/// Example:
175/// ```
176/// // Actual tree
177/// let diagnostics_hierarchy = DiagnosticsHierarchy {
178///     name: "key".to_string(),
179///     properties: vec![
180///         Property::String("sub".to_string(), "sub_value".to_string()),
181///         Property::String("sub2".to_string(), "sub2_value".to_string()),
182///     ],
183///     children: vec![
184///        DiagnosticsHierarchy {
185///            name: "child1".to_string(),
186///            properties: vec![
187///                Property::Int("child1_sub".to_string(), 10i64),
188///            ],
189///            children: vec![],
190///        },
191///        DiagnosticsHierarchy {
192///            name: "child2".to_string(),
193///            properties: vec![
194///                Property::Uint("child2_sub".to_string(), 20u64),
195///            ],
196///            children: vec![],
197///        },
198///    ],
199/// };
200///
201/// assert_data_tree!(
202///     diagnostics_hierarchy,
203///     key: {
204///         sub: AnyProperty,   // only verify that `sub` is a property of `key`
205///         sub2: "sub2_value",
206///         child1: {
207///             child1_sub: 10,
208///         },
209///         child2: {
210///             child2_sub: 20,
211///         },
212///     }
213/// );
214/// ```
215///
216/// In order to do a partial match on a tree, use the `contains` keyword:
217/// ```
218/// assert_data_tree!(diagnostics_hierarchy, key: contains {
219///     sub: "sub_value",
220///     child1: contains {},
221/// });
222/// ```
223///
224/// In order to do a match on a tree where the keys need to be computed (they are some
225/// expression), you'll need to use `=>` instead of `:`:
226///
227/// ```
228/// assert_data_tree!(diagnostics_hierarchy, key: {
229///     key_fn() => "value",
230/// })
231/// ```
232/// Note that `key_fn` has to return a `String`.
233///
234/// The first argument can be an `Inspector`, in which case the whole tree is read from the
235/// `Inspector` and matched against:
236/// ```
237/// let inspector = Inspector::default();
238/// assert_data_tree!(inspector, root: {});
239/// ```
240///
241/// `TreeAssertion`s made elsewhere can be included bodily in the macro, but must always be followed
242/// by a trailing comma:
243/// assert_data_tree!(
244///     diagnostics_hierarchy,
245///     key: {
246///         make_child_tree_assertion(), // required trailing comma
247///     }
248/// );
249///
250/// A tree may contain multiple properties or children with the same name. This macro does *not*
251/// support matching against them, and will throw an error if it detects duplicates. This is
252/// to provide warning for users who accidentally log the same name multiple times, as the
253/// behavior for reading properties or children with duplicate names is not well defined.
254#[macro_export]
255macro_rules! assert_data_tree {
256    (@executor $exec:expr, $diagnostics_hierarchy:expr, $($rest:tt)+) => {{
257        use $crate::DiagnosticsHierarchyGetter as _;
258        let tree_assertion = $crate::tree_assertion!($($rest)+);
259
260        let mut fut = core::pin::pin!($diagnostics_hierarchy.get_diagnostics_hierarchy());
261        let hierarchy = loop {
262            if let core::task::Poll::Ready(h) = $exec.run_until_stalled(&mut fut) {
263                break h;
264            }
265
266            $exec.wake_next_timer();
267        };
268
269        if let Err(e) = tree_assertion.run(hierarchy.as_ref()) {
270            panic!("tree assertion fails: {}", e);
271        }
272    }};
273    (@retry $times:expr, $diagnostics_hierarchy:expr, $($rest:tt)+) => {{
274        use $crate::DiagnosticsHierarchyGetter as _;
275        use fuchsia_async as fasync;
276        use zx::MonotonicDuration;
277
278        let tree_assertion = $crate::tree_assertion!($($rest)+);
279        let max_retries = $times;
280
281        for i in 0..max_retries {
282            let result = tree_assertion.run(
283                $diagnostics_hierarchy.get_diagnostics_hierarchy().await.as_ref()
284            );
285
286            if result.is_ok() {
287                break;
288            } else if i == (max_retries - 1) && result.is_err() {
289                panic!("tree assertion fails: {}", result.unwrap_err());
290            }
291
292            fasync::Timer::new(MonotonicDuration::from_seconds(1)).await;
293        }
294
295    }};
296    ($diagnostics_hierarchy:expr, $($rest:tt)+) => {{
297        use $crate::DiagnosticsHierarchyGetter as _;
298        let tree_assertion = $crate::tree_assertion!($($rest)+);
299
300        if let Err(e) = tree_assertion.run(
301            $diagnostics_hierarchy.get_diagnostics_hierarchy().await.as_ref()
302        ) {
303            panic!("tree assertion fails: {}", e);
304        }
305    }};
306}
307
308/// Macro to check a hierarchy with a nice JSON diff.
309/// The syntax of the `expected` value is the same as that of `hierarchy!`, and
310/// essentially the same as `assert_data_tree!`, except that partial tree matching
311/// is not supported (i.e. the keyword `contains`).
312#[macro_export]
313macro_rules! assert_json_diff {
314    ($diagnostics_hierarchy:expr, $($rest:tt)+) => {{
315        use $crate::JsonGetter as _;
316        let expected = $diagnostics_hierarchy.get_pretty_json().await;
317        let actual_hierarchy: $crate::DiagnosticsHierarchy = $crate::hierarchy!{$($rest)+};
318        let actual = actual_hierarchy.get_pretty_json().await;
319
320        if actual != expected {
321            panic!("{}", $crate::Diff::from_text(&expected, &actual));
322        }
323    }}
324}
325
326/// A difference between expected and actual output.
327pub struct Diff(Changeset);
328
329impl Diff {
330    fn new(expected: &dyn Debug, actual: &dyn Debug) -> Self {
331        let expected = format!("{expected:#?}");
332        let actual = format!("{actual:#?}");
333        Self::from_text(&expected, &actual)
334    }
335
336    #[doc(hidden)]
337    pub fn from_text(expected: &str, actual: &str) -> Self {
338        Diff(Changeset::new(expected, actual, "\n"))
339    }
340}
341
342impl Display for Diff {
343    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
344        writeln!(f, "(-) Expected vs. (+) Actual:")?;
345        for diff in &self.0.diffs {
346            let (prefix, contents) = match diff {
347                Difference::Same(same) => ("  ", same),
348                Difference::Add(added) => ("+ ", added),
349                Difference::Rem(removed) => ("- ", removed),
350            };
351            for line in contents.split("\n") {
352                writeln!(f, "{prefix}{line}")?;
353            }
354        }
355        Ok(())
356    }
357}
358
359macro_rules! eq_or_bail {
360    ($expected:expr, $actual:expr) => {{
361        if $expected != $actual {
362            let changes = Diff::new(&$expected, &$actual);
363            return Err(format_err!("\n {}", changes));
364        }
365    }};
366    ($expected:expr, $actual:expr, $($args:tt)+) => {{
367        if $expected != $actual {
368            let changes = Diff::new(&$expected, &$actual);
369            return Err(format_err!("{}:\n {}", format!($($args)+), changes));
370        }
371    }}
372}
373
374/// Struct for matching against a Data tree (DiagnosticsHierarchy).
375pub struct TreeAssertion<K = String> {
376    /// Expected name of the node being compared against
377    name: String,
378    /// Friendly name that includes path from ancestors. Mainly used to indicate which node fails
379    /// in error message
380    path: String,
381    /// Expected property names and assertions to match the actual properties against
382    properties: Vec<(String, Arc<dyn PropertyAssertion<K>>)>,
383    /// Assertions to match against child trees
384    children: Vec<TreeAssertion<K>>,
385    /// Whether all properties and children of the tree should be checked
386    exact_match: bool,
387}
388
389impl<K> TreeAssertion<K>
390where
391    K: AsRef<str>,
392{
393    /// Create a new `TreeAssertion`. The |name| argument is the expected name of the tree to be
394    /// compared against. Set |exact_match| to true to specify that all properties and children of
395    /// the tree should be checked. To perform partial matching of the tree, set it to false.
396    pub fn new(name: &str, exact_match: bool) -> Self {
397        Self {
398            name: name.to_string(),
399            path: name.to_string(),
400            properties: vec![],
401            children: vec![],
402            exact_match,
403        }
404    }
405
406    /// Adds a property assertion to this tree assertion.
407    pub fn add_property_assertion(&mut self, key: &str, assertion: Arc<dyn PropertyAssertion<K>>) {
408        self.properties.push((key.to_owned(), assertion));
409    }
410
411    /// Adds a tree assertion as a child of this tree assertion.
412    pub fn add_child_assertion(&mut self, mut assertion: TreeAssertion<K>) {
413        assertion.path = format!("{}.{}", self.path, assertion.name);
414        self.children.push(assertion);
415    }
416
417    /// Check whether |actual| tree satisfies criteria defined by `TreeAssertion`. Return `Ok` if
418    /// assertion passes and `Error` if assertion fails.
419    pub fn run(&self, actual: &DiagnosticsHierarchy<K>) -> Result<(), Error> {
420        eq_or_bail!(self.name, actual.name, "node `{}` - expected node name != actual", self.path);
421
422        if self.exact_match {
423            let properties_names = self.properties.iter().map(|p| p.0.to_string());
424            let children_names = self.children.iter().map(|c| c.name.to_string());
425            let keys: BTreeSet<String> = properties_names.chain(children_names).collect();
426
427            let actual_props = actual.properties.iter().map(|p| p.name().to_string());
428            let actual_children = actual.children.iter().map(|c| c.name.to_string());
429            let actual_keys: BTreeSet<String> = actual_props.chain(actual_children).collect();
430            eq_or_bail!(keys, actual_keys, "node `{}` - expected keys != actual", self.path);
431        }
432
433        for (name, assertion) in self.properties.iter() {
434            let mut matched = actual.properties.iter().filter(|p| p.key().as_ref() == name);
435            let first_match = matched.next();
436            if let Some(_second_match) = matched.next() {
437                bail!("node `{}` - multiple properties found with name `{}`", self.path, name);
438            }
439            match first_match {
440                Some(property) => {
441                    if let Err(e) = assertion.run(property) {
442                        bail!(
443                            "node `{}` - assertion fails for property `{}`. Reason: {}",
444                            self.path,
445                            name,
446                            e
447                        );
448                    }
449                }
450                None => bail!("node `{}` - no property named `{}`", self.path, name),
451            }
452        }
453        for assertion in self.children.iter() {
454            let mut matched = actual.children.iter().filter(|c| c.name == assertion.name);
455            let first_match = matched.next();
456            if let Some(_second_match) = matched.next() {
457                bail!(
458                    "node `{}` - multiple children found with name `{}`",
459                    self.path,
460                    assertion.name
461                );
462            }
463            match first_match {
464                Some(child) => assertion.run(child)?,
465                None => bail!("node `{}` - no child named `{}`", self.path, assertion.name),
466            }
467        }
468        Ok(())
469    }
470}
471
472/// Trait implemented by types that can act as properies for assertion.
473pub trait PropertyAssertion<K = String>: Send + Sync {
474    /// Check whether |actual| property satisfies criteria. Return `Ok` if assertion passes and
475    /// `Error` if assertion fails.
476    fn run(&self, actual: &Property<K>) -> Result<(), Error>;
477}
478
479macro_rules! impl_property_assertion {
480    ($prop_variant:ident, $($ty:ty),+) => {
481        $(
482            impl<K> PropertyAssertion<K> for $ty {
483                fn run(&self, actual: &Property<K>) -> Result<(), Error> {
484                    if let Property::$prop_variant(_key, value, ..) = actual {
485                        eq_or_bail!(self, value);
486                    } else {
487                        return Err(format_err!("expected {}, found {}",
488                            stringify!($prop_variant), actual.discriminant_name()));
489                    }
490                    Ok(())
491                }
492            }
493        )+
494    };
495
496    ($prop_variant:ident, $cast:ty; $($ty:ty),+) => {
497        $(
498            impl<K> PropertyAssertion<K> for $ty {
499                fn run(&self, actual: &Property<K>) -> Result<(), Error> {
500                    if let Property::$prop_variant(_key, value, ..) = actual {
501                        eq_or_bail!(*self as $cast, *value);
502                    } else {
503                        return Err(format_err!("expected {}, found {}",
504                            stringify!($prop_variant), actual.discriminant_name()));
505                    }
506                    Ok(())
507                }
508            }
509        )+
510    }
511}
512
513macro_rules! impl_array_properties_assertion {
514    ($prop_variant:ident, $($ty:ty),+) => {
515        $(
516            /// Asserts plain numeric arrays
517            impl<K> PropertyAssertion<K> for Vec<$ty> {
518                fn run(&self, actual: &Property<K>) -> Result<(), Error> {
519                    if let Property::$prop_variant(_key, value, ..) = actual {
520                        match &value {
521                            ArrayContent::Values(values) => eq_or_bail!(self, values),
522                            _ => {
523                                return Err(format_err!(
524                                    "expected a plain {} array, got a {}",
525                                    stringify!($prop_variant),
526                                    actual.discriminant_name()
527                                ));
528                            }
529                        }
530                    } else {
531                        return Err(format_err!("expected {}, found {}",
532                            stringify!($prop_variant), actual.discriminant_name()));
533                    }
534                    Ok(())
535                }
536            }
537
538            impl<K> PropertyAssertion<K> for LinearHistogram<$ty> {
539                fn run(&self, actual: &Property<K>) -> Result<(), Error> {
540                    if let Property::$prop_variant(_key, value, ..) = actual {
541                        match &value {
542                            ArrayContent::LinearHistogram(histogram) => {
543                                eq_or_bail!(self, histogram)
544                            }
545                            _ => {
546                                return Err(format_err!(
547                                    "expected a linear {} histogram, got a {}",
548                                    stringify!($prop_variant),
549                                    actual.discriminant_name()
550                                ));
551                            }
552                        }
553                    } else {
554                        return Err(format_err!("expected {}, found {}",
555                            stringify!($prop_variant), actual.discriminant_name()));
556                    }
557                    Ok(())
558                }
559            }
560
561            impl<K> PropertyAssertion<K> for ExponentialHistogram<$ty> {
562                fn run(&self, actual: &Property<K>) -> Result<(), Error> {
563                    if let Property::$prop_variant(_key, value, ..) = actual {
564                        match &value {
565                            ArrayContent::ExponentialHistogram(histogram) => {
566                                eq_or_bail!(self, histogram)
567                            }
568                            _ => {
569                                return Err(format_err!(
570                                    "expected an exponential {} histogram, got a {}",
571                                    stringify!($prop_variant),
572                                    actual.discriminant_name()
573                                ));
574                            }
575                        }
576                    } else {
577                        return Err(format_err!("expected {}, found {}",
578                            stringify!($prop_variant), actual.discriminant_name()));
579                    }
580                    Ok(())
581                }
582            }
583
584            /// Asserts a histogram.
585            impl<K> PropertyAssertion<K> for HistogramAssertion<$ty> {
586                fn run(&self, actual: &Property<K>) -> Result<(), Error> {
587                    if let Property::$prop_variant(_key, value, ..) = actual {
588                        let expected_content =
589                            ArrayContent::new(self.values.clone(), self.format.clone()).map_err(
590                                |e| {
591                                    format_err!(
592                                        "failed to load array content for expected assertion {}: {:?}",
593                                        stringify!($prop_variant),
594                                        e
595                                    )
596                                },
597                            )?;
598                        eq_or_bail!(&expected_content, value);
599                    } else {
600                        return Err(format_err!(
601                            "expected {}, found {}",
602                            stringify!($prop_variant),
603                            actual.discriminant_name(),
604                        ));
605                    }
606                    Ok(())
607                }
608            }
609        )+
610    }
611}
612
613impl_property_assertion!(String, &str, String);
614impl_property_assertion!(Bytes, Vec<u8>);
615impl_property_assertion!(Uint, u64; u64, u32, u16, u8);
616impl_property_assertion!(Int, i64; i64, i32, i16, i8);
617impl_property_assertion!(Double, f64; f64, f32);
618impl_property_assertion!(Bool, bool);
619impl_array_properties_assertion!(DoubleArray, f64);
620impl_array_properties_assertion!(IntArray, i64);
621impl_array_properties_assertion!(UintArray, u64);
622
623/// A PropertyAssertion that always passes
624pub struct AnyProperty;
625
626impl<K> PropertyAssertion<K> for AnyProperty {
627    fn run(&self, _actual: &Property<K>) -> Result<(), Error> {
628        Ok(())
629    }
630}
631
632/// A PropertyAssertion that passes for any String.
633pub struct AnyStringProperty;
634
635impl<K> PropertyAssertion<K> for AnyStringProperty {
636    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
637        match actual {
638            Property::String(..) => Ok(()),
639            _ => Err(format_err!("expected String, found {}", actual.discriminant_name())),
640        }
641    }
642}
643
644/// A PropertyAssertion that passes for any String containing
645/// a matching for the given regular expression.
646impl<K> PropertyAssertion<K> for Regex {
647    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
648        (&self).run(actual)
649    }
650}
651
652impl<K> PropertyAssertion<K> for &Regex {
653    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
654        if let Property::String(_, ref v) = actual {
655            if self.is_match(v) {
656                return Ok(());
657            } else {
658                return Err(format_err!(
659                    "expected String matching \"{}\", found \"{}\"",
660                    self.as_str(),
661                    v
662                ));
663            }
664        }
665        Err(format_err!(
666            "expected String matching \"{}\", found {}",
667            self.as_str(),
668            actual.discriminant_name()
669        ))
670    }
671}
672
673/// A PropertyAssertion that passes for any Bytes.
674pub struct AnyBytesProperty;
675
676impl<K> PropertyAssertion<K> for AnyBytesProperty {
677    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
678        match actual {
679            Property::Bytes(..) => Ok(()),
680            _ => Err(format_err!("expected bytes, found {}", actual.discriminant_name())),
681        }
682    }
683}
684
685/// A PropertyAssertion that passes for any Uint.
686pub struct AnyUintProperty;
687
688impl<K> PropertyAssertion<K> for AnyUintProperty {
689    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
690        match actual {
691            Property::Uint(..) => Ok(()),
692            _ => Err(format_err!("expected Uint, found {}", actual.discriminant_name())),
693        }
694    }
695}
696
697/// A PropertyAssertion that passes for non-zero, unsigned integers.
698///
699/// TODO(https://fxbug.dev/42140843): generalize this to use the >= operator.
700pub struct NonZeroUintProperty;
701
702impl<K> PropertyAssertion<K> for NonZeroUintProperty {
703    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
704        match actual {
705            Property::Uint(_, v) if *v != 0 => Ok(()),
706            Property::Uint(_, v) if *v == 0 => {
707                Err(format_err!("expected non-zero integer, found 0"))
708            }
709            _ => {
710                Err(format_err!("expected non-zero integer, found {}", actual.discriminant_name()))
711            }
712        }
713    }
714}
715
716/// A PropertyAssertion that passes for non-zero, signed integers.
717///
718/// TODO(https://fxbug.dev/42140843): generalize this to use the >= operator.
719pub struct NonZeroIntProperty;
720
721impl<K> PropertyAssertion<K> for NonZeroIntProperty {
722    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
723        match actual {
724            Property::Int(_, v) if *v != 0 => Ok(()),
725            Property::Int(_, v) if *v == 0 => {
726                Err(format_err!("expected non-zero signed integer, found 0"))
727            }
728            _ => Err(format_err!(
729                "expected non-zero signed integer, found {}",
730                actual.discriminant_name()
731            )),
732        }
733    }
734}
735
736/// A PropertyAssertion that passes for any Int.
737pub struct AnyIntProperty;
738
739impl<K> PropertyAssertion<K> for AnyIntProperty {
740    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
741        match actual {
742            Property::Int(..) => Ok(()),
743            _ => Err(format_err!("expected Int, found {}", actual.discriminant_name())),
744        }
745    }
746}
747
748/// A PropertyAssertion that passes for any Double.
749pub struct AnyDoubleProperty;
750
751impl<K> PropertyAssertion<K> for AnyDoubleProperty {
752    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
753        match actual {
754            Property::Double(..) => Ok(()),
755            _ => Err(format_err!("expected Double, found {}", actual.discriminant_name())),
756        }
757    }
758}
759
760/// A PropertyAssertion that passes for any Int, Uint, or Double.
761pub struct AnyNumericProperty;
762
763impl<K> PropertyAssertion<K> for AnyNumericProperty {
764    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
765        match actual {
766            Property::Int(..) | Property::Uint(..) | Property::Double(..) => Ok(()),
767            _ => Err(format_err!(
768                "expected an Int, Uint or Double. found {}",
769                actual.discriminant_name()
770            )),
771        }
772    }
773}
774
775/// A PropertyAssertion that passes for any Boolean.
776pub struct AnyBoolProperty;
777
778impl<K> PropertyAssertion<K> for AnyBoolProperty {
779    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
780        match actual {
781            Property::Bool(..) => Ok(()),
782            _ => Err(format_err!("expected Bool, found {}", actual.discriminant_name())),
783        }
784    }
785}
786
787impl<K> PropertyAssertion<K> for Vec<String> {
788    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
789        let this = self.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
790        this.run(actual)
791    }
792}
793
794impl<K> PropertyAssertion<K> for Vec<&str> {
795    fn run(&self, actual: &Property<K>) -> Result<(), Error> {
796        match actual {
797            Property::StringList(_key, value) => {
798                eq_or_bail!(self, value);
799                Ok(())
800            }
801            _ => Err(format_err!("expected StringList, found {}", actual.discriminant_name())),
802        }
803    }
804}
805
806/// An assertion for a histogram property.
807#[derive(Clone)]
808pub struct HistogramAssertion<T> {
809    format: ArrayFormat,
810    values: Vec<T>,
811}
812
813impl<T: MulAssign + AddAssign + PartialOrd + Add<Output = T> + Copy + Default + One>
814    HistogramAssertion<T>
815{
816    /// Creates a new histogram assertion for a linear histogram with the given parameters.
817    pub fn linear(params: LinearHistogramParams<T>) -> Self {
818        let mut values = vec![T::default(); params.buckets + LINEAR_HISTOGRAM_EXTRA_SLOTS];
819        values[0] = params.floor;
820        values[1] = params.step_size;
821        Self { format: ArrayFormat::LinearHistogram, values }
822    }
823
824    /// Creates a new histogram assertion for an exponential histogram with the given parameters.
825    pub fn exponential(params: ExponentialHistogramParams<T>) -> Self {
826        let mut values = vec![T::default(); params.buckets + EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS];
827        values[0] = params.floor;
828        values[1] = params.initial_step;
829        values[2] = params.step_multiplier;
830        Self { format: ArrayFormat::ExponentialHistogram, values }
831    }
832
833    /// Inserts the list of values to the histogram for asserting them.
834    pub fn insert_values(&mut self, values: impl IntoIterator<Item = T>) {
835        match self.format {
836            ArrayFormat::ExponentialHistogram => {
837                for value in values {
838                    self.insert_exp(value);
839                }
840            }
841            ArrayFormat::LinearHistogram => {
842                for value in values {
843                    self.insert_linear(value);
844                }
845            }
846            ArrayFormat::Default => {
847                unreachable!("can't construct a histogram assertion for arrays");
848            }
849        }
850    }
851
852    fn insert_linear(&mut self, value: T) {
853        let value_index = {
854            let mut current_floor = self.values[0];
855            let step_size = self.values[1];
856            // Start in the underflow index.
857            let mut index = LINEAR_HISTOGRAM_EXTRA_SLOTS - 2;
858            while value >= current_floor && index < self.values.len() - 1 {
859                current_floor += step_size;
860                index += 1;
861            }
862            index
863        };
864        self.values[value_index] += T::one();
865    }
866
867    fn insert_exp(&mut self, value: T) {
868        let value_index = {
869            let floor = self.values[0];
870            let mut current_floor = self.values[0];
871            let mut offset = self.values[1];
872            let step_multiplier = self.values[2];
873            // Start in the underflow index.
874            let mut index = EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS - 2;
875            while value >= current_floor && index < self.values.len() - 1 {
876                current_floor = floor + offset;
877                offset *= step_multiplier;
878                index += 1;
879            }
880            index
881        };
882        self.values[value_index] += T::one();
883    }
884}
885
886#[cfg(test)]
887mod tests {
888    use super::*;
889    use crate::Difference::{Add, Rem, Same};
890    use diagnostics_hierarchy::testing::CondensableOnDemand;
891    use std::sync::LazyLock;
892
893    static TEST_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("a").unwrap());
894
895    #[fuchsia::test]
896    fn keep_tree_assertion_send_sync() {
897        fn assert_send_sync<T: Send + Sync>() {}
898        assert_send_sync::<TreeAssertion>();
899    }
900
901    #[fuchsia::test]
902    async fn test_assert_json_diff() {
903        assert_json_diff!(
904            simple_tree(),
905             key: {
906                sub: "sub_value",
907                sub2: "sub2_value",
908            }
909        );
910
911        let diagnostics_hierarchy = complex_tree();
912        assert_json_diff!(diagnostics_hierarchy, key: {
913            sub: "sub_value",
914            sub2: "sub2_value",
915            child1: {
916                child1_sub: 10,
917            },
918            child2: {
919                child2_sub: 20,
920            },
921        });
922    }
923
924    #[fuchsia::test]
925    #[should_panic]
926    async fn test_panicking_assert_json_diff() {
927        assert_json_diff!(
928            simple_tree(),
929             key: {
930                sub: "sub_value",
931                sb2: "sub2_value",
932            }
933        );
934
935        let diagnostics_hierarchy = complex_tree();
936        assert_json_diff!(diagnostics_hierarchy, key: {
937            sb: "sub_value",
938            sub2: "sub2_value",
939            child: {
940                child1_sub: 10,
941            },
942            child3: {
943                child2_sub: 20,
944            },
945        });
946    }
947
948    #[fuchsia::test]
949    async fn test_exact_match_simple() {
950        let diagnostics_hierarchy = simple_tree();
951        assert_data_tree!(diagnostics_hierarchy, key: {
952            sub: "sub_value",
953            sub2: "sub2_value",
954        });
955    }
956
957    #[fuchsia::test]
958    async fn test_exact_match_complex() {
959        let diagnostics_hierarchy = complex_tree();
960        assert_data_tree!(diagnostics_hierarchy, key: {
961            sub: "sub_value",
962            sub2: "sub2_value",
963            child1: {
964                child1_sub: 10,
965            },
966            child2: {
967                child2_sub: 20u64,
968            },
969        });
970    }
971
972    #[fuchsia::test]
973    #[should_panic]
974    async fn test_exact_match_mismatched_property_name() {
975        let diagnostics_hierarchy = simple_tree();
976        assert_data_tree!(diagnostics_hierarchy, key: {
977            sub: "sub_value",
978            sub3: "sub2_value",
979        });
980    }
981
982    #[fuchsia::test]
983    #[should_panic]
984    async fn test_exact_match_mismatched_child_name() {
985        let diagnostics_hierarchy = complex_tree();
986        assert_data_tree!(diagnostics_hierarchy, key: {
987            sub: "sub_value",
988            sub2: "sub2_value",
989            child1: {
990                child1_sub: 10,
991            },
992            child3: {
993                child2_sub: 20,
994            },
995        });
996    }
997
998    #[fuchsia::test]
999    #[should_panic]
1000    async fn test_exact_match_mismatched_property_name_in_child() {
1001        let diagnostics_hierarchy = complex_tree();
1002        assert_data_tree!(diagnostics_hierarchy, key: {
1003            sub: "sub_value",
1004            sub2: "sub2_value",
1005            child1: {
1006                child2_sub: 10,
1007            },
1008            child2: {
1009                child2_sub: 20,
1010            },
1011        });
1012    }
1013
1014    #[fuchsia::test]
1015    #[should_panic]
1016    async fn test_exact_match_mismatched_property_value() {
1017        let diagnostics_hierarchy = simple_tree();
1018        assert_data_tree!(diagnostics_hierarchy, key: {
1019            sub: "sub2_value",
1020            sub2: "sub2_value",
1021        });
1022    }
1023
1024    #[fuchsia::test]
1025    #[should_panic]
1026    async fn test_exact_match_missing_property() {
1027        let diagnostics_hierarchy = simple_tree();
1028        assert_data_tree!(diagnostics_hierarchy, key: {
1029            sub: "sub_value",
1030        });
1031    }
1032
1033    #[fuchsia::test]
1034    #[should_panic]
1035    async fn test_exact_match_missing_child() {
1036        let diagnostics_hierarchy = complex_tree();
1037        assert_data_tree!(diagnostics_hierarchy, key: {
1038            sub: "sub_value",
1039            sub2: "sub2_value",
1040            child1: {
1041                child1_sub: 10,
1042            },
1043        });
1044    }
1045
1046    #[fuchsia::test]
1047    async fn test_partial_match_success() {
1048        let diagnostics_hierarchy = complex_tree();
1049
1050        // only verify the top tree name
1051        assert_data_tree!(diagnostics_hierarchy, key: contains {});
1052
1053        // verify parts of the tree
1054        assert_data_tree!(diagnostics_hierarchy, key: contains {
1055            sub: "sub_value",
1056            child1: contains {},
1057        });
1058    }
1059
1060    #[fuchsia::test]
1061    #[should_panic]
1062    async fn test_partial_match_nonexistent_property() {
1063        let diagnostics_hierarchy = simple_tree();
1064        assert_data_tree!(diagnostics_hierarchy, key: contains {
1065            sub3: AnyProperty,
1066        });
1067    }
1068
1069    #[fuchsia::test]
1070    async fn test_ignore_property_value() {
1071        let diagnostics_hierarchy = simple_tree();
1072        assert_data_tree!(diagnostics_hierarchy, key: {
1073            sub: AnyProperty,
1074            sub2: "sub2_value",
1075        });
1076    }
1077
1078    #[fuchsia::test]
1079    #[should_panic]
1080    async fn test_ignore_property_value_property_name_is_still_checked() {
1081        let diagnostics_hierarchy = simple_tree();
1082        assert_data_tree!(diagnostics_hierarchy, key: {
1083            sub1: AnyProperty,
1084            sub2: "sub2_value",
1085        })
1086    }
1087
1088    #[fuchsia::test]
1089    async fn test_expr_key_syntax() {
1090        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1091            "key",
1092            vec![Property::String("@time".to_string(), "1.000".to_string())],
1093            vec![],
1094        );
1095        assert_data_tree!(diagnostics_hierarchy, key: {
1096            "@time": "1.000"
1097        });
1098    }
1099
1100    #[fuchsia::test]
1101    async fn test_var_key_syntax() {
1102        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1103            "key",
1104            vec![Property::String("@time".to_string(), "1.000".to_string())],
1105            vec![],
1106        );
1107        let time_key = "@time";
1108        assert_data_tree!(diagnostics_hierarchy, key: {
1109            var time_key: "1.000"
1110        });
1111    }
1112
1113    const KEY_AS_STR: &str = "@time";
1114
1115    // Similar to above, except the key is stored in a constant.
1116    #[fuchsia::test]
1117    async fn test_path_key_syntax() {
1118        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1119            "key",
1120            vec![Property::String("@time".to_string(), "1.000".to_string())],
1121            vec![],
1122        );
1123        assert_data_tree!(diagnostics_hierarchy, key: {
1124            // This is a fully qualified path on purpose.
1125            ref crate::tests::KEY_AS_STR: "1.000"
1126        });
1127    }
1128
1129    #[fuchsia::test]
1130    async fn test_arrays() {
1131        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1132            "key",
1133            vec![
1134                Property::UintArray("@uints".to_string(), ArrayContent::Values(vec![1, 2, 3])),
1135                Property::IntArray("@ints".to_string(), ArrayContent::Values(vec![-2, -4, 0])),
1136                Property::DoubleArray(
1137                    "@doubles".to_string(),
1138                    ArrayContent::Values(vec![1.3, 2.5, -3.6]),
1139                ),
1140            ],
1141            vec![],
1142        );
1143        assert_data_tree!(diagnostics_hierarchy, key: {
1144            "@uints": vec![1u64, 2, 3],
1145            "@ints": vec![-2i64, -4, 0],
1146            "@doubles": vec![1.3, 2.5, -3.6]
1147        });
1148    }
1149
1150    #[fuchsia::test]
1151    async fn test_histograms() {
1152        let mut condensed_int_histogram =
1153            ArrayContent::new(vec![6, 7, 0, 9, 0], ArrayFormat::LinearHistogram).unwrap();
1154        condensed_int_histogram.condense_histogram();
1155        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1156            "key",
1157            vec![
1158                Property::UintArray(
1159                    "@linear-uints".to_string(),
1160                    ArrayContent::new(vec![1, 2, 3, 4, 5], ArrayFormat::LinearHistogram).unwrap(),
1161                ),
1162                Property::IntArray(
1163                    "@linear-ints".to_string(),
1164                    ArrayContent::new(vec![6, 7, 8, 9, 10], ArrayFormat::LinearHistogram).unwrap(),
1165                ),
1166                Property::IntArray("@linear-sparse-ints".to_string(), condensed_int_histogram),
1167                Property::DoubleArray(
1168                    "@linear-doubles".to_string(),
1169                    ArrayContent::new(vec![1.0, 2.0, 4.0, 5.0, 6.0], ArrayFormat::LinearHistogram)
1170                        .unwrap(),
1171                ),
1172                Property::UintArray(
1173                    "@exp-uints".to_string(),
1174                    ArrayContent::new(vec![2, 4, 6, 8, 10, 12], ArrayFormat::ExponentialHistogram)
1175                        .unwrap(),
1176                ),
1177                Property::IntArray(
1178                    "@exp-ints".to_string(),
1179                    ArrayContent::new(vec![1, 3, 5, 7, 9, 11], ArrayFormat::ExponentialHistogram)
1180                        .unwrap(),
1181                ),
1182                Property::DoubleArray(
1183                    "@exp-doubles".to_string(),
1184                    ArrayContent::new(
1185                        vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1186                        ArrayFormat::ExponentialHistogram,
1187                    )
1188                    .unwrap(),
1189                ),
1190            ],
1191            vec![],
1192        );
1193        let mut linear_uint_assertion = HistogramAssertion::linear(LinearHistogramParams {
1194            floor: 1u64,
1195            step_size: 2,
1196            buckets: 1,
1197        });
1198        linear_uint_assertion.insert_values(vec![0, 0, 0, 2, 2, 2, 2, 4, 4, 4, 4, 4]);
1199        let mut exponential_double_assertion =
1200            HistogramAssertion::exponential(ExponentialHistogramParams {
1201                floor: 1.0,
1202                initial_step: 2.0,
1203                step_multiplier: 3.0,
1204                buckets: 1,
1205            });
1206        exponential_double_assertion.insert_values(vec![
1207            -3.1, -2.2, -1.3, 0.0, 1.1, 1.2, 2.5, 2.8, 2.0, 3.1, 4.2, 5.3, 6.4, 7.5, 8.6,
1208        ]);
1209        assert_data_tree!(diagnostics_hierarchy, key: {
1210            "@linear-uints": linear_uint_assertion,
1211            "@linear-ints": LinearHistogram {
1212                floor: 6i64,
1213                step: 7,
1214                counts: vec![8, 9, 10],
1215                indexes: None,
1216                size: 3
1217            },
1218            "@linear-sparse-ints": LinearHistogram {
1219                floor: 6i64,
1220                step: 7,
1221                counts: vec![9],
1222                indexes: Some(vec![1]),
1223                size: 3
1224            },
1225            "@linear-doubles": LinearHistogram {
1226                floor: 1.0,
1227                step: 2.0,
1228                counts: vec![4.0, 5.0, 6.0],
1229                indexes: None,
1230                size: 3
1231            },
1232            "@exp-uints": ExponentialHistogram {
1233                floor: 2u64,
1234                initial_step: 4,
1235                step_multiplier: 6,
1236                counts: vec![8, 10, 12],
1237                indexes: None,
1238                size: 3
1239            },
1240            "@exp-ints": ExponentialHistogram {
1241                floor: 1i64,
1242                initial_step: 3,
1243                step_multiplier: 5,
1244                counts: vec![7,9,11],
1245                indexes: None,
1246                size: 3
1247            },
1248            "@exp-doubles": exponential_double_assertion,
1249        });
1250    }
1251
1252    #[fuchsia::test]
1253    async fn test_matching_tree_assertion_expression() {
1254        let diagnostics_hierarchy = complex_tree();
1255        let child1 = tree_assertion!(
1256            child1: {
1257                child1_sub: 10,
1258            }
1259        );
1260        assert_data_tree!(diagnostics_hierarchy, key: {
1261            sub: "sub_value",
1262            sub2: "sub2_value",
1263            child1,
1264            tree_assertion!(
1265                child2: {
1266                    child2_sub: 20u64,
1267                }
1268            ),
1269        });
1270    }
1271
1272    #[fuchsia::test]
1273    #[should_panic]
1274    async fn test_matching_non_unique_property_fails() {
1275        let diagnostics_hierarchy = non_unique_prop_tree();
1276        assert_data_tree!(diagnostics_hierarchy, key: { prop: "prop_value#0" });
1277    }
1278
1279    #[fuchsia::test]
1280    #[should_panic]
1281    async fn test_matching_non_unique_property_fails_2() {
1282        let diagnostics_hierarchy = non_unique_prop_tree();
1283        assert_data_tree!(diagnostics_hierarchy, key: { prop: "prop_value#1" });
1284    }
1285
1286    #[fuchsia::test]
1287    #[should_panic]
1288    async fn test_matching_non_unique_property_fails_3() {
1289        let diagnostics_hierarchy = non_unique_prop_tree();
1290        assert_data_tree!(diagnostics_hierarchy, key: {
1291            prop: "prop_value#0",
1292            prop: "prop_value#1",
1293        });
1294    }
1295
1296    #[fuchsia::test]
1297    #[should_panic]
1298    async fn test_matching_non_unique_child_fails() {
1299        let diagnostics_hierarchy = non_unique_child_tree();
1300        assert_data_tree!(diagnostics_hierarchy, key: {
1301            child: {
1302                prop: 10
1303            }
1304        });
1305    }
1306
1307    #[fuchsia::test]
1308    #[should_panic]
1309    async fn test_matching_non_unique_child_fails_2() {
1310        let diagnostics_hierarchy = non_unique_child_tree();
1311        assert_data_tree!(diagnostics_hierarchy, key: {
1312            child: {
1313                prop: 20
1314            }
1315        });
1316    }
1317
1318    #[fuchsia::test]
1319    #[should_panic]
1320    async fn test_matching_non_unique_child_fails_3() {
1321        let diagnostics_hierarchy = non_unique_child_tree();
1322        assert_data_tree!(diagnostics_hierarchy, key: {
1323            child: {
1324                prop: 10,
1325            },
1326            child: {
1327                prop: 20,
1328            },
1329        });
1330    }
1331
1332    #[fuchsia::test]
1333    async fn test_any_string_property_passes() {
1334        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1335            "key",
1336            vec![
1337                Property::String("value1".to_string(), "a".to_string()),
1338                Property::String("value2".to_string(), "b".to_string()),
1339            ],
1340            vec![],
1341        );
1342        assert_data_tree!(diagnostics_hierarchy, key: {
1343            value1: AnyStringProperty,
1344            value2: AnyStringProperty,
1345        });
1346    }
1347
1348    #[fuchsia::test]
1349    #[should_panic]
1350    async fn test_any_string_property_fails() {
1351        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1352            "key",
1353            vec![Property::Int("value1".to_string(), 10i64)],
1354            vec![],
1355        );
1356        assert_data_tree!(diagnostics_hierarchy, key: {
1357            value1: AnyStringProperty,
1358        });
1359    }
1360
1361    #[fuchsia::test]
1362    async fn test_string_property_regex_passes() {
1363        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1364            "key",
1365            vec![Property::String("value1".to_string(), "aaaabbbb".to_string())],
1366            vec![],
1367        );
1368        assert_data_tree!(diagnostics_hierarchy, key: {
1369            value1: &*TEST_REGEX,
1370        });
1371        assert_data_tree!(diagnostics_hierarchy, key: {
1372            value1: Regex::new("a{4}b{4}").unwrap(),
1373        });
1374        assert_data_tree!(diagnostics_hierarchy, key: {
1375            value1: Regex::new("a{4}").unwrap(),
1376        });
1377        assert_data_tree!(diagnostics_hierarchy, key: {
1378            value1: Regex::new("a{2}b{2}").unwrap(),
1379        });
1380        assert_data_tree!(diagnostics_hierarchy, key: {
1381            value1: Regex::new("b{4}").unwrap(),
1382        });
1383    }
1384
1385    #[fuchsia::test]
1386    #[should_panic]
1387    async fn test_string_property_regex_no_match() {
1388        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1389            "key",
1390            vec![Property::String("value1".to_string(), "bbbbcccc".to_string())],
1391            vec![],
1392        );
1393        assert_data_tree!(diagnostics_hierarchy, key: {
1394                value: Regex::new("b{2}d{2}").unwrap(),
1395        });
1396    }
1397
1398    #[fuchsia::test]
1399    #[should_panic]
1400    async fn test_string_property_regex_wrong_type() {
1401        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1402            "key",
1403            vec![Property::Int("value1".to_string(), 10i64)],
1404            vec![],
1405        );
1406        assert_data_tree!(diagnostics_hierarchy, key: {
1407            value1: Regex::new("a{4}").unwrap(),
1408        });
1409    }
1410
1411    #[fuchsia::test]
1412    async fn test_any_bytes_property_passes() {
1413        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1414            "key",
1415            vec![
1416                Property::Bytes("value1".to_string(), vec![1, 2, 3]),
1417                Property::Bytes("value2".to_string(), vec![4, 5, 6]),
1418            ],
1419            vec![],
1420        );
1421        assert_data_tree!(diagnostics_hierarchy, key: {
1422            value1: AnyBytesProperty,
1423            value2: AnyBytesProperty,
1424        });
1425    }
1426
1427    #[fuchsia::test]
1428    #[should_panic]
1429    async fn test_any_bytes_property_fails() {
1430        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1431            "key",
1432            vec![Property::Int("value1".to_string(), 10i64)],
1433            vec![],
1434        );
1435        assert_data_tree!(diagnostics_hierarchy, key: {
1436            value1: AnyBytesProperty,
1437        });
1438    }
1439
1440    #[fuchsia::test]
1441    async fn test_nonzero_uint_property_passes() {
1442        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1443            "key",
1444            vec![
1445                Property::Uint("value1".to_string(), 10u64),
1446                Property::Uint("value2".to_string(), 20u64),
1447            ],
1448            vec![],
1449        );
1450        assert_data_tree!(diagnostics_hierarchy, key: {
1451            value1: NonZeroUintProperty,
1452            value2: NonZeroUintProperty,
1453        });
1454    }
1455
1456    #[fuchsia::test]
1457    #[should_panic]
1458    async fn test_nonzero_uint_property_fails_on_zero() {
1459        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1460            "key",
1461            vec![Property::Uint("value1".to_string(), 0u64)],
1462            vec![],
1463        );
1464        assert_data_tree!(diagnostics_hierarchy, key: {
1465            value1: NonZeroUintProperty,
1466        });
1467    }
1468
1469    #[fuchsia::test]
1470    #[should_panic]
1471    async fn test_nonzero_uint_property_fails() {
1472        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1473            "key",
1474            vec![Property::Int("value1".to_string(), 10i64)],
1475            vec![],
1476        );
1477        assert_data_tree!(diagnostics_hierarchy, key: {
1478            value1: NonZeroUintProperty,
1479        });
1480    }
1481
1482    #[fuchsia::test]
1483    async fn test_nonzero_int_property_passes() {
1484        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1485            "key",
1486            vec![Property::Int("value1".to_string(), 10), Property::Int("value2".to_string(), 20)],
1487            vec![],
1488        );
1489        assert_data_tree!(diagnostics_hierarchy, key: {
1490            value1: NonZeroIntProperty,
1491            value2: NonZeroIntProperty,
1492        });
1493    }
1494
1495    #[fuchsia::test]
1496    #[should_panic]
1497    async fn test_nonzero_int_property_fails_on_zero() {
1498        let diagnostics_hierarchy =
1499            DiagnosticsHierarchy::new("key", vec![Property::Int("value1".to_string(), 0)], vec![]);
1500        assert_data_tree!(diagnostics_hierarchy, key: {
1501            value1: NonZeroIntProperty,
1502        });
1503    }
1504
1505    #[fuchsia::test]
1506    #[should_panic]
1507    async fn test_nonzero_int_property_fails() {
1508        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1509            "key",
1510            vec![Property::Uint("value1".to_string(), 10u64)],
1511            vec![],
1512        );
1513        assert_data_tree!(diagnostics_hierarchy, key: {
1514            value1: NonZeroIntProperty,
1515        });
1516    }
1517
1518    #[fuchsia::test]
1519    async fn test_uint_property_passes() {
1520        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1521            "key",
1522            vec![
1523                Property::Uint("value1".to_string(), 10u64),
1524                Property::Uint("value2".to_string(), 20u64),
1525            ],
1526            vec![],
1527        );
1528        assert_data_tree!(diagnostics_hierarchy, key: {
1529            value1: AnyUintProperty,
1530            value2: AnyUintProperty,
1531        });
1532    }
1533
1534    #[fuchsia::test]
1535    #[should_panic]
1536    async fn test_uint_property_fails() {
1537        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1538            "key",
1539            vec![Property::Int("value1".to_string(), 10i64)],
1540            vec![],
1541        );
1542        assert_data_tree!(diagnostics_hierarchy, key: {
1543            value1: AnyUintProperty,
1544        });
1545    }
1546
1547    #[fuchsia::test]
1548    async fn test_int_property_passes() {
1549        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1550            "key",
1551            vec![
1552                Property::Int("value1".to_string(), 10i64),
1553                Property::Int("value2".to_string(), 20i64),
1554            ],
1555            vec![],
1556        );
1557        assert_data_tree!(diagnostics_hierarchy, key: {
1558            value1: AnyIntProperty,
1559            value2: AnyIntProperty,
1560        });
1561    }
1562
1563    #[fuchsia::test]
1564    #[should_panic]
1565    async fn test_int_property_fails() {
1566        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1567            "key",
1568            vec![Property::Uint("value1".to_string(), 0u64)],
1569            vec![],
1570        );
1571        assert_data_tree!(diagnostics_hierarchy, key: {
1572            value1: AnyIntProperty,
1573        });
1574    }
1575
1576    #[fuchsia::test]
1577    async fn test_double_property_passes() {
1578        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1579            "key",
1580            vec![
1581                Property::Double("value1".to_string(), std::f64::consts::PI),
1582                Property::Double("value2".to_string(), std::f64::consts::E),
1583            ],
1584            vec![],
1585        );
1586        assert_data_tree!(diagnostics_hierarchy, key: {
1587            value1: AnyDoubleProperty,
1588            value2: AnyDoubleProperty,
1589        });
1590    }
1591
1592    #[fuchsia::test]
1593    #[should_panic]
1594    async fn test_double_property_fails() {
1595        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1596            "key",
1597            vec![Property::Uint("value1".to_string(), 0u64)],
1598            vec![],
1599        );
1600        assert_data_tree!(diagnostics_hierarchy, key: {
1601            value1: AnyDoubleProperty,
1602        });
1603    }
1604
1605    #[fuchsia::test]
1606    async fn test_numeric_property_passes() {
1607        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1608            "key",
1609            vec![
1610                Property::Int("value1".to_string(), 10i64),
1611                Property::Uint("value2".to_string(), 20u64),
1612                Property::Double("value3".to_string(), std::f64::consts::PI),
1613            ],
1614            vec![],
1615        );
1616        assert_data_tree!(diagnostics_hierarchy, key: {
1617            value1: AnyNumericProperty,
1618            value2: AnyNumericProperty,
1619            value3: AnyNumericProperty,
1620        });
1621    }
1622
1623    #[fuchsia::test]
1624    #[should_panic]
1625    async fn test_numeric_property_fails() {
1626        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1627            "key",
1628            vec![Property::String("value1".to_string(), "a".to_string())],
1629            vec![],
1630        );
1631        assert_data_tree!(diagnostics_hierarchy, key: {
1632            value1: AnyNumericProperty,
1633        });
1634    }
1635
1636    #[fuchsia::test]
1637    async fn test_bool_property_passes() {
1638        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1639            "key",
1640            vec![
1641                Property::Bool("value1".to_string(), true),
1642                Property::Bool("value2".to_string(), false),
1643            ],
1644            vec![],
1645        );
1646        assert_data_tree!(diagnostics_hierarchy, key: {
1647            value1: AnyBoolProperty,
1648            value2: AnyBoolProperty,
1649        });
1650    }
1651
1652    #[fuchsia::test]
1653    #[should_panic]
1654    async fn test_bool_property_fails() {
1655        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1656            "key",
1657            vec![Property::Uint("value1".to_string(), 0u64)],
1658            vec![],
1659        );
1660        assert_data_tree!(diagnostics_hierarchy, key: {
1661            value1: AnyBoolProperty,
1662        });
1663    }
1664
1665    #[fuchsia::test]
1666    async fn test_string_list() {
1667        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1668            "key",
1669            vec![
1670                Property::StringList("value1".to_string(), vec!["a".to_string(), "b".to_string()]),
1671                Property::StringList("value2".to_string(), vec!["c".to_string(), "d".to_string()]),
1672            ],
1673            vec![],
1674        );
1675        assert_data_tree!(diagnostics_hierarchy, key: {
1676            value1: vec!["a", "b"],
1677            value2: vec!["c".to_string(), "d".to_string()],
1678        });
1679    }
1680
1681    #[fuchsia::test]
1682    #[should_panic]
1683    async fn test_string_list_failure() {
1684        let diagnostics_hierarchy = DiagnosticsHierarchy::new(
1685            "key",
1686            vec![Property::StringList(
1687                "value1".to_string(),
1688                vec!["a".to_string(), "b".to_string()],
1689            )],
1690            vec![],
1691        );
1692        assert_data_tree!(diagnostics_hierarchy, key: {
1693            value1: vec![1i64, 2],
1694        });
1695    }
1696
1697    #[test]
1698    fn test_diff_from_text() {
1699        let original = "foo\nbar\nbaz";
1700        let update = "foo\nbaz\nqux";
1701
1702        let changeset = Diff::from_text(original, update);
1703        assert_eq!(
1704            changeset.0.diffs,
1705            vec![
1706                Same("foo".to_string()),
1707                Rem("bar".to_string()),
1708                Same("baz".to_string()),
1709                Add("qux".to_string())
1710            ]
1711        )
1712    }
1713
1714    fn simple_tree() -> DiagnosticsHierarchy {
1715        DiagnosticsHierarchy::new(
1716            "key",
1717            vec![
1718                Property::String("sub".to_string(), "sub_value".to_string()),
1719                Property::String("sub2".to_string(), "sub2_value".to_string()),
1720            ],
1721            vec![],
1722        )
1723    }
1724
1725    fn complex_tree() -> DiagnosticsHierarchy {
1726        DiagnosticsHierarchy::new(
1727            "key",
1728            vec![
1729                Property::String("sub".to_string(), "sub_value".to_string()),
1730                Property::String("sub2".to_string(), "sub2_value".to_string()),
1731            ],
1732            vec![
1733                DiagnosticsHierarchy::new(
1734                    "child1",
1735                    vec![Property::Int("child1_sub".to_string(), 10i64)],
1736                    vec![],
1737                ),
1738                DiagnosticsHierarchy::new(
1739                    "child2",
1740                    vec![Property::Uint("child2_sub".to_string(), 20u64)],
1741                    vec![],
1742                ),
1743            ],
1744        )
1745    }
1746
1747    fn non_unique_prop_tree() -> DiagnosticsHierarchy {
1748        DiagnosticsHierarchy::new(
1749            "key",
1750            vec![
1751                Property::String("prop".to_string(), "prop_value#0".to_string()),
1752                Property::String("prop".to_string(), "prop_value#1".to_string()),
1753            ],
1754            vec![],
1755        )
1756    }
1757
1758    fn non_unique_child_tree() -> DiagnosticsHierarchy {
1759        DiagnosticsHierarchy::new(
1760            "key",
1761            vec![],
1762            vec![
1763                DiagnosticsHierarchy::new(
1764                    "child",
1765                    vec![Property::Int("prop".to_string(), 10i64)],
1766                    vec![],
1767                ),
1768                DiagnosticsHierarchy::new(
1769                    "child",
1770                    vec![Property::Int("prop".to_string(), 20i64)],
1771                    vec![],
1772                ),
1773            ],
1774        )
1775    }
1776}