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