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::{bail, format_err, Error};
8use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
9use base64::engine::Engine as _;
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::{ValueType, ROOT_ID};
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!(info
1446            .to_string()
1447            .contains("ILhist: IntArray([12, 3, 0, 0, 0, 0], LinearHistogram)"));
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!(info
1452            .to_string()
1453            .contains("ULhist: UintArray([34, 5, 0, 0, 0, 0], LinearHistogram)"));
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!(info
1460            .to_string()
1461            .contains("DLhist: DoubleArray([56.0, 7.0, 0.0, 0.0, 0.0, 0.0], LinearHistogram)"));
1462
1463        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 14, name: "IEhist",
1464            floor: 12, initial_step: 3, step_multiplier: 5, buckets: 2, type: IntT))?;
1465        assert!(info
1466            .to_string()
1467            .contains("IEhist: IntArray([12, 3, 5, 0, 0, 0, 0], ExponentialHistogram)"));
1468
1469        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 15, name: "UEhist",
1470            floor: 34, initial_step: 9, step_multiplier: 6, buckets: 2, type: UintT))?;
1471        assert!(info
1472            .to_string()
1473            .contains("UEhist: UintArray([34, 9, 6, 0, 0, 0, 0], ExponentialHistogram)"));
1474
1475        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 16, name: "DEhist",
1476            floor: 56.0, initial_step: 27.0, step_multiplier: 7.0, buckets: 2, type: DoubleT))?;
1477        assert!(info.to_string().contains(
1478            "DEhist: DoubleArray([56.0, 27.0, 7.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1479        ));
1480
1481        info.apply(&create_bool_property!(parent: ROOT_ID, id: 17, name: "bool", value: true))?;
1482        assert!(info.to_string().contains("bool: Bool(true)"));
1483
1484        info.apply(&delete_property!(id: 3))?;
1485        assert!(!info.to_string().contains("int-42") && info.to_string().contains("stringfoo"));
1486        info.apply(&delete_property!(id: 4))?;
1487        assert!(!info.to_string().contains("stringfoo"));
1488        info.apply(&delete_property!(id: 5))?;
1489        assert!(!info.to_string().contains("uint"));
1490        info.apply(&delete_property!(id: 6))?;
1491        assert!(!info.to_string().contains("frac"));
1492        info.apply(&delete_property!(id: 7))?;
1493        assert!(!info.to_string().contains("bytes"));
1494        info.apply(&delete_property!(id: 8))?;
1495        assert!(!info.to_string().contains("i_ntarr"));
1496        info.apply(&delete_property!(id: 9))?;
1497        assert!(!info.to_string().contains("u_intarr"));
1498        info.apply(&delete_property!(id: 10))?;
1499        assert!(!info.to_string().contains("dblarr"));
1500        info.apply(&delete_property!(id: 11))?;
1501        assert!(!info.to_string().contains("ILhist"));
1502        info.apply(&delete_property!(id: 12))?;
1503        assert!(!info.to_string().contains("ULhist"));
1504        info.apply(&delete_property!(id: 13))?;
1505        assert!(!info.to_string().contains("DLhist"));
1506        info.apply(&delete_property!(id: 14))?;
1507        assert!(!info.to_string().contains("IEhist"));
1508        info.apply(&delete_property!(id: 15))?;
1509        assert!(!info.to_string().contains("UEhist"));
1510        info.apply(&delete_property!(id: 16))?;
1511        assert!(!info.to_string().contains("DEhist"));
1512        info.apply(&delete_property!(id: 17))?;
1513        assert!(!info.to_string().contains("bool"));
1514        info.apply(&delete_node!(id:2))?;
1515        assert!(!info.to_string().contains("grandchild") && info.to_string().contains("child"));
1516        info.apply(&delete_node!( id: 1 ))?;
1517        assert_eq!(info.to_string(), "root ->");
1518        Ok(())
1519    }
1520
1521    #[fuchsia::test]
1522    fn test_basic_int_ops() -> Result<(), Error> {
1523        let mut info = Data::new();
1524        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1525                                     value: Value::IntT(-42)))?;
1526        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_ok());
1527        assert!(info.to_string().contains("value: Int(-39)"));
1528        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1529        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1530        assert!(info.to_string().contains("value: Int(-39)"));
1531        assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_ok());
1532        assert!(info.to_string().contains("value: Int(-44)"));
1533        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1534        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1535        assert!(info.to_string().contains("value: Int(-44)"));
1536        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(22))).is_ok());
1537        assert!(info.to_string().contains("value: Int(22)"));
1538        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1539        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1540        assert!(info.to_string().contains("value: Int(22)"));
1541        Ok(())
1542    }
1543
1544    #[fuchsia::test]
1545    fn test_array_string_ops() -> Result<(), Error> {
1546        let mut info = Data::new();
1547        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1548                                           type: ValueType::String))?;
1549        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1550        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1551        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1552        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1553        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""]"#));
1554        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1555        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1556        assert!(info
1557            .apply(&array_subtract!(id: 3, index: 2,
1558                                            value: Value::DoubleT(5.0)))
1559            .is_err());
1560        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1561        assert!(info
1562            .apply(&array_set!(id: 3, index: 1, value: Value::StringT("data".into())))
1563            .is_ok());
1564        assert!(info.to_string().contains(r#"value: StringArray(["", "data", ""])"#));
1565        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1566        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1567        assert!(info.to_string().contains(r#"value: StringArray(["", "data", ""])"#));
1568        let long: String = (0..3000).map(|_| ".").collect();
1569        let expected = format!(r#"value: StringArray(["{}", "data", ""])"#, long.clone());
1570        assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::StringT(long))).is_ok());
1571        assert!(info.to_string().contains(&expected));
1572        Ok(())
1573    }
1574
1575    #[fuchsia::test]
1576    fn test_array_int_ops() -> Result<(), Error> {
1577        let mut info = Data::new();
1578        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1579                                           type: ValueType::Int))?;
1580        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1581        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1582        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1583        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1584        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1585        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_ok());
1586        assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1587        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1588        assert!(info
1589            .apply(&array_subtract!(id: 3, index: 2,
1590                                            value: Value::DoubleT(5.0)))
1591            .is_err());
1592        assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1593        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(22))).is_ok());
1594        assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1595        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1596        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1597        assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1598        Ok(())
1599    }
1600
1601    #[fuchsia::test]
1602    fn test_linear_int_ops() -> Result<(), Error> {
1603        let mut info = Data::new();
1604        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1605                    floor: 4, step_size: 2, buckets: 2, type: IntT))?;
1606        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1607        assert!(info.apply(&insert!(id: 3, value: Value::IntT(4))).is_ok());
1608        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1609        assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1610        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1611        assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1612        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1613        assert!(info.apply(&insert!(id: 3, value: Value::IntT(8))).is_ok());
1614        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1615        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1616        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1617        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1618        assert!(info.to_string().contains("value: IntArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1619        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1620        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1621        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1622        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1623        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1624        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1625        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(7), count: 4)).is_ok());
1626        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 5, 2], LinearHistogram)"));
1627        Ok(())
1628    }
1629
1630    #[fuchsia::test]
1631    fn test_exponential_int_ops() -> Result<(), Error> {
1632        let mut info = Data::new();
1633        // Bucket boundaries are 5, 7, 13
1634        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1635                    floor: 5, initial_step: 2,
1636                    step_multiplier: 4, buckets: 2, type: IntT))?;
1637        assert!(info
1638            .to_string()
1639            .contains("value: IntArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)"));
1640        assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1641        assert!(info
1642            .to_string()
1643            .contains("value: IntArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)"));
1644        assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1645        assert!(info
1646            .to_string()
1647            .contains("value: IntArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)"));
1648        assert!(info.apply(&insert!(id: 3, value: Value::IntT(7))).is_ok());
1649        assert!(info
1650            .to_string()
1651            .contains("value: IntArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)"));
1652        assert!(info.apply(&insert!(id: 3, value: Value::IntT(13))).is_ok());
1653        assert!(info
1654            .to_string()
1655            .contains("value: IntArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)"));
1656        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1657        assert!(info
1658            .to_string()
1659            .contains("value: IntArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)"));
1660        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1661        assert!(info
1662            .to_string()
1663            .contains("value: IntArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)"));
1664        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1665        assert!(info
1666            .to_string()
1667            .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)"));
1668        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1669        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1670        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1671        assert!(info
1672            .to_string()
1673            .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)"));
1674        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(12), count: 4)).is_ok());
1675        assert!(info
1676            .to_string()
1677            .contains("value: IntArray([5, 2, 4, 2, 2, 5, 2], ExponentialHistogram)"));
1678        Ok(())
1679    }
1680
1681    #[fuchsia::test]
1682    fn test_array_out_of_bounds_nop() -> Result<(), Error> {
1683        // Accesses to indexes beyond the array are legal and should have no effect on the data.
1684        let mut info = Data::new();
1685        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1686                                           type: ValueType::Int))?;
1687        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1688        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1689        assert!(info.apply(&array_add!(id: 3, index: 3, value: Value::IntT(3))).is_ok());
1690        assert!(info.apply(&array_add!(id: 3, index: 6, value: Value::IntT(3))).is_ok());
1691        assert!(info.apply(&array_add!(id: 3, index: 12345, value: Value::IntT(3))).is_ok());
1692        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1693        Ok(())
1694    }
1695
1696    #[fuchsia::test]
1697    fn test_basic_uint_ops() -> Result<(), Error> {
1698        let mut info = Data::new();
1699        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1700                                     value: Value::UintT(42)))?;
1701        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_ok());
1702        assert!(info.to_string().contains("value: Uint(45)"));
1703        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1704        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1705        assert!(info.to_string().contains("value: Uint(45)"));
1706        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_ok());
1707        assert!(info.to_string().contains("value: Uint(40)"));
1708        assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_err());
1709        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1710        assert!(info.to_string().contains("value: Uint(40)"));
1711        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(22))).is_ok());
1712        assert!(info.to_string().contains("value: Uint(22)"));
1713        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(23))).is_err());
1714        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1715        assert!(info.to_string().contains("value: Uint(22)"));
1716        Ok(())
1717    }
1718
1719    #[fuchsia::test]
1720    fn test_array_uint_ops() -> Result<(), Error> {
1721        let mut info = Data::new();
1722        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1723                                     type: ValueType::Uint))?;
1724        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_ok());
1725        assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1726        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1727        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1728        assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1729        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(22))).is_ok());
1730        assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1731        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1732        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1733        assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1734        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::UintT(5))).is_ok());
1735        assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1736        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::IntT(5))).is_err());
1737        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::DoubleT(5.0))).is_err());
1738        assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1739        Ok(())
1740    }
1741
1742    #[fuchsia::test]
1743    fn test_linear_uint_ops() -> Result<(), Error> {
1744        let mut info = Data::new();
1745        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1746                    floor: 4, step_size: 2, buckets: 2, type: UintT))?;
1747        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1748        assert!(info.apply(&insert!(id: 3, value: Value::UintT(4))).is_ok());
1749        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1750        assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1751        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1752        assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1753        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1754        assert!(info.apply(&insert!(id: 3, value: Value::UintT(8))).is_ok());
1755        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1756        assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1757        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1758        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1759        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1760        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1761        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1762        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1763        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1764        assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(7), count: 4)).is_ok());
1765        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 5, 2], LinearHistogram)"));
1766        Ok(())
1767    }
1768
1769    #[fuchsia::test]
1770    fn test_exponential_uint_ops() -> Result<(), Error> {
1771        let mut info = Data::new();
1772        // Bucket boundaries are 5, 7, 13
1773        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1774                    floor: 5, initial_step: 2,
1775                    step_multiplier: 4, buckets: 2, type: UintT))?;
1776        assert!(info
1777            .to_string()
1778            .contains("value: UintArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)"));
1779        assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1780        assert!(info
1781            .to_string()
1782            .contains("value: UintArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)"));
1783        assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1784        assert!(info
1785            .to_string()
1786            .contains("value: UintArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)"));
1787        assert!(info.apply(&insert!(id: 3, value: Value::UintT(7))).is_ok());
1788        assert!(info
1789            .to_string()
1790            .contains("value: UintArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)"));
1791        assert!(info.apply(&insert!(id: 3, value: Value::UintT(13))).is_ok());
1792        assert!(info
1793            .to_string()
1794            .contains("value: UintArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)"));
1795        assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1796        assert!(info
1797            .to_string()
1798            .contains("value: UintArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)"));
1799        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1800        assert!(info
1801            .to_string()
1802            .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)"));
1803        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1804        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1805        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1806        assert!(info
1807            .to_string()
1808            .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)"));
1809        assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(12), count: 4)).is_ok());
1810        assert!(info
1811            .to_string()
1812            .contains("value: UintArray([5, 2, 4, 1, 2, 5, 2], ExponentialHistogram)"));
1813        Ok(())
1814    }
1815
1816    #[fuchsia::test]
1817    fn test_basic_double_ops() -> Result<(), Error> {
1818        let mut info = Data::new();
1819        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1820                                     value: Value::DoubleT(42.0)))?;
1821        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_ok());
1822        assert!(info.to_string().contains("value: Double(45.0)"));
1823        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1824        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1825        assert!(info.to_string().contains("value: Double(45.0)"));
1826        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1827        assert!(info.to_string().contains("value: Double(40.0)"));
1828        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1829        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1830        assert!(info.to_string().contains("value: Double(40.0)"));
1831        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(22.0))).is_ok());
1832        assert!(info.to_string().contains("value: Double(22.0)"));
1833        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1834        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(24))).is_err());
1835        assert!(info.to_string().contains("value: Double(22.0)"));
1836        Ok(())
1837    }
1838
1839    #[fuchsia::test]
1840    fn test_array_double_ops() -> Result<(), Error> {
1841        let mut info = Data::new();
1842        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1843                                     type: ValueType::Double))?;
1844        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_ok());
1845        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1846        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1847        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1848        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1849        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::DoubleT(5.0))).is_ok());
1850        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1851        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1852        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1853        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1854        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(22.0))).is_ok());
1855        assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1856        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1857        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(24))).is_err());
1858        assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1859        Ok(())
1860    }
1861
1862    #[fuchsia::test]
1863    fn test_linear_double_ops() -> Result<(), Error> {
1864        let mut info = Data::new();
1865        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1866                    floor: 4.0, step_size: 0.5, buckets: 2, type: DoubleT))?;
1867        assert!(info
1868            .to_string()
1869            .contains("value: DoubleArray([4.0, 0.5, 0.0, 0.0, 0.0, 0.0], LinearHistogram)"));
1870        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.0))).is_ok());
1871        assert!(info
1872            .to_string()
1873            .contains("value: DoubleArray([4.0, 0.5, 0.0, 1.0, 0.0, 0.0], LinearHistogram)"));
1874        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.25))).is_ok());
1875        assert!(info
1876            .to_string()
1877            .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 0.0, 0.0], LinearHistogram)"));
1878        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.75))).is_ok());
1879        assert!(info
1880            .to_string()
1881            .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 0.0], LinearHistogram)"));
1882        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.1))).is_ok());
1883        assert!(info
1884            .to_string()
1885            .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 1.0], LinearHistogram)"));
1886        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1887        assert!(info
1888            .to_string()
1889            .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1890        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1891        assert!(info
1892            .to_string()
1893            .contains("value: DoubleArray([4.0, 0.5, 1.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1894        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1895        assert!(info
1896            .to_string()
1897            .contains("value: DoubleArray([4.0, 0.5, 2.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1898        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1899        assert!(info
1900            .to_string()
1901            .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1902        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1903        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1904        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1905        assert!(info
1906            .to_string()
1907            .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1908        assert!(info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(4.5), count: 4)).is_ok());
1909        assert!(info
1910            .to_string()
1911            .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 5.0, 2.0], LinearHistogram)"));
1912        Ok(())
1913    }
1914
1915    #[fuchsia::test]
1916    fn test_exponential_double_ops() -> Result<(), Error> {
1917        let mut info = Data::new();
1918        // Bucket boundaries are 5, 7, 13, 37
1919        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1920                    floor: 5.0, initial_step: 2.0,
1921                    step_multiplier: 4.0, buckets: 3, type: DoubleT))?;
1922        assert!(info.to_string().contains(
1923            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1924        ));
1925        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1926        assert!(info.to_string().contains(
1927            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 1.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1928        ));
1929        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(6.9))).is_ok());
1930        assert!(info.to_string().contains(
1931            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1932        ));
1933        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(7.1))).is_ok());
1934        assert!(info.to_string().contains(
1935            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 1.0, 0.0, 0.0], ExponentialHistogram)"
1936        ));
1937        assert!(info
1938            .apply(&insert_multiple!(id: 3, value: Value::DoubleT(12.9), count: 4))
1939            .is_ok());
1940        assert!(info.to_string().contains(
1941            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 0.0, 0.0], ExponentialHistogram)"
1942        ));
1943        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(13.1))).is_ok());
1944        assert!(info.to_string().contains(
1945            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 1.0, 0.0], ExponentialHistogram)"
1946        ));
1947        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(36.9))).is_ok());
1948        assert!(info.to_string().contains(
1949            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 0.0], ExponentialHistogram)"
1950        ));
1951        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(37.1))).is_ok());
1952        assert!(info.to_string().contains(
1953            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 1.0], ExponentialHistogram)"
1954        ));
1955        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1956        assert!(info.to_string().contains(
1957            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1958        ));
1959        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1960        assert!(info.to_string().contains(
1961            "value: DoubleArray([5.0, 2.0, 4.0, 1.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1962        ));
1963        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1964        assert!(info.to_string().contains(
1965            "value: DoubleArray([5.0, 2.0, 4.0, 2.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1966        ));
1967        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1968        assert!(info.to_string().contains(
1969            "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1970        ));
1971        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1972        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1973        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1974        assert!(info.to_string().contains(
1975            "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1976        ));
1977        Ok(())
1978    }
1979
1980    #[fuchsia::test]
1981    fn test_basic_vector_ops() -> Result<(), Error> {
1982        let mut info = Data::new();
1983        info.apply(&create_string_property!(parent: ROOT_ID, id: 3, name: "value",
1984                                     value: "foo"))?;
1985        assert!(info.to_string().contains("value: String(\"foo\")"));
1986        assert!(info.apply(&set_string!(id: 3, value: "bar")).is_ok());
1987        assert!(info.to_string().contains("value: String(\"bar\")"));
1988        assert!(info.apply(&set_bytes!(id: 3, value: vec!(3u8))).is_err());
1989        assert!(info.to_string().contains("value: String(\"bar\")"));
1990        info.apply(&create_bytes_property!(parent: ROOT_ID, id: 4, name: "bvalue",
1991                                     value: vec!(1u8, 2u8)))?;
1992        assert!(info.to_string().contains("bvalue: Bytes([1, 2])"));
1993        assert!(info.apply(&set_bytes!(id: 4, value: vec!(3u8, 4u8))).is_ok());
1994        assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
1995        assert!(info.apply(&set_string!(id: 4, value: "baz")).is_err());
1996        assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
1997        Ok(())
1998    }
1999
2000    #[fuchsia::test]
2001    fn test_basic_lazy_node_ops() -> Result<(), Error> {
2002        let mut info = Data::new();
2003        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))]))?;
2004        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))]))?;
2005
2006        // Outputs 'Inline' and 'Child' dispositions differently.
2007        assert_eq!(
2008            info.to_string(),
2009            "root ->\n> inline_bytes: Bytes([3, 4])\n> child ->\n> > child_bytes: Bytes([3, 4])"
2010        );
2011
2012        info.apply_lazy(&delete_lazy_node!(id: 1))?;
2013        // Outputs only 'Inline' lazy node since 'Child' lazy node was deleted
2014        assert_eq!(info.to_string(), "root ->\n> inline_bytes: Bytes([3, 4])");
2015
2016        Ok(())
2017    }
2018
2019    #[fuchsia::test]
2020    fn test_illegal_node_actions() -> Result<(), Error> {
2021        let mut info = Data::new();
2022        // Parent must exist
2023        assert!(info.apply(&create_node!(parent: 42, id: 1, name: "child")).is_err());
2024        // Can't reuse node IDs
2025        info = Data::new();
2026        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
2027        assert!(info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "another_child")).is_err());
2028        // Can't delete root
2029        info = Data::new();
2030        assert!(info.apply(&delete_node!(id: ROOT_ID)).is_err());
2031        // Can't delete nonexistent node
2032        info = Data::new();
2033        assert!(info.apply(&delete_node!(id: 333)).is_err());
2034        Ok(())
2035    }
2036
2037    #[fuchsia::test]
2038    fn test_illegal_property_actions() -> Result<(), Error> {
2039        let mut info = Data::new();
2040        // Parent must exist
2041        assert!(info
2042            .apply(
2043                &create_numeric_property!(parent: 42, id: 1, name: "answer", value: Value::IntT(42))
2044            )
2045            .is_err());
2046        // Can't reuse property IDs
2047        info = Data::new();
2048        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "answer",
2049                                    value: Value::IntT(42)))?;
2050        assert!(info
2051            .apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "another_answer",
2052                                    value: Value::IntT(7)))
2053            .is_err());
2054        // Can't delete nonexistent property
2055        info = Data::new();
2056        assert!(info.apply(&delete_property!(id: 1)).is_err());
2057        // Can't do basic-int on array or histogram, or any vice versa
2058        info = Data::new();
2059        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
2060                                     value: Value::IntT(42)))?;
2061        info.apply(&create_array_property!(parent: ROOT_ID, id: 4, name: "array", slots: 2,
2062                                     type: ValueType::Int))?;
2063        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 5, name: "lin",
2064                                floor: 5, step_size: 2,
2065                                buckets: 2, type: IntT))?;
2066        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 6, name: "exp",
2067                                floor: 5, initial_step: 2,
2068                                step_multiplier: 2, buckets: 2, type: IntT))?;
2069        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(5))).is_ok());
2070        assert!(info.apply(&array_set!(id: 4, index: 0, value: Value::IntT(5))).is_ok());
2071        assert!(info.apply(&insert!(id: 5, value: Value::IntT(2))).is_ok());
2072        assert!(info.apply(&insert!(id: 6, value: Value::IntT(2))).is_ok());
2073        assert!(info.apply(&insert_multiple!(id: 5, value: Value::IntT(2), count: 3)).is_ok());
2074        assert!(info.apply(&insert_multiple!(id: 6, value: Value::IntT(2), count: 3)).is_ok());
2075        assert!(info.apply(&set_number!(id: 4, value: Value::IntT(5))).is_err());
2076        assert!(info.apply(&set_number!(id: 5, value: Value::IntT(5))).is_err());
2077        assert!(info.apply(&set_number!(id: 6, value: Value::IntT(5))).is_err());
2078        assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::IntT(5))).is_err());
2079        assert!(info.apply(&array_set!(id: 5, index: 0, value: Value::IntT(5))).is_err());
2080        assert!(info.apply(&array_set!(id: 6, index: 0, value: Value::IntT(5))).is_err());
2081        assert!(info.apply(&insert!(id: 3, value: Value::IntT(2))).is_err());
2082        assert!(info.apply(&insert!(id: 4, value: Value::IntT(2))).is_err());
2083        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(2), count: 3)).is_err());
2084        assert!(info.apply(&insert_multiple!(id: 4, value: Value::IntT(2), count: 3)).is_err());
2085        Ok(())
2086    }
2087
2088    #[fuchsia::test]
2089    fn test_enum_values() {
2090        assert_eq!(BlockType::IntValue.to_isize().unwrap(), ArrayType::Int.to_isize().unwrap());
2091        assert_eq!(BlockType::UintValue.to_isize().unwrap(), ArrayType::Uint.to_isize().unwrap());
2092        assert_eq!(
2093            BlockType::DoubleValue.to_isize().unwrap(),
2094            ArrayType::Double.to_isize().unwrap()
2095        );
2096    }
2097
2098    #[fuchsia::test]
2099    fn test_create_node_checks() {
2100        let mut data = Data::new();
2101        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2102        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "second")).is_ok());
2103        assert!(data.apply(&create_node!(parent: 1, id: 3, name: "child")).is_ok());
2104        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "double")).is_err());
2105        let mut data = Data::new();
2106        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "orphan")).is_err());
2107    }
2108
2109    #[fuchsia::test]
2110    fn test_delete_node_checks() {
2111        let mut data = Data::new();
2112        assert!(data.apply(&delete_node!(id: 0)).is_err());
2113        let mut data = Data::new();
2114        data.apply(&create_node!(parent: 0, id: 1, name: "first")).ok();
2115        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2116        assert!(data.apply(&delete_node!(id: 1)).is_err());
2117    }
2118
2119    #[fuchsia::test]
2120    // Make sure tombstoning works correctly (tracking implicitly deleted descendants).
2121    fn test_node_tombstoning() {
2122        // Can delete, but not double-delete, a tombstoned node.
2123        let mut data = Data::new();
2124        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2125        assert!(data
2126            .apply(&create_numeric_property!(parent: 1, id: 2,
2127            name: "answer", value: Value::IntT(42)))
2128            .is_ok());
2129        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2130        assert!(data.apply(&delete_property!(id: 2)).is_ok());
2131        assert!(data.apply(&delete_property!(id: 2)).is_err());
2132        // Can tombstone, then delete, then create.
2133        let mut data = Data::new();
2134        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2135        assert!(data
2136            .apply(&create_numeric_property!(parent: 1, id: 2,
2137            name: "answer", value: Value::IntT(42)))
2138            .is_ok());
2139        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2140        assert!(data.apply(&delete_property!(id: 2)).is_ok());
2141        assert!(data
2142            .apply(&create_numeric_property!(parent: 0, id: 2,
2143            name: "root_answer", value: Value::IntT(42)))
2144            .is_ok());
2145        // Cannot tombstone, then create.
2146        let mut data = Data::new();
2147        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2148        assert!(data
2149            .apply(&create_numeric_property!(parent: 1, id: 2,
2150            name: "answer", value: Value::IntT(42)))
2151            .is_ok());
2152        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2153        assert!(data
2154            .apply(&create_numeric_property!(parent: 0, id: 2,
2155            name: "root_answer", value: Value::IntT(42)))
2156            .is_err());
2157    }
2158
2159    #[fuchsia::test]
2160    fn test_property_tombstoning() {
2161        // Make sure tombstoning works correctly (tracking implicitly deleted descendants).
2162        // Can delete, but not double-delete, a tombstoned property.
2163        let mut data = Data::new();
2164        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2165        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2166        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2167        assert!(data.apply(&delete_node!(id: 2)).is_ok());
2168        assert!(data.apply(&delete_node!(id: 2)).is_err());
2169        // Can tombstone, then delete, then create.
2170        let mut data = Data::new();
2171        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2172        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2173        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2174        assert!(data.apply(&delete_node!(id: 2)).is_ok());
2175        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_ok());
2176        // Cannot tombstone, then create.
2177        let mut data = Data::new();
2178        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2179        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2180        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2181        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_err());
2182    }
2183
2184    const DIFF_STRING: &str = r#"-- DIFF --
2185 same: "root ->"
2186 same: "> node ->"
2187local: "> > prop1: String(\"foo\")"
2188other: "> > prop1: String(\"bar\")""#;
2189
2190    const FULL_STRING: &str = r#"-- LOCAL --
2191root ->
2192> node ->
2193> > prop1: String("foo")
2194-- OTHER --
2195root ->
2196> node ->
2197> > prop1: String("bar")"#;
2198
2199    #[fuchsia::test]
2200    fn diff_modes_work() -> Result<(), Error> {
2201        let mut local = Data::new();
2202        let mut remote = Data::new();
2203        local.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2204        local.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "foo"))?;
2205        remote.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2206        remote.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "bar"))?;
2207        match local.compare(&remote, DiffType::Diff) {
2208            Err(error) => {
2209                let error_string = format!("{error:?}");
2210                assert_eq!("Trees differ:\n".to_string() + DIFF_STRING, error_string);
2211            }
2212            _ => return Err(format_err!("Didn't get failure")),
2213        }
2214        match local.compare(&remote, DiffType::Full) {
2215            Err(error) => {
2216                let error_string = format!("{error:?}");
2217                assert_eq!("Trees differ:\n".to_string() + FULL_STRING, error_string);
2218            }
2219            _ => return Err(format_err!("Didn't get failure")),
2220        }
2221        match local.compare(&remote, DiffType::Both) {
2222            Err(error) => {
2223                let error_string = format!("{error:?}");
2224                assert_eq!(["Trees differ:", FULL_STRING, DIFF_STRING].join("\n"), error_string);
2225            }
2226            _ => return Err(format_err!("Didn't get failure")),
2227        }
2228        Ok(())
2229    }
2230}