// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! # fuchsia-inspect
//!
//! Components in Fuchsia may expose structured information about themselves conforming to the
//! [Inspect API][inspect]. This crate is the core library for writing inspect data in Rust
//! components.
//!
//! For a comprehensive guide on how to start using inspect, please refer to the
//! [codelab].
//!
//! ## Library concepts
//!
//! There's two types of inspect values: nodes and properties. These have the following
//! characteristics:
//!
//!   - A Node may have any number of key/value pairs called Properties.
//!   - A Node may have any number of children, which are also Nodes.
//!   - Properties and nodes are created under a parent node. Inspect is already initialized with a
//!     root node.
//!   - The key for a value in a Node is always a UTF-8 string, the value may be one of the
//!     supported types (a node or a property of any type).
//!   - Nodes and properties have strict ownership semantics. Whenever a node or property is
//!     created, it is written to the backing [VMO][inspect-vmo] and whenever it is dropped it is
//!     removed from the VMO.
//!   - Inspection is best effort, if an error occurs, no panic will happen and nodes and properties
//!     might become No-Ops. For example, when the VMO becomes full, any further creation of a
//!     property or a node will result in no changes in the VMO and a silent failure. However,
//!     mutation of existing properties in the VMO will continue to work.
//!   - All nodes and properties are thread safe.
//!
//! ### Creating vs Recording
//!
//! There are two functions each for initializing nodes and properties:
//!
//!   - `create_*`: returns the created node/property and it's up to the caller to handle its
//!     lifetime.
//!   - `record_*`: creates the node/property but doesn't return it and ties its lifetime to
//!     the node where the function was called.
//!
//! ### Lazy value support
//!
//! Lazy (or dynamic) values are values that are created on demand, this is, whenever they are read.
//! Unlike regular nodes, they don't take any space on the VMO until a reader comes and requests
//! its data.
//!
//! There's two ways of creating lazy values:
//!
//!   - **Lazy node**: creates a child node of root with the given name. The callback returns a
//!     future for an [`Inspector`][inspector] whose root node is spliced into the parent node when
//!     read.
//!   - **Lazy values**: works like the previous one, except that all properties and nodes under the
//!     future root node node are added directly as children of the parent node.
//!
//! ## Quickstart
//!
//! Add the following to your component main:
//!
//! ```rust
//! use fuchsia_inspect::component;
//! use fuchsia_component::server::ServiceFs;
//!
//! let mut fs = ServiceFs::new();
//! component::inspector().root().serve(&mut fs)?;
//!
//! // Now you can create nodes and properties anywhere!
//! let child = component::inspector().root().create_child("foo");
//! child.record_uint("bar", 42);
//! ```
//!
//! [inspect]: https://fuchsia.dev/fuchsia-src/development/diagnostics/inspect
//! [codelab]: https://fuchsia.dev/fuchsia-src/development/diagnostics/inspect/codelab
//! [inspect-vmo]: https://fuchsia.dev/fuchsia-src/reference/diagnostics/inspect/vmo-format
//! [inspector]: Inspector

use {
    crate::{
        format::{
            block::{ArrayFormat, LinkNodeDisposition, PropertyFormat},
            constants,
        },
        heap::Heap,
        state::State,
    },
    anyhow,
    derivative::Derivative,
    diagnostics_hierarchy::testing::DiagnosticsHierarchyGetter,
    fidl::endpoints::DiscoverableService,
    fidl_fuchsia_inspect::TreeMarker,
    fidl_fuchsia_io::{DirectoryMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE},
    fuchsia_component::server::{ServiceFs, ServiceObjTrait},
    fuchsia_zircon::{self as zx, HandleBased},
    futures::{future::BoxFuture, prelude::*},
    lazy_static::lazy_static,
    mapped_vmo::Mapping,
    parking_lot::Mutex,
    paste,
    std::{
        borrow::Cow,
        cmp::max,
        default::Default,
        fmt::Debug,
        sync::{
            atomic::{AtomicUsize, Ordering},
            Arc, Weak,
        },
    },
    tracing::error,
    vfs::{
        directory::entry::DirectoryEntry, execution_scope::ExecutionScope, path::Path,
        pseudo_directory, service as pseudo_fs_service,
    },
};

#[cfg(test)]
use crate::format::block::Block;

pub use diagnostics_hierarchy::{
    DiagnosticsHierarchy, ExponentialHistogramParams, LinearHistogramParams,
};
pub use testing::{assert_inspect_tree, tree_assertion};

pub use {crate::error::Error, crate::state::Stats};

pub mod component;
mod error;
pub mod format;
pub mod health;
pub mod heap;
pub mod reader;
pub mod service;
mod state;
mod utils;

pub mod testing {
    pub use diagnostics_hierarchy::{
        assert_data_tree as assert_inspect_tree,
        testing::{
            AnyProperty, DiagnosticsHierarchyGetter, HistogramAssertion, NonZeroUintProperty,
            PropertyAssertion, TreeAssertion,
        },
        tree_assertion,
    };
}

/// Directiory within the outgoing directory of a component where the diagnostics service should be
/// added.
pub const DIAGNOSTICS_DIR: &str = "diagnostics";

lazy_static! {
  // Suffix used for unique names.
  static ref UNIQUE_NAME_SUFFIX: AtomicUsize = AtomicUsize::new(0);
}

/// Root of the Inspect API. Through this API, further nodes can be created and inspect can be
/// served.
#[derive(Clone)]
pub struct Inspector {
    /// The root node.
    root_node: Arc<Node>,

    /// The VMO backing the inspector
    pub(in crate) vmo: Option<Arc<zx::Vmo>>,
}

impl DiagnosticsHierarchyGetter<String> for Inspector {
    fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy> {
        let hierarchy = futures::executor::block_on(async move { reader::read(self).await })
            .expect("failed to get hierarchy");
        Cow::Owned(hierarchy)
    }
}

/// Holds a list of inspect types that won't change.
#[derive(Derivative)]
#[derivative(Debug, PartialEq, Eq)]
struct ValueList {
    #[derivative(PartialEq = "ignore")]
    #[derivative(Debug = "ignore")]
    values: Mutex<Option<InspectTypeList>>,
}

impl Default for ValueList {
    fn default() -> Self {
        ValueList::new()
    }
}

type InspectTypeList = Vec<Box<dyn InspectType>>;

impl ValueList {
    /// Creates a new empty value list.
    pub fn new() -> Self {
        Self { values: Mutex::new(None) }
    }

    /// Stores an inspect type that won't change.
    pub fn record(&self, value: impl InspectType + 'static) {
        let boxed_value = Box::new(value);
        let mut values_lock = self.values.lock();
        if let Some(ref mut values) = *values_lock {
            values.push(boxed_value);
        } else {
            *values_lock = Some(vec![boxed_value]);
        }
    }
}

impl Inspector {
    /// Initializes a new Inspect VMO object with the
    /// [`defalt maximum size`][constants::DEFAULT_VMO_SIZE_BYTES].
    pub fn new() -> Self {
        Inspector::new_with_size(constants::DEFAULT_VMO_SIZE_BYTES)
    }

    /// Returns statistics about the current inspect state.
    pub fn stats(&self) -> Option<Stats> {
        self.root_node
            .inner
            .inner_ref()
            .and_then(|inner_ref| inner_ref.state.try_lock().ok().map(|state| state.stats()))
    }

    /// True if the Inspector was created successfully (it's not No-Op)
    pub fn is_valid(&self) -> bool {
        self.vmo.is_some() && self.root_node.is_valid()
    }

    /// Initializes a new Inspect VMO object with the given maximum size. If the
    /// given size is less than 4K, it will be made 4K which is the minimum size
    /// the VMO should have.
    pub fn new_with_size(max_size: usize) -> Self {
        match Inspector::new_root(max_size) {
            Ok((vmo, root_node)) => {
                Inspector { vmo: Some(Arc::new(vmo)), root_node: Arc::new(root_node) }
            }
            Err(e) => {
                error!("Failed to create root node. Error: {:?}", e);
                Inspector::new_no_op()
            }
        }
    }

    /// Returns a duplicate of the underlying VMO for this Inspector.
    ///
    /// The duplicated VMO will be read-only, and is suitable to send to clients over FIDL.
    pub fn duplicate_vmo(&self) -> Option<zx::Vmo> {
        self.vmo
            .as_ref()
            .map(|vmo| {
                vmo.duplicate_handle(zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP).ok()
            })
            .unwrap_or(None)
    }

