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