inspect_validator/
data.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::metrics::Metrics;
6use crate::puppet::DiffType;
7use anyhow::{Error, bail, format_err};
8use base64::engine::Engine as _;
9use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
10use diagnostics_hierarchy::{
11    ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram,
12    Property as iProperty,
13};
14use fidl_diagnostics_validate::{self as validate, Value};
15use inspect_format::{ArrayFormat, BlockIndex, LinkNodeDisposition};
16use num_traits::Zero;
17use std::collections::{HashMap, HashSet};
18use std::fmt;
19
20mod scanner;
21pub use scanner::Scanner;
22mod fetch;
23pub use fetch::LazyNode;
24
25#[cfg(test)]
26use num_traits::ToPrimitive;
27
28const ROOT_NAME: &str = "root";
29
30/// A local store of Inspect-like data which can be built by Action or filled
31/// from a VMO.
32///
33/// For now, Data assumes it will not be given two sibling-nodes or
34/// properties with the same name, and does not truncate any data or names.
35#[derive(Debug, Clone)]
36pub struct Data {
37    nodes: HashMap<BlockIndex, Node>,
38    properties: HashMap<BlockIndex, Property>,
39    tombstone_nodes: HashSet<BlockIndex>,
40    tombstone_properties: HashSet<BlockIndex>,
41}
42
43// Data is the only public struct in this file. The internal data structures are
44// a bit complicated...
45//
46// Node, Property, and Payload are the "clean data" holders - they can be created
47// either by reading from a VMO, or by applying Actions (the same actions that
48// are sent to the puppets and act on their VMOs).
49//
50// The Actions specify arbitrary u32 keys to refer to nodes and properties to
51// create, modify, and delete. It's an error to misuse a key (e.g. double-create
52// a key or delete a missing key).
53//
54// In both Data-from-Actions and Data-from-VMO, the "root" node is virtual; nodes
55// and properties with a "parent" ID of 0 are directly under the "root" of the tree.
56// A placeholder Node is placed at index 0 upon creation to hold the real Nodes and
57// properties added during scanning VMO or processing Actions.
58
59#[derive(Debug, Clone)]
60pub struct Node {
61    name: String,
62    parent: BlockIndex,
63    children: HashSet<BlockIndex>,
64    properties: HashSet<BlockIndex>,
65}
66
67#[derive(Debug, Clone)]
68pub struct Property {
69    name: String,
70    // TODO(https://fxbug.dev/42165549)
71    #[allow(unused)]
72    id: BlockIndex,
73    parent: BlockIndex,
74    payload: Payload,
75}
76
77#[derive(Debug, Clone)]
78// Note: Some of the inner members on this struct are flagged by the compiler as
79// Unused. In reality, they're used in the `Debug` impl.
80enum Payload {
81    String(#[allow(unused)] String),
82    Bytes(Vec<u8>),
83    Int(i64),
84    Uint(u64),
85    Double(f64),
86    Bool(#[allow(unused)] bool),
87    IntArray(Vec<i64>, ArrayFormat),
88    UintArray(Vec<u64>, ArrayFormat),
89    DoubleArray(Vec<f64>, ArrayFormat),
90    StringArray(Vec<String>),
91    Link { disposition: LinkNodeDisposition, parsed_data: Data },
92
93    // Used when parsing from JSON. We have trouble identifying numeric types and types of
94    // histograms from the output. We can use these generic types to be safe for comparison from
95    // JSON.
96    GenericNumber(#[allow(unused)] String),
97    GenericArray(#[allow(unused)] Vec<String>),
98    GenericLinearHistogram(#[allow(unused)] Vec<String>),
99    GenericExponentialHistogram(#[allow(unused)] Vec<String>),
100}
101
102fn to_string<T: std::fmt::Display>(v: T) -> String {
103    format!("{v}")
104}
105
106fn stringify_list<T: std::fmt::Display>(values: Vec<T>) -> Vec<String> {
107    values.into_iter().map(|x| to_string(x)).collect()
108}
109
110fn stringify_linear_histogram<T: Clone + std::fmt::Display + Zero>(
111    histogram: LinearHistogram<T>,
112) -> Vec<String> {
113    let mut values = vec![T::zero(); histogram.size + 2];
114    values[0] = histogram.floor;
115    values[1] = histogram.step;
116    if let Some(indexes) = histogram.indexes {
117        for (index, count) in indexes.iter().zip(histogram.counts) {
118            values[index + 2] = count.clone();
119        }
120    } else {
121        for (index, count) in histogram.counts.iter().enumerate() {
122            values[index + 2] = count.clone();
123        }
124    }
125    stringify_list(values)
126}
127
128fn stringify_exponential_histogram<T: Clone + std::fmt::Display + Zero>(
129    histogram: ExponentialHistogram<T>,
130) -> Vec<String> {
131    let mut values = vec![T::zero(); histogram.size + 3];
132    values[0] = histogram.floor;
133    values[1] = histogram.initial_step;
134    values[2] = histogram.step_multiplier;
135    if let Some(indexes) = histogram.indexes {
136        for (index, count) in indexes.iter().zip(histogram.counts) {
137            values[index + 3] = count.clone();
138        }
139    } else {
140        for (index, count) in histogram.counts.iter().enumerate() {
141            values[index + 3] = count.clone();
142        }
143    }
144    stringify_list(values)
145}
146
147fn handle_array(values: Vec<String>, format: ArrayFormat) -> Payload {
148    match format {
149        ArrayFormat::LinearHistogram => Payload::GenericLinearHistogram(values),
150        ArrayFormat::ExponentialHistogram => Payload::GenericExponentialHistogram(values),
151        _ => Payload::GenericArray(values),
152    }
153}
154
155trait ToGeneric {
156    fn to_generic(self) -> Payload;
157}
158
159impl ToGeneric for Payload {
160    /// Convert this payload into a generic representation that is compatible with JSON.
161    fn to_generic(self) -> Self {
162        match self {
163            Payload::IntArray(val, format) => handle_array(stringify_list(val), format),
164            Payload::UintArray(val, format) => handle_array(stringify_list(val), format),
165            Payload::DoubleArray(val, format) => handle_array(stringify_list(val), format),
166            Payload::StringArray(val) => handle_array(val, ArrayFormat::Default),
167            Payload::Bytes(val) => Payload::String(format!("b64:{}", BASE64_STANDARD.encode(&val))),
168            Payload::Int(val) => Payload::GenericNumber(to_string(val)),
169            Payload::Uint(val) => Payload::GenericNumber(to_string(val)),
170            Payload::Double(val) => Payload::GenericNumber(to_string(val)),
171            Payload::Link { disposition, parsed_data } => {
172                Payload::Link { disposition, parsed_data: parsed_data.clone_generic() }
173            }
174            val => val,
175        }
176    }
177}
178
179impl<T: std::fmt::Display + Clone + Zero> ToGeneric for ArrayContent<T> {
180    fn to_generic(self) -> Payload {
181        match self {
182            ArrayContent::Values(values) => Payload::GenericArray(stringify_list(values)),
183            ArrayContent::LinearHistogram(histogram) => {
184                Payload::GenericLinearHistogram(stringify_linear_histogram(histogram))
185            }
186            ArrayContent::ExponentialHistogram(histogram) => {
187                Payload::GenericExponentialHistogram(stringify_exponential_histogram(histogram))
188            }
189        }
190    }
191}
192
193struct FormattedEntries {
194    nodes: Vec<String>,
195    properties: Vec<String>,
196}
197
198impl Property {
199    fn to_string(&self, prefix: &str) -> String {
200        format!("{}{}: {:?}", prefix, self.name, &self.payload)
201    }
202
203    /// Formats this property and any additional properties and nodes it may contain (in the case
204    /// of links).
205    fn format_entries(&self, prefix: &str) -> FormattedEntries {
206        match &self.payload {
207            Payload::Link { disposition, parsed_data } => match disposition {
208                // Return a node for the child, replacing its name.
209                LinkNodeDisposition::Child => FormattedEntries {
210                    nodes: vec![format!(
211                        "{}{} ->\n{}",
212                        prefix,
213                        self.name,
214                        parsed_data.nodes[&0.into()].to_string(prefix, parsed_data, true)
215                    )],
216                    properties: vec![],
217                },
218                // Return the nodes and properties (which may themselves have linked nodes) inline
219                // from this property.
220                LinkNodeDisposition::Inline => {
221                    let root = &parsed_data.nodes[&BlockIndex::ROOT];
222                    let mut nodes = root
223                        .children
224                        .iter()
225                        .map(|v| {
226                            parsed_data.nodes.get(v).map_or_else(
227                                || "Missing child".into(),
228                                |n| n.to_string(prefix, parsed_data, false),
229                            )
230                        })
231                        .collect::<Vec<_>>();
232                    let mut properties = vec![];
233                    for FormattedEntries { nodes: mut n, properties: mut p } in
234                        root.properties.iter().map(|v| {
235                            parsed_data.properties.get(v).map_or_else(
236                                || FormattedEntries {
237                                    nodes: vec![],
238                                    properties: vec!["Missing property".into()],
239                                },
240                                |p| p.format_entries(prefix),
241                            )
242                        })
243                    {
244                        nodes.append(&mut n);
245                        properties.append(&mut p);
246                    }
247                    FormattedEntries { nodes, properties }
248                }
249            },
250            // Non-link property, just format as the only returned property.
251            _ => FormattedEntries { nodes: vec![], properties: vec![self.to_string(prefix)] },
252        }
253    }
254}
255
256impl Node {
257    /// If `hide_root` is true and the node is the root,
258    /// then the name and and prefix of the generated string is omitted.
259    /// This is used for lazy nodes wherein we don't what to show the label "root" for lazy nodes.
260    fn to_string(&self, prefix: &str, tree: &Data, hide_root: bool) -> String {
261        let sub_prefix = format!("{prefix}> ");
262        let mut nodes = vec![];
263        for node_id in self.children.iter() {
264            nodes.push(tree.nodes.get(node_id).map_or_else(
265                || "Missing child".into(),
266                |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_else(
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                && !parent.children.remove(&id)
762            {
763                // Some of these can only happen in case of internal logic errors.
764                // I can't think of a way to test them; I think the errors are
765                // actually impossible. Should I leave them untested? Remove them
766                // from the code? Add a special test_cfg make_illegal_node()
767                // function just to test them?
768                bail!("Internal error! Parent {} didn't know about this child {}", node.parent, id);
769            }
770        } else {
771            return Err(format_err!("Delete of nonexistent node {}", id));
772        }
773        Ok(())
774    }
775
776    fn make_tombstone_node(&mut self, id: BlockIndex) -> Result<(), Error> {
777        if id == BlockIndex::ROOT {
778            return Err(format_err!("Internal error! Do not try to delete node 0."));
779        }
780        if let Some(node) = self.nodes.remove(&id) {
781            for child in node.children.clone().iter() {
782                self.make_tombstone_node(*child)?;
783            }
784            for property in node.properties.clone().iter() {
785                self.make_tombstone_property(*property)?;
786            }
787        } else {
788            return Err(format_err!("Internal error! Tried to tombstone nonexistent node {}", id));
789        }
790        self.tombstone_nodes.insert(id);
791        Ok(())
792    }
793
794    fn make_tombstone_property(&mut self, id: BlockIndex) -> Result<(), Error> {
795        if self.properties.remove(&id).is_none() {
796            return Err(format_err!(
797                "Internal error! Tried to tombstone nonexistent property {}",
798                id
799            ));
800        }
801        self.tombstone_properties.insert(id);
802        Ok(())
803    }
804
805    fn add_property(
806        &mut self,
807        parent: BlockIndex,
808        id: BlockIndex,
809        name: &str,
810        payload: Payload,
811    ) -> Result<(), Error> {
812        if let Some(node) = self.nodes.get_mut(&parent) {
813            node.properties.insert(id);
814        } else {
815            return Err(format_err!("Parent {} of property {} not found", parent, id));
816        }
817        if self.tombstone_properties.contains(&id) {
818            return Err(format_err!("Tried to create implicitly deleted property {}", id));
819        }
820        let property = Property { parent, id, name: name.into(), payload };
821        if self.properties.insert(id, property).is_some() {
822            return Err(format_err!("Property insert called on existing id {}", id));
823        }
824        Ok(())
825    }
826
827    fn delete_property(&mut self, id: BlockIndex) -> Result<(), Error> {
828        if self.tombstone_properties.remove(&id) {
829            return Ok(());
830        }
831        if let Some(property) = self.properties.remove(&id) {
832            if let Some(node) = self.nodes.get_mut(&property.parent) {
833                if !node.properties.remove(&id) {
834                    bail!(
835                        "Internal error! Property {}'s parent {} didn't have it as child",
836                        id,
837                        property.parent
838                    );
839                }
840            } else {
841                bail!(
842                    "Internal error! Property {}'s parent {} doesn't exist on delete",
843                    id,
844                    property.parent
845                );
846            }
847        } else {
848            return Err(format_err!("Delete of nonexistent property {}", id));
849        }
850        Ok(())
851    }
852
853    fn modify_number(
854        &mut self,
855        id: BlockIndex,
856        value: &validate::Value,
857        op: Op,
858    ) -> Result<(), Error> {
859        if let Some(property) = self.properties.get_mut(&id) {
860            match (&property, value) {
861                (Property { payload: Payload::Int(old), .. }, Value::IntT(value)) => {
862                    property.payload = Payload::Int((op.int)(*old, *value));
863                }
864                (Property { payload: Payload::Uint(old), .. }, Value::UintT(value)) => {
865                    property.payload = Payload::Uint((op.uint)(*old, *value));
866                }
867                (Property { payload: Payload::Double(old), .. }, Value::DoubleT(value)) => {
868                    property.payload = Payload::Double((op.double)(*old, *value));
869                }
870                unexpected => {
871                    return Err(format_err!("Bad types {:?} trying to set number", unexpected));
872                }
873            }
874        } else {
875            return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
876        }
877        Ok(())
878    }
879
880    fn modify_array(
881        &mut self,
882        id: BlockIndex,
883        index64: u64,
884        value: &validate::Value,
885        op: Op,
886    ) -> Result<(), Error> {
887        if let Some(mut property) = self.properties.get_mut(&id) {
888            let index = index64 as usize;
889            // Out of range index is a NOP, not an error.
890            let array_len = match &property {
891                Property { payload: Payload::IntArray(numbers, ArrayFormat::Default), .. } => {
892                    numbers.len()
893                }
894                Property { payload: Payload::UintArray(numbers, ArrayFormat::Default), .. } => {
895                    numbers.len()
896                }
897                Property {
898                    payload: Payload::DoubleArray(numbers, ArrayFormat::Default), ..
899                } => numbers.len(),
900                Property { payload: Payload::StringArray(values), .. } => values.len(),
901                unexpected => {
902                    return Err(format_err!(
903                        "Bad types {:?} trying to set number of elements",
904                        unexpected
905                    ));
906                }
907            };
908            if index >= array_len {
909                return Ok(());
910            }
911            match (&mut property, value) {
912                (Property { payload: Payload::IntArray(numbers, _), .. }, Value::IntT(value)) => {
913                    numbers[index] = (op.int)(numbers[index], *value);
914                }
915                (Property { payload: Payload::UintArray(numbers, _), .. }, Value::UintT(value)) => {
916                    numbers[index] = (op.uint)(numbers[index], *value);
917                }
918                (
919                    Property { payload: Payload::DoubleArray(numbers, _), .. },
920                    Value::DoubleT(value),
921                ) => {
922                    numbers[index] = (op.double)(numbers[index], *value);
923                }
924                (Property { payload: Payload::StringArray(values), .. }, Value::StringT(value)) => {
925                    values[index] = (op.string)(values[index].clone(), value.clone());
926                }
927                unexpected => {
928                    return Err(format_err!("Type mismatch {:?} trying to set value", unexpected));
929                }
930            }
931        } else {
932            return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
933        }
934        Ok(())
935    }
936
937    fn set_string(&mut self, id: BlockIndex, value: &String) -> Result<(), Error> {
938        if let Some(property) = self.properties.get_mut(&id) {
939            match &property {
940                Property { payload: Payload::String(_), .. } => {
941                    property.payload = Payload::String(value.to_owned())
942                }
943                unexpected => {
944                    return Err(format_err!("Bad type {:?} trying to set string", unexpected));
945                }
946            }
947        } else {
948            return Err(format_err!("Tried to set string on nonexistent property {}", id));
949        }
950        Ok(())
951    }
952
953    fn set_bytes(&mut self, id: BlockIndex, value: &Vec<u8>) -> Result<(), Error> {
954        if let Some(property) = self.properties.get_mut(&id) {
955            match &property {
956                Property { payload: Payload::Bytes(_), .. } => {
957                    property.payload = Payload::Bytes(value.to_owned())
958                }
959                unexpected => {
960                    return Err(format_err!("Bad type {:?} trying to set bytes", unexpected));
961                }
962            }
963        } else {
964            return Err(format_err!("Tried to set bytes on nonexistent property {}", id));
965        }
966        Ok(())
967    }
968
969    fn set_bool(&mut self, id: BlockIndex, value: bool) -> Result<(), Error> {
970        if let Some(property) = self.properties.get_mut(&id) {
971            match &property {
972                Property { payload: Payload::Bool(_), .. } => {
973                    property.payload = Payload::Bool(value)
974                }
975                unexpected => {
976                    return Err(format_err!("Bad type {:?} trying to set bool", unexpected));
977                }
978            }
979        } else {
980            return Err(format_err!("Tried to set bool on nonexistent property {}", id));
981        }
982        Ok(())
983    }
984
985    fn add_lazy_node(
986        &mut self,
987        parent: BlockIndex,
988        id: BlockIndex,
989        name: &str,
990        disposition: &validate::LinkDisposition,
991        actions: &Vec<validate::Action>,
992    ) -> Result<(), Error> {
993        let mut parsed_data = Data::new();
994        parsed_data.apply_multiple(actions)?;
995        self.add_property(
996            parent,
997            id,
998            name,
999            Payload::Link {
1000                disposition: match disposition {
1001                    validate::LinkDisposition::Child => LinkNodeDisposition::Child,
1002                    validate::LinkDisposition::Inline => LinkNodeDisposition::Inline,
1003                },
1004                parsed_data,
1005            },
1006        )?;
1007        Ok(())
1008    }
1009
1010    // ***** Here are the functions to compare two Data (by converting to a
1011    // ***** fully informative string).
1012
1013    /// Make a clone of this Data with all properties replaced with their generic version.
1014    fn clone_generic(&self) -> Self {
1015        let mut clone = self.clone();
1016
1017        let mut to_remove = vec![];
1018        let mut names = HashSet::new();
1019
1020        clone.properties = clone
1021            .properties
1022            .into_iter()
1023            .filter_map(|(id, mut v)| {
1024                // We do not support duplicate property names within a single node in our JSON
1025                // output.
1026                // Delete one of the nodes from the tree.
1027                //
1028                // Note: This can cause errors if the children do not have the same properties.
1029                if !names.insert((v.parent, v.name.clone())) {
1030                    to_remove.push((v.parent, id));
1031                    None
1032                } else {
1033                    v.payload = v.payload.to_generic();
1034                    Some((id, v))
1035                }
1036            })
1037            .collect::<HashMap<BlockIndex, Property>>();
1038
1039        // Clean up removed properties.
1040        for (parent, id) in to_remove {
1041            if clone.nodes.contains_key(&parent) {
1042                clone.nodes.get_mut(&parent).unwrap().properties.remove(&id);
1043            }
1044        }
1045
1046        clone
1047    }
1048
1049    /// Compare this data with data that should be equivalent but was parsed from JSON.
1050    ///
1051    /// This method tweaks some types to deal with JSON representation of the data, which is not as
1052    /// precise as the Inspect format itself.
1053    pub fn compare_to_json(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1054        self.clone_generic().compare(other, diff_type)
1055    }
1056
1057    /// Compares two in-memory Inspect trees, returning Ok(()) if they have the
1058    /// same data and an Err<> if they are different. The string in the Err<>
1059    /// may be very large.
1060    pub fn compare(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1061        let self_string = self.to_string();
1062        let other_string = other.to_string();
1063
1064        let difference::Changeset { diffs, distance, .. } =
1065            difference::Changeset::new(&self_string, &other_string, "\n");
1066
1067        if distance == 0 {
1068            Ok(())
1069        } else {
1070            let diff_lines = diffs
1071                .into_iter()
1072                .flat_map(|diff| {
1073                    let (prefix, val) = match diff {
1074                        // extra space so that all ':'s in output are aligned
1075                        difference::Difference::Same(val) => (" same", val),
1076                        difference::Difference::Add(val) => ("other", val),
1077                        difference::Difference::Rem(val) => ("local", val),
1078                    };
1079                    val.split("\n").map(|line| format!("{prefix}: {line:?}")).collect::<Vec<_>>()
1080                })
1081                .collect::<Vec<_>>();
1082
1083            match diff_type {
1084                DiffType::Full => Err(format_err!(
1085                    "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}",
1086                    self_string,
1087                    other_string
1088                )),
1089                DiffType::Diff => {
1090                    Err(format_err!("Trees differ:\n-- DIFF --\n{}", diff_lines.join("\n")))
1091                }
1092                DiffType::Both => Err(format_err!(
1093                    "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}\n-- DIFF --\n{}",
1094                    self_string,
1095                    other_string,
1096                    diff_lines.join("\n")
1097                )),
1098            }
1099        }
1100    }
1101
1102    /// This creates a new Data. Note that the standard "root" node of the VMO API
1103    /// corresponds to the index-0 node added here.
1104    pub fn new() -> Data {
1105        let mut ret = Data {
1106            nodes: HashMap::new(),
1107            properties: HashMap::new(),
1108            tombstone_nodes: HashSet::new(),
1109            tombstone_properties: HashSet::new(),
1110        };
1111        ret.nodes.insert(
1112            BlockIndex::ROOT,
1113            Node {
1114                name: ROOT_NAME.into(),
1115                parent: BlockIndex::ROOT,
1116                children: HashSet::new(),
1117                properties: HashSet::new(),
1118            },
1119        );
1120        ret
1121    }
1122
1123    fn build(nodes: HashMap<BlockIndex, Node>, properties: HashMap<BlockIndex, Property>) -> Data {
1124        Data {
1125            nodes,
1126            properties,
1127            tombstone_nodes: HashSet::new(),
1128            tombstone_properties: HashSet::new(),
1129        }
1130    }
1131
1132    fn apply_multiple(&mut self, actions: &Vec<validate::Action>) -> Result<(), Error> {
1133        for action in actions {
1134            self.apply(action)?;
1135        }
1136        Ok(())
1137    }
1138
1139    /// Removes a top-level tree if present (if not, does nothing)
1140    pub fn remove_tree(&mut self, name: &str) {
1141        if self.is_empty() {
1142            return;
1143        }
1144        let mut target_node_id = None;
1145        let root = &self.nodes[&BlockIndex::ROOT];
1146        for child_id in &root.children {
1147            if let Some(node) = self.nodes.get(child_id)
1148                && node.name == name
1149            {
1150                target_node_id = Some(*child_id);
1151                break;
1152            }
1153        }
1154        if let Some(id) = target_node_id {
1155            self.delete_node(id).unwrap();
1156        }
1157    }
1158
1159    /// Return true if this data is not just an empty root node.
1160    pub fn is_empty(&self) -> bool {
1161        if !self.nodes.contains_key(&BlockIndex::ROOT) {
1162            // No root
1163            return true;
1164        }
1165
1166        // There are issues with displaying a tree that has no properties.
1167        // TODO(https://fxbug.dev/42126876): Support empty trees in archive.
1168        if self.properties.len() == 0 {
1169            return true;
1170        }
1171
1172        // Root has no properties and any children it may have are tombstoned.
1173        let root = &self.nodes[&BlockIndex::ROOT];
1174        root.children.is_subset(&self.tombstone_nodes) && root.properties.len() == 0
1175    }
1176}
1177
1178impl fmt::Display for Data {
1179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1180        let s = if let Some(node) = self.nodes.get(&BlockIndex::ROOT) {
1181            node.to_string("", self, false)
1182        } else {
1183            "No root node; internal error\n".to_owned()
1184        };
1185        write!(f, "{s}",)
1186    }
1187}
1188
1189impl From<DiagnosticsHierarchy> for Data {
1190    fn from(hierarchy: DiagnosticsHierarchy) -> Self {
1191        let mut nodes = HashMap::new();
1192        let mut properties = HashMap::new();
1193
1194        nodes.insert(
1195            0.into(),
1196            Node {
1197                name: hierarchy.name.clone(),
1198                parent: 0.into(),
1199                children: HashSet::new(),
1200                properties: HashSet::new(),
1201            },
1202        );
1203
1204        let mut queue = vec![(0.into(), &hierarchy)];
1205        let mut next_id = BlockIndex::new(1);
1206
1207        while let Some((id, value)) = queue.pop() {
1208            for node in value.children.iter() {
1209                let child_id = next_id;
1210                next_id += 1;
1211                nodes.insert(
1212                    child_id,
1213                    Node {
1214                        name: node.name.clone(),
1215                        parent: id,
1216                        children: HashSet::new(),
1217                        properties: HashSet::new(),
1218                    },
1219                );
1220                nodes.get_mut(&id).expect("parent must exist").children.insert(child_id);
1221                queue.push((child_id, node));
1222            }
1223            for property in value.properties.iter() {
1224                let prop_id = next_id;
1225                next_id += 1;
1226
1227                let (name, payload) = match property.clone() {
1228                    iProperty::String(n, v) => (n, Payload::String(v).to_generic()),
1229                    iProperty::Bytes(n, v) => (n, Payload::Bytes(v).to_generic()),
1230                    iProperty::Int(n, v) => (n, Payload::Int(v).to_generic()),
1231                    iProperty::Uint(n, v) => (n, Payload::Uint(v).to_generic()),
1232                    iProperty::Double(n, v) => (n, Payload::Double(v).to_generic()),
1233                    iProperty::Bool(n, v) => (n, Payload::Bool(v).to_generic()),
1234                    iProperty::IntArray(n, content) => (n, content.to_generic()),
1235                    iProperty::UintArray(n, content) => (n, content.to_generic()),
1236                    iProperty::DoubleArray(n, content) => (n, content.to_generic()),
1237                    iProperty::StringList(n, content) => {
1238                        (n, Payload::StringArray(content).to_generic())
1239                    }
1240                };
1241
1242                properties.insert(prop_id, Property { name, id: prop_id, parent: id, payload });
1243                nodes.get_mut(&id).expect("parent must exist").properties.insert(prop_id);
1244            }
1245        }
1246
1247        Data {
1248            nodes,
1249            properties,
1250            tombstone_nodes: HashSet::new(),
1251            tombstone_properties: HashSet::new(),
1252        }
1253    }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258    use super::*;
1259    use crate::*;
1260    use fidl_diagnostics_validate::{ROOT_ID, ValueType};
1261    use fuchsia_inspect::reader::ArrayContent as iArrayContent;
1262    use inspect_format::BlockType;
1263    use num_derive::{FromPrimitive, ToPrimitive};
1264
1265    #[fuchsia::test]
1266    fn test_basic_data_strings() -> Result<(), Error> {
1267        let mut info = Data::new();
1268        assert_eq!(info.to_string(), "root ->");
1269
1270        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "foo"))?;
1271        assert_eq!(info.to_string(), "root ->\n> foo ->");
1272
1273        info.apply(&delete_node!( id: 1 ))?;
1274        assert_eq!(info.to_string(), "root ->");
1275
1276        Ok(())
1277    }
1278
1279    const EXPECTED_HIERARCHY: &str = r#"root ->
1280> double: GenericNumber("2.5")
1281> int: GenericNumber("-5")
1282> string: String("value")
1283> uint: GenericNumber("10")
1284> child ->
1285> > bytes: String("b64:AQI=")
1286> > grandchild ->
1287> > > double_a: GenericArray(["0.5", "1"])
1288> > > double_eh: GenericExponentialHistogram(["0.5", "0.5", "2", "1", "2", "3"])
1289> > > double_lh: GenericLinearHistogram(["0.5", "0.5", "1", "2", "3"])
1290> > > int_a: GenericArray(["-1", "-2"])
1291> > > int_eh: GenericExponentialHistogram(["-1", "1", "2", "1", "2", "3"])
1292> > > int_lh: GenericLinearHistogram(["-1", "1", "1", "2", "3"])
1293> > > uint_a: GenericArray(["1", "2"])
1294> > > uint_eh: GenericExponentialHistogram(["1", "1", "2", "1", "2", "3"])
1295> > > uint_lh: GenericLinearHistogram(["1", "1", "1", "2", "3"])"#;
1296
1297    // There's no enum in fuchsia_inspect::format::block which contains only
1298    // values that are valid for an ArrayType.
1299    #[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
1300    enum ArrayType {
1301        Int = 4,
1302        Uint = 5,
1303        Double = 6,
1304    }
1305
1306    #[fuchsia::test]
1307    fn test_parse_hierarchy() -> Result<(), Error> {
1308        let hierarchy = DiagnosticsHierarchy {
1309            name: "root".to_string(),
1310            properties: vec![
1311                iProperty::String("string".to_string(), "value".to_string()),
1312                iProperty::Uint("uint".to_string(), 10u64),
1313                iProperty::Int("int".to_string(), -5i64),
1314                iProperty::Double("double".to_string(), 2.5f64),
1315            ],
1316            children: vec![DiagnosticsHierarchy {
1317                name: "child".to_string(),
1318                properties: vec![iProperty::Bytes("bytes".to_string(), vec![1u8, 2u8])],
1319                children: vec![DiagnosticsHierarchy {
1320                    name: "grandchild".to_string(),
1321                    properties: vec![
1322                        iProperty::UintArray(
1323                            "uint_a".to_string(),
1324                            iArrayContent::Values(vec![1, 2]),
1325                        ),
1326                        iProperty::IntArray(
1327                            "int_a".to_string(),
1328                            iArrayContent::Values(vec![-1i64, -2i64]),
1329                        ),
1330                        iProperty::DoubleArray(
1331                            "double_a".to_string(),
1332                            iArrayContent::Values(vec![0.5, 1.0]),
1333                        ),
1334                        iProperty::UintArray(
1335                            "uint_lh".to_string(),
1336                            iArrayContent::new(vec![1, 1, 1, 2, 3], ArrayFormat::LinearHistogram)
1337                                .unwrap(),
1338                        ),
1339                        iProperty::IntArray(
1340                            "int_lh".to_string(),
1341                            iArrayContent::new(
1342                                vec![-1i64, 1, 1, 2, 3],
1343                                ArrayFormat::LinearHistogram,
1344                            )
1345                            .unwrap(),
1346                        ),
1347                        iProperty::DoubleArray(
1348                            "double_lh".to_string(),
1349                            iArrayContent::new(
1350                                vec![0.5, 0.5, 1.0, 2.0, 3.0],
1351                                ArrayFormat::LinearHistogram,
1352                            )
1353                            .unwrap(),
1354                        ),
1355                        iProperty::UintArray(
1356                            "uint_eh".to_string(),
1357                            iArrayContent::new(
1358                                vec![1, 1, 2, 1, 2, 3],
1359                                ArrayFormat::ExponentialHistogram,
1360                            )
1361                            .unwrap(),
1362                        ),
1363                        iProperty::IntArray(
1364                            "int_eh".to_string(),
1365                            iArrayContent::new(
1366                                vec![-1i64, 1, 2, 1, 2, 3],
1367                                ArrayFormat::ExponentialHistogram,
1368                            )
1369                            .unwrap(),
1370                        ),
1371                        iProperty::DoubleArray(
1372                            "double_eh".to_string(),
1373                            iArrayContent::new(
1374                                vec![0.5, 0.5, 2.0, 1.0, 2.0, 3.0],
1375                                ArrayFormat::ExponentialHistogram,
1376                            )
1377                            .unwrap(),
1378                        ),
1379                    ],
1380                    children: vec![],
1381                    missing: vec![],
1382                }],
1383                missing: vec![],
1384            }],
1385            missing: vec![],
1386        };
1387
1388        let data: Data = hierarchy.into();
1389        assert_eq!(EXPECTED_HIERARCHY, data.to_string());
1390
1391        Ok(())
1392    }
1393
1394    // Make sure every action correctly modifies the string representation of the data tree.
1395    #[fuchsia::test]
1396    fn test_creation_deletion() -> Result<(), Error> {
1397        let mut info = Data::new();
1398        assert!(!info.to_string().contains("child ->"));
1399        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
1400        assert!(info.to_string().contains("child ->"));
1401
1402        info.apply(&create_node!(parent: 1, id: 2, name: "grandchild"))?;
1403        assert!(
1404            info.to_string().contains("grandchild ->") && info.to_string().contains("child ->")
1405        );
1406
1407        info.apply(
1408            &create_numeric_property!(parent: ROOT_ID, id: 3, name: "int-42", value: Value::IntT(-42)),
1409        )?;
1410
1411        assert!(info.to_string().contains("int-42: Int(-42)")); // Make sure it can hold negative #
1412        info.apply(&create_string_property!(parent: 1, id: 4, name: "stringfoo", value: "foo"))?;
1413        assert_eq!(
1414            info.to_string(),
1415            "root ->\n> int-42: Int(-42)\n> child ->\
1416             \n> > stringfoo: String(\"foo\")\n> > grandchild ->"
1417        );
1418
1419        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 5, name: "uint", value: Value::UintT(1024)))?;
1420        assert!(info.to_string().contains("uint: Uint(1024)"));
1421
1422        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 6, name: "frac", value: Value::DoubleT(0.5)))?;
1423        assert!(info.to_string().contains("frac: Double(0.5)"));
1424
1425        info.apply(
1426            &create_bytes_property!(parent: ROOT_ID, id: 7, name: "bytes", value: vec!(1u8, 2u8)),
1427        )?;
1428        assert!(info.to_string().contains("bytes: Bytes([1, 2])"));
1429
1430        info.apply(&create_array_property!(parent: ROOT_ID, id: 8, name: "i_ntarr", slots: 1, type: ValueType::Int))?;
1431        assert!(info.to_string().contains("i_ntarr: IntArray([0], Default)"));
1432
1433        info.apply(&create_array_property!(parent: ROOT_ID, id: 9, name: "u_intarr", slots: 2, type: ValueType::Uint))?;
1434        assert!(info.to_string().contains("u_intarr: UintArray([0, 0], Default)"));
1435
1436        info.apply(&create_array_property!(parent: ROOT_ID, id: 10, name: "dblarr", slots: 3, type: ValueType::Double))?;
1437        assert!(info.to_string().contains("dblarr: DoubleArray([0.0, 0.0, 0.0], Default)"));
1438
1439        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 11, name: "ILhist", floor: 12,
1440            step_size: 3, buckets: 2, type: IntT))?;
1441        assert!(
1442            info.to_string().contains("ILhist: IntArray([12, 3, 0, 0, 0, 0], LinearHistogram)")
1443        );
1444
1445        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 12, name: "ULhist", floor: 34,
1446            step_size: 5, buckets: 2, type: UintT))?;
1447        assert!(
1448            info.to_string().contains("ULhist: UintArray([34, 5, 0, 0, 0, 0], LinearHistogram)")
1449        );
1450
1451        info.apply(
1452            &create_linear_histogram!(parent: ROOT_ID, id: 13, name: "DLhist", floor: 56.0,
1453            step_size: 7.0, buckets: 2, type: DoubleT),
1454        )?;
1455        assert!(
1456            info.to_string()
1457                .contains("DLhist: DoubleArray([56.0, 7.0, 0.0, 0.0, 0.0, 0.0], LinearHistogram)")
1458        );
1459
1460        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 14, name: "IEhist",
1461            floor: 12, initial_step: 3, step_multiplier: 5, buckets: 2, type: IntT))?;
1462        assert!(
1463            info.to_string()
1464                .contains("IEhist: IntArray([12, 3, 5, 0, 0, 0, 0], ExponentialHistogram)")
1465        );
1466
1467        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 15, name: "UEhist",
1468            floor: 34, initial_step: 9, step_multiplier: 6, buckets: 2, type: UintT))?;
1469        assert!(
1470            info.to_string()
1471                .contains("UEhist: UintArray([34, 9, 6, 0, 0, 0, 0], ExponentialHistogram)")
1472        );
1473
1474        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 16, name: "DEhist",
1475            floor: 56.0, initial_step: 27.0, step_multiplier: 7.0, buckets: 2, type: DoubleT))?;
1476        assert!(info.to_string().contains(
1477            "DEhist: DoubleArray([56.0, 27.0, 7.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1478        ));
1479
1480        info.apply(&create_bool_property!(parent: ROOT_ID, id: 17, name: "bool", value: true))?;
1481        assert!(info.to_string().contains("bool: Bool(true)"));
1482
1483        info.apply(&delete_property!(id: 3))?;
1484        assert!(!info.to_string().contains("int-42") && info.to_string().contains("stringfoo"));
1485        info.apply(&delete_property!(id: 4))?;
1486        assert!(!info.to_string().contains("stringfoo"));
1487        info.apply(&delete_property!(id: 5))?;
1488        assert!(!info.to_string().contains("uint"));
1489        info.apply(&delete_property!(id: 6))?;
1490        assert!(!info.to_string().contains("frac"));
1491        info.apply(&delete_property!(id: 7))?;
1492        assert!(!info.to_string().contains("bytes"));
1493        info.apply(&delete_property!(id: 8))?;
1494        assert!(!info.to_string().contains("i_ntarr"));
1495        info.apply(&delete_property!(id: 9))?;
1496        assert!(!info.to_string().contains("u_intarr"));
1497        info.apply(&delete_property!(id: 10))?;
1498        assert!(!info.to_string().contains("dblarr"));
1499        info.apply(&delete_property!(id: 11))?;
1500        assert!(!info.to_string().contains("ILhist"));
1501        info.apply(&delete_property!(id: 12))?;
1502        assert!(!info.to_string().contains("ULhist"));
1503        info.apply(&delete_property!(id: 13))?;
1504        assert!(!info.to_string().contains("DLhist"));
1505        info.apply(&delete_property!(id: 14))?;
1506        assert!(!info.to_string().contains("IEhist"));
1507        info.apply(&delete_property!(id: 15))?;
1508        assert!(!info.to_string().contains("UEhist"));
1509        info.apply(&delete_property!(id: 16))?;
1510        assert!(!info.to_string().contains("DEhist"));
1511        info.apply(&delete_property!(id: 17))?;
1512        assert!(!info.to_string().contains("bool"));
1513        info.apply(&delete_node!(id:2))?;
1514        assert!(!info.to_string().contains("grandchild") && info.to_string().contains("child"));
1515        info.apply(&delete_node!( id: 1 ))?;
1516        assert_eq!(info.to_string(), "root ->");
1517        Ok(())
1518    }
1519
1520    #[fuchsia::test]
1521    fn test_basic_int_ops() -> Result<(), Error> {
1522        let mut info = Data::new();
1523        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1524                                     value: Value::IntT(-42)))?;
1525        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_ok());
1526        assert!(info.to_string().contains("value: Int(-39)"));
1527        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1528        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1529        assert!(info.to_string().contains("value: Int(-39)"));
1530        assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_ok());
1531        assert!(info.to_string().contains("value: Int(-44)"));
1532        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1533        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1534        assert!(info.to_string().contains("value: Int(-44)"));
1535        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(22))).is_ok());
1536        assert!(info.to_string().contains("value: Int(22)"));
1537        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1538        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1539        assert!(info.to_string().contains("value: Int(22)"));
1540        Ok(())
1541    }
1542
1543    #[fuchsia::test]
1544    fn test_array_string_ops() -> Result<(), Error> {
1545        let mut info = Data::new();
1546        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1547                                           type: ValueType::String))?;
1548        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1549        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1550        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1551        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1552        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""]"#));
1553        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1554        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1555        assert!(
1556            info.apply(&array_subtract!(id: 3, index: 2,
1557                                            value: Value::DoubleT(5.0)))
1558                .is_err()
1559        );
1560        assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1561        assert!(
1562            info.apply(&array_set!(id: 3, index: 1, value: Value::StringT("data".into()))).is_ok()
1563        );
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!(
1589            info.apply(&array_subtract!(id: 3, index: 2,
1590                                            value: Value::DoubleT(5.0)))
1591                .is_err()
1592        );
1593        assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1594        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(22))).is_ok());
1595        assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1596        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1597        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1598        assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1599        Ok(())
1600    }
1601
1602    #[fuchsia::test]
1603    fn test_linear_int_ops() -> Result<(), Error> {
1604        let mut info = Data::new();
1605        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1606                    floor: 4, step_size: 2, buckets: 2, type: IntT))?;
1607        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1608        assert!(info.apply(&insert!(id: 3, value: Value::IntT(4))).is_ok());
1609        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1610        assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1611        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1612        assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1613        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1614        assert!(info.apply(&insert!(id: 3, value: Value::IntT(8))).is_ok());
1615        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1616        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1617        assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1618        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1619        assert!(info.to_string().contains("value: IntArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1620        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1621        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1622        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1623        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1624        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1625        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1626        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(7), count: 4)).is_ok());
1627        assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 5, 2], LinearHistogram)"));
1628        Ok(())
1629    }
1630
1631    #[fuchsia::test]
1632    fn test_exponential_int_ops() -> Result<(), Error> {
1633        let mut info = Data::new();
1634        // Bucket boundaries are 5, 7, 13
1635        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1636                    floor: 5, initial_step: 2,
1637                    step_multiplier: 4, buckets: 2, type: IntT))?;
1638        assert!(
1639            info.to_string()
1640                .contains("value: IntArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)")
1641        );
1642        assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1643        assert!(
1644            info.to_string()
1645                .contains("value: IntArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)")
1646        );
1647        assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1648        assert!(
1649            info.to_string()
1650                .contains("value: IntArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)")
1651        );
1652        assert!(info.apply(&insert!(id: 3, value: Value::IntT(7))).is_ok());
1653        assert!(
1654            info.to_string()
1655                .contains("value: IntArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)")
1656        );
1657        assert!(info.apply(&insert!(id: 3, value: Value::IntT(13))).is_ok());
1658        assert!(
1659            info.to_string()
1660                .contains("value: IntArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)")
1661        );
1662        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1663        assert!(
1664            info.to_string()
1665                .contains("value: IntArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)")
1666        );
1667        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1668        assert!(
1669            info.to_string()
1670                .contains("value: IntArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1671        );
1672        assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1673        assert!(
1674            info.to_string()
1675                .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)")
1676        );
1677        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1678        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1679        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1680        assert!(
1681            info.to_string()
1682                .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)")
1683        );
1684        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(12), count: 4)).is_ok());
1685        assert!(
1686            info.to_string()
1687                .contains("value: IntArray([5, 2, 4, 2, 2, 5, 2], ExponentialHistogram)")
1688        );
1689        Ok(())
1690    }
1691
1692    #[fuchsia::test]
1693    fn test_array_out_of_bounds_nop() -> Result<(), Error> {
1694        // Accesses to indexes beyond the array are legal and should have no effect on the data.
1695        let mut info = Data::new();
1696        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1697                                           type: ValueType::Int))?;
1698        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1699        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1700        assert!(info.apply(&array_add!(id: 3, index: 3, value: Value::IntT(3))).is_ok());
1701        assert!(info.apply(&array_add!(id: 3, index: 6, value: Value::IntT(3))).is_ok());
1702        assert!(info.apply(&array_add!(id: 3, index: 12345, value: Value::IntT(3))).is_ok());
1703        assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1704        Ok(())
1705    }
1706
1707    #[fuchsia::test]
1708    fn test_basic_uint_ops() -> Result<(), Error> {
1709        let mut info = Data::new();
1710        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1711                                     value: Value::UintT(42)))?;
1712        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_ok());
1713        assert!(info.to_string().contains("value: Uint(45)"));
1714        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1715        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1716        assert!(info.to_string().contains("value: Uint(45)"));
1717        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_ok());
1718        assert!(info.to_string().contains("value: Uint(40)"));
1719        assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_err());
1720        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1721        assert!(info.to_string().contains("value: Uint(40)"));
1722        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(22))).is_ok());
1723        assert!(info.to_string().contains("value: Uint(22)"));
1724        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(23))).is_err());
1725        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1726        assert!(info.to_string().contains("value: Uint(22)"));
1727        Ok(())
1728    }
1729
1730    #[fuchsia::test]
1731    fn test_array_uint_ops() -> Result<(), Error> {
1732        let mut info = Data::new();
1733        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1734                                     type: ValueType::Uint))?;
1735        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_ok());
1736        assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1737        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1738        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1739        assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1740        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(22))).is_ok());
1741        assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1742        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1743        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1744        assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1745        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::UintT(5))).is_ok());
1746        assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1747        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::IntT(5))).is_err());
1748        assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::DoubleT(5.0))).is_err());
1749        assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1750        Ok(())
1751    }
1752
1753    #[fuchsia::test]
1754    fn test_linear_uint_ops() -> Result<(), Error> {
1755        let mut info = Data::new();
1756        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1757                    floor: 4, step_size: 2, buckets: 2, type: UintT))?;
1758        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1759        assert!(info.apply(&insert!(id: 3, value: Value::UintT(4))).is_ok());
1760        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1761        assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1762        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1763        assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1764        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1765        assert!(info.apply(&insert!(id: 3, value: Value::UintT(8))).is_ok());
1766        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1767        assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1768        assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1769        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1770        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1771        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1772        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1773        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1774        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1775        assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(7), count: 4)).is_ok());
1776        assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 5, 2], LinearHistogram)"));
1777        Ok(())
1778    }
1779
1780    #[fuchsia::test]
1781    fn test_exponential_uint_ops() -> Result<(), Error> {
1782        let mut info = Data::new();
1783        // Bucket boundaries are 5, 7, 13
1784        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1785                    floor: 5, initial_step: 2,
1786                    step_multiplier: 4, buckets: 2, type: UintT))?;
1787        assert!(
1788            info.to_string()
1789                .contains("value: UintArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)")
1790        );
1791        assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1792        assert!(
1793            info.to_string()
1794                .contains("value: UintArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)")
1795        );
1796        assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1797        assert!(
1798            info.to_string()
1799                .contains("value: UintArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)")
1800        );
1801        assert!(info.apply(&insert!(id: 3, value: Value::UintT(7))).is_ok());
1802        assert!(
1803            info.to_string()
1804                .contains("value: UintArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)")
1805        );
1806        assert!(info.apply(&insert!(id: 3, value: Value::UintT(13))).is_ok());
1807        assert!(
1808            info.to_string()
1809                .contains("value: UintArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)")
1810        );
1811        assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1812        assert!(
1813            info.to_string()
1814                .contains("value: UintArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)")
1815        );
1816        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1817        assert!(
1818            info.to_string()
1819                .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1820        );
1821        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1822        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1823        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1824        assert!(
1825            info.to_string()
1826                .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1827        );
1828        assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(12), count: 4)).is_ok());
1829        assert!(
1830            info.to_string()
1831                .contains("value: UintArray([5, 2, 4, 1, 2, 5, 2], ExponentialHistogram)")
1832        );
1833        Ok(())
1834    }
1835
1836    #[fuchsia::test]
1837    fn test_basic_double_ops() -> Result<(), Error> {
1838        let mut info = Data::new();
1839        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1840                                     value: Value::DoubleT(42.0)))?;
1841        assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_ok());
1842        assert!(info.to_string().contains("value: Double(45.0)"));
1843        assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1844        assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1845        assert!(info.to_string().contains("value: Double(45.0)"));
1846        assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1847        assert!(info.to_string().contains("value: Double(40.0)"));
1848        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1849        assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1850        assert!(info.to_string().contains("value: Double(40.0)"));
1851        assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(22.0))).is_ok());
1852        assert!(info.to_string().contains("value: Double(22.0)"));
1853        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1854        assert!(info.apply(&set_number!(id: 3, value: Value::UintT(24))).is_err());
1855        assert!(info.to_string().contains("value: Double(22.0)"));
1856        Ok(())
1857    }
1858
1859    #[fuchsia::test]
1860    fn test_array_double_ops() -> Result<(), Error> {
1861        let mut info = Data::new();
1862        info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1863                                     type: ValueType::Double))?;
1864        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_ok());
1865        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1866        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1867        assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1868        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1869        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::DoubleT(5.0))).is_ok());
1870        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1871        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1872        assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1873        assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1874        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(22.0))).is_ok());
1875        assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1876        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1877        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(24))).is_err());
1878        assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1879        Ok(())
1880    }
1881
1882    #[fuchsia::test]
1883    fn test_linear_double_ops() -> Result<(), Error> {
1884        let mut info = Data::new();
1885        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1886                    floor: 4.0, step_size: 0.5, buckets: 2, type: DoubleT))?;
1887        assert!(
1888            info.to_string()
1889                .contains("value: DoubleArray([4.0, 0.5, 0.0, 0.0, 0.0, 0.0], LinearHistogram)")
1890        );
1891        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.0))).is_ok());
1892        assert!(
1893            info.to_string()
1894                .contains("value: DoubleArray([4.0, 0.5, 0.0, 1.0, 0.0, 0.0], LinearHistogram)")
1895        );
1896        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.25))).is_ok());
1897        assert!(
1898            info.to_string()
1899                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 0.0, 0.0], LinearHistogram)")
1900        );
1901        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.75))).is_ok());
1902        assert!(
1903            info.to_string()
1904                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 0.0], LinearHistogram)")
1905        );
1906        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.1))).is_ok());
1907        assert!(
1908            info.to_string()
1909                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 1.0], LinearHistogram)")
1910        );
1911        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1912        assert!(
1913            info.to_string()
1914                .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 2.0], LinearHistogram)")
1915        );
1916        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1917        assert!(
1918            info.to_string()
1919                .contains("value: DoubleArray([4.0, 0.5, 1.0, 2.0, 1.0, 2.0], LinearHistogram)")
1920        );
1921        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1922        assert!(
1923            info.to_string()
1924                .contains("value: DoubleArray([4.0, 0.5, 2.0, 2.0, 1.0, 2.0], LinearHistogram)")
1925        );
1926        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1927        assert!(
1928            info.to_string()
1929                .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)")
1930        );
1931        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1932        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1933        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1934        assert!(
1935            info.to_string()
1936                .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)")
1937        );
1938        assert!(info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(4.5), count: 4)).is_ok());
1939        assert!(
1940            info.to_string()
1941                .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 5.0, 2.0], LinearHistogram)")
1942        );
1943        Ok(())
1944    }
1945
1946    #[fuchsia::test]
1947    fn test_exponential_double_ops() -> Result<(), Error> {
1948        let mut info = Data::new();
1949        // Bucket boundaries are 5, 7, 13, 37
1950        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1951                    floor: 5.0, initial_step: 2.0,
1952                    step_multiplier: 4.0, buckets: 3, type: DoubleT))?;
1953        assert!(info.to_string().contains(
1954            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1955        ));
1956        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1957        assert!(info.to_string().contains(
1958            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 1.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1959        ));
1960        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(6.9))).is_ok());
1961        assert!(info.to_string().contains(
1962            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1963        ));
1964        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(7.1))).is_ok());
1965        assert!(info.to_string().contains(
1966            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 1.0, 0.0, 0.0], ExponentialHistogram)"
1967        ));
1968        assert!(
1969            info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(12.9), count: 4)).is_ok()
1970        );
1971        assert!(info.to_string().contains(
1972            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 0.0, 0.0], ExponentialHistogram)"
1973        ));
1974        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(13.1))).is_ok());
1975        assert!(info.to_string().contains(
1976            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 1.0, 0.0], ExponentialHistogram)"
1977        ));
1978        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(36.9))).is_ok());
1979        assert!(info.to_string().contains(
1980            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 0.0], ExponentialHistogram)"
1981        ));
1982        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(37.1))).is_ok());
1983        assert!(info.to_string().contains(
1984            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 1.0], ExponentialHistogram)"
1985        ));
1986        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1987        assert!(info.to_string().contains(
1988            "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1989        ));
1990        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1991        assert!(info.to_string().contains(
1992            "value: DoubleArray([5.0, 2.0, 4.0, 1.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1993        ));
1994        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1995        assert!(info.to_string().contains(
1996            "value: DoubleArray([5.0, 2.0, 4.0, 2.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1997        ));
1998        assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1999        assert!(info.to_string().contains(
2000            "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2001        ));
2002        assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
2003        assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
2004        assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
2005        assert!(info.to_string().contains(
2006            "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2007        ));
2008        Ok(())
2009    }
2010
2011    #[fuchsia::test]
2012    fn test_basic_vector_ops() -> Result<(), Error> {
2013        let mut info = Data::new();
2014        info.apply(&create_string_property!(parent: ROOT_ID, id: 3, name: "value",
2015                                     value: "foo"))?;
2016        assert!(info.to_string().contains("value: String(\"foo\")"));
2017        assert!(info.apply(&set_string!(id: 3, value: "bar")).is_ok());
2018        assert!(info.to_string().contains("value: String(\"bar\")"));
2019        assert!(info.apply(&set_bytes!(id: 3, value: vec!(3u8))).is_err());
2020        assert!(info.to_string().contains("value: String(\"bar\")"));
2021        info.apply(&create_bytes_property!(parent: ROOT_ID, id: 4, name: "bvalue",
2022                                     value: vec!(1u8, 2u8)))?;
2023        assert!(info.to_string().contains("bvalue: Bytes([1, 2])"));
2024        assert!(info.apply(&set_bytes!(id: 4, value: vec!(3u8, 4u8))).is_ok());
2025        assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
2026        assert!(info.apply(&set_string!(id: 4, value: "baz")).is_err());
2027        assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
2028        Ok(())
2029    }
2030
2031    #[fuchsia::test]
2032    fn test_basic_lazy_node_ops() -> Result<(), Error> {
2033        let mut info = Data::new();
2034        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))]))?;
2035        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))]))?;
2036
2037        // Outputs 'Inline' and 'Child' dispositions differently.
2038        assert_eq!(
2039            info.to_string(),
2040            "root ->\n> inline_bytes: Bytes([3, 4])\n> child ->\n> > child_bytes: Bytes([3, 4])"
2041        );
2042
2043        info.apply_lazy(&delete_lazy_node!(id: 1))?;
2044        // Outputs only 'Inline' lazy node since 'Child' lazy node was deleted
2045        assert_eq!(info.to_string(), "root ->\n> inline_bytes: Bytes([3, 4])");
2046
2047        Ok(())
2048    }
2049
2050    #[fuchsia::test]
2051    fn test_illegal_node_actions() -> Result<(), Error> {
2052        let mut info = Data::new();
2053        // Parent must exist
2054        assert!(info.apply(&create_node!(parent: 42, id: 1, name: "child")).is_err());
2055        // Can't reuse node IDs
2056        info = Data::new();
2057        info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
2058        assert!(info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "another_child")).is_err());
2059        // Can't delete root
2060        info = Data::new();
2061        assert!(info.apply(&delete_node!(id: ROOT_ID)).is_err());
2062        // Can't delete nonexistent node
2063        info = Data::new();
2064        assert!(info.apply(&delete_node!(id: 333)).is_err());
2065        Ok(())
2066    }
2067
2068    #[fuchsia::test]
2069    fn test_illegal_property_actions() -> Result<(), Error> {
2070        let mut info = Data::new();
2071        // Parent must exist
2072        assert!(
2073            info.apply(
2074                &create_numeric_property!(parent: 42, id: 1, name: "answer", value: Value::IntT(42))
2075            )
2076            .is_err()
2077        );
2078        // Can't reuse property IDs
2079        info = Data::new();
2080        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "answer",
2081                                    value: Value::IntT(42)))?;
2082        assert!(
2083            info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "another_answer",
2084                                    value: Value::IntT(7)))
2085                .is_err()
2086        );
2087        // Can't delete nonexistent property
2088        info = Data::new();
2089        assert!(info.apply(&delete_property!(id: 1)).is_err());
2090        // Can't do basic-int on array or histogram, or any vice versa
2091        info = Data::new();
2092        info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
2093                                     value: Value::IntT(42)))?;
2094        info.apply(&create_array_property!(parent: ROOT_ID, id: 4, name: "array", slots: 2,
2095                                     type: ValueType::Int))?;
2096        info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 5, name: "lin",
2097                                floor: 5, step_size: 2,
2098                                buckets: 2, type: IntT))?;
2099        info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 6, name: "exp",
2100                                floor: 5, initial_step: 2,
2101                                step_multiplier: 2, buckets: 2, type: IntT))?;
2102        assert!(info.apply(&set_number!(id: 3, value: Value::IntT(5))).is_ok());
2103        assert!(info.apply(&array_set!(id: 4, index: 0, value: Value::IntT(5))).is_ok());
2104        assert!(info.apply(&insert!(id: 5, value: Value::IntT(2))).is_ok());
2105        assert!(info.apply(&insert!(id: 6, value: Value::IntT(2))).is_ok());
2106        assert!(info.apply(&insert_multiple!(id: 5, value: Value::IntT(2), count: 3)).is_ok());
2107        assert!(info.apply(&insert_multiple!(id: 6, value: Value::IntT(2), count: 3)).is_ok());
2108        assert!(info.apply(&set_number!(id: 4, value: Value::IntT(5))).is_err());
2109        assert!(info.apply(&set_number!(id: 5, value: Value::IntT(5))).is_err());
2110        assert!(info.apply(&set_number!(id: 6, value: Value::IntT(5))).is_err());
2111        assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::IntT(5))).is_err());
2112        assert!(info.apply(&array_set!(id: 5, index: 0, value: Value::IntT(5))).is_err());
2113        assert!(info.apply(&array_set!(id: 6, index: 0, value: Value::IntT(5))).is_err());
2114        assert!(info.apply(&insert!(id: 3, value: Value::IntT(2))).is_err());
2115        assert!(info.apply(&insert!(id: 4, value: Value::IntT(2))).is_err());
2116        assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(2), count: 3)).is_err());
2117        assert!(info.apply(&insert_multiple!(id: 4, value: Value::IntT(2), count: 3)).is_err());
2118        Ok(())
2119    }
2120
2121    #[fuchsia::test]
2122    fn test_enum_values() {
2123        assert_eq!(BlockType::IntValue.to_isize().unwrap(), ArrayType::Int.to_isize().unwrap());
2124        assert_eq!(BlockType::UintValue.to_isize().unwrap(), ArrayType::Uint.to_isize().unwrap());
2125        assert_eq!(
2126            BlockType::DoubleValue.to_isize().unwrap(),
2127            ArrayType::Double.to_isize().unwrap()
2128        );
2129    }
2130
2131    #[fuchsia::test]
2132    fn test_create_node_checks() {
2133        let mut data = Data::new();
2134        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2135        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "second")).is_ok());
2136        assert!(data.apply(&create_node!(parent: 1, id: 3, name: "child")).is_ok());
2137        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "double")).is_err());
2138        let mut data = Data::new();
2139        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "orphan")).is_err());
2140    }
2141
2142    #[fuchsia::test]
2143    fn test_delete_node_checks() {
2144        let mut data = Data::new();
2145        assert!(data.apply(&delete_node!(id: 0)).is_err());
2146        let mut data = Data::new();
2147        data.apply(&create_node!(parent: 0, id: 1, name: "first")).ok();
2148        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2149        assert!(data.apply(&delete_node!(id: 1)).is_err());
2150    }
2151
2152    #[fuchsia::test]
2153    // Make sure tombstoning works correctly (tracking implicitly deleted descendants).
2154    fn test_node_tombstoning() {
2155        // Can delete, but not double-delete, a tombstoned node.
2156        let mut data = Data::new();
2157        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2158        assert!(
2159            data.apply(&create_numeric_property!(parent: 1, id: 2,
2160            name: "answer", value: Value::IntT(42)))
2161                .is_ok()
2162        );
2163        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2164        assert!(data.apply(&delete_property!(id: 2)).is_ok());
2165        assert!(data.apply(&delete_property!(id: 2)).is_err());
2166        // Can tombstone, then delete, then create.
2167        let mut data = Data::new();
2168        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2169        assert!(
2170            data.apply(&create_numeric_property!(parent: 1, id: 2,
2171            name: "answer", value: Value::IntT(42)))
2172                .is_ok()
2173        );
2174        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2175        assert!(data.apply(&delete_property!(id: 2)).is_ok());
2176        assert!(
2177            data.apply(&create_numeric_property!(parent: 0, id: 2,
2178            name: "root_answer", value: Value::IntT(42)))
2179                .is_ok()
2180        );
2181        // Cannot tombstone, then create.
2182        let mut data = Data::new();
2183        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2184        assert!(
2185            data.apply(&create_numeric_property!(parent: 1, id: 2,
2186            name: "answer", value: Value::IntT(42)))
2187                .is_ok()
2188        );
2189        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2190        assert!(
2191            data.apply(&create_numeric_property!(parent: 0, id: 2,
2192            name: "root_answer", value: Value::IntT(42)))
2193                .is_err()
2194        );
2195    }
2196
2197    #[fuchsia::test]
2198    fn test_property_tombstoning() {
2199        // Make sure tombstoning works correctly (tracking implicitly deleted descendants).
2200        // Can delete, but not double-delete, a tombstoned property.
2201        let mut data = Data::new();
2202        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2203        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2204        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2205        assert!(data.apply(&delete_node!(id: 2)).is_ok());
2206        assert!(data.apply(&delete_node!(id: 2)).is_err());
2207        // Can tombstone, then delete, then create.
2208        let mut data = Data::new();
2209        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2210        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2211        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2212        assert!(data.apply(&delete_node!(id: 2)).is_ok());
2213        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_ok());
2214        // Cannot tombstone, then create.
2215        let mut data = Data::new();
2216        assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2217        assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2218        assert!(data.apply(&delete_node!(id: 1)).is_ok());
2219        assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_err());
2220    }
2221
2222    const DIFF_STRING: &str = r#"-- DIFF --
2223 same: "root ->"
2224 same: "> node ->"
2225local: "> > prop1: String(\"foo\")"
2226other: "> > prop1: String(\"bar\")""#;
2227
2228    const FULL_STRING: &str = r#"-- LOCAL --
2229root ->
2230> node ->
2231> > prop1: String("foo")
2232-- OTHER --
2233root ->
2234> node ->
2235> > prop1: String("bar")"#;
2236
2237    #[fuchsia::test]
2238    fn diff_modes_work() -> Result<(), Error> {
2239        let mut local = Data::new();
2240        let mut remote = Data::new();
2241        local.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2242        local.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "foo"))?;
2243        remote.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2244        remote.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "bar"))?;
2245        match local.compare(&remote, DiffType::Diff) {
2246            Err(error) => {
2247                let error_string = format!("{error:?}");
2248                assert_eq!("Trees differ:\n".to_string() + DIFF_STRING, error_string);
2249            }
2250            _ => return Err(format_err!("Didn't get failure")),
2251        }
2252        match local.compare(&remote, DiffType::Full) {
2253            Err(error) => {
2254                let error_string = format!("{error:?}");
2255                assert_eq!("Trees differ:\n".to_string() + FULL_STRING, error_string);
2256            }
2257            _ => return Err(format_err!("Didn't get failure")),
2258        }
2259        match local.compare(&remote, DiffType::Both) {
2260            Err(error) => {
2261                let error_string = format!("{error:?}");
2262                assert_eq!(["Trees differ:", FULL_STRING, DIFF_STRING].join("\n"), error_string);
2263            }
2264            _ => return Err(format_err!("Didn't get failure")),
2265        }
2266        Ok(())
2267    }
2268}