    /// Returns a VMO holding a copy of the data in this inspector.
    ///
    /// The copied VMO will be read-only.
    pub fn copy_vmo(&self) -> Option<zx::Vmo> {
        self.copy_vmo_data().and_then(|data| {
            if let Ok(vmo) = zx::Vmo::create(data.len() as u64) {
                vmo.write(&data, 0).ok().map(|_| vmo)
            } else {
                None
            }
        })
    }

    /// Returns a copy of the bytes stored in the VMO for this inspector.
    ///
    /// The output will be truncated to only those bytes that are needed to accurately read the
    /// stored data.
    pub fn copy_vmo_data(&self) -> Option<Vec<u8>> {
        self.root_node.inner.inner_ref().map(|inner_ref| inner_ref.state.copy_vmo_bytes())
    }

    /// Spawns a server for handling `fuchsia.inspect.Tree` requests in the outgoing diagnostics
    /// directory.
    pub fn serve<'a, ServiceObjTy: ServiceObjTrait>(
        &self,
        service_fs: &mut ServiceFs<ServiceObjTy>,
    ) -> Result<(), Error> {
        let (proxy, server) = fidl::endpoints::create_proxy::<DirectoryMarker>()
            .map_err(|e| Error::Fidl(e.into()))?;
        let inspector_clone = self.clone();
        let dir = pseudo_directory! {
            TreeMarker::SERVICE_NAME => pseudo_fs_service::host(move |stream| {
                let inspector_clone_clone = inspector_clone.clone();
                async move {
                    service::handle_request_stream(
                        inspector_clone_clone, service::TreeServerSettings::default(), stream
                        )
                        .await
                        .unwrap_or_else(|e| error!("failed to run server: {:?}", e));
                }
                .boxed()
            }),
        };

        let server_end = server.into_channel().into();
        let scope = ExecutionScope::new();
        dir.open(scope, OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE, 0, Path::empty(), server_end);
        service_fs.add_remote(DIAGNOSTICS_DIR, proxy);

        Ok(())
    }

    /// Returns the root node of the inspect hierarchy.
    pub fn root(&self) -> &Node {
        &self.root_node
    }

    fn state(&self) -> Option<State> {
        self.root().inner.inner_ref().map(|inner_ref| inner_ref.state.clone())
    }

    /// Creates a new No-Op inspector
    fn new_no_op() -> Self {
        Inspector { vmo: None, root_node: Arc::new(Node::new_no_op()) }
    }

    /// Allocates a new VMO and initializes it.
    fn new_root(max_size: usize) -> Result<(zx::Vmo, Node), Error> {
        let mut size = max(constants::MINIMUM_VMO_SIZE_BYTES, max_size);
        // If the size is not a multiple of 4096, round up.
        if size % constants::MINIMUM_VMO_SIZE_BYTES != 0 {
            size =
                (1 + size / constants::MINIMUM_VMO_SIZE_BYTES) * constants::MINIMUM_VMO_SIZE_BYTES;
        }
        let (mapping, vmo) = Mapping::allocate_with_name(size, "InspectHeap")
            .map_err(|status| Error::AllocateVmo(status))?;
        let heap = Heap::new(Arc::new(mapping)).map_err(|e| Error::CreateHeap(Box::new(e)))?;
        let state = State::create(heap).map_err(|e| Error::CreateState(Box::new(e)))?;
        Ok((vmo, Node::new_root(state)))
    }

    /// Creates an no-op inspector from the given Vmo. If the VMO is corrupted, reading can fail.
    fn no_op_from_vmo(vmo: Arc<zx::Vmo>) -> Inspector {
        Inspector { vmo: Some(vmo), root_node: Arc::new(Node::new_no_op()) }
    }
}

/// Trait implemented by all inspect types.
pub trait InspectType: Send + Sync {}

/// Trait implemented by all inspect types. It provides constructor functions that are not
/// intended for use outside the crate.
trait InspectTypeInternal {
    fn new(state: State, block_index: u32) -> Self;
    fn new_no_op() -> Self;
    fn is_valid(&self) -> bool;
}

/// An inner type of all inspect nodes and properties. Each variant implies a
/// different relationship with the underlying inspect VMO.
#[derive(Debug, Derivative)]
#[derivative(Default)]
enum Inner<T: InnerType> {
    /// The node or property is not attached to the inspect VMO.
    #[derivative(Default)]
    None,

    /// The node or property is attached to the inspect VMO, iff its strong
    /// reference is still alive.
    Weak(Weak<InnerRef<T>>),

    /// The node or property is attached to the inspect VMO.
    Strong(Arc<InnerRef<T>>),
}

impl<T: InnerType> Inner<T> {
    /// Creates a new Inner with the desired block index within the inspect VMO
    fn new(state: State, block_index: u32) -> Self {
        Self::Strong(Arc::new(InnerRef { state, block_index, data: T::Data::default() }))
    }

    /// Returns true if the number of strong references to this node or property
    /// is greater than 0.
    fn is_valid(&self) -> bool {
        match self {
            Self::None => false,
            Self::Weak(weak_ref) => weak_ref.strong_count() > 0,
            Self::Strong(_) => true,
        }
    }

    /// Returns a `Some(Arc<InnerRef>)` iff the node or property is currently
    /// attached to inspect, or `None` otherwise. Weak pointers are upgraded
    /// if possible, but their lifetime as strong references are expected to be
    /// short.
    fn inner_ref(&self) -> Option<Arc<InnerRef<T>>> {
        match self {
            Self::None => None,
            Self::Weak(weak_ref) => weak_ref.upgrade(),
            Self::Strong(inner_ref) => Some(Arc::clone(inner_ref)),
        }
    }

    /// Make a weak reference.
    fn clone_weak(&self) -> Self {
        match self {
            Self::None => Self::None,
            Self::Weak(weak_ref) => Self::Weak(weak_ref.clone()),
            Self::Strong(inner_ref) => Self::Weak(Arc::downgrade(inner_ref)),
        }
    }
}

/// Inspect API types implement Eq,PartialEq returning true all the time so that
/// structs embedding inspect types can derive these traits as well.
/// IMPORTANT: Do not rely on these traits implementations for real comparisons
/// or validation tests, instead leverage the reader.
impl<T: InnerType> PartialEq for Inner<T> {
    fn eq(&self, _other: &Self) -> bool {
        true
    }
}

impl<T: InnerType> Eq for Inner<T> {}

/// A type that is owned by inspect nodes and properties, sharing ownership of
/// the inspect VMO heap, and with numerical pointers to the location in the
/// heap in which it resides.
#[derive(Debug)]
struct InnerRef<T: InnerType> {
    /// Index of the block in the VMO.
    block_index: u32,

    /// Reference to the VMO heap.
    state: State,

    /// Associated data for this type.
    data: T::Data,
}

impl<T: InnerType> Drop for InnerRef<T> {
    /// InnerRef has a manual drop impl, to guarantee a single deallocation in
    /// the case of multiple strong references.
    fn drop(&mut self) {
        T::free(&self.state, self.block_index).unwrap();
    }
}

/// De-allocation behavior and associated data for an inner type.
trait InnerType {
    /// Associated data stored on the InnerRef
    type Data: Default + Debug;

    /// De-allocation behavior for when the InnerRef gets dropped
    fn free(state: &State, block_index: u32) -> Result<(), Error>;
}

#[derive(Default, Debug)]
struct InnerNodeType;

impl InnerType for InnerNodeType {
    // Each node has a list of recorded values.
    type Data = ValueList;

    fn free(state: &State, block_index: u32) -> Result<(), Error> {
        if block_index == 0 {
            return Ok(());
        }
        let mut state_lock = state.try_lock()?;
        state_lock.free_value(block_index).map_err(|err| Error::free("node", block_index, err))
    }
}

#[derive(Default, Debug)]
struct InnerValueType;

impl InnerType for InnerValueType {
    type Data = ();
    fn free(state: &State, block_index: u32) -> Result<(), Error> {
        let mut state_lock = state.try_lock()?;
        state_lock.free_value(block_index).map_err(|err| Error::free("value", block_index, err))
    }
}

#[derive(Default, Debug)]
struct InnerPropertyType;

impl InnerType for InnerPropertyType {
    type Data = ();
    fn free(state: &State, block_index: u32) -> Result<(), Error> {
        let mut state_lock = state.try_lock()?;
        state_lock
            .free_property(block_index)
            .map_err(|err| Error::free("property", block_index, err))
    }
}

#[derive(Default, Debug)]
struct InnerLazyNodeType;

impl InnerType for InnerLazyNodeType {
    type Data = ();
    fn free(state: &State, block_index: u32) -> Result<(), Error> {
        let mut state_lock = state.try_lock()?;
        state_lock
            .free_lazy_node(block_index)
            .map_err(|err| Error::free("lazy node", block_index, err))
    }
}

