blob: 494e2ee8ce156296e1ac07d6a97950dfb061a24c [file] [log] [blame]
// Copyright 2019 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.
use {
core::ops::{Deref, DerefMut},
derivative::Derivative,
fuchsia_inspect::{Node, Property, StringProperty},
std::borrow::Borrow,
};
/// Generic wrapper for exporting variables via Inspect. Mutations to
/// the wrapped `value` occur through an `InspectableGuard`, which
/// notifies the `watcher` when `Drop`ped, transparently keeping the Inspect
/// state up-to-date.
///
/// How the `value` is exported is determined by the `Watch` implementation,
/// see e.g. `InspectableDebugString`.
///
/// Not correct for `V`s with interior mutability, because `Inspectable`
/// `Deref`s to `V`, mutations to which will bypass the `watcher`.
#[derive(Derivative)]
#[derivative(Debug, Eq, PartialEq, Hash)]
pub struct Inspectable<V, W>
where
W: Watch<V>,
{
value: V,
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
watcher: W,
}
impl<V, W> Inspectable<V, W>
where
W: Watch<V>,
{
/// Creates an `Inspectable` wrapping `value`. Exports `value` via Inspect.
pub fn new(value: V, node: &Node, name: impl AsRef<str>) -> Self {
let watcher = W::new(&value, node, name);
Self { value, watcher }
}
/// Returns a guard that `DerefMut`s to the wrapped `value`. The `watcher`
/// will receive the updated `value` when the guard is `Drop`ped.
pub fn get_mut(&mut self) -> InspectableGuard<V, W> {
InspectableGuard { value: &mut self.value, watcher: &mut self.watcher }
}
}
impl<V, W> Deref for Inspectable<V, W>
where
W: Watch<V>,
{
type Target = V;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<V, W> Borrow<V> for Inspectable<V, W>
where
W: Watch<V>,
{
fn borrow(&self) -> &V {
&self.value
}
}
pub trait Watch<V> {
/// Used by `Inspectable::new()` to create a `Watch`er that exports via
/// Inspect the `Inspectable`'s wrapped `value`.
fn new(value: &V, node: &Node, name: impl AsRef<str>) -> Self;
/// Called by `InspectableGuard` when the guard is dropped, letting the
/// `Watch`er update its state with the updated `value`.
fn watch(&mut self, value: &V);
}
#[must_use]
/// Calls self.watcher.watch(self.value) when dropped.
pub struct InspectableGuard<'a, V, W>
where
W: Watch<V>,
{
value: &'a mut V,
watcher: &'a mut W,
}
impl<'a, V, W> Deref for InspectableGuard<'a, V, W>
where
W: Watch<V>,
{
type Target = V;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, V, W> DerefMut for InspectableGuard<'a, V, W>
where
W: Watch<V>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.value
}
}
impl<'a, V, W> Drop for InspectableGuard<'a, V, W>
where
W: Watch<V>,
{
fn drop(&mut self) {
self.watcher.watch(self.value);
}
}
/// Exports via an Inspect `UintProperty` the `len` of the wrapped `Vec`.
#[derive(Debug)]
pub struct InspectableVectorSizeWatcher {
len: fuchsia_inspect::UintProperty,
}
impl<V> Watch<Vec<V>> for InspectableVectorSizeWatcher {
fn new(value: &Vec<V>, node: &Node, name: impl AsRef<str>) -> Self {
Self { len: node.create_uint(name, value.len() as u64) }
}
fn watch(&mut self, vec: &Vec<V>) {
self.len.set(vec.len() as u64);
}
}
pub type InspectableVectorSize<V> = Inspectable<Vec<V>, InspectableVectorSizeWatcher>;
/// Exports via an Inspect `StringProperty` the `Debug` representation of
/// the wrapped `value`.
#[derive(Debug)]
pub struct InspectableDebugStringWatcher {
debug_string: StringProperty,
}
impl<V> Watch<V> for InspectableDebugStringWatcher
where
V: std::fmt::Debug,
{
fn new(value: &V, node: &Node, name: impl AsRef<str>) -> Self {
Self { debug_string: node.create_string(name, format!("{:?}", value)) }
}
fn watch(&mut self, value: &V) {
self.debug_string.set(&format!("{:?}", value))
}
}
pub type InspectableDebugString<V> = Inspectable<V, InspectableDebugStringWatcher>;
#[cfg(test)]
mod test {
use {
super::*,
fuchsia_inspect::{assert_inspect_tree, Inspector, IntProperty},
std::collections::HashSet,
};
struct InspectableIntWatcher {
i: IntProperty,
}
impl Watch<i64> for InspectableIntWatcher {
fn new(value: &i64, node: &Node, name: impl AsRef<str>) -> Self {
Self { i: node.create_int(name, *value) }
}
fn watch(&mut self, value: &i64) {
self.i.set(*value)
}
}
type InspectableInt = Inspectable<i64, InspectableIntWatcher>;
fn make_inspectable(i: i64) -> (InspectableInt, Inspector) {
let inspector = Inspector::new();
let inspectable = InspectableInt::new(i, inspector.root(), "inspectable-int");
(inspectable, inspector)
}
#[test]
fn test_inspectable_deref() {
let inspectable = make_inspectable(1).0;
assert_eq!(*inspectable, 1);
}
#[test]
fn test_guard_deref() {
let mut inspectable = make_inspectable(1).0;
assert_eq!(*inspectable.get_mut(), 1);
}
#[test]
fn test_guard_deref_mut() {
let mut inspectable = make_inspectable(1).0;
*inspectable.get_mut() = 2;
assert_eq!(*inspectable, 2);
}
#[test]
fn test_guard_drop_calls_callback() {
let (mut inspectable, inspector) = make_inspectable(1);
let mut guard = inspectable.get_mut();
*guard = 2;
drop(guard);
assert_inspect_tree!(
inspector,
root: {
"inspectable-int": 2i64
}
);
}
#[test]
fn test_partial_eq() {
let inspectable0 = make_inspectable(1).0;
let inspectable1 = make_inspectable(1).0;
assert_eq!(inspectable0, inspectable1);
}
#[test]
fn test_partial_eq_not() {
let inspectable0 = make_inspectable(1).0;
let inspectable1 = make_inspectable(2).0;
assert_ne!(inspectable0, inspectable1);
}
#[test]
fn test_borrow() {
let inspectable = make_inspectable(1).0;
assert_eq!(Borrow::<i64>::borrow(&inspectable), &1);
}
#[test]
fn test_inspectable_in_hash_set() {
let inspectable = make_inspectable(1).0;
let mut set = HashSet::new();
set.insert(inspectable);
assert!(set.contains(&1));
set.remove(&1);
assert!(set.is_empty());
}
}
#[cfg(test)]
mod test_inspectable_vector_size {
use super::*;
use fuchsia_inspect::assert_inspect_tree;
#[test]
fn test_initialization() {
let inspector = fuchsia_inspect::Inspector::new();
let _inspectable = InspectableVectorSize::new(vec![0], inspector.root(), "test-property");
assert_inspect_tree!(
inspector,
root: {
"test-property": 1u64
}
);
}
#[test]
fn test_watcher() {
let inspector = fuchsia_inspect::Inspector::new();
let mut inspectable =
InspectableVectorSize::new(vec![0], inspector.root(), "test-property");
inspectable.get_mut().push(1);
assert_inspect_tree!(
inspector,
root: {
"test-property": 2u64
}
);
}
}
#[cfg(test)]
mod test_inspectable_debug_string {
use super::*;
use fuchsia_inspect::assert_inspect_tree;
#[test]
fn test_initialization() {
let inspector = fuchsia_inspect::Inspector::new();
let _inspectable = InspectableDebugString::new(vec![0], inspector.root(), "test-property");
assert_inspect_tree!(
inspector,
root: {
"test-property": "[0]"
}
);
}
#[test]
fn test_watcher() {
let inspector = fuchsia_inspect::Inspector::new();
let mut inspectable =
InspectableDebugString::new(vec![0], inspector.root(), "test-property");
inspectable.get_mut().push(1);
assert_inspect_tree!(
inspector,
root: {
"test-property": "[0, 1]"
}
);
}
}