| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Testing utilities for a `DiagnosticsHierarchy`. |
| //! |
| //! Pretty much the useful [`assert_data_tree`][assert_data_tree] macro plus some utilities for it. |
| |
| use { |
| crate::{ |
| ArrayContent, ArrayFormat, Bucket, DiagnosticsHierarchy, ExponentialHistogramParams, |
| LinearHistogramParams, Property, EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS, |
| LINEAR_HISTOGRAM_EXTRA_SLOTS, |
| }, |
| anyhow::{bail, format_err, Error}, |
| difference::{Changeset, Difference}, |
| num_traits::One, |
| std::{ |
| borrow::Cow, |
| collections::BTreeSet, |
| fmt::{Debug, Display, Formatter, Result as FmtResult}, |
| ops::{Add, AddAssign, MulAssign}, |
| }, |
| }; |
| |
| /// Macro to simplify creating `TreeAssertion`s. Commonly used indirectly through the second |
| /// parameter of `assert_data_tree!`. See `assert_data_tree!` for more usage examples. |
| /// |
| /// Each leaf value must be a type that implements either `PropertyAssertion` or `TreeAssertion`. |
| /// |
| /// Example: |
| /// ``` |
| /// // Manual creation of `TreeAssertion`. |
| /// let mut root = TreeAssertion::new("root", true); |
| /// root.add_property_assertion("a-string-property", Box::new("expected-string-value")); |
| /// let mut child = TreeAssertion::new("child", true); |
| /// child.add_property_assertion("any-property", Box::new(AnyProperty)); |
| /// root.add_child_assertion(child); |
| /// root.add_child_assertion(opaque_child); |
| /// |
| /// // Creation with `tree_assertion!`. |
| /// let root = tree_assertion!( |
| /// root: { |
| /// "a-string-property": "expected-string-value", |
| /// child: { |
| /// "any-property": AnyProperty, |
| /// }, |
| /// opaque_child, // required trailing comma for `TreeAssertion` expressions |
| /// } |
| /// ); |
| /// ``` |
| /// |
| /// Note that `TreeAssertion`s given directly to the macro must always be followed by `,`. |
| #[macro_export] |
| macro_rules! tree_assertion { |
| (@build $tree_assertion:expr,) => {}; |
| |
| // Exact match of tree |
| (@build $tree_assertion:expr, var $key:ident: { $($sub:tt)* }) => {{ |
| #[allow(unused_mut)] |
| let mut child_tree_assertion = TreeAssertion::new($key, true); |
| $crate::tree_assertion!(@build child_tree_assertion, $($sub)*); |
| $tree_assertion.add_child_assertion(child_tree_assertion); |
| }}; |
| (@build $tree_assertion:expr, var $key:ident: { $($sub:tt)* }, $($rest:tt)*) => {{ |
| $crate::tree_assertion!(@build $tree_assertion, var $key: { $($sub)* }); |
| $crate::tree_assertion!(@build $tree_assertion, $($rest)*); |
| }}; |
| |
| // Partial match of tree |
| (@build $tree_assertion:expr, var $key:ident: contains { $($sub:tt)* }) => {{ |
| #[allow(unused_mut)] |
| let mut child_tree_assertion = TreeAssertion::new($key, false); |
| $crate::tree_assertion!(@build child_tree_assertion, $($sub)*); |
| $tree_assertion.add_child_assertion(child_tree_assertion); |
| }}; |
| (@build $tree_assertion:expr, var $key:ident: contains { $($sub:tt)* }, $($rest:tt)*) => {{ |
| $crate::tree_assertion!(@build $tree_assertion, var $key: contains { $($sub)* }); |
| $crate::tree_assertion!(@build $tree_assertion, $($rest)*); |
| }}; |
| |
| // Matching properties of a tree |
| (@build $tree_assertion:expr, var $key:ident: $assertion:expr) => {{ |
| $tree_assertion.add_property_assertion($key, Box::new($assertion)) |
| }}; |
| (@build $tree_assertion:expr, var $key:ident: $assertion:expr, $($rest:tt)*) => {{ |
| $crate::tree_assertion!(@build $tree_assertion, var $key: $assertion); |
| $crate::tree_assertion!(@build $tree_assertion, $($rest)*); |
| }}; |
| |
| // Key identifier format |
| (@build $tree_assertion:expr, $key:ident: $($rest:tt)+) => {{ |
| let key = stringify!($key); |
| $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+); |
| }}; |
| // Allows string literal for key |
| (@build $tree_assertion:expr, $key:tt: $($rest:tt)+) => {{ |
| let key: &'static str = $key; |
| $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+); |
| }}; |
| // Allows an expression that resolves into a String for key |
| (@build $tree_assertion:expr, $key:expr => $($rest:tt)+) => {{ |
| let key_string : String = $key; |
| let key = &key_string; |
| $crate::tree_assertion!(@build $tree_assertion, var key: $($rest)+); |
| }}; |
| // Allows an expression that resolves into a TreeAssertion |
| (@build $tree_assertion:expr, $child_assertion:expr, $($rest:tt)*) => {{ |
| $tree_assertion.add_child_assertion($child_assertion); |
| $crate::tree_assertion!(@build $tree_assertion, $($rest)*); |
| }}; |
| |
| // Entry points |
| (var $key:ident: { $($sub:tt)* }) => {{ |
| use $crate::testing::TreeAssertion; |
| #[allow(unused_mut)] |
| let mut tree_assertion = TreeAssertion::new($key, true); |
| $crate::tree_assertion!(@build tree_assertion, $($sub)*); |
| tree_assertion |
| }}; |
| (var $key:ident: contains { $($sub:tt)* }) => {{ |
| use $crate::testing::TreeAssertion; |
| #[allow(unused_mut)] |
| let mut tree_assertion = TreeAssertion::new($key, false); |
| $crate::tree_assertion!(@build tree_assertion, $($sub)*); |
| tree_assertion |
| }}; |
| ($key:ident: $($rest:tt)+) => {{ |
| let key = stringify!($key); |
| $crate::tree_assertion!(var key: $($rest)+) |
| }}; |
| ($key:tt: $($rest:tt)+) => {{ |
| let key: &'static str = $key; |
| $crate::tree_assertion!(var key: $($rest)+) |
| }}; |
| } |
| |
| /// Macro to simplify tree matching in tests. The first argument is the actual tree passed as a |
| /// `DiagnosticsHierarchyGetter` (e.g. a `DiagnosticsHierarchy` or an `Inspector`). The second argument is given |
| /// to `tree_assertion!` which creates a `TreeAssertion` to validate the tree. |
| /// |
| /// Each leaf value must be a type that implements either `PropertyAssertion` or `TreeAssertion`. |
| /// |
| /// Example: |
| /// ``` |
| /// // Actual tree |
| /// let diagnostics_hierarchy = DiagnosticsHierarchy { |
| /// name: "key".to_string(), |
| /// properties: vec![ |
| /// Property::String("sub".to_string(), "sub_value".to_string()), |
| /// Property::String("sub2".to_string(), "sub2_value".to_string()), |
| /// ], |
| /// children: vec![ |
| /// DiagnosticsHierarchy { |
| /// name: "child1".to_string(), |
| /// properties: vec![ |
| /// Property::Int("child1_sub".to_string(), 10i64), |
| /// ], |
| /// children: vec![], |
| /// }, |
| /// DiagnosticsHierarchy { |
| /// name: "child2".to_string(), |
| /// properties: vec![ |
| /// Property::Uint("child2_sub".to_string(), 20u64), |
| /// ], |
| /// children: vec![], |
| /// }, |
| /// ], |
| /// }; |
| /// |
| /// assert_data_tree!( |
| /// diagnostics_hierarchy, |
| /// key: { |
| /// sub: AnyProperty, // only verify that `sub` is a property of `key` |
| /// sub2: "sub2_value", |
| /// child1: { |
| /// child1_sub: 10i64, |
| /// }, |
| /// child2: { |
| /// child2_sub: 20u64, |
| /// }, |
| /// } |
| /// ); |
| /// ``` |
| /// |
| /// In order to do a partial match on a tree, use the `contains` keyword: |
| /// ``` |
| /// assert_data_tree!(diagnostics_hierarchy, key: contains { |
| /// sub: "sub_value", |
| /// child1: contains {}, |
| /// }); |
| /// ``` |
| /// |
| /// In order to do a match on a tree where the keys need to be computed (they are some |
| /// expression), you'll need to use `=>` instead of `:`: |
| /// |
| /// ``` |
| /// assert_data_tree!(diagnostics_hierarchy, key: { |
| /// key_fn() => "value", |
| /// }) |
| /// ``` |
| /// Note that `key_fn` has to return a `String`. |
| /// |
| /// The first argument can be an `Inspector`, in which case the whole tree is read from the |
| /// `Inspector` and matched against: |
| /// ``` |
| /// let inspector = Inspector::new().unwrap(); |
| /// assert_data_tree!(inspector, root: {}); |
| /// ``` |
| /// |
| /// `TreeAssertion`s made elsewhere can be included bodily in the macro, but must always be followed |
| /// by a trailing comma: |
| /// assert_data_tree!( |
| /// diagnostics_hierarchy, |
| /// key: { |
| /// make_child_tree_assertion(), // required trailing comma |
| /// } |
| /// ); |
| /// |
| /// A tree may contain multiple properties or children with the same name. This macro does *not* |
| /// support matching against them, and will throw an error if it detects duplicates. This is |
| /// to provide warning for users who accidentally log the same name multiple times, as the |
| /// behavior for reading properties or children with duplicate names is not well defined. |
| #[macro_export] |
| macro_rules! assert_data_tree { |
| ($diagnostics_hierarchy:expr, $($rest:tt)+) => {{ |
| let tree_assertion = $crate::tree_assertion!($($rest)+); |
| |
| use $crate::testing::DiagnosticsHierarchyGetter as _; |
| if let Err(e) = tree_assertion.run($diagnostics_hierarchy.get_diagnostics_hierarchy().as_ref()) { |
| panic!("tree assertion fails: {}", e); |
| } |
| }}; |
| } |
| |
| /// A type which can function as a "view" into a diagnostics hierarchy, optionally allocating a new |
| /// instance to service a request. |
| pub trait DiagnosticsHierarchyGetter<K: Clone> { |
| fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy<K>>; |
| } |
| |
| impl<K: Clone> DiagnosticsHierarchyGetter<K> for DiagnosticsHierarchy<K> { |
| fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy<K>> { |
| Cow::Borrowed(self) |
| } |
| } |
| |
| /// A difference between expected and actual output. |
| struct Diff(Changeset); |
| |
| impl Diff { |
| fn new(expected: &dyn Debug, actual: &dyn Debug) -> Self { |
| let expected = format!("{:#?}", expected); |
| let actual = format!("{:#?}", actual); |
| Diff(Changeset::new(&expected, &actual, "\n")) |
| } |
| } |
| |
| impl Display for Diff { |
| fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |
| writeln!(f, "(-) Expected vs. (+) Actual:")?; |
| for diff in &self.0.diffs { |
| let (prefix, contents) = match diff { |
| Difference::Same(same) => (" ", same), |
| Difference::Add(added) => ("+ ", added), |
| Difference::Rem(removed) => ("- ", removed), |
| }; |
| for line in contents.split("\n") { |
| writeln!(f, "{}{}", prefix, line)?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| macro_rules! eq_or_bail { |
| ($expected:expr, $actual:expr) => {{ |
| if $expected != $actual { |
| let changes = Diff::new(&$expected, &$actual); |
| return Err(format_err!("\n {}", changes)); |
| } |
| }}; |
| ($expected:expr, $actual:expr, $($args:tt)+) => {{ |
| if $expected != $actual { |
| let changes = Diff::new(&$expected, &$actual); |
| return Err(format_err!("{}:\n {}", format!($($args)+), changes)); |
| } |
| }} |
| } |
| |
| /// Struct for matching against a Data tree (DiagnosticsHierarchy). |
| pub struct TreeAssertion<K = String> { |
| /// Expected name of the node being compared against |
| name: String, |
| /// Friendly name that includes path from ancestors. Mainly used to indicate which node fails |
| /// in error message |
| path: String, |
| /// Expected property names and assertions to match the actual properties against |
| properties: Vec<(String, Box<dyn PropertyAssertion<K>>)>, |
| /// Assertions to match against child trees |
| children: Vec<TreeAssertion<K>>, |
| /// Whether all properties and children of the tree should be checked |
| exact_match: bool, |
| } |
| |
| impl<K> TreeAssertion<K> |
| where |
| K: AsRef<str>, |
| { |
| /// Create a new `TreeAssertion`. The |name| argument is the expected name of the tree to be |
| /// compared against. Set |exact_match| to true to specify that all properties and children of |
| /// the tree should be checked. To perform partial matching of the tree, set it to false. |
| pub fn new(name: &str, exact_match: bool) -> Self { |
| Self { |
| name: name.to_string(), |
| path: name.to_string(), |
| properties: vec![], |
| children: vec![], |
| exact_match, |
| } |
| } |
| |
| /// Adds a property assertion to this tree assertion. |
| pub fn add_property_assertion(&mut self, key: &str, assertion: Box<dyn PropertyAssertion<K>>) { |
| self.properties.push((key.to_owned(), assertion)); |
| } |
| |
| /// Adds a tree assertion as a child of this tree assertion. |
| pub fn add_child_assertion(&mut self, mut assertion: TreeAssertion<K>) { |
| assertion.path = format!("{}.{}", self.path, assertion.name); |
| self.children.push(assertion); |
| } |
| |
| /// Check whether |actual| tree satisfies criteria defined by `TreeAssertion`. Return `Ok` if |
| /// assertion passes and `Error` if assertion fails. |
| pub fn run(&self, actual: &DiagnosticsHierarchy<K>) -> Result<(), Error> { |
| eq_or_bail!(self.name, actual.name, "node `{}` - expected node name != actual", self.path); |
| |
| if self.exact_match { |
| let properties_names = self.properties.iter().map(|p| p.0.to_string()); |
| let children_names = self.children.iter().map(|c| c.name.to_string()); |
| let keys: BTreeSet<String> = properties_names.chain(children_names).collect(); |
| |
| let actual_props = actual.properties.iter().map(|p| p.name().to_string()); |
| let actual_children = actual.children.iter().map(|c| c.name.to_string()); |
| let actual_keys: BTreeSet<String> = actual_props.chain(actual_children).collect(); |
| eq_or_bail!(keys, actual_keys, "node `{}` - expected keys != actual", self.path); |
| } |
| |
| for (name, assertion) in self.properties.iter() { |
| let mut matched = actual.properties.iter().filter(|p| p.key().as_ref() == name); |
| let first_match = matched.next(); |
| if let Some(_second_match) = matched.next() { |
| bail!("node `{}` - multiple properties found with name `{}`", self.path, name); |
| } |
| match first_match { |
| Some(property) => { |
| if let Err(e) = assertion.run(property) { |
| bail!( |
| "node `{}` - assertion fails for property `{}`. Reason: {}", |
| self.path, |
| name, |
| e |
| ); |
| } |
| } |
| None => bail!("node `{}` - no property named `{}`", self.path, name), |
| } |
| } |
| for assertion in self.children.iter() { |
| let mut matched = actual.children.iter().filter(|c| c.name == assertion.name); |
| let first_match = matched.next(); |
| if let Some(_second_match) = matched.next() { |
| bail!( |
| "node `{}` - multiple children found with name `{}`", |
| self.path, |
| assertion.name |
| ); |
| } |
| match first_match { |
| Some(child) => assertion.run(&child)?, |
| None => bail!("node `{}` - no child named `{}`", self.path, assertion.name), |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Trait implemented by types that can act as properies for assertion. |
| pub trait PropertyAssertion<K = String> { |
| /// Check whether |actual| property satisfies criteria. Return `Ok` if assertion passes and |
| /// `Error` if assertion fails. |
| fn run(&self, actual: &Property<K>) -> Result<(), Error>; |
| } |
| |
| macro_rules! impl_property_assertion { |
| ($prop_variant:ident, $($ty:ty),+) => { |
| $( |
| impl<K> PropertyAssertion<K> for $ty { |
| fn run(&self, actual: &Property<K>) -> Result<(), Error> { |
| if let Property::$prop_variant(_key, value, ..) = actual { |
| eq_or_bail!(self, value); |
| } else { |
| return Err(format_err!("expected {}, found {}", |
| stringify!($prop_variant), actual.discriminant_name())); |
| } |
| Ok(()) |
| } |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! impl_array_properties_assertion { |
| ($prop_variant:ident, $($ty:ty),+) => { |
| $( |
| /// Asserts primitive arrays |
| impl<K> PropertyAssertion<K> for Vec<$ty> { |
| fn run(&self, actual: &Property<K>) -> Result<(), Error> { |
| if let Property::$prop_variant(_key, value, ..) = actual { |
| match &value { |
| ArrayContent::Values(values) => eq_or_bail!(self, values), |
| _ => { |
| return Err(format_err!( |
| "expected a {} array, got a histogram", |
| stringify!($prop_variant) |
| )); |
| } |
| } |
| } else { |
| return Err(format_err!("expected {}, found {}", |
| stringify!($prop_variant), actual.discriminant_name())); |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Asserts an array of buckets |
| impl<K> PropertyAssertion<K> for Vec<Bucket<$ty>> { |
| fn run(&self, actual: &Property<K>) -> Result<(), Error> { |
| if let Property::$prop_variant(_key, value, ..) = actual { |
| match &value { |
| ArrayContent::Buckets(buckets) => eq_or_bail!(self, buckets), |
| _ => { |
| return Err(format_err!( |
| "expected a {} array, got a histogram", |
| stringify!($prop_variant) |
| )); |
| } |
| } |
| } else { |
| return Err(format_err!("expected {}, found {}", |
| stringify!($prop_variant), actual.discriminant_name())); |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Asserts a histogram. |
| impl<K> PropertyAssertion<K> for HistogramAssertion<$ty> { |
| fn run(&self, actual: &Property<K>) -> Result<(), Error> { |
| if let Property::$prop_variant(_key, value, ..) = actual { |
| let expected_content = |
| ArrayContent::new(self.values.clone(), self.format.clone()).map_err( |
| |e| { |
| format_err!( |
| "failed to load array content for expected assertion {}: {:?}", |
| stringify!($prop_variant), |
| e |
| ) |
| }, |
| )?; |
| eq_or_bail!(&expected_content, value); |
| } else { |
| return Err(format_err!( |
| "expected {}, found {}", |
| stringify!($prop_variant), |
| actual.discriminant_name(), |
| )); |
| } |
| Ok(()) |
| } |
| } |
| )+ |
| } |
| } |
| |
| impl_property_assertion!(String, &str, String); |
| impl_property_assertion!(Bytes, Vec<u8>); |
| impl_property_assertion!(Uint, u64); |
| impl_property_assertion!(Int, i64); |
| impl_property_assertion!(Double, f64); |
| impl_property_assertion!(Bool, bool); |
| impl_array_properties_assertion!(DoubleArray, f64); |
| impl_array_properties_assertion!(IntArray, i64); |
| impl_array_properties_assertion!(UintArray, u64); |
| |
| /// A PropertyAssertion that always passes |
| pub struct AnyProperty; |
| |
| impl<K> PropertyAssertion<K> for AnyProperty { |
| fn run(&self, _actual: &Property<K>) -> Result<(), Error> { |
| Ok(()) |
| } |
| } |
| |
| /// A PropertyAssertion that passes for non-zero, unsigned integers. |
| /// |
| /// TODO(fxbug.dev/62447): generalize this to use the >= operator. |
| pub struct NonZeroUintProperty; |
| |
| impl<K> PropertyAssertion<K> for NonZeroUintProperty { |
| fn run(&self, actual: &Property<K>) -> Result<(), Error> { |
| match actual { |
| Property::Uint(_, v) if *v != 0 => Ok(()), |
| Property::Uint(_, v) if *v == 0 => { |
| Err(format_err!("expected non-zero integer, found 0")) |
| } |
| _ => { |
| Err(format_err!("expected non-zero integer, found {}", actual.discriminant_name())) |
| } |
| } |
| } |
| } |
| |
| /// An assertion for a histogram property. |
| pub struct HistogramAssertion<T> { |
| format: ArrayFormat, |
| values: Vec<T>, |
| } |
| |
| impl<T: MulAssign + AddAssign + PartialOrd + Add<Output = T> + Copy + Default + One> |
| HistogramAssertion<T> |
| { |
| /// Creates a new histogram assertion for a linear histogram with the given parameters. |
| pub fn linear(params: LinearHistogramParams<T>) -> Self { |
| let mut values = vec![T::default(); params.buckets + LINEAR_HISTOGRAM_EXTRA_SLOTS]; |
| values[0] = params.floor; |
| values[1] = params.step_size; |
| Self { format: ArrayFormat::LinearHistogram, values } |
| } |
| |
| /// Creates a new histogram assertion for an exponential histogram with the given parameters. |
| pub fn exponential(params: ExponentialHistogramParams<T>) -> Self { |
| let mut values = vec![T::default(); params.buckets + EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS]; |
| values[0] = params.floor; |
| values[1] = params.initial_step; |
| values[2] = params.step_multiplier; |
| Self { format: ArrayFormat::ExponentialHistogram, values } |
| } |
| |
| /// Inserts the list of values to the histogram for asserting them. |
| pub fn insert_values(&mut self, values: Vec<T>) { |
| match self.format { |
| ArrayFormat::ExponentialHistogram => { |
| for value in values { |
| self.insert_exp(value); |
| } |
| } |
| ArrayFormat::LinearHistogram => { |
| for value in values { |
| self.insert_linear(value); |
| } |
| } |
| ArrayFormat::Default => { |
| unreachable!("can't construct a histogram assertion for arrays"); |
| } |
| } |
| } |
| |
| fn insert_linear(&mut self, value: T) { |
| let value_index = { |
| let mut current_floor = self.values[0]; |
| let step_size = self.values[1]; |
| // Start in the underflow index. |
| let mut index = LINEAR_HISTOGRAM_EXTRA_SLOTS - 2; |
| while value >= current_floor && index < self.values.len() - 1 { |
| current_floor += step_size; |
| index += 1; |
| } |
| index as usize |
| }; |
| self.values[value_index] += T::one(); |
| } |
| |
| fn insert_exp(&mut self, value: T) { |
| let value_index = { |
| let floor = self.values[0]; |
| let mut current_floor = self.values[0]; |
| let mut offset = self.values[1]; |
| let step_multiplier = self.values[2]; |
| // Start in the underflow index. |
| let mut index = EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS - 2; |
| while value >= current_floor && index < self.values.len() - 1 { |
| current_floor = floor + offset; |
| offset *= step_multiplier; |
| index += 1; |
| } |
| index as usize |
| }; |
| self.values[value_index] += T::one(); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use {super::*, crate::Bucket}; |
| |
| #[test] |
| fn test_exact_match_simple() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub2: "sub2_value", |
| }); |
| } |
| |
| #[test] |
| fn test_exact_match_complex() { |
| let diagnostics_hierarchy = complex_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub2: "sub2_value", |
| child1: { |
| child1_sub: 10i64, |
| }, |
| child2: { |
| child2_sub: 20u64, |
| }, |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_exact_match_mismatched_property_name() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub3: "sub2_value", |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_exact_match_mismatched_child_name() { |
| let diagnostics_hierarchy = complex_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub2: "sub2_value", |
| child1: { |
| child1_sub: 10i64, |
| }, |
| child3: { |
| child2_sub: 20u64, |
| }, |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_exact_match_mismatched_property_name_in_child() { |
| let diagnostics_hierarchy = complex_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub2: "sub2_value", |
| child1: { |
| child2_sub: 10i64, |
| }, |
| child2: { |
| child2_sub: 20u64, |
| }, |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_exact_match_mismatched_property_value() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub2_value", |
| sub2: "sub2_value", |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_exact_match_missing_property() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_exact_match_missing_child() { |
| let diagnostics_hierarchy = complex_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub2: "sub2_value", |
| child1: { |
| child1_sub: 10i64, |
| }, |
| }); |
| } |
| |
| #[test] |
| fn test_partial_match_success() { |
| let diagnostics_hierarchy = complex_tree(); |
| |
| // only verify the top tree name |
| assert_data_tree!(diagnostics_hierarchy, key: contains {}); |
| |
| // verify parts of the tree |
| assert_data_tree!(diagnostics_hierarchy, key: contains { |
| sub: "sub_value", |
| child1: contains {}, |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_partial_match_nonexistent_property() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: contains { |
| sub3: AnyProperty, |
| }); |
| } |
| |
| #[test] |
| fn test_ignore_property_value() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: AnyProperty, |
| sub2: "sub2_value", |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_ignore_property_value_property_name_is_still_checked() { |
| let diagnostics_hierarchy = simple_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub1: AnyProperty, |
| sub2: "sub2_value", |
| }) |
| } |
| |
| #[test] |
| fn test_expr_key_syntax() { |
| let diagnostics_hierarchy = DiagnosticsHierarchy::new( |
| "key", |
| vec![Property::String("@time".to_string(), "1.000".to_string())], |
| vec![], |
| ); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| "@time": "1.000" |
| }); |
| } |
| |
| #[test] |
| fn test_var_key_syntax() { |
| let diagnostics_hierarchy = DiagnosticsHierarchy::new( |
| "key", |
| vec![Property::String("@time".to_string(), "1.000".to_string())], |
| vec![], |
| ); |
| let time_key = "@time"; |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| var time_key: "1.000" |
| }); |
| } |
| |
| #[test] |
| fn test_arrays() { |
| let diagnostics_hierarchy = DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::UintArray("@uints".to_string(), ArrayContent::Values(vec![1, 2, 3])), |
| Property::IntArray("@ints".to_string(), ArrayContent::Values(vec![-2, -4, 0])), |
| Property::DoubleArray( |
| "@doubles".to_string(), |
| ArrayContent::Values(vec![1.3, 2.5, -3.6]), |
| ), |
| ], |
| vec![], |
| ); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| "@uints": vec![1u64, 2, 3], |
| "@ints": vec![-2i64, -4, 0], |
| "@doubles": vec![1.3, 2.5, -3.6] |
| }); |
| } |
| |
| #[test] |
| fn test_histograms() { |
| let diagnostics_hierarchy = DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::UintArray( |
| "@linear-uints".to_string(), |
| ArrayContent::new(vec![1, 2, 3, 4, 5], ArrayFormat::LinearHistogram).unwrap(), |
| ), |
| Property::IntArray( |
| "@linear-ints".to_string(), |
| ArrayContent::new(vec![6, 7, 8, 9, 10], ArrayFormat::LinearHistogram).unwrap(), |
| ), |
| Property::DoubleArray( |
| "@linear-doubles".to_string(), |
| ArrayContent::new(vec![1.0, 2.0, 4.0, 5.0, 6.0], ArrayFormat::LinearHistogram) |
| .unwrap(), |
| ), |
| Property::UintArray( |
| "@exp-uints".to_string(), |
| ArrayContent::new(vec![2, 4, 6, 8, 10, 12], ArrayFormat::ExponentialHistogram) |
| .unwrap(), |
| ), |
| Property::IntArray( |
| "@exp-ints".to_string(), |
| ArrayContent::new(vec![1, 3, 5, 7, 9, 11], ArrayFormat::ExponentialHistogram) |
| .unwrap(), |
| ), |
| Property::DoubleArray( |
| "@exp-doubles".to_string(), |
| ArrayContent::new( |
| vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], |
| ArrayFormat::ExponentialHistogram, |
| ) |
| .unwrap(), |
| ), |
| ], |
| vec![], |
| ); |
| let mut linear_assertion = HistogramAssertion::linear(LinearHistogramParams { |
| floor: 1u64, |
| step_size: 2, |
| buckets: 1, |
| }); |
| linear_assertion.insert_values(vec![0, 0, 0, 2, 2, 2, 2, 4, 4, 4, 4, 4]); |
| let mut exponential_assertion = |
| HistogramAssertion::exponential(ExponentialHistogramParams { |
| floor: 1.0, |
| initial_step: 2.0, |
| step_multiplier: 3.0, |
| buckets: 1, |
| }); |
| exponential_assertion.insert_values(vec![ |
| -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, |
| ]); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| "@linear-uints": linear_assertion, |
| "@linear-ints": vec![ |
| Bucket { floor: i64::MIN, ceiling: 6, count: 8 }, |
| Bucket { floor: 6, ceiling: 13, count: 9 }, |
| Bucket { floor: 13, ceiling: i64::MAX, count: 10 } |
| ], |
| "@linear-doubles": vec![ |
| Bucket { floor: f64::MIN, ceiling: 1.0, count: 4.0 }, |
| Bucket { floor: 1.0, ceiling: 3.0, count: 5.0 }, |
| Bucket { floor: 3.0, ceiling: f64::MAX, count: 6.0 } |
| ], |
| "@exp-uints": vec![ |
| Bucket { floor: 0, ceiling: 2, count: 8 }, |
| Bucket { floor: 2, ceiling: 6, count: 10 }, |
| Bucket { floor: 6, ceiling: u64::MAX, count: 12 } |
| ], |
| "@exp-ints": vec![ |
| Bucket { floor: i64::MIN, ceiling: 1, count: 7 }, |
| Bucket { floor: 1, ceiling: 4, count: 9 }, |
| Bucket { floor: 4, ceiling: i64::MAX, count: 11 } |
| ], |
| "@exp-doubles": exponential_assertion, |
| }); |
| } |
| |
| #[test] |
| fn test_matching_tree_assertion_expression() { |
| let diagnostics_hierarchy = complex_tree(); |
| let child1 = tree_assertion!( |
| child1: { |
| child1_sub: 10i64, |
| } |
| ); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| sub: "sub_value", |
| sub2: "sub2_value", |
| child1, |
| tree_assertion!( |
| child2: { |
| child2_sub: 20u64, |
| } |
| ), |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_matching_non_unique_property_fails() { |
| let diagnostics_hierarchy = non_unique_prop_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { prop: "prop_value#0" }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_matching_non_unique_property_fails_2() { |
| let diagnostics_hierarchy = non_unique_prop_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { prop: "prop_value#1" }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_matching_non_unique_property_fails_3() { |
| let diagnostics_hierarchy = non_unique_prop_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| prop: "prop_value#0", |
| prop: "prop_value#1", |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_matching_non_unique_child_fails() { |
| let diagnostics_hierarchy = non_unique_child_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| child: { |
| prop: 10i64 |
| } |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_matching_non_unique_child_fails_2() { |
| let diagnostics_hierarchy = non_unique_child_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| child: { |
| prop: 20i64 |
| } |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_matching_non_unique_child_fails_3() { |
| let diagnostics_hierarchy = non_unique_child_tree(); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| child: { |
| prop: 10i64, |
| }, |
| child: { |
| prop: 20i64, |
| }, |
| }); |
| } |
| |
| #[test] |
| fn test_nonzero_uint_property_passes() { |
| let diagnostics_hierarchy = DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::Uint("value1".to_string(), 10u64), |
| Property::Uint("value2".to_string(), 20u64), |
| ], |
| vec![], |
| ); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| value1: NonZeroUintProperty, |
| value2: NonZeroUintProperty, |
| }); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_nonzero_uint_property_fails() { |
| let diagnostics_hierarchy = DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::Int("value1".to_string(), 10i64), |
| Property::Uint("value2".to_string(), 0u64), |
| Property::String("value3".to_string(), "string_value".to_string()), |
| ], |
| vec![], |
| ); |
| assert_data_tree!(diagnostics_hierarchy, key: { |
| value1: NonZeroUintProperty, |
| value2: NonZeroUintProperty, |
| value3: NonZeroUintProperty, |
| }); |
| } |
| |
| fn simple_tree() -> DiagnosticsHierarchy { |
| DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::String("sub".to_string(), "sub_value".to_string()), |
| Property::String("sub2".to_string(), "sub2_value".to_string()), |
| ], |
| vec![], |
| ) |
| } |
| |
| fn complex_tree() -> DiagnosticsHierarchy { |
| DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::String("sub".to_string(), "sub_value".to_string()), |
| Property::String("sub2".to_string(), "sub2_value".to_string()), |
| ], |
| vec![ |
| DiagnosticsHierarchy::new( |
| "child1", |
| vec![Property::Int("child1_sub".to_string(), 10i64)], |
| vec![], |
| ), |
| DiagnosticsHierarchy::new( |
| "child2", |
| vec![Property::Uint("child2_sub".to_string(), 20u64)], |
| vec![], |
| ), |
| ], |
| ) |
| } |
| |
| fn non_unique_prop_tree() -> DiagnosticsHierarchy { |
| DiagnosticsHierarchy::new( |
| "key", |
| vec![ |
| Property::String("prop".to_string(), "prop_value#0".to_string()), |
| Property::String("prop".to_string(), "prop_value#1".to_string()), |
| ], |
| vec![], |
| ) |
| } |
| |
| fn non_unique_child_tree() -> DiagnosticsHierarchy { |
| DiagnosticsHierarchy::new( |
| "key", |
| vec![], |
| vec![ |
| DiagnosticsHierarchy::new( |
| "child", |
| vec![Property::Int("prop".to_string(), 10i64)], |
| vec![], |
| ), |
| DiagnosticsHierarchy::new( |
| "child", |
| vec![Property::Int("prop".to_string(), 20i64)], |
| vec![], |
| ), |
| ], |
| ) |
| } |
| } |