// Utility for generating the implementation of all inspect types (including the struct):
//  - All Inspect Types (*Property, Node) can be No-Op. This macro generates the
//    appropiate internal constructors.
//  - All Inspect Types derive PartialEq, Eq. This generates the dummy implementation
//    for the wrapped type.
macro_rules! inspect_type_impl {
    ($(#[$attr:meta])* struct $name:ident, $type:ident) => {
        paste::paste! {
            $(#[$attr])*
            ///
            /// NOTE: do not rely on PartialEq implementation for true comparison.
            /// Instead leverage the reader.
            ///
            /// NOTE: Operations on a Default value are no-ops.
            #[derive(Debug, PartialEq, Eq, Default)]
            pub struct $name {
                inner: Inner<$type>,
            }

            #[cfg(test)]
            impl $name {
                /// Returns the [`Block`][Block] associated with this value.
                pub fn get_block(&self) -> Option<Block<Arc<Mapping>>> {
                    self.inner.inner_ref().and_then(|inner_ref| {
                        inner_ref.state.try_lock()
                            .and_then(|state| state.heap().get_block(inner_ref.block_index)).ok()
                    })
                }

                /// Returns the index of the value's block in the VMO.
                pub fn block_index(&self) -> u32 {
                    self.inner.inner_ref().unwrap().block_index
                }
            }

            impl InspectType for $name {}

            impl InspectTypeInternal for $name {
                fn new(state: State, block_index: u32) -> Self {
                    Self {
                        inner: Inner::new(state, block_index),
                    }
                }

                fn is_valid(&self) -> bool {
                    self.inner.is_valid()
                }

                fn new_no_op() -> Self {
                    Self { inner: Inner::None }
                }
            }
        }
    }
}

// Utility for generating functions to create a numeric property.
//   `name`: identifier for the name (example: double)
//   `name_cap`: identifier for the name capitalized (example: Double)
//   `type`: the type of the numeric property (example: f64)
macro_rules! create_numeric_property_fn {
    ($name:ident, $name_cap:ident, $type:ident) => {
        paste::paste! {
            #[doc = "Creates a new `" $name_cap "` with the given `name` and `value`."]
            #[must_use]
            pub fn [<create_ $name >](&self, name: impl AsRef<str>, value: $type)
                -> [<$name_cap Property>] {
                    self.inner.inner_ref().and_then(|inner_ref| {
                        inner_ref.state
                            .try_lock()
                            .and_then(|mut state| {
                                state.[<create_ $name _metric>](
                                    name.as_ref(), value, inner_ref.block_index)
                            })
                            .map(|block| {
                                [<$name_cap Property>]::new(inner_ref.state.clone(), block.index())
                            })
                            .ok()
                    })
                    .unwrap_or([<$name_cap Property>]::new_no_op())
            }

            #[doc = "Records a new `" $name_cap "` with the given `name` and `value`."]
            pub fn [<record_ $name >](&self, name: impl AsRef<str>, value: $type) {
                let property = self.[<create_ $name>](name, value);
                self.record(property);
            }
        }
    };
}

// Utility for generating functions to create an array property.
//   `name`: identifier for the name (example: double)
//   `name_cap`: identifier for the name capitalized (example: Double)
//   `type`: the type of the numeric property (example: f64)
macro_rules! create_array_property_fn {
    ($name:ident, $name_cap:ident, $type:ident) => {
        paste::paste! {
            #[doc = "Creates a new `" $name_cap "ArrayProperty` with the given `name` and `slots`."]
            #[must_use]
            pub fn [<create_ $name _array>](&self, name: impl AsRef<str>, slots: usize)
                -> [<$name_cap ArrayProperty>] {
                    self.[<create_ $name _array_internal>](name, slots, ArrayFormat::Default)
            }

            fn [<create_ $name _array_internal>](
                &self, name: impl AsRef<str>, slots: usize, format: ArrayFormat)
                -> [<$name_cap ArrayProperty>] {
                    self.inner.inner_ref().and_then(|inner_ref| {
                        inner_ref.state
                            .try_lock()
                            .and_then(|mut state| {
                                state.[<create_ $name _array>](
                                    name.as_ref(), slots, format, inner_ref.block_index)
                            })
                            .map(|block| {
                                [<$name_cap ArrayProperty>]::new(inner_ref.state.clone(), block.index())
                            })
                            .ok()
                    })
                    .unwrap_or([<$name_cap ArrayProperty>]::new_no_op())
            }
        }
    };
}

// Utility for generating functions to create a linear histogram property.
//   `name`: identifier for the name (example: double)
//   `name_cap`: identifier for the name capitalized (example: Double)
//   `type`: the type of the numeric property (example: f64)
macro_rules! create_linear_histogram_property_fn {
    ($name:ident, $name_cap:ident, $type:ident) => {
        paste::paste! {
            #[doc = "Creates a new `" $name_cap
               "LinearHistogramProperty` with the given `name` and `params`."]
            #[must_use]
            pub fn [<create_ $name _linear_histogram>](
                &self, name: impl AsRef<str>, params: LinearHistogramParams<$type>)
                -> [<$name_cap LinearHistogramProperty>] {
                let slots = params.buckets + constants::LINEAR_HISTOGRAM_EXTRA_SLOTS;
                let array = self.[<create_ $name _array_internal>](
                    name, slots, ArrayFormat::LinearHistogram);
                array.set(0, params.floor);
                array.set(1, params.step_size);
                [<$name_cap LinearHistogramProperty>] {
                    floor: params.floor,
                    step_size: params.step_size,
                    slots,
                    array
                }
            }
        }
    };
}

// Utility for generating functions to create an exponential histogram property.
//   `name`: identifier for the name (example: double)
//   `name_cap`: identifier for the name capitalized (example: Double)
//   `type`: the type of the numeric property (example: f64)
macro_rules! create_exponential_histogram_property_fn {
    ($name:ident, $name_cap:ident, $type:ident) => {
        paste::paste! {
            #[doc = "Creates a new `" $name_cap
               "ExponentialHistogramProperty` with the given `name` and `params`."]
            #[must_use]
            pub fn [<create_ $name _exponential_histogram>](
              &self, name: impl AsRef<str>, params: ExponentialHistogramParams<$type>)
              -> [<$name_cap ExponentialHistogramProperty>] {
                let slots = params.buckets + constants::EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS;
                let array = self.[<create_ $name _array_internal>](
                    name, slots, ArrayFormat::ExponentialHistogram);
                array.set(0, params.floor);
                array.set(1, params.initial_step);
                array.set(2, params.step_multiplier);
                [<$name_cap ExponentialHistogramProperty>] {
                    floor: params.floor,
                    initial_step: params.initial_step,
                    step_multiplier: params.step_multiplier,
                    slots,
                    array
                }
            }
        }
    };
}

/// Utility for generating functions to create lazy nodes.
///   `fn_suffix`: identifier for the fn name.
///   `disposition`: identifier for the type of LinkNodeDisposition.
macro_rules! create_lazy_property_fn {
    ($fn_suffix:ident, $disposition:ident) => {
        paste::paste! {
            #[must_use]
            #[doc = "Creates a new lazy " $fn_suffix " link with the given `name` and `callback`."]
            pub fn [<create_lazy_ $fn_suffix>]<F>(&self, name: impl AsRef<str>, callback: F) -> LazyNode
            where F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static {
                self.inner.inner_ref().and_then(|inner_ref| {
                    inner_ref
                        .state
                        .try_lock()
                        .and_then(|mut state| state.create_lazy_node(
                            name.as_ref(),
                            inner_ref.block_index,
                            LinkNodeDisposition::$disposition,
                            callback,
                        ))
                        .map(|block| LazyNode::new(inner_ref.state.clone(), block.index()))
                        .ok()

                })
                .unwrap_or(LazyNode::new_no_op())
            }

            #[doc = "Records a new lazy " $fn_suffix " link with the given `name` and `callback`."]
            pub fn [<record_lazy_ $fn_suffix>]<F>(
                &self, name: impl AsRef<str>, callback: F)
            where F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static {
                let property = self.[<create_lazy_ $fn_suffix>](name, callback);
                self.record(property);
            }
        }
    }
}

inspect_type_impl!(
    /// Inspect Node data type.
    struct Node,
    InnerNodeType
);

inspect_type_impl!(
    /// Inspect Lazy Node data type.
    struct LazyNode,
    InnerLazyNodeType
);

