fuchsia_inspect/reader/
mod.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
5//! # Reading inspect data
6//!
7//! Provides an API for reading inspect data from a given [`Inspector`][Inspector], VMO, byte
8//! vectors, etc.
9//!
10//! ## Concepts
11//!
12//! ### Diagnostics hierarchy
13//!
14//! Represents the inspect VMO as a regular tree of data. The API ensures that this structure
15//! always contains the lazy values loaded as well.
16//!
17//! ### Partial node hierarchy
18//!
19//! Represents the inspect VMO as a regular tree of data, but unlike the Diagnostics Hierarchy, it
20//! won't contain the lazy values loaded. An API is provided for converting this to a diagnostics
21//! hierarchy ([`Into<DiagnosticsHierarchy>`](impl-Into<DiagnosticsHierarchy<String>>)), but keep
22//! in mind that the resulting diagnostics hierarchy won't contain any of the lazy values.
23//!
24//! ## Example usage
25//!
26//! ```rust
27//! use fuchsia_inspect::{Inspector, reader};
28//!
29//! let inspector = Inspector::default();
30//! // ...
31//! let hierarchy = reader::read(&inspector)?;
32//! ```
33
34use crate::reader::snapshot::{MakePrimitiveProperty, ScannedBlock, Snapshot};
35use diagnostics_hierarchy::*;
36use inspect_format::{
37    Array, BlockIndex, BlockType, Bool, Buffer, Double, Int, Link, Node, Uint, Unknown,
38    ValueBlockKind,
39};
40use maplit::btreemap;
41use std::borrow::Cow;
42use std::collections::BTreeMap;
43
44pub use crate::reader::error::ReaderError;
45pub use crate::reader::readable_tree::ReadableTree;
46pub use crate::reader::tree_reader::{read, read_with_timeout};
47pub use diagnostics_hierarchy::{ArrayContent, ArrayFormat, DiagnosticsHierarchy, Property};
48pub use inspect_format::LinkNodeDisposition;
49
50mod error;
51mod readable_tree;
52pub mod snapshot;
53mod tree_reader;
54
55/// A partial node hierarchy represents a node in an inspect tree without
56/// the linked (lazy) nodes expanded.
57/// Usually a client would prefer to use a `DiagnosticsHierarchy` to get the full
58/// inspect tree.
59#[derive(Clone, Debug, PartialEq)]
60pub struct PartialNodeHierarchy {
61    /// The name of this node.
62    pub(crate) name: String,
63
64    /// The properties for the node.
65    pub(crate) properties: Vec<Property>,
66
67    /// The children of this node.
68    pub(crate) children: Vec<PartialNodeHierarchy>,
69
70    /// Links this node hierarchy haven't expanded yet.
71    pub(crate) links: Vec<LinkValue>,
72}
73
74/// A lazy node in a hierarchy.
75#[derive(Debug, PartialEq, Clone)]
76pub(crate) struct LinkValue {
77    /// The name of the link.
78    pub name: String,
79
80    /// The content of the link.
81    pub content: String,
82
83    /// The disposition of the link in the hierarchy when evaluated.
84    pub disposition: LinkNodeDisposition,
85}
86
87impl PartialNodeHierarchy {
88    /// Creates an `PartialNodeHierarchy` with the given `name`, `properties` and `children`
89    pub fn new(
90        name: impl Into<String>,
91        properties: Vec<Property>,
92        children: Vec<PartialNodeHierarchy>,
93    ) -> Self {
94        Self { name: name.into(), properties, children, links: vec![] }
95    }
96
97    /// Creates an empty `PartialNodeHierarchy`
98    pub fn empty() -> Self {
99        PartialNodeHierarchy::new("", vec![], vec![])
100    }
101
102    /// Whether the partial hierarchy is complete or not. A complete node hierarchy
103    /// has all the links loaded into it.
104    pub fn is_complete(&self) -> bool {
105        self.links.is_empty()
106    }
107}
108
109/// Transforms the partial hierarchy into a `DiagnosticsHierarchy`. If the node hierarchy had
110/// unexpanded links, those will appear as missing values.
111impl From<PartialNodeHierarchy> for DiagnosticsHierarchy {
112    fn from(partial: PartialNodeHierarchy) -> DiagnosticsHierarchy {
113        DiagnosticsHierarchy {
114            name: partial.name,
115            children: partial.children.into_iter().map(|child| child.into()).collect(),
116            properties: partial.properties,
117            missing: partial
118                .links
119                .into_iter()
120                .map(|link_value| MissingValue {
121                    reason: MissingValueReason::LinkNeverExpanded,
122                    name: link_value.name,
123                })
124                .collect(),
125        }
126    }
127}
128
129impl DiagnosticsHierarchyGetter<String> for PartialNodeHierarchy {
130    fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy> {
131        let hierarchy: DiagnosticsHierarchy = self.clone().into();
132        if !hierarchy.missing.is_empty() {
133            panic!(
134                "Missing links: {:?}",
135                hierarchy
136                    .missing
137                    .iter()
138                    .map(|missing| {
139                        format!("(name:{:?}, reason:{:?})", missing.name, missing.reason)
140                    })
141                    .collect::<Vec<_>>()
142                    .join(", ")
143            );
144        }
145        Cow::Owned(hierarchy)
146    }
147}
148
149impl TryFrom<Snapshot> for PartialNodeHierarchy {
150    type Error = ReaderError;
151
152    fn try_from(snapshot: Snapshot) -> Result<Self, Self::Error> {
153        read_snapshot(&snapshot)
154    }
155}
156
157#[cfg(target_os = "fuchsia")]
158impl TryFrom<&zx::Vmo> for PartialNodeHierarchy {
159    type Error = ReaderError;
160
161    fn try_from(vmo: &zx::Vmo) -> Result<Self, Self::Error> {
162        let snapshot = Snapshot::try_from(vmo)?;
163        read_snapshot(&snapshot)
164    }
165}
166
167impl TryFrom<Vec<u8>> for PartialNodeHierarchy {
168    type Error = ReaderError;
169
170    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
171        let snapshot = Snapshot::try_from(bytes)?;
172        read_snapshot(&snapshot)
173    }
174}
175
176/// Read the blocks in the snapshot as a node hierarchy.
177fn read_snapshot(snapshot: &Snapshot) -> Result<PartialNodeHierarchy, ReaderError> {
178    let result = scan_blocks(snapshot)?;
179    result.reduce()
180}
181
182fn scan_blocks(snapshot: &Snapshot) -> Result<ScanResult<'_>, ReaderError> {
183    let mut result = ScanResult::new(snapshot);
184    for block in snapshot.scan() {
185        if block.index() == BlockIndex::ROOT && block.block_type() != Some(BlockType::Header) {
186            return Err(ReaderError::MissingHeader);
187        }
188        match block.block_type().ok_or(ReaderError::InvalidVmo)? {
189            BlockType::NodeValue => {
190                result.parse_node(block.cast_unchecked::<Node>())?;
191            }
192            BlockType::IntValue => {
193                result.parse_primitive_property(block.cast_unchecked::<Int>())?;
194            }
195            BlockType::UintValue => {
196                result.parse_primitive_property(block.cast_unchecked::<Uint>())?;
197            }
198            BlockType::DoubleValue => {
199                result.parse_primitive_property(block.cast_unchecked::<Double>())?;
200            }
201            BlockType::BoolValue => {
202                result.parse_primitive_property(block.cast_unchecked::<Bool>())?;
203            }
204            BlockType::ArrayValue => {
205                result.parse_array_property(block.cast_unchecked::<Array<Unknown>>())?;
206            }
207            BlockType::BufferValue => {
208                result.parse_property(block.cast_unchecked::<Buffer>())?;
209            }
210            BlockType::LinkValue => {
211                result.parse_link(block.cast_unchecked::<Link>())?;
212            }
213            BlockType::Free
214            | BlockType::Reserved
215            | BlockType::Header
216            | BlockType::Extent
217            | BlockType::Name
218            | BlockType::Tombstone
219            | BlockType::StringReference => {}
220        }
221    }
222    Ok(result)
223}
224
225/// Result of scanning a snapshot before aggregating hierarchies.
226struct ScanResult<'a> {
227    /// All the nodes found while scanning the snapshot.
228    /// Scanned nodes NodeHierarchies won't have their children filled.
229    parsed_nodes: BTreeMap<BlockIndex, ScannedNode>,
230
231    /// A snapshot of the Inspect VMO tree.
232    snapshot: &'a Snapshot,
233}
234
235/// A scanned node in the Inspect VMO tree.
236#[derive(Debug)]
237struct ScannedNode {
238    /// The node hierarchy with properties and children nodes filled.
239    partial_hierarchy: PartialNodeHierarchy,
240
241    /// The number of children nodes this node has.
242    child_nodes_count: usize,
243
244    /// The index of the parent node of this node.
245    parent_index: BlockIndex,
246
247    /// True only if this node was intialized. Uninitialized nodes will be ignored.
248    initialized: bool,
249}
250
251impl ScannedNode {
252    fn new() -> Self {
253        ScannedNode {
254            partial_hierarchy: PartialNodeHierarchy::empty(),
255            child_nodes_count: 0,
256            parent_index: BlockIndex::EMPTY,
257            initialized: false,
258        }
259    }
260
261    /// Sets the name and parent index of the node.
262    fn initialize(&mut self, name: String, parent_index: BlockIndex) {
263        self.partial_hierarchy.name = name;
264        self.parent_index = parent_index;
265        self.initialized = true;
266    }
267
268    /// A scanned node is considered complete if the number of children in the
269    /// hierarchy is the same as the number of children counted while scanning.
270    fn is_complete(&self) -> bool {
271        self.partial_hierarchy.children.len() == self.child_nodes_count
272    }
273
274    /// A scanned node is considered initialized if a NodeValue was parsed for it.
275    fn is_initialized(&self) -> bool {
276        self.initialized
277    }
278}
279
280macro_rules! get_or_create_scanned_node {
281    ($map:expr, $key:expr) => {
282        $map.entry($key).or_insert(ScannedNode::new())
283    };
284}
285
286impl<'a> ScanResult<'a> {
287    fn new(snapshot: &'a Snapshot) -> Self {
288        let mut root_node = ScannedNode::new();
289        root_node.initialize("root".to_string(), BlockIndex::ROOT);
290        let parsed_nodes = btreemap!(
291            BlockIndex::ROOT => root_node,
292        );
293        ScanResult { snapshot, parsed_nodes }
294    }
295
296    fn reduce(self) -> Result<PartialNodeHierarchy, ReaderError> {
297        // Stack of nodes that have been found that are complete.
298        let mut complete_nodes = Vec::<ScannedNode>::new();
299
300        // Maps a block index to the node there. These nodes are still not
301        // complete.
302        let mut pending_nodes = BTreeMap::<BlockIndex, ScannedNode>::new();
303
304        let mut uninitialized_nodes = std::collections::BTreeSet::new();
305
306        // Split the parsed_nodes into complete nodes and pending nodes.
307        for (index, scanned_node) in self.parsed_nodes.into_iter() {
308            if !scanned_node.is_initialized() {
309                // Skip all nodes that were not initialized.
310                uninitialized_nodes.insert(index);
311                continue;
312            }
313            if scanned_node.is_complete() {
314                if index == BlockIndex::ROOT {
315                    return Ok(scanned_node.partial_hierarchy);
316                }
317                complete_nodes.push(scanned_node);
318            } else {
319                pending_nodes.insert(index, scanned_node);
320            }
321        }
322
323        // Build a valid hierarchy by attaching completed nodes to their parent.
324        // Once the parent is complete, it's added to the stack and we recurse
325        // until the root is found (parent index = 0).
326        while let Some(scanned_node) = complete_nodes.pop() {
327            if uninitialized_nodes.contains(&scanned_node.parent_index) {
328                // Skip children of initialized nodes. These nodes were implicitly unlinked due to
329                // tombstoning.
330                continue;
331            }
332            {
333                // Add the current node to the parent hierarchy.
334                let parent_node = pending_nodes
335                    .get_mut(&scanned_node.parent_index)
336                    .ok_or(ReaderError::ParentIndexNotFound(scanned_node.parent_index))?;
337                parent_node.partial_hierarchy.children.push(scanned_node.partial_hierarchy);
338            }
339            if pending_nodes
340                .get(&scanned_node.parent_index)
341                .ok_or(ReaderError::ParentIndexNotFound(scanned_node.parent_index))?
342                .is_complete()
343            {
344                // Safety: if pending_nodes did not contain scanned_node.parent_index,
345                // we would've returned above with ParentIndexNotFound
346                let parent_node = pending_nodes.remove(&scanned_node.parent_index).unwrap();
347                if scanned_node.parent_index == BlockIndex::ROOT {
348                    return Ok(parent_node.partial_hierarchy);
349                }
350                complete_nodes.push(parent_node);
351            }
352        }
353
354        Err(ReaderError::MalformedTree)
355    }
356
357    pub fn get_name(&self, index: BlockIndex) -> Option<String> {
358        self.snapshot.get_name(index)
359    }
360
361    fn parse_node(&mut self, block: ScannedBlock<'_, Node>) -> Result<(), ReaderError> {
362        let name_index = block.name_index();
363        let name = self.get_name(name_index).ok_or(ReaderError::ParseName(name_index))?;
364        let parent_index = block.parent_index();
365        get_or_create_scanned_node!(self.parsed_nodes, block.index())
366            .initialize(name, parent_index);
367        if parent_index != block.index() {
368            get_or_create_scanned_node!(self.parsed_nodes, parent_index).child_nodes_count += 1;
369        }
370        Ok(())
371    }
372
373    fn push_property(
374        &mut self,
375        parent_index: BlockIndex,
376        property: Property,
377    ) -> Result<(), ReaderError> {
378        let parent = get_or_create_scanned_node!(self.parsed_nodes, parent_index);
379        parent.partial_hierarchy.properties.push(property);
380        Ok(())
381    }
382
383    fn parse_primitive_property<'b, K>(
384        &mut self,
385        block: ScannedBlock<'b, K>,
386    ) -> Result<(), ReaderError>
387    where
388        K: ValueBlockKind,
389        ScannedBlock<'b, K>: MakePrimitiveProperty,
390    {
391        let parent_index = block.parent_index();
392        let property = self.snapshot.parse_primitive_property(block)?;
393        self.push_property(parent_index, property)?;
394        Ok(())
395    }
396
397    fn parse_array_property(
398        &mut self,
399        block: ScannedBlock<'_, Array<Unknown>>,
400    ) -> Result<(), ReaderError> {
401        let parent_index = block.parent_index();
402        let property = self.snapshot.parse_array_property(block)?;
403        self.push_property(parent_index, property)?;
404        Ok(())
405    }
406
407    fn parse_property(&mut self, block: ScannedBlock<'_, Buffer>) -> Result<(), ReaderError> {
408        let parent_index = block.parent_index();
409        let property = self.snapshot.parse_property(block)?;
410        self.push_property(parent_index, property)?;
411        Ok(())
412    }
413
414    fn parse_link(&mut self, block: ScannedBlock<'_, Link>) -> Result<(), ReaderError> {
415        let parent_index = block.parent_index();
416        let parent = get_or_create_scanned_node!(self.parsed_nodes, parent_index);
417        parent.partial_hierarchy.links.push(self.snapshot.parse_link(block)?);
418        Ok(())
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425    use crate::types::private::InspectTypeInternal;
426    use crate::writer::testing_utils::GetBlockExt;
427    use crate::{ArrayProperty, HistogramProperty, Inspector};
428    use anyhow::Error;
429    use diagnostics_assertions::{assert_data_tree, assert_json_diff};
430    use futures::prelude::*;
431    use inspect_format::{constants, BlockContainer, CopyBytes, PayloadFields};
432
433    #[fuchsia::test]
434    async fn test_load_string_reference() {
435        let inspector = Inspector::default();
436        let root = inspector.root();
437
438        let name_value = "abc";
439        let longer_name_value = "abcdefg";
440
441        let child = root.create_child(name_value);
442        child.record_int(name_value, 5);
443
444        root.record_bool(longer_name_value, false);
445
446        let result = read(&inspector).await.unwrap();
447        assert_json_diff!(result, root: {
448            abc: {
449                abc: 5i64,
450            },
451            abcdefg: false,
452        });
453    }
454
455    #[fuchsia::test]
456    async fn read_string_array() {
457        let inspector = Inspector::default();
458        let root = inspector.root();
459
460        let zero = (0..3000).map(|_| '0').collect::<String>();
461        let one = "1";
462        let two = "two";
463        let three = "three three three";
464        let four = "fourth";
465
466        let array = root.create_string_array("array", 5);
467        array.set(0, zero.as_str());
468        array.set(1, one);
469        array.set(2, two);
470        array.set(3, three);
471        array.set(4, four);
472
473        let result = read(&inspector).await.unwrap();
474        assert_json_diff!(result, root: {
475            "array": vec![
476                zero,
477                one.to_string(),
478                two.to_string(),
479                three.to_string(),
480                four.to_string(),
481            ],
482        });
483    }
484
485    #[fuchsia::test]
486    async fn read_unset_string_array() {
487        let inspector = Inspector::default();
488        let root = inspector.root();
489
490        let zero = (0..3000).map(|_| '0').collect::<String>();
491        let one = "1";
492        let four = "fourth";
493
494        let array = root.create_string_array("array", 5);
495        array.set(0, zero.as_str());
496        array.set(1, one);
497        array.set(4, four);
498
499        let result = read(&inspector).await.unwrap();
500        assert_json_diff!(result, root: {
501            "array": vec![zero, one.to_string(), "".into(), "".into(), four.to_string()],
502        });
503    }
504
505    #[fuchsia::test]
506    async fn read_vmo() {
507        let inspector = Inspector::default();
508        let root = inspector.root();
509        let _root_int = root.create_int("int-root", 3);
510        let root_double_array = root.create_double_array("property-double-array", 5);
511        let double_array_data = vec![-1.2, 2.3, 3.4, 4.5, -5.6];
512        for (i, x) in double_array_data.iter().enumerate() {
513            root_double_array.set(i, *x);
514        }
515
516        let child1 = root.create_child("child-1");
517        let _child1_uint = child1.create_uint("property-uint", 10);
518        let _child1_double = child1.create_double("property-double", -3.4);
519        let _child1_bool = child1.create_bool("property-bool", true);
520
521        let chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
522        let string_data = chars.iter().cycle().take(6000).collect::<String>();
523        let _string_prop = child1.create_string("property-string", &string_data);
524
525        let child1_int_array = child1.create_int_linear_histogram(
526            "property-int-array",
527            LinearHistogramParams { floor: 1, step_size: 2, buckets: 3 },
528        );
529        for x in [-1, 2, 3, 5, 8].iter() {
530            child1_int_array.insert(*x);
531        }
532
533        let child2 = root.create_child("child-2");
534        let _child2_double = child2.create_double("property-double", 5.8);
535        let _child2_bool = child2.create_bool("property-bool", false);
536
537        let child3 = child1.create_child("child-1-1");
538        let _child3_int = child3.create_int("property-int", -9);
539        let bytes_data = (0u8..=9u8).cycle().take(5000).collect::<Vec<u8>>();
540        let _bytes_prop = child3.create_bytes("property-bytes", &bytes_data);
541
542        let child3_uint_array = child3.create_uint_exponential_histogram(
543            "property-uint-array",
544            ExponentialHistogramParams {
545                floor: 1,
546                initial_step: 1,
547                step_multiplier: 2,
548                buckets: 5,
549            },
550        );
551        for x in [1, 2, 3, 4].iter() {
552            child3_uint_array.insert(*x);
553        }
554
555        let result = read(&inspector).await.unwrap();
556
557        assert_data_tree!(result, root: {
558            "int-root": 3i64,
559            "property-double-array": double_array_data,
560            "child-1": {
561                "property-uint": 10u64,
562                "property-double": -3.4,
563                "property-bool": true,
564                "property-string": string_data,
565                "property-int-array": LinearHistogram {
566                    floor: 1i64,
567                    step: 2,
568                    counts: vec![1, 1, 1, 1, 1],
569                    indexes: None,
570                    size: 5
571                },
572                "child-1-1": {
573                    "property-int": -9i64,
574                    "property-bytes": bytes_data,
575                    "property-uint-array": ExponentialHistogram {
576                        floor: 1u64,
577                        initial_step: 1,
578                        step_multiplier: 2,
579                        counts: vec![1, 1, 2],
580                        indexes: Some(vec![1, 2, 3]),
581                        size: 7
582                    },
583                }
584            },
585            "child-2": {
586                "property-double": 5.8,
587                "property-bool": false,
588            }
589        })
590    }
591
592    #[fuchsia::test]
593    async fn siblings_with_same_name() {
594        let inspector = Inspector::default();
595
596        let foo = "foo";
597
598        inspector.root().record_int("foo", 0);
599        inspector.root().record_int("foo", 1);
600        inspector.root().record_int(foo, 2);
601        inspector.root().record_int(foo, 3);
602
603        let dh = read(&inspector).await.unwrap();
604        assert_eq!(dh.properties.len(), 4);
605        for i in 0..dh.properties.len() {
606            match &dh.properties[i] {
607                Property::Int(n, v) => {
608                    assert_eq!(n, "foo");
609                    assert_eq!(*v, i as i64);
610                }
611                _ => panic!("We only record int properties"),
612            }
613        }
614    }
615
616    #[fuchsia::test]
617    fn tombstone_reads() {
618        let inspector = Inspector::default();
619        let node1 = inspector.root().create_child("child1");
620        let node2 = node1.create_child("child2");
621        let node3 = node2.create_child("child3");
622        let prop1 = node1.create_string("val", "test");
623        let prop2 = node2.create_string("val", "test");
624        let prop3 = node3.create_string("val", "test");
625
626        assert_json_diff!(inspector,
627            root: {
628                child1: {
629                    val: "test",
630                    child2: {
631                        val: "test",
632                        child3: {
633                            val: "test",
634                        }
635                    }
636                }
637            }
638        );
639
640        std::mem::drop(node3);
641        assert_json_diff!(inspector,
642            root: {
643                child1: {
644                    val: "test",
645                    child2: {
646                        val: "test",
647                    }
648                }
649            }
650        );
651
652        std::mem::drop(node2);
653        assert_json_diff!(inspector,
654            root: {
655                child1: {
656                    val: "test",
657                }
658            }
659        );
660
661        // Recreate the nodes. Ensure that the old properties are not picked up.
662        let node2 = node1.create_child("child2");
663        let _node3 = node2.create_child("child3");
664        assert_json_diff!(inspector,
665            root: {
666                child1: {
667                    val: "test",
668                    child2: {
669                        child3: {}
670                    }
671                }
672            }
673        );
674
675        // Delete out of order, leaving 3 dangling.
676        std::mem::drop(node2);
677        assert_json_diff!(inspector,
678            root: {
679                child1: {
680                    val: "test",
681                }
682            }
683        );
684
685        std::mem::drop(node1);
686        assert_json_diff!(inspector,
687            root: {
688            }
689        );
690
691        std::mem::drop(prop3);
692        assert_json_diff!(inspector,
693            root: {
694            }
695        );
696
697        std::mem::drop(prop2);
698        assert_json_diff!(inspector,
699            root: {
700            }
701        );
702
703        std::mem::drop(prop1);
704        assert_json_diff!(inspector,
705            root: {
706            }
707        );
708    }
709
710    #[fuchsia::test]
711    async fn from_invalid_utf8_string() {
712        // Creates a perfectly normal Inspector with a perfectly normal string
713        // property with a perfectly normal value.
714        let inspector = Inspector::default();
715        let root = inspector.root();
716        let prop = root.create_string("property", "hello world");
717
718        // Now we will excavate the bytes that comprise the string property, then mess with them on
719        // purpose to produce an invalid UTF8 string in the property.
720        let vmo = inspector.vmo().await.unwrap();
721        let snapshot = Snapshot::try_from(&vmo).expect("getting snapshot");
722        let block = snapshot
723            .get_block(prop.block_index().unwrap())
724            .expect("getting block")
725            .cast::<Buffer>()
726            .unwrap();
727
728        // The first byte of the actual property string is at this byte offset in the VMO.
729        let byte_offset = constants::MIN_ORDER_SIZE * (*block.extent_index() as usize)
730            + constants::HEADER_SIZE_BYTES
731            + constants::STRING_REFERENCE_TOTAL_LENGTH_BYTES;
732
733        // Get the raw VMO bytes to mess with.
734        let vmo_size = BlockContainer::len(&vmo);
735        let mut buf = vec![0u8; vmo_size];
736        vmo.copy_bytes(&mut buf[..]);
737
738        // Mess up the first byte of the string property value such that the byte is an invalid
739        // UTF8 character.  Then build a new node hierarchy based off those bytes, see if invalid
740        // string is converted into a valid UTF8 string with some information lost.
741        buf[byte_offset] = 0xFE;
742        let hierarchy: DiagnosticsHierarchy = PartialNodeHierarchy::try_from(Snapshot::build(&buf))
743            .expect("creating node hierarchy")
744            .into();
745
746        assert_json_diff!(hierarchy, root: {
747            property: "\u{FFFD}ello world",
748        });
749    }
750
751    #[fuchsia::test]
752    async fn test_invalid_array_slots() -> Result<(), Error> {
753        let inspector = Inspector::default();
754        let root = inspector.root();
755        let array = root.create_int_array("int-array", 3);
756
757        // Mess up with the block slots by setting them to a too big number.
758        array.get_block_mut::<_, Array<Int>>(|array_block| {
759            PayloadFields::set_array_slots_count(array_block, 255);
760        });
761
762        let vmo = inspector.vmo().await.unwrap();
763        let vmo_size = BlockContainer::len(&vmo);
764
765        let mut buf = vec![0u8; vmo_size];
766        vmo.copy_bytes(&mut buf[..]);
767
768        assert!(PartialNodeHierarchy::try_from(Snapshot::build(&buf)).is_err());
769
770        Ok(())
771    }
772
773    #[fuchsia::test]
774    async fn lazy_nodes() -> Result<(), Error> {
775        let inspector = Inspector::default();
776        inspector.root().record_int("int", 3);
777        let child = inspector.root().create_child("child");
778        child.record_double("double", 1.5);
779        inspector.root().record_lazy_child("lazy", || {
780            async move {
781                let inspector = Inspector::default();
782                inspector.root().record_uint("uint", 5);
783                inspector.root().record_lazy_values("nested-lazy-values", || {
784                    async move {
785                        let inspector = Inspector::default();
786                        inspector.root().record_string("string", "test");
787                        let child = inspector.root().create_child("nested-lazy-child");
788                        let array = child.create_int_array("array", 3);
789                        array.set(0, 1);
790                        child.record(array);
791                        inspector.root().record(child);
792                        Ok(inspector)
793                    }
794                    .boxed()
795                });
796                Ok(inspector)
797            }
798            .boxed()
799        });
800
801        inspector.root().record_lazy_values("lazy-values", || {
802            async move {
803                let inspector = Inspector::default();
804                let child = inspector.root().create_child("lazy-child-1");
805                child.record_string("test", "testing");
806                inspector.root().record(child);
807                inspector.root().record_uint("some-uint", 3);
808                inspector.root().record_lazy_values("nested-lazy-values", || {
809                    async move {
810                        let inspector = Inspector::default();
811                        inspector.root().record_int("lazy-int", -3);
812                        let child = inspector.root().create_child("one-more-child");
813                        child.record_double("lazy-double", 4.3);
814                        inspector.root().record(child);
815                        Ok(inspector)
816                    }
817                    .boxed()
818                });
819                inspector.root().record_lazy_child("nested-lazy-child", || {
820                    async move {
821                        let inspector = Inspector::default();
822                        // This will go out of scope and is not recorded, so it shouldn't appear.
823                        let _double = inspector.root().create_double("double", -1.2);
824                        Ok(inspector)
825                    }
826                    .boxed()
827                });
828                Ok(inspector)
829            }
830            .boxed()
831        });
832
833        let hierarchy = read(&inspector).await?;
834        assert_json_diff!(hierarchy, root: {
835            int: 3i64,
836            child: {
837                double: 1.5,
838            },
839            lazy: {
840                uint: 5u64,
841                string: "test",
842                "nested-lazy-child": {
843                    array: vec![1i64, 0, 0],
844                }
845            },
846            "some-uint": 3u64,
847            "lazy-child-1": {
848                test: "testing",
849            },
850            "lazy-int": -3i64,
851            "one-more-child": {
852                "lazy-double": 4.3,
853            },
854            "nested-lazy-child": {
855            }
856        });
857
858        Ok(())
859    }
860
861    #[fuchsia::test]
862    fn test_matching_with_inspector() {
863        let inspector = Inspector::default();
864        assert_json_diff!(inspector, root: {});
865    }
866
867    #[fuchsia::test]
868    fn test_matching_with_partial() {
869        let propreties = vec![Property::String("sub".to_string(), "sub_value".to_string())];
870        let partial = PartialNodeHierarchy::new("root", propreties, vec![]);
871        assert_json_diff!(partial, root: {
872            sub: "sub_value",
873        });
874    }
875
876    #[fuchsia::test]
877    #[should_panic]
878    fn test_missing_values_with_partial() {
879        let mut partial = PartialNodeHierarchy::new("root", vec![], vec![]);
880        partial.links = vec![LinkValue {
881            name: "missing-link".to_string(),
882            content: "missing-link-404".to_string(),
883            disposition: LinkNodeDisposition::Child,
884        }];
885        assert_json_diff!(partial, root: {});
886    }
887
888    #[fuchsia::test]
889    fn test_matching_with_expression_as_key() {
890        let properties = vec![Property::String("sub".to_string(), "sub_value".to_string())];
891        let partial = PartialNodeHierarchy::new("root", properties, vec![]);
892        let value = || "sub_value";
893        let key = || "sub".to_string();
894        assert_json_diff!(partial, root: {
895            key() => value(),
896        });
897    }
898}