inspect_validator/
data.rs

1// Copyright 2019 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
5use crate::metrics::Metrics;
6use crate::puppet::DiffType;
7use anyhow::{Error, bail, format_err};
8use base64::engine::Engine as _;
9use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
10use diagnostics_hierarchy::{
11    ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram,
12    Property as iProperty,
13};
14use fidl_diagnostics_validate::{self as validate, Value};
15use inspect_format::{ArrayFormat, BlockIndex, LinkNodeDisposition};
16use num_traits::Zero;
17use std::collections::{HashMap, HashSet};
18use std::fmt;
19
20mod scanner;
21pub use scanner::Scanner;
22mod fetch;
23pub use fetch::LazyNode;
24
25#[cfg(test)]
26use num_traits::ToPrimitive;
27
28const ROOT_NAME: &str = "root";
29
30/// A local store of Inspect-like data which can be built by Action or filled
31/// from a VMO.
32///
33/// For now, Data assumes it will not be given two sibling-nodes or
34/// properties with the same name, and does not truncate any data or names.
35#[derive(Debug, Clone)]
36pub struct Data {
37    nodes: HashMap<BlockIndex, Node>,
38    properties: HashMap<BlockIndex, Property>,
39    tombstone_nodes: HashSet<BlockIndex>,
40    tombstone_properties: HashSet<BlockIndex>,
41}
42
43// Data is the only public struct in this file. The internal data structures are
44// a bit complicated...
45//
46// Node, Property, and Payload are the "clean data" holders - they can be created
47// either by reading from a VMO, or by applying Actions (the same actions that
48// are sent to the puppets and act on their VMOs).
49//
50// The Actions specify arbitrary u32 keys to refer to nodes and properties to
51// create, modify, and delete. It's an error to misuse a key (e.g. double-create
52// a key or delete a missing key).
53//
54// In both Data-from-Actions and Data-from-VMO, the "root" node is virtual; nodes
55// and properties with a "parent" ID of 0 are directly under the "root" of the tree.
56// A placeholder Node is placed at index 0 upon creation to hold the real Nodes and
57// properties added during scanning VMO or processing Actions.
58
59#[derive(Debug, Clone)]
60pub struct Node {
61    name: String,
62    parent: BlockIndex,
63    children: HashSet<BlockIndex>,
64    properties: HashSet<BlockIndex>,
65}
66
67#[derive(Debug, Clone)]
68pub struct Property {
69    name: String,
70    // TODO(https://fxbug.dev/42165549)
71    #[allow(unused)]
72    id: BlockIndex,
73    parent: BlockIndex,
74    payload: Payload,
75}
76
77#[derive(Debug, Clone)]
78// Note: Some of the inner members on this struct are flagged by the compiler as
79// Unused. In reality, they're used in the `Debug` impl.
80enum Payload {
81    String(#[allow(unused)] String),
82    Bytes(Vec<u8>),
83    Int(i64),
84    Uint(u64),
85    Double(f64),
86    Bool(#[allow(unused)] bool),
87    IntArray(Vec<i64>, ArrayFormat),
88    UintArray(Vec<u64>, ArrayFormat),
89    DoubleArray(Vec<f64>, ArrayFormat),
90    StringArray(Vec<String>),
91    Link { disposition: LinkNodeDisposition, parsed_data: Data },
92
93    // Used when parsing from JSON. We have trouble identifying numeric types and types of
94    // histograms from the output. We can use these generic types to be safe for comparison from
95    // JSON.
96    GenericNumber(#[allow(unused)] String),
97    GenericArray(#[allow(unused)] Vec<String>),
98    GenericLinearHistogram(#[allow(unused)] Vec<String>),
99    GenericExponentialHistogram(#[allow(unused)] Vec<String>),
100}
101
102fn to_string<T: std::fmt::Display>(v: T) -> String {
103    format!("{v}")
104}
105
106fn stringify_list<T: std::fmt::Display>(values: Vec<T>) -> Vec<String> {
107    values.into_iter().map(|x| to_string(x)).collect()
108}
109
110fn stringify_linear_histogram<T: Clone + std::fmt::Display + Zero>(
111    histogram: LinearHistogram<T>,
112) -> Vec<String> {
113    let mut values = vec![T::zero(); histogram.size + 2];
114    values[0] = histogram.floor;
115    values[1] = histogram.step;
116    if let Some(indexes) = histogram.indexes {
117        for (index, count) in indexes.iter().zip(histogram.counts) {
118            values[index + 2] = count.clone();
119        }
120    } else {
121        for (index, count) in histogram.counts.iter().enumerate() {
122            values[index + 2] = count.clone();
123        }
124    }
125    stringify_list(values)
126}
127
128fn stringify_exponential_histogram<T: Clone + std::fmt::Display + Zero>(
129    histogram: ExponentialHistogram<T>,
130) -> Vec<String> {
131    let mut values = vec![T::zero(); histogram.size + 3];
132    values[0] = histogram.floor;
133    values[1] = histogram.initial_step;
134    values[2] = histogram.step_multiplier;
135    if let Some(indexes) = histogram.indexes {
136        for (index, count) in indexes.iter().zip(histogram.counts) {
137            values[index + 3] = count.clone();
138        }
139    } else {
140        for (index, count) in histogram.counts.iter().enumerate() {
141            values[index + 3] = count.clone();
142        }
143    }
144    stringify_list(values)
145}
146
147fn handle_array(values: Vec<String>, format: ArrayFormat) -> Payload {
148    match format {
149        ArrayFormat::LinearHistogram => Payload::GenericLinearHistogram(values),
150        ArrayFormat::ExponentialHistogram => Payload::GenericExponentialHistogram(values),
151        _ => Payload::GenericArray(values),
152    }
153}
154
155trait ToGeneric {
156    fn to_generic(self) -> Payload;
157}
158
159impl ToGeneric for Payload {
160    /// Convert this payload into a generic representation that is compatible with JSON.
161    fn to_generic(self) -> Self {
162        match self {
163            Payload::IntArray(val, format) => handle_array(stringify_list(val), format),
164            Payload::UintArray(val, format) => handle_array(stringify_list(val), format),
165            Payload::DoubleArray(val, format) => handle_array(stringify_list(val), format),
166            Payload::StringArray(val) => handle_array(val, ArrayFormat::Default),
167            Payload::Bytes(val) => Payload::String(format!("b64:{}", BASE64_STANDARD.encode(&val))),
168            Payload::Int(val) => Payload::GenericNumber(to_string(val)),
169            Payload::Uint(val) => Payload::GenericNumber(to_string(val)),
170            Payload::Double(val) => Payload::GenericNumber(to_string(val)),
171            Payload::Link { disposition, parsed_data } => {
172                Payload::Link { disposition, parsed_data: parsed_data.clone_generic() }
173            }
174            val => val,
175        }
176    }
177}
178
179impl<T: std::fmt::Display + Clone + Zero> ToGeneric for ArrayContent<T> {
180    fn to_generic(self) -> Payload {
181        match self {
182            ArrayContent::Values(values) => Payload::GenericArray(stringify_list(values)),
183            ArrayContent::LinearHistogram(histogram) => {
184                Payload::GenericLinearHistogram(stringify_linear_histogram(histogram))
185            }
186            ArrayContent::ExponentialHistogram(histogram) => {
187                Payload::GenericExponentialHistogram(stringify_exponential_histogram(histogram))
188            }
189        }
190    }
191}
192
193struct FormattedEntries {
194    nodes: Vec<String>,
195    properties: Vec<String>,
196}
197
198impl Property {
199    fn to_string(&self, prefix: &str) -> String {
200        format!("{}{}: {:?}", prefix, self.name, &self.payload)
201    }
202
203    /// Formats this property and any additional properties and nodes it may contain (in the case
204    /// of links).
205    fn format_entries(&self, prefix: &str) -> FormattedEntries {
206        match &self.payload {
207            Payload::Link { disposition, parsed_data } => match disposition {
208                // Return a node for the child, replacing its name.
209                LinkNodeDisposition::Child => FormattedEntries {
210                    nodes: vec![format!(
211                        "{}{} ->\n{}",
212                        prefix,
213                        self.name,
214                        parsed_data.nodes[&0.into()].to_string(prefix, parsed_data, true)
215                    )],
216                    properties: vec![],
217                },
218                // Return the nodes and properties (which may themselves have linked nodes) inline
219                // from this property.
220                LinkNodeDisposition::Inline => {
221                    let root = &parsed_data.nodes[&BlockIndex::ROOT];
222                    let mut nodes = root
223                        .children
224                        .iter()
225                        .map(|v| {
226                            parsed_data.nodes.get(v).map_or("Missing child".into(), |n| {
227                                n.to_string(prefix, parsed_data, false)
228                            })
229                        })
230                        .collect::<Vec<_>>();
231                    let mut properties = vec![];
232                    for FormattedEntries { nodes: mut n, properties: mut p } in
233                        root.properties.iter().map(|v| {
234                            parsed_data.properties.get(v).map_or(
235                                FormattedEntries {
236                                    nodes: vec![],
237                                    properties: vec!["Missing property".into()],
238                                },
239                                |p| p.format_entries(prefix),
240                            )
241                        })
242                    {
243                        nodes.append(&mut n);
244                        properties.append(&mut p);
245                    }
246                    FormattedEntries { nodes, properties }
247                }
248            },
249            // Non-link property, just format as the only returned property.
250            _ => FormattedEntries { nodes: vec![], properties: vec![self.to_string(prefix)] },
251        }
252    }
253}
254
255impl Node {
256    /// If `hide_root` is true and the node is the root,
257    /// then the name and and prefix of the generated string is omitted.
258    /// This is used for lazy nodes wherein we don't what to show the label "root" for lazy nodes.
259    fn to_string(&self, prefix: &str, tree: &Data, hide_root: bool) -> String {
260        let sub_prefix = format!("{prefix}> ");
261        let mut nodes = vec![];
262        for node_id in self.children.iter() {
263            nodes.push(
264                tree.nodes
265                    .get(node_id)
266                    .map_or("Missing child".into(), |n| n.to_string(&sub_prefix, tree, hide_root)),
267            );
268        }
269        let mut properties = vec![];
270
271        for property_id in self.properties.iter() {
272            let FormattedEntries { nodes: mut n, properties: mut p } =
273                tree.properties.get(property_id).map_or(
274                    FormattedEntries {
275                        nodes: vec![],
276                        properties: vec!["Missing property".to_string()],
277                    },
278                    |p| p.format_entries(&sub_prefix),
279                );
280            properties.append(&mut p);
281            nodes.append(&mut n);
282        }
283
284        nodes.sort_unstable();
285        properties.sort_unstable();
286
287        let mut output_lines = vec![];
288
289        if self.name != ROOT_NAME || !hide_root {
290            output_lines.push(format!("{}{} ->", prefix, self.name));
291        }
292        output_lines.append(&mut properties);
293        output_lines.append(&mut nodes);
294
295        output_lines.join("\n")
296    }
297}
298
299struct Op {
300    int: fn(i64, i64) -> i64,
301    uint: fn(u64, u64) -> u64,
302    double: fn(f64, f64) -> f64,
303    string: fn(String, String) -> String,
304    name: &'static str,
305}
306
307const ADD: Op = Op {
308    int: |a, b| a + b,
309    uint: |a, b| a + b,
310    double: |a, b| a + b,
311    string: |_, _| unreachable!(),
312    name: "add",
313};
314const SUBTRACT: Op = Op {
315    int: |a, b| a - b,
316    uint: |a, b| a - b,
317    double: |a, b| a - b,
318    string: |_, _| unreachable!(),
319    name: "subtract",
320};
321const SET: Op =
322    Op { int: |_a, b| b, uint: |_a, b| b, double: |_a, b| b, string: |_a, b| b, name: "set" };
323
324macro_rules! insert_linear_fn {
325    ($name:ident, $type:ident) => {
326        fn $name(numbers: &mut [$type], value: $type, count: u64) -> Result<(), Error> {
327            let buckets: $type = (numbers.len() as i32 - 4).try_into().unwrap();
328            let floor = numbers[0];
329            let step_size = numbers[1];
330            let index: usize = if value < floor {
331                2
332            } else if value >= floor + buckets * step_size {
333                numbers.len() - 1
334            } else {
335                (((value - floor) / step_size) as $type + 3 as $type) as i32 as usize
336            };
337            numbers[index] += count as $type;
338            Ok(())
339        }
340    };
341}
342
343insert_linear_fn! {insert_linear_i, i64}
344insert_linear_fn! {insert_linear_u, u64}
345insert_linear_fn! {insert_linear_d, f64}
346
347// DO NOT USE this algorithm in non-test libraries!
348// It's good to implement the test with a different algorithm than the code being tested.
349// But this is a BAD algorithm in real life.
350// 1) Too many casts - extreme values may not be handled correctly.
351// 2) Floating point math is imprecise; int/uint values over 2^56 or so won't be
352//     calculated correctly because they can't be expressed precisely, and the log2/log2
353//     division may come down on the wrong side of the bucket boundary. That's why there's
354//     a fudge factor added to int results - but that's only correct up to a million or so.
355macro_rules! insert_exponential_fn {
356    ($name:ident, $type:ident, $fudge_factor:expr) => {
357        fn $name(numbers: &mut [$type], value: $type, count: u64) -> Result<(), Error> {
358            let buckets = numbers.len() - 5;
359            let floor = numbers[0];
360            let initial_step = numbers[1];
361            let step_multiplier = numbers[2];
362            let index = if value < floor {
363                3
364            } else if value < floor + initial_step {
365                4
366            } else if value
367                >= floor + initial_step * (step_multiplier as f64).powi(buckets as i32 - 1) as $type
368            {
369                numbers.len() - 1
370            } else {
371                ((((value as f64 - floor as f64) / initial_step as f64) as f64).log2()
372                    / (step_multiplier as f64 + $fudge_factor).log2())
373                .trunc() as usize
374                    + 5
375            };
376            numbers[index] += count as $type;
377            Ok(())
378        }
379    };
380}
381
382insert_exponential_fn! {insert_exponential_i, i64, 0.0000000000000000000001}
383insert_exponential_fn! {insert_exponential_u, u64, 0.0000000000000000000001}
384insert_exponential_fn! {insert_exponential_d, f64, 0.0}
385
386impl Data {
387    // ***** Here are the functions to apply Actions to a Data.
388
389    /// Applies the given action to this in-memory state.
390    pub fn apply(&mut self, action: &validate::Action) -> Result<(), Error> {
391        match action {
392            validate::Action::CreateNode(validate::CreateNode { parent, id, name }) => {
393                self.create_node(parent.into(), id.into(), name)
394            }
395            validate::Action::DeleteNode(validate::DeleteNode { id }) => {
396                self.delete_node(id.into())
397            }
398            validate::Action::CreateNumericProperty(validate::CreateNumericProperty {
399                parent,
400                id,
401                name,
402                value,
403            }) => self.add_property(
404                parent.into(),
405                id.into(),
406                name,
407                match value {
408                    validate::Value::IntT(value) => Payload::Int(*value),
409                    validate::Value::UintT(value) => Payload::Uint(*value),
410                    validate::Value::DoubleT(value) => Payload::Double(*value),
411                    unknown => return Err(format_err!("Unknown number type {:?}", unknown)),
412                },
413            ),
414            validate::Action::CreateBytesProperty(validate::CreateBytesProperty {
415                parent,
416                id,
417                name,
418                value,
419            }) => self.add_property(parent.into(), id.into(), name, Payload::Bytes(value.clone())),
420            validate::Action::CreateStringProperty(validate::CreateStringProperty {
421                parent,
422                id,
423                name,
424                value,
425            }) => self.add_property(
426                parent.into(),
427                id.into(),
428                name,
429                Payload::String(value.to_string()),
430            ),
431            validate::Action::CreateBoolProperty(validate::CreateBoolProperty {
432                parent,
433                id,
434                name,
435                value,
436            }) => self.add_property(parent.into(), id.into(), name, Payload::Bool(*value)),
437            validate::Action::DeleteProperty(validate::DeleteProperty { id }) => {
438                self.delete_property(id.into())
439            }
440            validate::Action::SetNumber(validate::SetNumber { id, value }) => {
441                self.modify_number(id.into(), value, SET)
442            }
443            validate::Action::AddNumber(validate::AddNumber { id, value }) => {
444                self.modify_number(id.into(), value, ADD)
445            }
446            validate::Action::SubtractNumber(validate::SubtractNumber { id, value }) => {
447                self.modify_number(id.into(), value, SUBTRACT)
448            }
449            validate::Action::SetBytes(validate::SetBytes { id, value }) => {
450                self.set_bytes(id.into(), value)
451            }
452            validate::Action::SetString(validate::SetString { id, value }) => {
453                self.set_string(id.into(), value)
454            }
455            validate::Action::SetBool(validate::SetBool { id, value }) => {
456                self.set_bool(id.into(), *value)
457            }
458            validate::Action::CreateArrayProperty(validate::CreateArrayProperty {
459                parent,
460                id,
461                name,
462                slots,
463                value_type,
464            }) => self.add_property(
465                parent.into(),
466                id.into(),
467                name,
468                match value_type {
469                    validate::ValueType::Int => {
470                        Payload::IntArray(vec![0; *slots as usize], ArrayFormat::Default)
471                    }
472                    validate::ValueType::Uint => {
473                        Payload::UintArray(vec![0; *slots as usize], ArrayFormat::Default)
474                    }
475                    validate::ValueType::Double => {
476                        Payload::DoubleArray(vec![0.0; *slots as usize], ArrayFormat::Default)
477                    }
478                    validate::ValueType::String => {
479                        Payload::StringArray(vec![String::new(); *slots as usize])
480                    }
481                    unknown => return Err(format_err!("unknown value type {unknown:?}")),
482                },
483            ),
484            validate::Action::ArrayAdd(validate::ArrayAdd { id, index, value }) => {
485                self.modify_array(id.into(), *index, value, ADD)
486            }
487            validate::Action::ArraySubtract(validate::ArraySubtract { id, index, value }) => {
488                self.modify_array(id.into(), *index, value, SUBTRACT)
489            }
490            validate::Action::ArraySet(validate::ArraySet { id, index, value }) => {
491                self.modify_array(id.into(), *index, value, SET)
492            }
493            validate::Action::CreateLinearHistogram(validate::CreateLinearHistogram {
494                parent,
495                id,
496                name,
497                floor,
498                step_size,
499                buckets,
500            }) => self.add_property(
501                parent.into(),
502                id.into(),
503                name,
504                match (floor, step_size) {
505                    (validate::Value::IntT(floor), validate::Value::IntT(step_size)) => {
506                        let mut data = vec![0; *buckets as usize + 4];
507                        data[0] = *floor;
508                        data[1] = *step_size;
509                        Payload::IntArray(data, ArrayFormat::LinearHistogram)
510                    }
511                    (validate::Value::UintT(floor), validate::Value::UintT(step_size)) => {
512                        let mut data = vec![0; *buckets as usize + 4];
513                        data[0] = *floor;
514                        data[1] = *step_size;
515                        Payload::UintArray(data, ArrayFormat::LinearHistogram)
516                    }
517                    (validate::Value::DoubleT(floor), validate::Value::DoubleT(step_size)) => {
518                        let mut data = vec![0.0; *buckets as usize + 4];
519                        data[0] = *floor;
520                        data[1] = *step_size;
521                        Payload::DoubleArray(data, ArrayFormat::LinearHistogram)
522                    }
523                    unexpected => {
524                        return Err(format_err!(
525                            "Bad types in CreateLinearHistogram: {:?}",
526                            unexpected
527                        ));
528                    }
529                },
530            ),
531            validate::Action::CreateExponentialHistogram(
532                validate::CreateExponentialHistogram {
533                    parent,
534                    id,
535                    name,
536                    floor,
537                    initial_step,
538                    step_multiplier,
539                    buckets,
540                },
541            ) => self.add_property(
542                parent.into(),
543                id.into(),
544                name,
545                match (floor, initial_step, step_multiplier) {
546                    (
547                        validate::Value::IntT(floor),
548                        validate::Value::IntT(initial_step),
549                        validate::Value::IntT(step_multiplier),
550                    ) => {
551                        let mut data = vec![0i64; *buckets as usize + 5];
552                        data[0] = *floor;
553                        data[1] = *initial_step;
554                        data[2] = *step_multiplier;
555                        Payload::IntArray(data, ArrayFormat::ExponentialHistogram)
556                    }
557                    (
558                        validate::Value::UintT(floor),
559                        validate::Value::UintT(initial_step),
560                        validate::Value::UintT(step_multiplier),
561                    ) => {
562                        let mut data = vec![0u64; *buckets as usize + 5];
563                        data[0] = *floor;
564                        data[1] = *initial_step;
565                        data[2] = *step_multiplier;
566                        Payload::UintArray(data, ArrayFormat::ExponentialHistogram)
567                    }
568                    (
569                        validate::Value::DoubleT(floor),
570                        validate::Value::DoubleT(initial_step),
571                        validate::Value::DoubleT(step_multiplier),
572                    ) => {
573                        let mut data = vec![0.0f64; *buckets as usize + 5];
574                        data[0] = *floor;
575                        data[1] = *initial_step;
576                        data[2] = *step_multiplier;
577                        Payload::DoubleArray(data, ArrayFormat::ExponentialHistogram)
578                    }
579                    unexpected => {
580                        return Err(format_err!(
581                            "Bad types in CreateExponentialHistogram: {:?}",
582                            unexpected
583                        ));
584                    }
585                },
586            ),
587            validate::Action::Insert(validate::Insert { id, value }) => {
588                if let Some(mut property) = self.properties.get_mut(&id.into()) {
589                    match (&mut property, value) {
590                        (
591                            Property {
592                                payload: Payload::IntArray(numbers, ArrayFormat::LinearHistogram),
593                                ..
594                            },
595                            Value::IntT(value),
596                        ) => insert_linear_i(numbers, *value, 1),
597                        (
598                            Property {
599                                payload:
600                                    Payload::IntArray(numbers, ArrayFormat::ExponentialHistogram),
601                                ..
602                            },
603                            Value::IntT(value),
604                        ) => insert_exponential_i(numbers, *value, 1),
605                        (
606                            Property {
607                                payload: Payload::UintArray(numbers, ArrayFormat::LinearHistogram),
608                                ..
609                            },
610                            Value::UintT(value),
611                        ) => insert_linear_u(numbers, *value, 1),
612                        (
613                            Property {
614                                payload:
615                                    Payload::UintArray(numbers, ArrayFormat::ExponentialHistogram),
616                                ..
617                            },
618                            Value::UintT(value),
619                        ) => insert_exponential_u(numbers, *value, 1),
620                        (
621                            Property {
622                                payload: Payload::DoubleArray(numbers, ArrayFormat::LinearHistogram),
623                                ..
624                            },
625                            Value::DoubleT(value),
626                        ) => insert_linear_d(numbers, *value, 1),
627                        (
628                            Property {
629                                payload:
630                                    Payload::DoubleArray(numbers, ArrayFormat::ExponentialHistogram),
631                                ..
632                            },
633                            Value::DoubleT(value),
634                        ) => insert_exponential_d(numbers, *value, 1),
635                        unexpected => {
636                            Err(format_err!("Type mismatch {:?} trying to insert", unexpected))
637                        }
638                    }
639                } else {
640                    Err(format_err!("Tried to insert number on nonexistent property {}", id))
641                }
642            }
643            validate::Action::InsertMultiple(validate::InsertMultiple { id, value, count }) => {
644                if let Some(mut property) = self.properties.get_mut(&id.into()) {
645                    match (&mut property, value) {
646                        (
647                            Property {
648                                payload: Payload::IntArray(numbers, ArrayFormat::LinearHistogram),
649                                ..
650                            },
651                            Value::IntT(value),
652                        ) => insert_linear_i(numbers, *value, *count),
653                        (
654                            Property {
655                                payload:
656                                    Payload::IntArray(numbers, ArrayFormat::ExponentialHistogram),
657                                ..
658                            },
659                            Value::IntT(value),
660                        ) => insert_exponential_i(numbers, *value, *count),
661                        (
662                            Property {
663                                payload: Payload::UintArray(numbers, ArrayFormat::LinearHistogram),
664                                ..
665                            },
666                            Value::UintT(value),
667                        ) => insert_linear_u(numbers, *value, *count),
668                        (
669                            Property {
670                                payload:
671                                    Payload::UintArray(numbers, ArrayFormat::ExponentialHistogram),
672                                ..
673                            },
674                            Value::UintT(value),
675                        ) => insert_exponential_u(numbers, *value, *count),
676                        (
677                            Property {
678                                payload: Payload::DoubleArray(numbers, ArrayFormat::LinearHistogram),
679                                ..
680                            },
681                            Value::DoubleT(value),
682                        ) => insert_linear_d(numbers, *value, *count),
683                        (
684                            Property {
685                                payload:
686                                    Payload::DoubleArray(numbers, ArrayFormat::ExponentialHistogram),
687                                ..
688                            },
689                            Value::DoubleT(value),
690                        ) => insert_exponential_d(numbers, *value, *count),
691                        unexpected => Err(format_err!(
692                            "Type mismatch {:?} trying to insert multiple",
693                            unexpected
694                        )),
695                    }
696                } else {
697                    Err(format_err!(
698                        "Tried to insert_multiple number on nonexistent property {}",
699                        id
700                    ))
701                }
702            }
703            _ => Err(format_err!("Unknown action {:?}", action)),
704        }
705    }
706
707    pub fn apply_lazy(&mut self, lazy_action: &validate::LazyAction) -> Result<(), Error> {
708        match lazy_action {
709            validate::LazyAction::CreateLazyNode(validate::CreateLazyNode {
710                parent,
711                id,
712                name,
713                disposition,
714                actions,
715            }) => self.add_lazy_node(parent.into(), id.into(), name, disposition, actions),
716            validate::LazyAction::DeleteLazyNode(validate::DeleteLazyNode { id }) => {
717                self.delete_property(id.into())
718            }
719            _ => Err(format_err!("Unknown lazy action {:?}", lazy_action)),
720        }
721    }
722
723    fn create_node(&mut self, parent: BlockIndex, id: BlockIndex, name: &str) -> Result<(), Error> {
724        let node = Node {
725            name: name.to_owned(),
726            parent,
727            children: HashSet::new(),
728            properties: HashSet::new(),
729        };
730        if self.tombstone_nodes.contains(&id) {
731            return Err(format_err!("Tried to create implicitly deleted node {}", id));
732        }
733        if self.nodes.insert(id, node).is_some() {
734            return Err(format_err!("Create called when node already existed at {}", id));
735        }
736        if let Some(parent_node) = self.nodes.get_mut(&parent) {
737            parent_node.children.insert(id);
738        } else {
739            return Err(format_err!("Parent {} of created node {} doesn't exist", parent, id));
740        }
741        Ok(())
742    }
743
744    fn delete_node(&mut self, id: BlockIndex) -> Result<(), Error> {
745        if id == BlockIndex::ROOT {
746            return Err(format_err!("Do not try to delete node 0"));
747        }
748        if self.tombstone_nodes.remove(&id) {
749            return Ok(());
750        }
751        if let Some(node) = self.nodes.remove(&id) {
752            // Tombstone all descendents. An orphan descendent may reappear improperly if a new
753            // node is created with a recycled ID.
754            for child in node.children.clone().iter() {
755                self.make_tombstone_node(*child)?;
756            }
757            for property in node.properties.clone().iter() {
758                self.make_tombstone_property(*property)?;
759            }
760            if let Some(parent) = self.nodes.get_mut(&node.parent) {
761                if !parent.children.remove(&id) {
762                    // Some of these can only happen in case of internal logic errors.
763                    // I can't think of a way to test them; I think the errors are
764                    // actually impossible. Should I leave them untested? Remove them
765                    // from the code? Add a special test_cfg make_illegal_node()
766                    // function just to test them?
767                    bail!(
768                        "Internal error! Parent {} didn't know about this child {}",
769                        node.parent,
770                        id
771                    );
772                }
773            }
774        } else {
775            return Err(format_err!("Delete of nonexistent node {}", id));
776        }
777        Ok(())
778    }
779
780    fn make_tombstone_node(&mut self, id: BlockIndex) -> Result<(), Error> {
781        if id == BlockIndex::ROOT {
782            return Err(format_err!("Internal error! Do not try to delete node 0."));
783        }
784        if let Some(node) = self.nodes.remove(&id) {
785            for child in node.children.clone().iter() {
786                self.make_tombstone_node(*child)?;
787            }
788            for property in node.properties.clone().iter() {
789                self.make_tombstone_property(*property)?;
790            }
791        } else {
792            return Err(format_err!("Internal error! Tried to tombstone nonexistent node {}", id));
793        }
794        self.tombstone_nodes.insert(id);
795        Ok(())
796    }
797
798    fn make_tombstone_property(&mut self, id: BlockIndex) -> Result<(), Error> {
799        if self.properties.remove(&id).is_none() {
800            return Err(format_err!(
801                "Internal error! Tried to tombstone nonexistent property {}",
802                id
803            ));
804        }
805        self.tombstone_properties.insert(id);
806        Ok(())
807    }
808
809    fn add_property(
810        &mut self,
811        parent: BlockIndex,
812        id: BlockIndex,
813        name: &str,
814        payload: Payload,
815    ) -> Result<(), Error> {
816        if let Some(node) = self.nodes.get_mut(&parent) {
817            node.properties.insert(id);
818        } else {
819            return Err(format_err!("Parent {} of property {} not found", parent, id));
820        }
821        if self.tombstone_properties.contains(&id) {
822            return Err(format_err!("Tried to create implicitly deleted property {}", id));
823        }
824        let property = Property { parent, id, name: name.into(), payload };
825        if self.properties.insert(id, property).is_some() {
826            return Err(format_err!("Property insert called on existing id {}", id));
827        }
828        Ok(())
829    }
830
831    fn delete_property(&mut self, id: BlockIndex) -> Result<(), Error> {
832        if self.tombstone_properties.remove(&id) {
833            return Ok(());
834        }
835        if let Some(property) = self.properties.remove(&id) {
836            if let Some(node) = self.nodes.get_mut(&property.parent) {
837                if !node.properties.remove(&id) {
838                    bail!(
839                        "Internal error! Property {}'s parent {} didn't have it as child",
840                        id,
841                        property.parent
842                    );
843                }
844            } else {
845                bail!(
846                    "Internal error! Property {}'s parent {} doesn't exist on delete",
847                    id,
848                    property.parent
849                );
850            }
851        } else {
852            return Err(format_err!("Delete of nonexistent property {}", id));
853        }
854        Ok(())
855    }
856
857    fn modify_number(
858        &mut self,
859        id: BlockIndex,
860        value: &validate::Value,
861        op: Op,
862    ) -> Result<(), Error> {
863        if let Some(property) = self.properties.get_mut(&id) {
864            match (&property, value) {
865                (Property { payload: Payload::Int(old), .. }, Value::IntT(value)) => {
866                    property.payload = Payload::Int((op.int)(*old, *value));
867                }
868                (Property { payload: Payload::Uint(old), .. }, Value::UintT(value)) => {
869                    property.payload = Payload::Uint((op.uint)(*old, *value));
870                }
871                (Property { payload: Payload::Double(old), .. }, Value::DoubleT(value)) => {
872                    property.payload = Payload::Double((op.double)(*old, *value));
873                }
874                unexpected => {
875                    return Err(format_err!("Bad types {:?} trying to set number", unexpected));
876                }
877            }
878        } else {
879            return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
880        }
881        Ok(())
882    }
883
884    fn modify_array(
885        &mut self,
886        id: BlockIndex,
887        index64: u64,
888        value: &validate::Value,
889        op: Op,
890    ) -> Result<(), Error> {
891        if let Some(mut property) = self.properties.get_mut(&id) {
892            let index = index64 as usize;
893            // Out of range index is a NOP, not an error.
894            let array_len = match &property {
895                Property { payload: Payload::IntArray(numbers, ArrayFormat::Default), .. } => {
896                    numbers.len()
897                }
898                Property { payload: Payload::UintArray(numbers, ArrayFormat::Default), .. } => {
899                    numbers.len()
900                }
901                Property {
902                    payload: Payload::DoubleArray(numbers, ArrayFormat::Default), ..
903                } => numbers.len(),
904                Property { payload: Payload::StringArray(values), .. } => values.len(),
905                unexpected => {
906                    return Err(format_err!(
907                        "Bad types {:?} trying to set number of elements",
908                        unexpected
909                    ));
910                }
911            };
912            if index >= array_len {
913                return Ok(());
914            }
915            match (&mut property, value) {
916                (Property { payload: Payload::IntArray(numbers, _), .. }, Value::IntT(value)) => {
917                    numbers[index] = (op.int)(numbers[index], *value);
918                }
919                (Property { payload: Payload::UintArray(numbers, _), .. }, Value::UintT(value)) => {
920                    numbers[index] = (op.uint)(numbers[index], *value);
921                }
922                (
923                    Property { payload: Payload::DoubleArray(numbers, _), .. },
924                    Value::DoubleT(value),
925                ) => {
926                    numbers[index] = (op.double)(numbers[index], *value);
927                }
928                (Property { payload: Payload::StringArray(values), .. }, Value::StringT(value)) => {
929                    values[index] = (op.string)(values[index].clone(), value.clone());
930                }
931                unexpected => {
932                    return Err(format_err!("Type mismatch {:?} trying to set value", unexpected));
933                }
934            }
935        } else {
936            return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
937        }
938        Ok(())
939    }
940
941    fn set_string(&mut self, id: BlockIndex, value: &String) -> Result<(), Error> {
942        if let Some(property) = self.properties.get_mut(&id) {
943            match &property {
944                Property { payload: Payload::String(_), .. } => {
945                    property.payload = Payload::String(value.to_owned())
946                }
947                unexpected => {
948                    return Err(format_err!("Bad type {:?} trying to set string", unexpected));
949                }
950            }
951        } else {
952            return Err(format_err!("Tried to set string on nonexistent property {}", id));
953        }
954        Ok(())
955    }
956
957    fn set_bytes(&mut self, id: BlockIndex, value: &Vec<u8>) -> Result<(), Error> {
958        if let Some(property) = self.properties.get_mut(&id) {
959            match &property {
960                Property { payload: Payload::Bytes(_), .. } => {
961                    property.payload = Payload::Bytes(value.to_owned())
962                }
963                unexpected => {
964                    return Err(format_err!("Bad type {:?} trying to set bytes", unexpected));
965                }
966            }
967        } else {
968            return Err(format_err!("Tried to set bytes on nonexistent property {}", id));
969        }
970        Ok(())
971    }
972
973    fn set_bool(&mut self, id: BlockIndex, value: bool) -> Result<(), Error> {
974        if let Some(property) = self.properties.get_mut(&id) {
975            match &property {
976                Property { payload: Payload::Bool(_), .. } => {
977                    property.payload = Payload::Bool(value)
978                }
979                unexpected => {
980                    return Err(format_err!("Bad type {:?} trying to set bool", unexpected));
981                }
982            }
983        } else {
984            return Err(format_err!("Tried to set bool on nonexistent property {}", id));
985        }
986        Ok(())
987    }
988
989    fn add_lazy_node(
990        &mut self,
991        parent: BlockIndex,
992        id: BlockIndex,
993        name: &str,
994        disposition: &validate::LinkDisposition,
995        actions: &Vec<validate::Action>,
996    ) -> Result<(), Error> {
997        let mut parsed_data = Data::new();
998        parsed_data.apply_multiple(actions)?;
999        self.add_property(
1000            parent,
1001            id,
1002            name,
1003            Payload::Link {
1004                disposition: match disposition {
1005                    validate::LinkDisposition::Child => LinkNodeDisposition::Child,
1006                    validate::LinkDisposition::Inline => LinkNodeDisposition::Inline,
1007                },
1008                parsed_data,
1009            },
1010        )?;
1011        Ok(())
1012    }
1013
1014    // ***** Here are the functions to compare two Data (by converting to a
1015    // ***** fully informative string).
1016
1017    /// Make a clone of this Data with all properties replaced with their generic version.
1018    fn clone_generic(&self) -> Self {
1019        let mut clone = self.clone();
1020
1021        let mut to_remove = vec![];
1022        let mut names = HashSet::new();
1023
1024        clone.properties = clone
1025            .properties
1026            .into_iter()
1027            .filter_map(|(id, mut v)| {
1028                // We do not support duplicate property names within a single node in our JSON
1029                // output.
1030                // Delete one of the nodes from the tree.
1031                //
1032                // Note: This can cause errors if the children do not have the same properties.
1033                if !names.insert((v.parent, v.name.clone())) {
1034                    to_remove.push((v.parent, id));
1035                    None
1036                } else {
1037                    v.payload = v.payload.to_generic();
1038                    Some((id, v))
1039                }
1040            })
1041            .collect::<HashMap<BlockIndex, Property>>();
1042
1043        // Clean up removed properties.
1044        for (parent, id) in to_remove {
1045            if clone.nodes.contains_key(&parent) {
1046                clone.nodes.get_mut(&parent).unwrap().properties.remove(&id);
1047            }
1048        }
1049
1050        clone
1051    }
1052
1053    /// Compare this data with data that should be equivalent but was parsed from JSON.
1054    ///
1055    /// This method tweaks some types to deal with JSON representation of the data, which is not as
1056    /// precise as the Inspect format itself.
1057    pub fn compare_to_json(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1058        self.clone_generic().compare(other, diff_type)
1059    }
1060
1061    /// Compares two in-memory Inspect trees, returning Ok(()) if they have the
1062    /// same data and an Err<> if they are different. The string in the Err<>
1063    /// may be very large.
1064    pub fn compare(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1065        let self_string = self.to_string();
1066        let other_string = other.to_string();
1067
1068        let difference::Changeset { diffs, distance, .. } =
1069            difference::Changeset::new(&self_string, &other_string, "\n");
1070
1071        if distance == 0 {
1072            Ok(())
1073        } else {
1074            let diff_lines = diffs
1075                .into_iter()
1076                .flat_map(|diff| {
1077                    let (prefix, val) = match diff {
1078                        // extra space so that all ':'s in output are aligned
1079                        difference::Difference::Same(val) => (" same", val),
1080                        difference::Difference::Add(val) => ("other", val),
1081                        difference::Difference::Rem(val) => ("local", val),
1082                    };
1083                    val.split("\n").map(|line| format!("{prefix}: {line:?}")).collect::<Vec<_>>()
1084                })
1085                .collect::<Vec<_>>();
1086
1087            match diff_type {
1088                DiffType::Full => Err(format_err!(
1089                    "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}",
1090                    self_string,
1091                    other_string
1092                )),
1093                DiffType::Diff => {
1094                    Err(format_err!("Trees differ:\n-- DIFF --\n{}", diff_lines.join("\n")))
1095                }
1096                DiffType::Both => Err(format_err!(
1097                    "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}\n-- DIFF --\n{}",
1098                    self_string,
1099                    other_string,
1100                    diff_lines.join("\n")
1101                )),
1102            }
1103        }
1104    }
1105
1106    /// This creates a new Data. Note that the standard "root" node of the VMO API
1107    /// corresponds to the index-0 node added here.
1108    pub fn new() -> Data {
1109        let mut ret = Data {
1110            nodes: HashMap::new(),
1111            properties: HashMap::new(),
1112            tombstone_nodes: HashSet::new(),
1113            tombstone_properties: HashSet::new(),
1114        };
1115        ret.nodes.insert(
1116            BlockIndex::ROOT,
1117            Node {
1118                name: ROOT_NAME.into(),
1119                parent: BlockIndex::ROOT,
1120                children: HashSet::new(),
1121                properties: HashSet::new(),
1122            },
1123        );
1124        ret
1125    }
1126
1127    fn build(nodes: HashMap<BlockIndex, Node>, properties: HashMap<BlockIndex, Property>) -> Data {
1128        Data {
1129            nodes,
1130            properties,
1131            tombstone_nodes: HashSet::new(),
1132            tombstone_properties: HashSet::new(),
1133        }
1134    }
1135
1136    fn apply_multiple(&mut self, actions: &Vec<validate::Action>) -> Result<(), Error> {
1137        for action in actions {
1138            self.apply(action)?;
1139        }
1140        Ok(())
1141    }
1142
1143    /// Removes a top-level tree if present (if not, does nothing)
1144    pub fn remove_tree(&mut self, name: &str) {
1145        if self.is_empty() {
1146            return;
1147        }
1148        let mut target_node_id = None;
1149        let root = &self.nodes[&BlockIndex::ROOT];
1150        for child_id in &root.children {
1151            if let Some(node) = self.nodes.get(child_id) {
1152                if node.name == name {
1153                    target_node_id = Some(*child_id);
1154                    break;
1155                }
1156            }
1157        }
1158        if let Some(id) = target_node_id {
1159            self.delete_node(id).unwrap();
1160        }
1161    }
1162
1163    /// Return true if this data is not just an empty root node.
1164    pub fn is_empty(&self) -> bool {
1165        if !self.nodes.contains_key(&BlockIndex::ROOT) {
1166            // No root
1167            return true;
1168        }
1169
1170        // There are issues with displaying a tree that has no properties.
1171        // TODO(https://fxbug.dev/42126876): Support empty trees in archive.
1172        if self.properties.len() == 0 {
1173            return true;
1174        }
1175
1176        // Root has no properties and any children it may have are tombstoned.
1177        let root = &self.nodes[&BlockIndex::ROOT];
1178        root.children.is_subset(&self.tombstone_nodes) && root.properties.len() == 0
1179    }
1180}
1181
1182impl fmt::Display for Data {
1183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1184        let s = if let Some(node) = self.nodes.get(&BlockIndex::ROOT) {
1185            node.to_string("", self, false)
1186        } else {
1187            "No root node; internal error\n".to_owned()
1188        };
1189        write!(f, "{s}",)
1190    }
1191}
1192
1193impl From<DiagnosticsHierarchy> for Data {
1194    fn from(hierarchy: DiagnosticsHierarchy) -> Self {
1195        let mut nodes = HashMap::new();
1196        let mut properties = HashMap::new();
1197
1198        nodes.insert(
1199            0.into(),
1200            Node {
1201                name: hierarchy.name.clone(),
1202                parent: 0.into(),
1203                children: HashSet::new(),
1204                properties: HashSet::new(),
1205            },
1206        );
1207
1208        let mut queue = vec![(0.into(), &hierarchy)];
1209        let mut next_id = BlockIndex::new(1);
1210
1211        while let Some((id, value)) = queue.pop() {
1212            for node in value.children.iter() {
1213                let child_id = next_id;
1214                next_id += 1;
1215                nodes.insert(
1216                    child_id,
1217                    Node {
1218                        name: node.name.clone(),
1219                        parent: id,
1220                        children: HashSet::new(),
1221                        properties: HashSet::new(),
1222                    },
1223                );
1224                nodes.get_mut(&id).expect("parent must exist").children.insert(child_id);
1225                queue.push((child_id, node));
1226            }
1227            for property in value.properties.iter() {
1228                let prop_id = next_id;
1229                next_id += 1;
1230
1231                let (name, payload) = match property.clone() {
1232                    iProperty::String(n, v) => (n, Payload::String(v).to_generic()),
1233                    iProperty::Bytes(n, v) => (n, Payload::Bytes(v).to_generic()),
1234                    iProperty::Int(n, v) => (n, Payload::Int(v).to_generic()),
1235                    iProperty::Uint(n, v) => (n, Payload::Uint(v).to_generic()),
1236                    iProperty::Double(n, v) => (n, Payload::Double(v).to_generic()),
1237                    iProperty::Bool(n, v) => (n, Payload::Bool(v).to_generic()),
1238                    iProperty::IntArray(n, content) => (n, content.to_generic()),
1239                    iProperty::UintArray(n, content) => (n, content.to_generic()),
1240                    iProperty::DoubleArray(n, content) => (n, content.to_generic()),
1241                    iProperty::StringList(n, content) => {
1242                        (n, Payload::StringArray(content).to_generic())
1243                    }
1244                };
1245
1246                properties.insert(prop_id, Property { name, id: prop_id, parent: id, payload });
1247                nodes.get_mut(&id).expect("parent must exist").properties.insert(prop_id);
1248            }
1249        }
1250
1251        Data {
1252            nodes,
1253            properties,
1254            tombstone_nodes: HashSet::new(),
1255            tombstone_properties: HashSet::new(),
1256        }
1257    }
1258}
1259
1260#[cfg(test)]
1261mod tests {
1262    use super::*;
1263    use crate::*;
1264    use fidl_diagnostics_validate::{ROOT_ID, ValueType};
1265    use fuchsia_inspect::reader::ArrayContent as iArrayContent;
1266    use inspect_format::BlockType;
1267    use num_derive::{FromPrimitive, ToPrimitive};
1268
1269    #[fuchsia::test]
1270    fn test_basic_data_strings() -> Result<(), Error> {
1271        let mut info = Data::new();
1272        assert_eq!(info.to_string(), "root ->");
1273
1274        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "foo"))?;
1275        assert_eq!(info.to_string(), "root ->\n> foo ->");
1276
1277        info.apply(&delete_node!( id: 1 ))?;
1278        assert_eq!(info.to_string(), "root ->");
1279
1280        Ok(())
1281    }
1282
1283    const EXPECTED_HIERARCHY: &str = r#"root ->
1284> double: GenericNumber("2.5")
1285> int: GenericNumber("-5")
1286> string: String("value")
1287> uint: GenericNumber("10")
1288> child ->
1289> > bytes: String("b64:AQI=")
1290> > grandchild ->
1291> > > double_a: GenericArray(["0.5", "1"])
1292> > > double_eh: GenericExponentialHistogram(["0.5", "0.5", "2", "1", "2", "3"])
1293> > > double_lh: GenericLinearHistogram(["0.5", "0.5", "1", "2", "3"])
1294> > > int_a: GenericArray(["-1", "-2"])
1295> > > int_eh: GenericExponentialHistogram(["-1", "1", "2", "1", "2", "3"])
1296> > > int_lh: GenericLinearHistogram(["-1", "1", "1", "2", "3"])
1297> > > uint_a: GenericArray(["1", "2"])
1298> > > uint_eh: GenericExponentialHistogram(["1", "1", "2", "1", "2", "3"])
1299> > > uint_lh: GenericLinearHistogram(["1", "1", "1", "2", "3"])"#;
1300
1301    // There's no enum in fuchsia_inspect::format::block which contains only
1302    // values that are valid for an ArrayType.
1303    #[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
1304    enum ArrayType {
1305        Int = 4,
1306        Uint = 5,
1307        Double = 6,
1308    }
1309
1310    #[fuchsia::test]
1311    fn test_parse_hierarchy() -> Result<(), Error> {
1312        let hierarchy = DiagnosticsHierarchy {
1313            name: "root".to_string(),
1314            properties: vec![
1315                iProperty::String("string".to_string(), "value".to_string()),
1316                iProperty::Uint("uint".to_string(), 10u64),
1317                iProperty::Int("int".to_string(), -5i64),
1318                iProperty::Double("double".to_string(), 2.5f64),
1319            ],
1320            children: vec![DiagnosticsHierarchy {
1321                name: "child".to_string(),
1322                properties: vec![iProperty::Bytes("bytes".to_string(), vec![1u8, 2u8])],
1323                children: vec![DiagnosticsHierarchy {
1324                    name: "grandchild".to_string(),
1325                    properties: vec![
1326                        iProperty::UintArray(
1327                            "uint_a".to_string(),
1328                            iArrayContent::Values(vec![1, 2]),
1329                        ),
1330                        iProperty::IntArray(
1331                            "int_a".to_string(),
1332                            iArrayContent::Values(vec![-1i64, -2i64]),
1333                        ),
1334                        iProperty::DoubleArray(
1335                            "double_a".to_string(),
1336                            iArrayContent::Values(vec![0.5, 1.0]),
1337                        ),
1338                        iProperty::UintArray(
1339                            "uint_lh".to_string(),
1340                            iArrayContent::new(vec![1, 1, 1, 2, 3], ArrayFormat::LinearHistogram)
1341                                .unwrap(),
1342                        ),
1343                        iProperty::IntArray(
1344                            "int_lh".to_string(),
1345                            iArrayContent::new(
1346                                vec![-1i64, 1, 1, 2, 3],
1347                                ArrayFormat::LinearHistogram,
1348                            )
1349                            .unwrap(),
1350                        ),
1351                        iProperty::DoubleArray(
1352                            "double_lh".to_string(),
1353                            iArrayContent::new(
1354                                vec![0.5, 0.5, 1.0, 2.0, 3.0],
1355                                ArrayFormat::LinearHistogram,
1356                            )
1357                            .unwrap(),
1358                        ),
1359                        iProperty::UintArray(
1360                            "uint_eh".to_string(),
1361                            iArrayContent::new(
1362                                vec![1, 1, 2, 1, 2, 3],
1363                                ArrayFormat::ExponentialHistogram,
1364                            )
1365                            .unwrap(),
1366                        ),
1367                        iProperty::IntArray(
1368                            "int_eh".to_string(),
1369                            iArrayContent::new(
1370                                vec![-1i64, 1, 2, 1, 2, 3],
1371                                ArrayFormat::ExponentialHistogram,
1372                            )
1373                            .unwrap(),
1374                        ),
1375                        iProperty::DoubleArray(
1376                            "double_eh".to_string(),
1377                            iArrayContent::new(
1378                                vec![0.5, 0.5, 2.0, 1.0, 2.0, 3.0],
1379                                ArrayFormat::ExponentialHistogram,
1380                            )
1381                            .unwrap(),
1382                        ),
1383                    ],
1384                    children: vec![],
1385                    missing: vec![],
1386                }],
1387                missing: vec![],
1388            }],
1389            missing: vec![],
1390        };
1391
1392        let data: Data = hierarchy.into();
1393        assert_eq!(EXPECTED_HIERARCHY, data.to_string());
1394
1395        Ok(())
1396    }
1397
1398    // Make sure every action correctly modifies the string representation of the data tree.
1399    #[fuchsia::test]
1400    fn test_creation_deletion() -> Result<(), Error> {
1401        let mut info = Data::new();
1402        assert!(!info.to_string().contains("child ->"));
1403        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
1404        assert!(info.to_string().contains("child ->"));
1405
1406        info.apply(&create_node!(parent: 1, id: 2, name: "grandchild"))?;
1407        assert!(
1408            info.to_string().contains("grandchild ->") && info.to_string().contains("child ->")
1409        );
1410
1411        info.apply(
1412            &create_numeric_property!(parent: ROOT_ID, id: 3, name: "int-42", value: Value::IntT(-42)),
1413        )?;
1414
1415        assert!(info.to_string().contains("int-42: Int(-42)")); // Make sure it can hold negative #
1416        info.apply(&create_string_property!(parent: 1, id: 4, name: "stringfoo", value: "foo"))?;
1417        assert_eq!(
1418            info.to_string(),
1419            "root ->\n> int-42: Int(-42)\n> child ->\
1420             \n> > stringfoo: String(\"foo\")\n> > grandchild ->"
1421        );
1422
1423        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 5, name: "uint", value: Value::UintT(1024)))?;
1424        assert!(info.to_string().contains("uint: Uint(1024)"));
1425
1426        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 6, name: "frac", value: Value::DoubleT(0.5)))?;
1427        assert!(info.to_string().contains("frac: Double(0.5)"));
1428
1429        info.apply(
1430            &create_bytes_property!(parent: ROOT_ID, id: 7, name: "bytes", value: vec!(1u8, 2u8)),
1431        )?;
1432        assert!(info.to_string().contains("bytes: Bytes([1, 2])"));
1433
1434        info.apply(&create_array_property!(parent: ROOT_ID, id: 8, name: "i_ntarr", slots: 1, type: ValueType::Int))?;
1435        assert!(info.to_string().contains("i_ntarr: IntArray([0], Default)"));
1436
1437        info.apply(&create_array_property!(parent: ROOT_ID, id: 9, name: "u_intarr", slots: 2, type: ValueType::Uint))?;
1438        assert!(info.to_string().contains("u_intarr: UintArray([0, 0], Default)"));
1439
1440        info.apply(&create_array_property!(parent: ROOT_ID, id: 10, name: "dblarr", slots: 3, type: ValueType::Double))?;
1441        assert!(info.to_string().contains("dblarr: DoubleArray([0.0, 0.0, 0.0], Default)"));
1442
1443        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 11, name: "ILhist", floor: 12,
1444            step_size: 3, buckets: 2, type: IntT))?;
1445        assert!(
1446            info.to_string().contains("ILhist: IntArray([12, 3, 0, 0, 0, 0], LinearHistogram)")
1447        );
1448
1449        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 12, name: "ULhist", floor: 34,
1450            step_size: 5, buckets: 2, type: UintT))?;
1451        assert!(
1452            info.to_string().contains("ULhist: UintArray([34, 5, 0, 0, 0, 0], LinearHistogram)")
1453        );
1454
1455        info.apply(
1456            &create_linear_histogram!(parent: ROOT_ID, id: 13, name: "DLhist", floor: 56.0,
1457            step_size: 7.0, buckets: 2, type: DoubleT),
1458        )?;
1459        assert!(
1460            info.to_string()
1461                .contains("DLhist: DoubleArray([56.0, 7.0, 0.0, 0.0, 0.0, 0.0], LinearHistogram)")
1462        );
1463
1464        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 14, name: "IEhist",
1465            floor: 12, initial_step: 3, step_multiplier: 5, buckets: 2, type: IntT))?;
1466        assert!(
1467            info.to_string()
1468                .contains("IEhist: IntArray([12, 3, 5, 0, 0, 0, 0], ExponentialHistogram)")
1469        );
1470
1471        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 15, name: "UEhist",
1472            floor: 34, initial_step: 9, step_multiplier: 6, buckets: 2, type: UintT))?;
1473        assert!(
1474            info.to_string()
1475                .contains("UEhist: UintArray([34, 9, 6, 0, 0, 0, 0], ExponentialHistogram)")
1476        );
1477
1478        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 16, name: "DEhist",
1479            floor: 56.0, initial_step: 27.0, step_multiplier: 7.0, buckets: 2, type: DoubleT))?;
1480        assert!(info.to_string().contains(
1481            "DEhist: DoubleArray([56.0, 27.0, 7.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1482        ));
1483
1484        info.apply(&create_bool_property!(parent: ROOT_ID, id: 17, name: "bool", value: true))?;
1485        assert!(info.to_string().contains("bool: Bool(true)"));
1486
1487        info.apply(&delete_property!(id: 3))?;
1488        assert!(!info.to_string().contains("int-42") && info.to_string().contains("stringfoo"));
1489        info.apply(&delete_property!(id: 4))?;
1490        assert!(!info.to_string().contains("stringfoo"));
1491        info.apply(&delete_property!(id: 5))?;
1492        assert!(!info.to_string().contains("uint"));
1493        info.apply(&delete_property!(id: 6))?;
1494        assert!(!info.to_string().contains("frac"));
1495        info.apply(&delete_property!(id: 7))?;
1496        assert!(!info.to_string().contains("bytes"));
1497        info.apply(&delete_property!(id: 8))?;
1498        assert!(!info.to_string().contains("i_ntarr"));
1499        info.apply(&delete_property!(id: 9))?;
1500        assert!(!info.to_string().contains("u_intarr"));
1501        info.apply(&delete_property!(id: 10))?;
1502        assert!(!info.to_string().contains("dblarr"));
1503        info.apply(&delete_property!(id: 11))?;
1504        assert!(!info.to_string().contains("ILhist"));
1505        info.apply(&delete_property!(id: 12))?;
1506        assert!(!info.to_string().contains("ULhist"));
1507        info.apply(&delete_property!(id: 13))?;
1508        assert!(!info.to_string().contains("DLhist"));
1509        info.apply(&delete_property!(id: 14))?;
1510        assert!(!info.to_string().contains("IEhist"));
1511        info.apply(&delete_property!(id: 15))?;
1512        assert!(!info.to_string().contains("UEhist"));
1513        info.apply(&delete_property!(id: 16))?;
1514        assert!(!info.to_string().contains("DEhist"));
1515        info.apply(&delete_property!(id: 17))?;
1516        assert!(!info.to_string().contains("bool"));
1517        info.apply(&delete_node!(id:2))?;
1518        assert!(!info.to_string().contains("grandchild") && info.to_string().contains("child"));
1519        info.apply(&delete_node!( id: 1 ))?;
1520        assert_eq!(info.to_string(), "root ->");
1521        Ok(())
1522    }
1523
1524    #[fuchsia::test]
1525    fn test_basic_int_ops() -> Result<(), Error> {
1526        let mut info = Data::new();
1527        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1528                                     value: Value::IntT(-42)))?;
1529        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_ok());
1530        assert!(info.to_string().contains("value: Int(-39)"));
1531        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1532        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1533        assert!(info.to_string().contains("value: Int(-39)"));
1534        assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_ok());
1535        assert!(info.to_string().contains("value: Int(-44)"));
1536        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1537        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1538        assert!(info.to_string().contains("value: Int(-44)"));
1539        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(22))).is_ok());
1540        assert!(info.to_string().contains("value: Int(22)"));
1541        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1542        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1543        assert!(info.to_string().contains("value: Int(22)"));
1544        Ok(())
1545    }
1546
1547    #[fuchsia::test]
1548    fn test_array_string_ops() -> Result<(), Error> {
1549        let mut info = Data::new();
1550        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1551                                           type: ValueType::String))?;
1552        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1553        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1554        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1555        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1556        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""]"#));
1557        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1558        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1559        assert!(
1560            info.apply(&array_subtract!(id: 3, index: 2,
1561                                            value: Value::DoubleT(5.0)))
1562                .is_err()
1563        );
1564        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1565        assert!(
1566            info.apply(&array_set!(id: 3, index: 1, value: Value::StringT("data".into()))).is_ok()
1567        );
1568        assert!(info.to_string().contains(r#"value: StringArray(["", "data", ""])"#));
1569        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1570        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1571        assert!(info.to_string().contains(r#"value: StringArray(["", "data", ""])"#));
1572        let long: String = (0..3000).map(|_| ".").collect();
1573        let expected = format!(r#"value: StringArray(["{}", "data", ""])"#, long.clone());
1574        assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::StringT(long))).is_ok());
1575        assert!(info.to_string().contains(&expected));
1576        Ok(())
1577    }
1578
1579    #[fuchsia::test]
1580    fn test_array_int_ops() -> Result<(), Error> {
1581        let mut info = Data::new();
1582        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1583                                           type: ValueType::Int))?;
1584        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1585        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1586        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1587        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1588        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1589        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_ok());
1590        assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1591        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1592        assert!(
1593            info.apply(&array_subtract!(id: 3, index: 2,
1594                                            value: Value::DoubleT(5.0)))
1595                .is_err()
1596        );
1597        assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1598        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(22))).is_ok());
1599        assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1600        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1601        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1602        assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1603        Ok(())
1604    }
1605
1606    #[fuchsia::test]
1607    fn test_linear_int_ops() -> Result<(), Error> {
1608        let mut info = Data::new();
1609        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1610                    floor: 4, step_size: 2, buckets: 2, type: IntT))?;
1611        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1612        assert!(info.apply(&insert!(id: 3, value: Value::IntT(4))).is_ok());
1613        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1614        assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1615        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1616        assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1617        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1618        assert!(info.apply(&insert!(id: 3, value: Value::IntT(8))).is_ok());
1619        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1620        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1621        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1622        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1623        assert!(info.to_string().contains("value: IntArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1624        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1625        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1626        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1627        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1628        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1629        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1630        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(7), count: 4)).is_ok());
1631        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 5, 2], LinearHistogram)"));
1632        Ok(())
1633    }
1634
1635    #[fuchsia::test]
1636    fn test_exponential_int_ops() -> Result<(), Error> {
1637        let mut info = Data::new();
1638        // Bucket boundaries are 5, 7, 13
1639        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1640                    floor: 5, initial_step: 2,
1641                    step_multiplier: 4, buckets: 2, type: IntT))?;
1642        assert!(
1643            info.to_string()
1644                .contains("value: IntArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)")
1645        );
1646        assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1647        assert!(
1648            info.to_string()
1649                .contains("value: IntArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)")
1650        );
1651        assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1652        assert!(
1653            info.to_string()
1654                .contains("value: IntArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)")
1655        );
1656        assert!(info.apply(&insert!(id: 3, value: Value::IntT(7))).is_ok());
1657        assert!(
1658            info.to_string()
1659                .contains("value: IntArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)")
1660        );
1661        assert!(info.apply(&insert!(id: 3, value: Value::IntT(13))).is_ok());
1662        assert!(
1663            info.to_string()
1664                .contains("value: IntArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)")
1665        );
1666        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1667        assert!(
1668            info.to_string()
1669                .contains("value: IntArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)")
1670        );
1671        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1672        assert!(
1673            info.to_string()
1674                .contains("value: IntArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1675        );
1676        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1677        assert!(
1678            info.to_string()
1679                .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)")
1680        );
1681        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1682        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1683        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1684        assert!(
1685            info.to_string()
1686                .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)")
1687        );
1688        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(12), count: 4)).is_ok());
1689        assert!(
1690            info.to_string()
1691                .contains("value: IntArray([5, 2, 4, 2, 2, 5, 2], ExponentialHistogram)")
1692        );
1693        Ok(())
1694    }
1695
1696    #[fuchsia::test]
1697    fn test_array_out_of_bounds_nop() -> Result<(), Error> {
1698        // Accesses to indexes beyond the array are legal and should have no effect on the data.
1699        let mut info = Data::new();
1700        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1701                                           type: ValueType::Int))?;
1702        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1703        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1704        assert!(info.apply(&array_add!(id: 3, index: 3, value: Value::IntT(3))).is_ok());
1705        assert!(info.apply(&array_add!(id: 3, index: 6, value: Value::IntT(3))).is_ok());
1706        assert!(info.apply(&array_add!(id: 3, index: 12345, value: Value::IntT(3))).is_ok());
1707        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1708        Ok(())
1709    }
1710
1711    #[fuchsia::test]
1712    fn test_basic_uint_ops() -> Result<(), Error> {
1713        let mut info = Data::new();
1714        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1715                                     value: Value::UintT(42)))?;
1716        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_ok());
1717        assert!(info.to_string().contains("value: Uint(45)"));
1718        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1719        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1720        assert!(info.to_string().contains("value: Uint(45)"));
1721        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_ok());
1722        assert!(info.to_string().contains("value: Uint(40)"));
1723        assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_err());
1724        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1725        assert!(info.to_string().contains("value: Uint(40)"));
1726        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(22))).is_ok());
1727        assert!(info.to_string().contains("value: Uint(22)"));
1728        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(23))).is_err());
1729        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1730        assert!(info.to_string().contains("value: Uint(22)"));
1731        Ok(())
1732    }
1733
1734    #[fuchsia::test]
1735    fn test_array_uint_ops() -> Result<(), Error> {
1736        let mut info = Data::new();
1737        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1738                                     type: ValueType::Uint))?;
1739        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_ok());
1740        assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1741        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1742        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1743        assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1744        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(22))).is_ok());
1745        assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1746        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1747        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1748        assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1749        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::UintT(5))).is_ok());
1750        assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1751        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::IntT(5))).is_err());
1752        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::DoubleT(5.0))).is_err());
1753        assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1754        Ok(())
1755    }
1756
1757    #[fuchsia::test]
1758    fn test_linear_uint_ops() -> Result<(), Error> {
1759        let mut info = Data::new();
1760        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1761                    floor: 4, step_size: 2, buckets: 2, type: UintT))?;
1762        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1763        assert!(info.apply(&insert!(id: 3, value: Value::UintT(4))).is_ok());
1764        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1765        assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1766        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1767        assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1768        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1769        assert!(info.apply(&insert!(id: 3, value: Value::UintT(8))).is_ok());
1770        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1771        assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1772        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1773        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1774        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1775        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1776        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1777        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1778        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1779        assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(7), count: 4)).is_ok());
1780        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 5, 2], LinearHistogram)"));
1781        Ok(())
1782    }
1783
1784    #[fuchsia::test]
1785    fn test_exponential_uint_ops() -> Result<(), Error> {
1786        let mut info = Data::new();
1787        // Bucket boundaries are 5, 7, 13
1788        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1789                    floor: 5, initial_step: 2,
1790                    step_multiplier: 4, buckets: 2, type: UintT))?;
1791        assert!(
1792            info.to_string()
1793                .contains("value: UintArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)")
1794        );
1795        assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1796        assert!(
1797            info.to_string()
1798                .contains("value: UintArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)")
1799        );
1800        assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1801        assert!(
1802            info.to_string()
1803                .contains("value: UintArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)")
1804        );
1805        assert!(info.apply(&insert!(id: 3, value: Value::UintT(7))).is_ok());
1806        assert!(
1807            info.to_string()
1808                .contains("value: UintArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)")
1809        );
1810        assert!(info.apply(&insert!(id: 3, value: Value::UintT(13))).is_ok());
1811        assert!(
1812            info.to_string()
1813                .contains("value: UintArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)")
1814        );
1815        assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1816        assert!(
1817            info.to_string()
1818                .contains("value: UintArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)")
1819        );
1820        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1821        assert!(
1822            info.to_string()
1823                .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1824        );
1825        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1826        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1827        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1828        assert!(
1829            info.to_string()
1830                .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1831        );
1832        assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(12), count: 4)).is_ok());
1833        assert!(
1834            info.to_string()
1835                .contains("value: UintArray([5, 2, 4, 1, 2, 5, 2], ExponentialHistogram)")
1836        );
1837        Ok(())
1838    }
1839
1840    #[fuchsia::test]
1841    fn test_basic_double_ops() -> Result<(), Error> {
1842        let mut info = Data::new();
1843        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1844                                     value: Value::DoubleT(42.0)))?;
1845        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_ok());
1846        assert!(info.to_string().contains("value: Double(45.0)"));
1847        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1848        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1849        assert!(info.to_string().contains("value: Double(45.0)"));
1850        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1851        assert!(info.to_string().contains("value: Double(40.0)"));
1852        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1853        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1854        assert!(info.to_string().contains("value: Double(40.0)"));
1855        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(22.0))).is_ok());
1856        assert!(info.to_string().contains("value: Double(22.0)"));
1857        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1858        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(24))).is_err());
1859        assert!(info.to_string().contains("value: Double(22.0)"));
1860        Ok(())
1861    }
1862
1863    #[fuchsia::test]
1864    fn test_array_double_ops() -> Result<(), Error> {
1865        let mut info = Data::new();
1866        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1867                                     type: ValueType::Double))?;
1868        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_ok());
1869        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1870        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1871        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1872        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1873        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::DoubleT(5.0))).is_ok());
1874        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1875        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1876        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1877        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1878        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(22.0))).is_ok());
1879        assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1880        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1881        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(24))).is_err());
1882        assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1883        Ok(())
1884    }
1885
1886    #[fuchsia::test]
1887    fn test_linear_double_ops() -> Result<(), Error> {
1888        let mut info = Data::new();
1889        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1890                    floor: 4.0, step_size: 0.5, buckets: 2, type: DoubleT))?;
1891        assert!(
1892            info.to_string()
1893                .contains("value: DoubleArray([4.0, 0.5, 0.0, 0.0, 0.0, 0.0], LinearHistogram)")
1894        );
1895        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.0))).is_ok());
1896        assert!(
1897            info.to_string()
1898                .contains("value: DoubleArray([4.0, 0.5, 0.0, 1.0, 0.0, 0.0], LinearHistogram)")
1899        );
1900        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.25))).is_ok());
1901        assert!(
1902            info.to_string()
1903                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 0.0, 0.0], LinearHistogram)")
1904        );
1905        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.75))).is_ok());
1906        assert!(
1907            info.to_string()
1908                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 0.0], LinearHistogram)")
1909        );
1910        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.1))).is_ok());
1911        assert!(
1912            info.to_string()
1913                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 1.0], LinearHistogram)")
1914        );
1915        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1916        assert!(
1917            info.to_string()
1918                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 2.0], LinearHistogram)")
1919        );
1920        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1921        assert!(
1922            info.to_string()
1923                .contains("value: DoubleArray([4.0, 0.5, 1.0, 2.0, 1.0, 2.0], LinearHistogram)")
1924        );
1925        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1926        assert!(
1927            info.to_string()
1928                .contains("value: DoubleArray([4.0, 0.5, 2.0, 2.0, 1.0, 2.0], LinearHistogram)")
1929        );
1930        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1931        assert!(
1932            info.to_string()
1933                .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)")
1934        );
1935        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1936        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1937        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1938        assert!(
1939            info.to_string()
1940                .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)")
1941        );
1942        assert!(info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(4.5), count: 4)).is_ok());
1943        assert!(
1944            info.to_string()
1945                .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 5.0, 2.0], LinearHistogram)")
1946        );
1947        Ok(())
1948    }
1949
1950    #[fuchsia::test]
1951    fn test_exponential_double_ops() -> Result<(), Error> {
1952        let mut info = Data::new();
1953        // Bucket boundaries are 5, 7, 13, 37
1954        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1955                    floor: 5.0, initial_step: 2.0,
1956                    step_multiplier: 4.0, buckets: 3, type: DoubleT))?;
1957        assert!(info.to_string().contains(
1958            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1959        ));
1960        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1961        assert!(info.to_string().contains(
1962            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 1.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1963        ));
1964        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(6.9))).is_ok());
1965        assert!(info.to_string().contains(
1966            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1967        ));
1968        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(7.1))).is_ok());
1969        assert!(info.to_string().contains(
1970            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 1.0, 0.0, 0.0], ExponentialHistogram)"
1971        ));
1972        assert!(
1973            info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(12.9), count: 4)).is_ok()
1974        );
1975        assert!(info.to_string().contains(
1976            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 0.0, 0.0], ExponentialHistogram)"
1977        ));
1978        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(13.1))).is_ok());
1979        assert!(info.to_string().contains(
1980            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 1.0, 0.0], ExponentialHistogram)"
1981        ));
1982        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(36.9))).is_ok());
1983        assert!(info.to_string().contains(
1984            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 0.0], ExponentialHistogram)"
1985        ));
1986        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(37.1))).is_ok());
1987        assert!(info.to_string().contains(
1988            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 1.0], ExponentialHistogram)"
1989        ));
1990        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1991        assert!(info.to_string().contains(
1992            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1993        ));
1994        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1995        assert!(info.to_string().contains(
1996            "value: DoubleArray([5.0, 2.0, 4.0, 1.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1997        ));
1998        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1999        assert!(info.to_string().contains(
2000            "value: DoubleArray([5.0, 2.0, 4.0, 2.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2001        ));
2002        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
2003        assert!(info.to_string().contains(
2004            "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2005        ));
2006        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
2007        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
2008        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
2009        assert!(info.to_string().contains(
2010            "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2011        ));
2012        Ok(())
2013    }
2014
2015    #[fuchsia::test]
2016    fn test_basic_vector_ops() -> Result<(), Error> {
2017        let mut info = Data::new();
2018        info.apply(&create_string_property!(parent: ROOT_ID, id: 3, name: "value",
2019                                     value: "foo"))?;
2020        assert!(info.to_string().contains("value: String(\"foo\")"));
2021        assert!(info.apply(&set_string!(id: 3, value: "bar")).is_ok());
2022        assert!(info.to_string().contains("value: String(\"bar\")"));
2023        assert!(info.apply(&set_bytes!(id: 3, value: vec!(3u8))).is_err());
2024        assert!(info.to_string().contains("value: String(\"bar\")"));
2025        info.apply(&create_bytes_property!(parent: ROOT_ID, id: 4, name: "bvalue",
2026                                     value: vec!(1u8, 2u8)))?;
2027        assert!(info.to_string().contains("bvalue: Bytes([1, 2])"));
2028        assert!(info.apply(&set_bytes!(id: 4, value: vec!(3u8, 4u8))).is_ok());
2029        assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
2030        assert!(info.apply(&set_string!(id: 4, value: "baz")).is_err());
2031        assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
2032        Ok(())
2033    }
2034
2035    #[fuchsia::test]
2036    fn test_basic_lazy_node_ops() -> Result<(), Error> {
2037        let mut info = Data::new();
2038        info.apply_lazy(&create_lazy_node!(parent: ROOT_ID, id: 1, name: "child", disposition: validate::LinkDisposition::Child, actions: vec![create_bytes_property!(parent: ROOT_ID, id: 1, name: "child_bytes",value: vec!(3u8, 4u8))]))?;
2039        info.apply_lazy(&create_lazy_node!(parent: ROOT_ID, id: 2, name: "inline", disposition: validate::LinkDisposition::Inline, actions: vec![create_bytes_property!(parent: ROOT_ID, id: 1, name: "inline_bytes",value: vec!(3u8, 4u8))]))?;
2040
2041        // Outputs 'Inline' and 'Child' dispositions differently.
2042        assert_eq!(
2043            info.to_string(),
2044            "root ->\n> inline_bytes: Bytes([3, 4])\n> child ->\n> > child_bytes: Bytes([3, 4])"
2045        );
2046
2047        info.apply_lazy(&delete_lazy_node!(id: 1))?;
2048        // Outputs only 'Inline' lazy node since 'Child' lazy node was deleted
2049        assert_eq!(info.to_string(), "root ->\n> inline_bytes: Bytes([3, 4])");
2050
2051        Ok(())
2052    }
2053
2054    #[fuchsia::test]
2055    fn test_illegal_node_actions() -> Result<(), Error> {
2056        let mut info = Data::new();
2057        // Parent must exist
2058        assert!(info.apply(&create_node!(parent: 42, id: 1, name: "child")).is_err());
2059        // Can't reuse node IDs
2060        info = Data::new();
2061        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
2062        assert!(info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "another_child")).is_err());
2063        // Can't delete root
2064        info = Data::new();
2065        assert!(info.apply(&delete_node!(id: ROOT_ID)).is_err());
2066        // Can't delete nonexistent node
2067        info = Data::new();
2068        assert!(info.apply(&delete_node!(id: 333)).is_err());
2069        Ok(())
2070    }
2071
2072    #[fuchsia::test]
2073    fn test_illegal_property_actions() -> Result<(), Error> {
2074        let mut info = Data::new();
2075        // Parent must exist
2076        assert!(
2077            info.apply(
2078                &create_numeric_property!(parent: 42, id: 1, name: "answer", value: Value::IntT(42))
2079            )
2080            .is_err()
2081        );
2082        // Can't reuse property IDs
2083        info = Data::new();
2084        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "answer",
2085                                    value: Value::IntT(42)))?;
2086        assert!(
2087            info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "another_answer",
2088                                    value: Value::IntT(7)))
2089                .is_err()
2090        );
2091        // Can't delete nonexistent property
2092        info = Data::new();
2093        assert!(info.apply(&delete_property!(id: 1)).is_err());
2094        // Can't do basic-int on array or histogram, or any vice versa
2095        info = Data::new();
2096        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
2097                                     value: Value::IntT(42)))?;
2098        info.apply(&create_array_property!(parent: ROOT_ID, id: 4, name: "array", slots: 2,
2099                                     type: ValueType::Int))?;
2100        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 5, name: "lin",
2101                                floor: 5, step_size: 2,
2102                                buckets: 2, type: IntT))?;
2103        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 6, name: "exp",
2104                                floor: 5, initial_step: 2,
2105                                step_multiplier: 2, buckets: 2, type: IntT))?;
2106        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(5))).is_ok());
2107        assert!(info.apply(&array_set!(id: 4, index: 0, value: Value::IntT(5))).is_ok());
2108        assert!(info.apply(&insert!(id: 5, value: Value::IntT(2))).is_ok());
2109        assert!(info.apply(&insert!(id: 6, value: Value::IntT(2))).is_ok());
2110        assert!(info.apply(&insert_multiple!(id: 5, value: Value::IntT(2), count: 3)).is_ok());
2111        assert!(info.apply(&insert_multiple!(id: 6, value: Value::IntT(2), count: 3)).is_ok());
2112        assert!(info.apply(&set_number!(id: 4, value: Value::IntT(5))).is_err());
2113        assert!(info.apply(&set_number!(id: 5, value: Value::IntT(5))).is_err());
2114        assert!(info.apply(&set_number!(id: 6, value: Value::IntT(5))).is_err());
2115        assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::IntT(5))).is_err());
2116        assert!(info.apply(&array_set!(id: 5, index: 0, value: Value::IntT(5))).is_err());
2117        assert!(info.apply(&array_set!(id: 6, index: 0, value: Value::IntT(5))).is_err());
2118        assert!(info.apply(&insert!(id: 3, value: Value::IntT(2))).is_err());
2119        assert!(info.apply(&insert!(id: 4, value: Value::IntT(2))).is_err());
2120        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(2), count: 3)).is_err());
2121        assert!(info.apply(&insert_multiple!(id: 4, value: Value::IntT(2), count: 3)).is_err());
2122        Ok(())
2123    }
2124
2125    #[fuchsia::test]
2126    fn test_enum_values() {
2127        assert_eq!(BlockType::IntValue.to_isize().unwrap(), ArrayType::Int.to_isize().unwrap());
2128        assert_eq!(BlockType::UintValue.to_isize().unwrap(), ArrayType::Uint.to_isize().unwrap());
2129        assert_eq!(
2130            BlockType::DoubleValue.to_isize().unwrap(),
2131            ArrayType::Double.to_isize().unwrap()
2132        );
2133    }
2134
2135    #[fuchsia::test]
2136    fn test_create_node_checks() {
2137        let mut data = Data::new();
2138        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2139        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "second")).is_ok());
2140        assert!(data.apply(&create_node!(parent: 1, id: 3, name: "child")).is_ok());
2141        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "double")).is_err());
2142        let mut data = Data::new();
2143        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "orphan")).is_err());
2144    }
2145
2146    #[fuchsia::test]
2147    fn test_delete_node_checks() {
2148        let mut data = Data::new();
2149        assert!(data.apply(&delete_node!(id: 0)).is_err());
2150        let mut data = Data::new();
2151        data.apply(&create_node!(parent: 0, id: 1, name: "first")).ok();
2152        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2153        assert!(data.apply(&delete_node!(id: 1)).is_err());
2154    }
2155
2156    #[fuchsia::test]
2157    // Make sure tombstoning works correctly (tracking implicitly deleted descendants).
2158    fn test_node_tombstoning() {
2159        // Can delete, but not double-delete, a tombstoned node.
2160        let mut data = Data::new();
2161        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2162        assert!(
2163            data.apply(&create_numeric_property!(parent: 1, id: 2,
2164            name: "answer", value: Value::IntT(42)))
2165                .is_ok()
2166        );
2167        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2168        assert!(data.apply(&delete_property!(id: 2)).is_ok());
2169        assert!(data.apply(&delete_property!(id: 2)).is_err());
2170        // Can tombstone, then delete, then create.
2171        let mut data = Data::new();
2172        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2173        assert!(
2174            data.apply(&create_numeric_property!(parent: 1, id: 2,
2175            name: "answer", value: Value::IntT(42)))
2176                .is_ok()
2177        );
2178        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2179        assert!(data.apply(&delete_property!(id: 2)).is_ok());
2180        assert!(
2181            data.apply(&create_numeric_property!(parent: 0, id: 2,
2182            name: "root_answer", value: Value::IntT(42)))
2183                .is_ok()
2184        );
2185        // Cannot tombstone, then create.
2186        let mut data = Data::new();
2187        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2188        assert!(
2189            data.apply(&create_numeric_property!(parent: 1, id: 2,
2190            name: "answer", value: Value::IntT(42)))
2191                .is_ok()
2192        );
2193        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2194        assert!(
2195            data.apply(&create_numeric_property!(parent: 0, id: 2,
2196            name: "root_answer", value: Value::IntT(42)))
2197                .is_err()
2198        );
2199    }
2200
2201    #[fuchsia::test]
2202    fn test_property_tombstoning() {
2203        // Make sure tombstoning works correctly (tracking implicitly deleted descendants).
2204        // Can delete, but not double-delete, a tombstoned property.
2205        let mut data = Data::new();
2206        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2207        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2208        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2209        assert!(data.apply(&delete_node!(id: 2)).is_ok());
2210        assert!(data.apply(&delete_node!(id: 2)).is_err());
2211        // Can tombstone, then delete, then create.
2212        let mut data = Data::new();
2213        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2214        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2215        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2216        assert!(data.apply(&delete_node!(id: 2)).is_ok());
2217        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_ok());
2218        // Cannot tombstone, then create.
2219        let mut data = Data::new();
2220        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2221        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2222        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2223        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_err());
2224    }
2225
2226    const DIFF_STRING: &str = r#"-- DIFF --
2227 same: "root ->"
2228 same: "> node ->"
2229local: "> > prop1: String(\"foo\")"
2230other: "> > prop1: String(\"bar\")""#;
2231
2232    const FULL_STRING: &str = r#"-- LOCAL --
2233root ->
2234> node ->
2235> > prop1: String("foo")
2236-- OTHER --
2237root ->
2238> node ->
2239> > prop1: String("bar")"#;
2240
2241    #[fuchsia::test]
2242    fn diff_modes_work() -> Result<(), Error> {
2243        let mut local = Data::new();
2244        let mut remote = Data::new();
2245        local.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2246        local.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "foo"))?;
2247        remote.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2248        remote.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "bar"))?;
2249        match local.compare(&remote, DiffType::Diff) {
2250            Err(error) => {
2251                let error_string = format!("{error:?}");
2252                assert_eq!("Trees differ:\n".to_string() + DIFF_STRING, error_string);
2253            }
2254            _ => return Err(format_err!("Didn't get failure")),
2255        }
2256        match local.compare(&remote, DiffType::Full) {
2257            Err(error) => {
2258                let error_string = format!("{error:?}");
2259                assert_eq!("Trees differ:\n".to_string() + FULL_STRING, error_string);
2260            }
2261            _ => return Err(format_err!("Didn't get failure")),
2262        }
2263        match local.compare(&remote, DiffType::Both) {
2264            Err(error) => {
2265                let error_string = format!("{error:?}");
2266                assert_eq!(["Trees differ:", FULL_STRING, DIFF_STRING].join("\n"), error_string);
2267            }
2268            _ => return Err(format_err!("Didn't get failure")),
2269        }
2270        Ok(())
2271    }
2272}