impl Node {
    /// Creates a new root node.
    pub(in crate) fn new_root(state: State) -> Node {
        Node::new(state, 0)
    }

    /// Create a weak reference to the original node. All operations on a weak
    /// reference have identical semantics to the original node for as long
    /// as the original node is live. After that, all operations are no-ops.
    pub fn clone_weak(&self) -> Node {
        Self { inner: self.inner.clone_weak() }
    }

    /// Creates and keeps track of a child with the given `name`.
    pub fn record_child<F>(&self, name: impl AsRef<str>, initialize: F)
    where
        F: FnOnce(&mut Node),
    {
        let mut child = self.create_child(name);
        initialize(&mut child);
        self.record(child);
    }

    /// Add a child to this node.
    #[must_use]
    pub fn create_child(&self, name: impl AsRef<str>) -> Node {
        self.inner
            .inner_ref()
            .and_then(|inner_ref| {
                inner_ref
                    .state
                    .try_lock()
                    .and_then(|mut state| state.create_node(name.as_ref(), inner_ref.block_index))
                    .map(|block| Node::new(inner_ref.state.clone(), block.index()))
                    .ok()
            })
            .unwrap_or(Node::new_no_op())
    }

    /// Keeps track of the given property for the lifetime of the node.
    pub fn record(&self, property: impl InspectType + 'static) {
        self.inner.inner_ref().map(|inner_ref| inner_ref.data.record(property));
    }

    // Add a lazy node property to this node:
    // - create_lazy_node: adds a lazy child to this node. This node will be
    //   populated by the given callback on demand.
    // - create_lazy_values: adds a lazy child to this node. The lazy node
    //   children and properties are added to this node on demand. Name is only
    //   used in the event that a reader does not obtain the values.
    create_lazy_property_fn!(child, Child);
    create_lazy_property_fn!(values, Inline);

    // Add a numeric property to this node: create_int, create_double,
    // create_uint.
    create_numeric_property_fn!(int, Int, i64);
    create_numeric_property_fn!(uint, Uint, u64);
    create_numeric_property_fn!(double, Double, f64);

    // Add an array property to this node: create_int_array, create_double_array,
    // create_uint_array.
    create_array_property_fn!(int, Int, i64);
    create_array_property_fn!(uint, Uint, u64);
    create_array_property_fn!(double, Double, f64);

    // Add a linear histogram property to this node: create_int_linear_histogram,
    // create_uint_linear_histogram, create_double_linear_histogram.
    create_linear_histogram_property_fn!(int, Int, i64);
    create_linear_histogram_property_fn!(uint, Uint, u64);
    create_linear_histogram_property_fn!(double, Double, f64);

    // Add an exponential histogram property to this node: create_int_exponential_histogram,
    // create_uint_exponential_histogram, create_double_exponential_histogram.
    create_exponential_histogram_property_fn!(int, Int, i64);
    create_exponential_histogram_property_fn!(uint, Uint, u64);
    create_exponential_histogram_property_fn!(double, Double, f64);

    /// Creates a lazy node from the given VMO.
    #[must_use]
    pub fn create_lazy_child_from_vmo(&self, name: impl AsRef<str>, vmo: Arc<zx::Vmo>) -> LazyNode {
        self.create_lazy_child(name.as_ref(), move || {
            let vmo_clone = vmo.clone();
            async move { Ok(Inspector::no_op_from_vmo(vmo_clone)) }.boxed()
        })
    }

    /// Records a lazy node from the given VMO.
    pub fn record_lazy_child_from_vmo(&self, name: impl AsRef<str>, vmo: Arc<zx::Vmo>) {
        self.record_lazy_child(name.as_ref(), move || {
            let vmo_clone = vmo.clone();
            async move { Ok(Inspector::no_op_from_vmo(vmo_clone)) }.boxed()
        });
    }

    /// Add a string property to this node.
    #[must_use]
    pub fn create_string(&self, name: impl AsRef<str>, value: impl AsRef<str>) -> StringProperty {
        self.inner
            .inner_ref()
            .and_then(|inner_ref| {
                inner_ref
                    .state
                    .try_lock()
                    .and_then(|mut state| {
                        state.create_property(
                            name.as_ref(),
                            value.as_ref().as_bytes(),
                            PropertyFormat::String,
                            inner_ref.block_index,
                        )
                    })
                    .map(|block| StringProperty::new(inner_ref.state.clone(), block.index()))
                    .ok()
            })
            .unwrap_or(StringProperty::new_no_op())
    }

    /// Creates and saves a string property for the lifetime of the node.
    pub fn record_string(&self, name: impl AsRef<str>, value: impl AsRef<str>) {
        let property = self.create_string(name, value);
        self.record(property);
    }

    /// Add a byte vector property to this node.
    #[must_use]
    pub fn create_bytes(&self, name: impl AsRef<str>, value: impl AsRef<[u8]>) -> BytesProperty {
        self.inner
            .inner_ref()
            .and_then(|inner_ref| {
                inner_ref
                    .state
                    .try_lock()
                    .and_then(|mut state| {
                        state.create_property(
                            name.as_ref(),
                            value.as_ref(),
                            PropertyFormat::Bytes,
                            inner_ref.block_index,
                        )
                    })
                    .map(|block| BytesProperty::new(inner_ref.state.clone(), block.index()))
                    .ok()
            })
            .unwrap_or(BytesProperty::new_no_op())
    }

    /// Creates and saves a bytes property for the lifetime of the node.
    pub fn record_bytes(&self, name: impl AsRef<str>, value: impl AsRef<[u8]>) {
        let property = self.create_bytes(name, value);
        self.record(property);
    }

    /// Add a bool property to this node.
    #[must_use]
    pub fn create_bool(&self, name: impl AsRef<str>, value: bool) -> BoolProperty {
        self.inner
            .inner_ref()
            .and_then(|inner_ref| {
                inner_ref
                    .state
                    .try_lock()
                    .and_then(|mut state| {
                        state.create_bool(name.as_ref(), value, inner_ref.block_index)
                    })
                    .map(|block| BoolProperty::new(inner_ref.state.clone(), block.index()))
                    .ok()
            })
            .unwrap_or(BoolProperty::new_no_op())
    }

    /// Creates and saves a bool property for the lifetime of the node.
    pub fn record_bool(&self, name: impl AsRef<str>, value: bool) {
        let property = self.create_bool(name, value);
        self.record(property);
    }
}

/// Trait implemented by properties.
pub trait Property<'t> {
    /// The type of the property.
    type Type;

    /// Set the property value to |value|.
    fn set(&'t self, value: Self::Type);
}

/// Trait implemented by numeric properties providing common operations.
pub trait NumericProperty {
    /// The type the property is handling.
    type Type;

    /// Add the given |value| to the property current value.
    fn add(&self, value: Self::Type);

    /// Subtract the given |value| from the property current value.
    fn subtract(&self, value: Self::Type);

    /// Return the current value of the property for testing.
    /// NOTE: This is a temporary feature to aid unit test of Inspect clients.
    /// It will be replaced by a more comprehensive Read API implementation.
    fn get(&self) -> Result<Self::Type, Error>;
}

// Utility for generating numeric property functions (example: set, add, subtract)
//   `fn_name`: the name of the function to generate (example: set)
//   `type`: the type of the argument of the function to generate (example: f64)
//   `name`: the readble name of the type of the function (example: double)
macro_rules! numeric_property_fn {
    ($fn_name:ident, $type:ident, $name:ident) => {
        paste::paste! {
            // Docs here come from the trait docs.
            fn $fn_name(&self, value: $type) {
                if let Some(ref inner_ref) = self.inner.inner_ref() {
                    inner_ref.state.try_lock()
                        .and_then(|state| {
                            state.[<$fn_name _ $name _metric>](inner_ref.block_index, value)
                        })
                        .unwrap_or_else(|e| {
                            error!("Failed to {} property. Error: {:?}", stringify!($fn_name), e);
                        });
                }
            }
        }
    };
}

// Utility for generating a numeric property datatype impl
//   `name`: the readble name of the type of the function (example: double)
//   `name_cap`: the capitalized readble name of the type of the function (example: Double)
//   `type`: the type of the argument of the function to generate (example: f64)
macro_rules! numeric_property {
    ($name:ident, $name_cap:ident, $type:ident) => {
        paste::paste! {
            inspect_type_impl!(
                /// Inspect API Numeric Property data type.
                struct [<$name_cap Property>],
                InnerValueType
            );

            impl<'t> Property<'t> for [<$name_cap Property>] {
                type Type = $type;

                numeric_property_fn!(set, $type, $name);
            }

            impl NumericProperty for [<$name_cap Property>] {
                type Type = $type;
                numeric_property_fn!(add, $type, $name);
                numeric_property_fn!(subtract, $type, $name);

                fn get(&self) -> Result<$type, Error> {
                    if let Some(ref inner_ref) = self.inner.inner_ref() {
                        inner_ref.state
                            .try_lock()
                            .and_then(|state| state.[<get_ $name _metric>](inner_ref.block_index))
                    } else {
                        Err(Error::NoOp("Property"))
                    }
                }
            }
        }
    };
}

