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