numeric_property!(int, Int, i64);
numeric_property!(uint, Uint, u64);
numeric_property!(double, Double, f64);

// Utility for generating a byte/string property datatype impl
//   `name`: the readable name of the type of the function (example: String)
//   `type`: the type of the argument of the function to generate (example: str)
//   `bytes`: an optional method to get the bytes of the property
macro_rules! property {
    ($name:ident, $type:ty $(, $bytes:ident)?) => {
        paste::paste! {
            inspect_type_impl!(
                /// Inspect API Property data type.
                struct [<$name Property>],
                InnerPropertyType
            );

            impl<'t> Property<'t> for [<$name Property>] {
                type Type = &'t $type;

                fn set(&'t self, value: &'t $type) {
                    if let Some(ref inner_ref) = self.inner.inner_ref() {
                        inner_ref.state
                            .try_lock()
                            .and_then(|mut state| {
                                state.set_property(inner_ref.block_index, value$(.$bytes())?)
                            })
                            .unwrap_or_else(|e| error!("Failed to set property. Error: {:?}", e));
                    }
                }

            }
        }
    };
}

property!(String, str, as_bytes);
property!(Bytes, [u8]);

inspect_type_impl!(
    /// Inspect API Bool Property data type.
    struct BoolProperty,
    InnerValueType
);

impl<'t> Property<'t> for BoolProperty {
    type Type = bool;

    fn set(&self, value: bool) {
        if let Some(ref inner_ref) = self.inner.inner_ref() {
            inner_ref
                .state
                .try_lock()
                .and_then(|state| state.set_bool(inner_ref.block_index, value))
                .unwrap_or_else(|e| {
                    error!("Failed to set property. Error: {:?}", e);
                });
        }
    }
}

/// Trait implemented by all array properties providing common operations on arrays.
pub trait ArrayProperty {
    /// The type of the array entries.
    type Type;

    /// Sets the array value to `value` at the given `index`.
    fn set(&self, index: usize, value: Self::Type);

    /// Adds the given `value` to the property current value at the given `index`.
    fn add(&self, index: usize, value: Self::Type);

    /// Subtracts the given `value` to the property current value at the given `index`.
    fn subtract(&self, index: usize, value: Self::Type);

    /// Sets all slots of the array to 0.
    fn clear(&self);
}

// Utility for generating array property functions (example: set, add, subtract)
//   `fn_name`: the name of the function to generate (example: set)
//   `type`: the type of the argument of the function to generate (example: f64)
//   `name`: the readble name of the type of the function (example: double)
macro_rules! array_property_fn {
    ($fn_name:ident, $type:ident, $name:ident) => {
        paste::paste! {
            // Docs here come from the trait docs.
            fn $fn_name(&self, index: usize, value: $type) {
                if let Some(ref inner_ref) = self.inner.inner_ref() {
                    inner_ref.state
                        .try_lock()
                        .and_then(|mut state| {
                            state.[<$fn_name _array_ $name _slot>](
                                inner_ref.block_index, index, value)
                        })
                        .unwrap_or_else(|e| {
                            error!("Failed to {} property. Error: {:?}", stringify!($fn_name), e);
                        });
                }
            }
        }
    };
}

// Utility for generating a numeric array datatype impl
//   `name`: the readble name of the type of the function (example: double)
//   `type`: the type of the argument of the function to generate (example: f64)
macro_rules! array_property {
    ($name:ident, $name_cap:ident, $type:ident) => {
        paste::paste! {
            inspect_type_impl!(
                /// Inspect API Array Property data type.
                struct [<$name_cap ArrayProperty>],
                InnerValueType
            );

            impl [<$name_cap ArrayProperty>] {
            }

            impl ArrayProperty for [<$name_cap ArrayProperty>] {
                type Type = $type;

                array_property_fn!(set, $type, $name);
                array_property_fn!(add, $type, $name);
                array_property_fn!(subtract, $type, $name);

                fn clear(&self) {
                    if let Some(ref inner_ref) = self.inner.inner_ref() {
                        inner_ref.state
                            .try_lock()
                            .and_then(|mut state| state.clear_array(inner_ref.block_index, 0))
                            .unwrap_or_else(|e| {
                                error!("Failed to clear property. Error: {:?}", e);
                            });
                    }
                }
            }
        }
    };
}

array_property!(int, Int, i64);
array_property!(uint, Uint, u64);
array_property!(double, Double, f64);

/// Trait implemented by all hitogram properties providing common operations.
pub trait HistogramProperty {
    /// The type of each value added to the histogram.
    type Type;

    /// Inserts the given `value` in the histogram.
    fn insert(&self, value: Self::Type);

    /// Inserts the given `value` in the histogram `count` times.
    fn insert_multiple(&self, value: Self::Type, count: usize);

    /// Clears all buckets of the histogram.
    fn clear(&self);
}

macro_rules! histogram_property {
    ($histogram_type:ident, $name_cap:ident, $type:ident, $clear_start_index:expr) => {
        paste::paste! {
            impl HistogramProperty for [<$name_cap $histogram_type HistogramProperty>] {
                type Type = $type;

                fn insert(&self, value: $type) {
                    self.insert_multiple(value, 1);
                }

                fn insert_multiple(&self, value: $type, count: usize) {
                    self.array.add(self.get_index(value), count as $type);
                }

                fn clear(&self) {
                    if let Some(ref inner_ref) = self.array.inner.inner_ref() {
                        // Ensure we don't delete the array slots that contain histogram metadata.
                        inner_ref.state
                            .try_lock()
                            .and_then(|mut state| {
                                state.clear_array(inner_ref.block_index, $clear_start_index)
                            })
                            .unwrap_or_else(|e| {
                                error!("Failed to {} property. Error: {:?}", stringify!($fn_name), e);
                            });
                    }
                }
            }
        }
    };
}

macro_rules! linear_histogram_property {
    ($name_cap:ident, $type:ident) => {
        paste::paste! {
            #[derive(Debug)]
            #[doc = "A linear histogram property for " $type " values."]
            pub struct [<$name_cap LinearHistogramProperty>] {
                array: [<$name_cap ArrayProperty>],
                floor: $type,
                slots: usize,
                step_size: $type,
            }

            impl [<$name_cap LinearHistogramProperty>] {
                fn get_index(&self, value: $type) -> usize {
                    let mut current_floor = self.floor;
                    // Start in the underflow index.
                    let mut index = constants::LINEAR_HISTOGRAM_EXTRA_SLOTS - 2;
                    while value >= current_floor && index < self.slots - 1 {
                        current_floor += self.step_size;
                        index += 1;
                    }
                    index as usize
                }

                #[cfg(test)]
                fn get_block(&self) -> Option<Block<Arc<Mapping>>> {
                    self.array.get_block()
                }
            }

            histogram_property!(
                Linear, $name_cap, $type,
                // -2 = the overflow and underflow slots which still need to be cleared.
                constants::LINEAR_HISTOGRAM_EXTRA_SLOTS - 2);
        }
    };
}

macro_rules! exponential_histogram_property {
    ($name_cap:ident, $type:ident) => {
        paste::paste! {
            #[derive(Debug)]
            #[doc = "An exponential histogram property for " $type " values."]
            pub struct [<$name_cap ExponentialHistogramProperty>] {
                array: [<$name_cap ArrayProperty>],
                floor: $type,
                initial_step: $type,
                step_multiplier: $type,
                slots: usize,
            }

            impl [<$name_cap ExponentialHistogramProperty>] {
                fn get_index(&self, value: $type) -> usize {
                    let mut current_floor = self.floor;
                    let mut offset = self.initial_step;
                    // Start in the underflow index.
                    let mut index = constants::EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS - 2;
                    while value >= current_floor && index < self.slots - 1 {
                        current_floor = self.floor + offset;
                        offset *= self.step_multiplier;
                        index += 1;
                    }
                    index as usize
                }

                #[cfg(test)]
                fn get_block(&self) -> Option<Block<Arc<Mapping>>> {
                    self.array.get_block()
                }
            }

            histogram_property!(
                Exponential, $name_cap, $type,
                // -2 = the overflow and underflow slots which still need to be cleared.
                constants::EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS - 2);
        }
    };
}

linear_histogram_property!(Double, f64);
linear_histogram_property!(Int, i64);
linear_histogram_property!(Uint, u64);
exponential_histogram_property!(Double, f64);
exponential_histogram_property!(Int, i64);
exponential_histogram_property!(Uint, u64);

/// Generates a unique name that can be used in inspect nodes and properties that will be prefixed
/// by the given `prefix`.
pub fn unique_name(prefix: &str) -> String {
    let suffix = UNIQUE_NAME_SUFFIX.fetch_add(1, Ordering::Relaxed);
    format!("{}{}", prefix, suffix)
}

#[cfg(test)]
mod tests {
    use {
        super::*,
        crate::{
            assert_inspect_tree,
            format::{block::LinkNodeDisposition, block_type::BlockType, constants},
            heap::Heap,
            reader,
        },
        anyhow::{bail, format_err},
        fdio,
        fidl::endpoints::DiscoverableService,
        fidl_fuchsia_sys::ComponentControllerEvent,
        fuchsia_async as fasync,
        fuchsia_component::client,
        fuchsia_component::server::ServiceObj,
        fuchsia_zircon::AsHandleRef,
        glob::glob,
        mapped_vmo::Mapping,
        std::ffi::CString,
    };

    const TEST_COMPONENT_CMX: &str = "inspect_test_component.cmx";
    const TEST_COMPONENT_URL: &str =
        "fuchsia-pkg://fuchsia.com/fuchsia-inspect-tests#meta/inspect_test_component.cmx";

    #[test]
    fn inspector_new() {
        let test_object = Inspector::new();
        assert_eq!(
            test_object.vmo.as_ref().unwrap().get_size().unwrap(),
            constants::DEFAULT_VMO_SIZE_BYTES as u64
        );
    }

    #[test]
    fn inspector_duplicate_vmo() {
        let test_object = Inspector::new();
        assert_eq!(
            test_object.vmo.as_ref().unwrap().get_size().unwrap(),
            constants::DEFAULT_VMO_SIZE_BYTES as u64
        );
        assert_eq!(
            test_object.duplicate_vmo().unwrap().get_size().unwrap(),
            constants::DEFAULT_VMO_SIZE_BYTES as u64
        );
    }

    #[test]
    fn inspector_copy_data() {
        let test_object = Inspector::new();

        assert_eq!(
            test_object.vmo.as_ref().unwrap().get_size().unwrap(),
            constants::DEFAULT_VMO_SIZE_BYTES as u64
        );
        // The copy will be a single page, since that is all that is used.
        assert_eq!(test_object.copy_vmo_data().unwrap().len(), 4096);
    }

    #[test]
    fn no_op() {
        let inspector = Inspector::new_with_size(4096);
        // Make the VMO full.
        let nodes = (0..127).map(|_| inspector.root().create_child("test")).collect::<Vec<Node>>();

        assert!(nodes.iter().all(|node| node.is_valid()));
        let no_op_node = inspector.root().create_child("no-op-child");
        assert!(!no_op_node.is_valid());
    }

    #[fasync::run_singlethreaded(test)]
    async fn new_no_op() -> Result<(), Error> {
        let mut fs: ServiceFs<ServiceObj<'_, ()>> = ServiceFs::new();

        let inspector = Inspector::new_no_op();
        assert!(!inspector.is_valid());
        assert!(!inspector.root().is_valid());

        // Ensure serve doesn't crash on a No-Op inspector
        inspector.serve(&mut fs)
    }

    #[test]
    fn inspector_new_with_size() {
        let test_object = Inspector::new_with_size(8192);
        assert_eq!(test_object.vmo.as_ref().unwrap().get_size().unwrap(), 8192);
        assert_eq!(
            CString::new("InspectHeap").unwrap(),
            test_object.vmo.as_ref().unwrap().get_name().expect("Has name")
        );

        // If size is not a multiple of 4096, it'll be rounded up.
        let test_object = Inspector::new_with_size(10000);
        assert_eq!(test_object.vmo.unwrap().get_size().unwrap(), 12288);

        // If size is less than the minimum size, the minimum will be set.
        let test_object = Inspector::new_with_size(2000);
        assert_eq!(test_object.vmo.unwrap().get_size().unwrap(), 4096);
    }

    #[test]
    fn inspector_new_root() {
        // Note, the small size we request should be rounded up to a full 4kB page.
        let (vmo, root_node) = Inspector::new_root(100).unwrap();
        assert_eq!(vmo.get_size().unwrap(), 4096);
        let inner = root_node.inner.inner_ref().unwrap();
        assert_eq!(inner.block_index, 0);
        assert_eq!(CString::new("InspectHeap").unwrap(), vmo.get_name().expect("Has name"));
    }

    #[test]
    fn node() {
        // Create and use a default value.
        let default = Node::default();
        default.record_int("a", 0);

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        assert_eq!(node_block.block_type(), BlockType::NodeValue);
        assert_eq!(node_block.child_count().unwrap(), 0);
        {
            let child = node.create_child("child");
            let child_block = child.get_block().unwrap();
            assert_eq!(child_block.block_type(), BlockType::NodeValue);
            assert_eq!(child_block.child_count().unwrap(), 0);
            assert_eq!(node_block.child_count().unwrap(), 1);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn node_no_op_clone_weak() {
        let default = Node::default();
        assert!(!default.is_valid());
        let weak = default.clone_weak();
        assert!(!weak.is_valid());
        let _ = weak.create_child("child");
        std::mem::drop(default);
        let _ = weak.create_uint("age", 1337);
        assert!(!weak.is_valid());
    }

    #[test]
    fn node_clone_weak() {
        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_weak = node.clone_weak();
        let node_weak_2 = node_weak.clone_weak(); // Weak from another weak

        let node_block = node.get_block().unwrap();
        assert_eq!(node_block.block_type(), BlockType::NodeValue);
        assert_eq!(node_block.child_count().unwrap(), 0);
        let node_weak_block = node.get_block().unwrap();
        assert_eq!(node_weak_block.block_type(), BlockType::NodeValue);
        assert_eq!(node_weak_block.child_count().unwrap(), 0);
        let node_weak_2_block = node.get_block().unwrap();
        assert_eq!(node_weak_2_block.block_type(), BlockType::NodeValue);
        assert_eq!(node_weak_2_block.child_count().unwrap(), 0);

        let child_from_strong = node.create_child("child");
        let child = node_weak.create_child("child_1");
        let child_2 = node_weak_2.create_child("child_2");
        std::mem::drop(node_weak_2);
        assert_eq!(node_weak_block.child_count().unwrap(), 3);
        std::mem::drop(child_from_strong);
        assert_eq!(node_weak_block.child_count().unwrap(), 2);
        std::mem::drop(child);
        assert_eq!(node_weak_block.child_count().unwrap(), 1);
        assert!(node_weak.is_valid());
        assert!(child_2.is_valid());
        std::mem::drop(node);
        assert!(!node_weak.is_valid());
        let _ = node_weak.create_child("orphan");
        let _ = child_2.create_child("orphan");
    }

    #[test]
    fn double_property() {
        // Create and use a default value.
        let default = DoubleProperty::default();
        default.add(1.0);

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let property = node.create_double("property", 1.0);
            let property_block = property.get_block().unwrap();
            assert_eq!(property_block.block_type(), BlockType::DoubleValue);
            assert_eq!(property_block.double_value().unwrap(), 1.0);
            assert_eq!(node_block.child_count().unwrap(), 1);

            property.set(2.0);
            assert_eq!(property_block.double_value().unwrap(), 2.0);
            assert_eq!(property.get().unwrap(), 2.0);

            property.subtract(5.5);
            assert_eq!(property_block.double_value().unwrap(), -3.5);

            property.add(8.1);
            assert_eq!(property_block.double_value().unwrap(), 4.6);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn int_property() {
        // Create and use a default value.
        let default = IntProperty::default();
        default.add(1);

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let property = node.create_int("property", 1);
            let property_block = property.get_block().unwrap();
            assert_eq!(property_block.block_type(), BlockType::IntValue);
            assert_eq!(property_block.int_value().unwrap(), 1);
            assert_eq!(node_block.child_count().unwrap(), 1);

            property.set(2);
            assert_eq!(property_block.int_value().unwrap(), 2);
            assert_eq!(property.get().unwrap(), 2);

            property.subtract(5);
            assert_eq!(property_block.int_value().unwrap(), -3);

            property.add(8);
            assert_eq!(property_block.int_value().unwrap(), 5);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn uint_property() {
        // Create and use a default value.
        let default = UintProperty::default();
        default.add(1);

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let property = node.create_uint("property", 1);
            let property_block = property.get_block().unwrap();
            assert_eq!(property_block.block_type(), BlockType::UintValue);
            assert_eq!(property_block.uint_value().unwrap(), 1);
            assert_eq!(node_block.child_count().unwrap(), 1);

            property.set(5);
            assert_eq!(property_block.uint_value().unwrap(), 5);
            assert_eq!(property.get().unwrap(), 5);

            property.subtract(3);
            assert_eq!(property_block.uint_value().unwrap(), 2);

            property.add(8);
            assert_eq!(property_block.uint_value().unwrap(), 10);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn bool_property() {
        // Create and use a default value.
        let default = BoolProperty::default();
        default.set(true);

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let property = node.create_bool("property", true);
            let property_block = property.get_block().unwrap();
            assert_eq!(property_block.block_type(), BlockType::BoolValue);
            assert_eq!(property_block.bool_value().unwrap(), true);
            assert_eq!(node_block.child_count().unwrap(), 1);

            property.set(false);
            assert_eq!(property_block.bool_value().unwrap(), false);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn string_property() {
        // Create and use a default value.
        let default = StringProperty::default();
        default.set("test");

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let property = node.create_string("property", "test");
            let property_block = property.get_block().unwrap();
            assert_eq!(property_block.block_type(), BlockType::BufferValue);
            assert_eq!(property_block.property_total_length().unwrap(), 4);
            assert_eq!(property_block.property_format().unwrap(), PropertyFormat::String);
            assert_eq!(node_block.child_count().unwrap(), 1);

            property.set("test-set");
            assert_eq!(property_block.property_total_length().unwrap(), 8);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn bytes_property() {
        // Create and use a default value.
        let default = BytesProperty::default();
        default.set(&[0u8, 3u8]);

        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let property = node.create_bytes("property", b"test");
            let property_block = property.get_block().unwrap();
            assert_eq!(property_block.block_type(), BlockType::BufferValue);
            assert_eq!(property_block.property_total_length().unwrap(), 4);
            assert_eq!(property_block.property_format().unwrap(), PropertyFormat::Bytes);
            assert_eq!(node_block.child_count().unwrap(), 1);

            property.set(b"test-set");
            assert_eq!(property_block.property_total_length().unwrap(), 8);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn test_array() {
        // Create and use a default value.
        let default = DoubleArrayProperty::default();
        default.add(1, 1.0);
        let default = IntArrayProperty::default();
        default.add(1, 1);
        let default = UintArrayProperty::default();
        default.add(1, 1);

        let inspector = Inspector::new();
        let root = inspector.root();
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let array = node.create_double_array("array_property", 5);
            let array_block = array.get_block().unwrap();

            array.set(0, 5.0);
            assert_eq!(array_block.array_get_double_slot(0).unwrap(), 5.0);

            array.add(0, 5.3);
            assert_eq!(array_block.array_get_double_slot(0).unwrap(), 10.3);

            array.subtract(0, 3.4);
            assert_eq!(array_block.array_get_double_slot(0).unwrap(), 6.9);

            array.set(1, 2.5);
            array.set(3, -3.1);

            for (i, value) in [6.9, 2.5, 0.0, -3.1, 0.0].iter().enumerate() {
                assert_eq!(array_block.array_get_double_slot(i).unwrap(), *value);
            }

            array.clear();
            for i in 0..5 {
                assert_eq!(0.0, array_block.array_get_double_slot(i).unwrap());
            }

            assert_eq!(node_block.child_count().unwrap(), 1);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn linear_histograms() {
        let inspector = Inspector::new();
        let root = inspector.root();
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let int_histogram = node.create_int_linear_histogram(
                "int-histogram",
                LinearHistogramParams { floor: 10, step_size: 5, buckets: 5 },
            );
            int_histogram.insert_multiple(-1, 2); // underflow
            int_histogram.insert(25);
            int_histogram.insert(500); // overflow
            let block = int_histogram.get_block().unwrap();
            for (i, value) in [10, 5, 2, 0, 0, 0, 1, 0, 1].iter().enumerate() {
                assert_eq!(block.array_get_int_slot(i).unwrap(), *value);
            }

            let uint_histogram = node.create_uint_linear_histogram(
                "uint-histogram",
                LinearHistogramParams { floor: 10, step_size: 5, buckets: 5 },
            );
            uint_histogram.insert_multiple(0, 2); // underflow
            uint_histogram.insert(25);
            uint_histogram.insert(500); // overflow
            let block = uint_histogram.get_block().unwrap();
            for (i, value) in [10, 5, 2, 0, 0, 0, 1, 0, 1].iter().enumerate() {
                assert_eq!(block.array_get_uint_slot(i).unwrap(), *value);
            }

            uint_histogram.clear();
            for (i, value) in [10, 5, 0, 0, 0, 0, 0, 0, 0].iter().enumerate() {
                assert_eq!(*value, block.array_get_uint_slot(i).unwrap());
            }

            let double_histogram = node.create_double_linear_histogram(
                "double-histogram",
                LinearHistogramParams { floor: 10.0, step_size: 5.0, buckets: 5 },
            );
            double_histogram.insert_multiple(0.0, 2); // underflow
            double_histogram.insert(25.3);
            double_histogram.insert(500.0); // overflow
            let block = double_histogram.get_block().unwrap();
            for (i, value) in [10.0, 5.0, 2.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0].iter().enumerate() {
                assert_eq!(block.array_get_double_slot(i).unwrap(), *value);
            }

            assert_eq!(node_block.child_count().unwrap(), 3);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn exponential_histograms() {
        let inspector = Inspector::new();
        let root = inspector.root();
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let int_histogram = node.create_int_exponential_histogram(
                "int-histogram",
                ExponentialHistogramParams {
                    floor: 1,
                    initial_step: 1,
                    step_multiplier: 2,
                    buckets: 4,
                },
            );
            int_histogram.insert_multiple(-1, 2); // underflow
            int_histogram.insert(8);
            int_histogram.insert(500); // overflow
            let block = int_histogram.get_block().unwrap();
            for (i, value) in [1, 1, 2, 2, 0, 0, 0, 1, 1].iter().enumerate() {
                assert_eq!(block.array_get_int_slot(i).unwrap(), *value);
            }

            let uint_histogram = node.create_uint_exponential_histogram(
                "uint-histogram",
                ExponentialHistogramParams {
                    floor: 1,
                    initial_step: 1,
                    step_multiplier: 2,
                    buckets: 4,
                },
            );
            uint_histogram.insert_multiple(0, 2); // underflow
            uint_histogram.insert(8);
            uint_histogram.insert(500); // overflow
            let block = uint_histogram.get_block().unwrap();
            for (i, value) in [1, 1, 2, 2, 0, 0, 0, 1, 1].iter().enumerate() {
                assert_eq!(block.array_get_uint_slot(i).unwrap(), *value);
            }

            uint_histogram.clear();
            for (i, value) in [1, 1, 2, 0, 0, 0, 0, 0, 0].iter().enumerate() {
                assert_eq!(*value, block.array_get_uint_slot(i).unwrap());
            }

            let double_histogram = node.create_double_exponential_histogram(
                "double-histogram",
                ExponentialHistogramParams {
                    floor: 1.0,
                    initial_step: 1.0,
                    step_multiplier: 2.0,
                    buckets: 4,
                },
            );
            double_histogram.insert_multiple(0.0, 2); // underflow
            double_histogram.insert(8.3);
            double_histogram.insert(500.0); // overflow
            let block = double_histogram.get_block().unwrap();
            for (i, value) in [1.0, 1.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0].iter().enumerate() {
                assert_eq!(block.array_get_double_slot(i).unwrap(), *value);
            }

            assert_eq!(node_block.child_count().unwrap(), 3);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn owned_method_argument_properties() {
        let mapping = Arc::new(Mapping::allocate(4096).unwrap().0);
        let state = get_state(mapping.clone());
        let root = Node::new_root(state);
        let node = root.create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let _string_property =
                node.create_string(String::from("string_property"), String::from("test"));
            let _bytes_property =
                node.create_bytes(String::from("bytes_property"), vec![0, 1, 2, 3]);
            let _double_property = node.create_double(String::from("double_property"), 1.0);
            let _int_property = node.create_int(String::from("int_property"), 1);
            let _uint_property = node.create_uint(String::from("uint_property"), 1);
            assert_eq!(node_block.child_count().unwrap(), 5);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn dummy_partialeq() {
        let inspector = Inspector::new();
        let root = inspector.root();

        // Types should all be equal to another type. This is to enable clients
        // with inspect types in their structs be able to derive PartialEq and
        // Eq smoothly.
        assert_eq!(root, &root.create_child("child1"));
        assert_eq!(root.create_int("property1", 1), root.create_int("property2", 2));
        assert_eq!(root.create_double("property1", 1.0), root.create_double("property2", 2.0));
        assert_eq!(root.create_uint("property1", 1), root.create_uint("property2", 2));
        assert_eq!(
            root.create_string("property1", "value1"),
            root.create_string("property2", "value2")
        );
        assert_eq!(
            root.create_bytes("property1", b"value1"),
            root.create_bytes("property2", b"value2")
        );
    }

    fn get_state(mapping: Arc<Mapping>) -> State {
        let heap = Heap::new(mapping).unwrap();
        State::create(heap).expect("create state")
    }

    #[test]
    fn exp_histogram_insert() {
        let inspector = Inspector::new();
        let root = inspector.root();
        let hist = root.create_int_exponential_histogram(
            "test",
            ExponentialHistogramParams {
                floor: 0,
                initial_step: 2,
                step_multiplier: 4,
                buckets: 4,
            },
        );
        for i in -200..200 {
            hist.insert(i);
        }
        let block = hist.get_block().unwrap();
        assert_eq!(block.array_get_int_slot(0).unwrap(), 0);
        assert_eq!(block.array_get_int_slot(1).unwrap(), 2);
        assert_eq!(block.array_get_int_slot(2).unwrap(), 4);

        // Buckets
        let i = 3;
        assert_eq!(block.array_get_int_slot(i).unwrap(), 200);
        assert_eq!(block.array_get_int_slot(i + 1).unwrap(), 2);
        assert_eq!(block.array_get_int_slot(i + 2).unwrap(), 6);
        assert_eq!(block.array_get_int_slot(i + 3).unwrap(), 24);
        assert_eq!(block.array_get_int_slot(i + 4).unwrap(), 96);
        assert_eq!(block.array_get_int_slot(i + 5).unwrap(), 72);
    }

    #[test]
    fn lazy_values() {
        let inspector = Inspector::new();
        let node = inspector.root().create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let lazy_node =
                node.create_lazy_values("lazy", || async move { Ok(Inspector::new()) }.boxed());
            let lazy_node_block = lazy_node.get_block().unwrap();
            assert_eq!(lazy_node_block.block_type(), BlockType::LinkValue);
            assert_eq!(
                lazy_node_block.link_node_disposition().unwrap(),
                LinkNodeDisposition::Inline
            );
            assert_eq!(lazy_node_block.link_content_index().unwrap(), 5);
            assert_eq!(node_block.child_count().unwrap(), 1);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn lazy_node() {
        let inspector = Inspector::new();
        let node = inspector.root().create_child("node");
        let node_block = node.get_block().unwrap();
        {
            let lazy_node =
                node.create_lazy_child("lazy", || async move { Ok(Inspector::new()) }.boxed());
            let lazy_node_block = lazy_node.get_block().unwrap();
            assert_eq!(lazy_node_block.block_type(), BlockType::LinkValue);
            assert_eq!(
                lazy_node_block.link_node_disposition().unwrap(),
                LinkNodeDisposition::Child
            );
            assert_eq!(lazy_node_block.link_content_index().unwrap(), 5);
            assert_eq!(node_block.child_count().unwrap(), 1);
        }
        assert_eq!(node_block.child_count().unwrap(), 0);
    }

    #[test]
    fn inspector_lazy_from_vmo() {
        let inspector = Inspector::new();
        inspector.root().record_uint("test", 3);

        let embedded_inspector = Inspector::new();
        embedded_inspector.root().record_uint("test2", 4);
        let vmo = embedded_inspector.duplicate_vmo().unwrap();

        inspector.root().record_lazy_child_from_vmo("lazy", Arc::new(vmo));
        assert_inspect_tree!(inspector, root: {
            test: 3u64,
            lazy: {
                test2: 4u64,
            }
        });
    }

    #[test]
    fn value_list_record() {
        let inspector = Inspector::new();
        let child = inspector.root().create_child("test");
        let value_list = ValueList::new();
        assert!(value_list.values.lock().is_none());
        value_list.record(child);
        assert_eq!(value_list.values.lock().as_ref().unwrap().len(), 1);
    }

    #[test]
    fn record() {
        let inspector = Inspector::new();
        let property = inspector.root().create_uint("a", 1);
        inspector.root().record_uint("b", 2);
        {
            let child = inspector.root().create_child("child");
            child.record(property);
            child.record_double("c", 3.14);
            assert_inspect_tree!(inspector, root: {
                a: 1u64,
                b: 2u64,
                child: {
                    c: 3.14,
                }
            });
        }
        // `child` went out of scope, meaning it was deleted.
        // Property `a` should be gone as well, given that it was being tracked by `child`.
        assert_inspect_tree!(inspector, root: {
            b: 2u64,
        });
    }

    #[test]
    fn record_child() {
        let inspector = Inspector::new();
        inspector.root().record_child("test", |node| {
            node.record_int("a", 1);
        });
        assert_inspect_tree!(inspector, root: {
            test: {
                a: 1i64,
            }
        })
    }

    #[test]
    fn record_weak() {
        let inspector = Inspector::new();
        let main = inspector.root().create_child("main");
        let main_weak = main.clone_weak();
        let property = main_weak.create_uint("a", 1);

        // Ensure either the weak or strong reference can be used for recording
        main_weak.record_uint("b", 2);
        main.record_uint("c", 3);
        {
            let child = main_weak.create_child("child");
            child.record(property);
            child.record_double("c", 3.14);
            assert_inspect_tree!(inspector, root: { main: {
                a: 1u64,
                b: 2u64,
                c: 3u64,
                child: {
                    c: 3.14,
                }
            }});
        }
        // `child` went out of scope, meaning it was deleted.
        // Property `a` should be gone as well, given that it was being tracked by `child`.
        assert_inspect_tree!(inspector, root: { main: {
            b: 2u64,
            c: 3u64
        }});
        std::mem::drop(main);
        // Recording after dropping a strong reference is a no-op
        main_weak.record_double("d", 1.0);
        // Verify that dropping a strong reference cleans up the state
        assert_inspect_tree!(inspector, root: { });
    }

    #[fasync::run_singlethreaded(test)]
    async fn connect_to_service() -> Result<(), anyhow::Error> {
        let mut service_fs = ServiceFs::new();
        let env = service_fs.create_nested_environment("test")?;
        let mut app = client::launch(&env.launcher(), TEST_COMPONENT_URL.to_string(), None)?;

        fasync::Task::spawn(service_fs.collect()).detach();

        let mut component_stream = app.controller().take_event_stream();
        match component_stream
            .next()
            .await
            .expect("component event stream ended before termination event")?
        {
            ComponentControllerEvent::OnTerminated { return_code, termination_reason } => {
                bail!(
                    "Component terminated unexpectedly. Code: {}. Reason: {:?}",
                    return_code,
                    termination_reason
                );
            }
            ComponentControllerEvent::OnDirectoryReady {} => {
                let pattern = format!(
                    "/hub/r/test/*/c/{}/*/out/diagnostics/{}",
                    TEST_COMPONENT_CMX,
                    TreeMarker::SERVICE_NAME
                );
                let path = glob(&pattern)?.next().unwrap().expect("failed to parse glob");
                let (tree, server_end) =
                    fidl::endpoints::create_proxy::<TreeMarker>().expect("failed to create proxy");
                fdio::service_connect(
                    &path.to_string_lossy().to_string(),
                    server_end.into_channel(),
                )
                .expect("failed to connect to service");

                let hierarchy = reader::read(&tree).await?;
                assert_inspect_tree!(hierarchy, root: {
                    int: 3i64,
                    "lazy-node": {
                        a: "test",
                        child: {
                            double: 3.14,
                        },
                    }
                });
                app.kill().map_err(|e| format_err!("failed to kill component: {}", e))
            }
        }
    }

    #[test]
    fn unique_name() {
        let inspector = Inspector::new();

        let name_1 = super::unique_name("a");
        assert_eq!(name_1, "a0");
        inspector.root().record_uint(name_1, 1);

        let name_2 = super::unique_name("a");
        assert_eq!(name_2, "a1");
        inspector.root().record_uint(name_2, 1);

        assert_inspect_tree!(inspector, root: {
            a0: 1u64,
            a1: 1u64,
        });
    }
}
