blob: f1fcb02e965d3cebeb1203c57511983ad599df0c [file] [log] [blame]
// 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.
#![cfg(test)]
use core::fmt;
use futures::lock;
use serde::Serialize;
use std::{cell, rc, sync};
use fuchsia_async as fasync;
use fuchsia_inspect::{
assert_inspect_tree, Inspector, Node, NumericProperty, Property, StringProperty, UintProperty,
};
use fuchsia_inspect_derive::{AttachError, IDebug, IValue, Inspect, Unit, WithInspect};
// TODO(fxbug.dev/49049): Add negative tests when compile failure tests are possible.
#[derive(Unit, Serialize)]
struct Yak {
name: String,
age: i64,
#[serde(rename = "Name")] // Unrelated field attributes together with inspect attributes allowed
#[inspect(skip)] // Hide PII of Yak
credit_card_no: String,
yakling: Yakling,
}
#[derive(Unit, Serialize)]
#[serde(rename = "YakLing")] // Unrelated container attributes allowed
struct Yakling {
#[serde(rename = "Name")] // Unrelated field attributes allowed
name: String,
#[inspect(rename = "years_old")]
age: u8,
}
#[derive(Debug)]
enum Horse {
Arabian,
Icelandic,
}
impl Default for Horse {
fn default() -> Self {
Self::Arabian
}
}
#[derive(Unit, Default)]
struct BasicTypes {
t_u8: u8,
t_u16: u16,
t_u32: u32,
t_u64: u64,
t_usize: usize,
t_i8: i8,
t_i16: i16,
t_i32: i32,
t_i64: i64,
t_isize: isize,
t_f32: f32,
t_f64: f64,
t_bool: bool,
t_string: String,
t_vec_u8: Vec<u8>,
}
// Support type and lifetime parameters in a limitied fashion
#[derive(Unit)]
struct GenericUnit<'a, T> {
#[inspect(skip)]
_convoluted: &'a T,
easy: String,
}
#[derive(Default)]
struct PowerYak {
name: IValue<String>,
age: sync::Arc<sync::Mutex<IDebug<u8>>>,
size: rc::Rc<lock::Mutex<IValue<String>>>,
ty: cell::RefCell<IDebug<Horse>>,
counter: Box<UintProperty>,
last_words: parking_lot::RwLock<StringProperty>,
inspect_node: Node,
}
impl PowerYak {
pub fn bday(&self) {
let mut age = self.age.lock().expect("Could not lock mutex");
*age.as_mut() += 1;
}
}
// Manual implementation of `Inspect`, proving that an implementation can
// easily be auto-generated (using a derive-macro).
impl Inspect for &mut PowerYak {
fn iattach(self, parent: &Node, name: impl AsRef<str>) -> Result<(), AttachError> {
let inspect_node = parent.create_child(name);
self.name.iattach(&inspect_node, "name")?;
self.age.iattach(&inspect_node, "age")?;
self.size.iattach(&inspect_node, "size")?;
self.ty.iattach(&inspect_node, "ty")?;
self.counter.iattach(&inspect_node, "counter")?;
self.last_words.iattach(&inspect_node, "last_words")?;
self.inspect_node = inspect_node;
Ok(())
}
}
// Display cannot be derived with std, so we require that the fields are `Display` instead.
// This is important so that 3p crates such as `Derivative` can auto-derive `Display`.
impl fmt::Display for PowerYak {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "name: {}, age: {}", self.name, self.age.lock().expect("lock poisoned"))
}
}
#[derive(Inspect)]
struct AutoYak {
name: IValue<Option<String>>,
#[inspect(skip)]
_credit_card_no: String,
#[inspect(rename = "horse_type")]
ty: IDebug<Horse>,
child: sync::Arc<lock::Mutex<inner::AutoYakling>>,
inspect_node: Node,
}
impl AutoYak {
fn new(name: String) -> Self {
Self {
name: Some(name).into(),
_credit_card_no: "<secret>".into(),
ty: Horse::Icelandic.into(),
child: sync::Arc::new(lock::Mutex::new(inner::AutoYakling::default())),
inspect_node: Node::default(),
}
}
async fn host_bday(&self) {
self.child.lock().await.bday();
}
}
// No inspect node, automatically forwards node to inner node
#[derive(Inspect)]
struct AutoYakWrapper {
// Attaches the inner field to parent[name]
#[inspect(forward)]
inner: parking_lot::Mutex<AutoYak>,
// Attaches `wrapper_data` directly to parent, ignoring the requested name
wrapper_data: IValue<String>,
}
impl From<AutoYak> for AutoYakWrapper {
fn from(yak: AutoYak) -> Self {
let inner = yak.into();
let wrapper_data = "some data".to_string().into();
Self { inner, wrapper_data }
}
}
// No inspect node, attaches name and age directly to the parent
#[derive(Inspect)]
struct NodeLessYak {
name: IValue<String>,
age: IValue<u16>,
}
impl NodeLessYak {
fn new(name: String, age: u16) -> Self {
Self { name: name.into(), age: age.into() }
}
}
mod inner {
use super::*;
#[derive(Default, Inspect)]
pub struct AutoYakling {
age: IValue<u8>,
inspect_node: Node,
}
impl AutoYakling {
pub fn bday(&mut self) {
self.age.iset(*self.age + 1);
}
}
}
// Support type and lifetime parameters in a limitied fashion
#[derive(Inspect)]
struct GenericYak<'a, T: Unit> {
#[inspect(skip)]
_age: &'a u16,
special_ability: IValue<T>,
inspect_node: Node,
}
#[test]
fn unit_primitive() {
let inspector = Inspector::new();
let root = inspector.root();
let mut num = 127i8;
let mut num_data = num.inspect_create(&root, "num");
assert_inspect_tree!(inspector, root: { num: 127i64 });
num = -128;
num.inspect_update(&mut num_data);
assert_inspect_tree!(inspector, root: { num: -128i64 });
std::mem::drop(num_data);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn unit_flat() {
let inspector = Inspector::new();
let root = inspector.root();
let mut yakling = Yakling { name: "Lil Sebastian".to_string(), age: 5 };
let mut yakling_data = yakling.inspect_create(&root, "yak");
assert_inspect_tree!(inspector, root: {
yak: { name: "Lil Sebastian", years_old: 5u64 }
});
yakling.name = "Sebastian".to_string();
yakling.age = 10;
yakling.inspect_update(&mut yakling_data);
assert_inspect_tree!(inspector, root: {
yak: { name: "Sebastian", years_old: 10u64 }
});
std::mem::drop(yakling_data);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn unit_nested() {
let inspector = Inspector::new();
let root = inspector.root();
let mut yak = Yak {
name: "Big Sebastian".to_string(),
age: 25,
credit_card_no: "12345678".to_string(),
yakling: Yakling { name: "Lil Sebastian".to_string(), age: 2 },
};
let mut yak_data = yak.inspect_create(&root, "my_yak");
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Big Sebastian",
age: 25i64,
yakling: {
name: "Lil Sebastian",
years_old: 2u64,
},
}
});
yak.yakling.age += 1; // Happy bday, Lil Sebastian
yak.name = "Big Sebastian Sr.".to_string();
yak.credit_card_no = "1234".to_string();
yak.inspect_update(&mut yak_data);
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Big Sebastian Sr.",
age: 25i64,
yakling: {
name: "Lil Sebastian",
years_old: 3u64,
},
}
});
std::mem::drop(yak_data);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn unit_basic_types() {
let inspector = Inspector::new();
let root = inspector.root();
let mut basic = BasicTypes::default();
let mut basic_data = basic.inspect_create(&root, "basic");
assert_inspect_tree!(inspector, root: {
basic: {
t_u8: 0u64,
t_u16: 0u64,
t_u32: 0u64,
t_u64: 0u64,
t_usize: 0u64,
t_i8: 0i64,
t_i16: 0i64,
t_i32: 0i64,
t_i64: 0i64,
t_isize: 0i64,
t_f32: 0f64,
t_f64: 0f64,
t_bool: false,
t_string: "",
t_vec_u8: Vec::<u8>::default(),
}
});
basic.t_string = "hello world".to_string();
basic.t_bool = true;
basic.t_f32 = 1.0;
basic.t_vec_u8 = vec![0x13, 0x37];
basic.inspect_update(&mut basic_data);
assert_inspect_tree!(inspector, root: {
basic: {
t_u8: 0u64,
t_u16: 0u64,
t_u32: 0u64,
t_u64: 0u64,
t_usize: 0u64,
t_i8: 0i64,
t_i16: 0i64,
t_i32: 0i64,
t_i64: 0i64,
t_isize: 0i64,
t_f32: 1f64,
t_f64: 0f64,
t_bool: true,
t_string: "hello world",
t_vec_u8: vec![0x13u8, 0x37u8],
}
});
std::mem::drop(basic_data);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn unit_generic() {
let inspector = Inspector::new();
let root = inspector.root();
let a = "some_ref".to_string();
let mut generic_unit = GenericUnit { _convoluted: &a, easy: "owned".to_string() };
let mut inspect_data = generic_unit.inspect_create(&root, "a_struct");
assert_inspect_tree!(inspector, root: {
a_struct: { easy: "owned" }
});
generic_unit.easy = "owned altered".to_string();
generic_unit.inspect_update(&mut inspect_data);
assert_inspect_tree!(inspector, root: {
a_struct: { easy: "owned altered" }
});
std::mem::drop(inspect_data);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn unit_option() -> Result<(), AttachError> {
let inspector = Inspector::new();
let mut option_yakling: Option<Yakling> = None;
let mut option_yakling_data = option_yakling.inspect_create(inspector.root(), "option_yakling");
assert_inspect_tree!(inspector, root: {});
option_yakling = Some(Yakling { name: "Sebastian".to_string(), age: 3 });
option_yakling.inspect_update(&mut option_yakling_data);
assert_inspect_tree!(inspector, root: {
option_yakling: {
name: "Sebastian",
years_old: 3u64
},
});
option_yakling.as_mut().unwrap().age = 4;
option_yakling.inspect_update(&mut option_yakling_data);
assert_inspect_tree!(inspector, root: {
option_yakling: {
name: "Sebastian",
years_old: 4u64
},
});
option_yakling = None;
option_yakling.inspect_update(&mut option_yakling_data);
assert_inspect_tree!(inspector, root: {});
std::mem::drop(option_yakling_data);
assert_inspect_tree!(inspector, root: {});
// Cover `inspect_create(..)` from `Some(..)`
let option_yakling = Some(Yakling { name: "Sebastian".to_string(), age: 3 });
let option_yakling_data = option_yakling.inspect_create(inspector.root(), "option_yakling");
assert_inspect_tree!(inspector, root: {
option_yakling: {
name: "Sebastian",
years_old: 3u64
},
});
std::mem::drop(option_yakling_data);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[test]
fn ivalue_primitive() {
let inspector = Inspector::new();
let root = inspector.root();
let mut num = IValue::attached(126i8, &root, "num");
assert_inspect_tree!(inspector, root: { num: 126i64 });
// Modifying num should change its value but not update inspect
{
let mut num = num.as_mut();
*num += 1;
assert_eq!(*num, 127);
assert_inspect_tree!(inspector, root: { num: 126i64 });
}
// Now inspect is updated
assert_inspect_tree!(inspector, root: { num: 127i64 });
num.iset(-128);
assert_eq!(*num, -128);
assert_inspect_tree!(inspector, root: { num: -128i64 });
std::mem::drop(num);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn ivalue_nested() {
let inspector = Inspector::new();
let root = inspector.root();
let yak_base = Yak {
name: "Big Sebastian".to_string(),
age: 25,
credit_card_no: "12345678".to_string(),
yakling: Yakling { name: "Lil Sebastian".to_string(), age: 2 },
};
let mut yak = IValue::attached(yak_base, &root, "my_yak");
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Big Sebastian",
age: 25i64,
yakling: {
name: "Lil Sebastian",
years_old: 2u64,
},
}
});
{
let mut yak = yak.as_mut();
yak.yakling.age += 1; // Happy bday, Lil Sebastian
yak.name = "Big Sebastian Sr.".to_string();
yak.credit_card_no = "1234".to_string();
}
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Big Sebastian Sr.",
age: 25i64,
yakling: {
name: "Lil Sebastian",
years_old: 3u64,
},
}
});
std::mem::drop(yak);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn idebug_enum() {
let inspector = Inspector::new();
let root = inspector.root();
let mut horse = IDebug::attached(Horse::Arabian, &root, "horse");
assert_inspect_tree!(inspector, root: { horse: "Arabian" });
horse.iset(Horse::Icelandic);
assert_inspect_tree!(inspector, root: { horse: "Icelandic" });
std::mem::drop(horse);
assert_inspect_tree!(inspector, root: {});
}
#[test]
fn iowned_new() {
let mut v = IValue::new(1u64);
assert_eq!(*v, 1u64);
v.iset(2);
assert_eq!(*v, 2u64);
let mut d = IDebug::new(1u64);
assert_eq!(*d, 1u64);
d.iset(2);
assert_eq!(*d, 2u64);
}
#[test]
fn iowned_default() {
let v: IValue<u16> = IValue::default();
assert_eq!(*v, 0u16);
let d: IDebug<String> = IDebug::default();
assert_eq!(d.as_str(), "");
}
#[test]
fn iowned_from() {
let v = IValue::from(17u16);
assert_eq!(*v, 17u16);
let d = IDebug::from("hello".to_string());
assert_eq!(d.as_str(), "hello");
}
#[test]
fn iowned_into() {
let v: IValue<_> = 17u16.into();
assert_eq!(*v, 17u16);
let d: IDebug<String> = "hello".to_string().into();
assert_eq!(d.as_str(), "hello");
}
#[test]
fn iowned_into_inner() {
let v = IValue::new(17u16);
assert_eq!(v.into_inner(), 17u16);
let d = IDebug::new("hello".to_string());
assert_eq!(d.into_inner(), "hello".to_string());
}
#[test]
fn iowned_debug() {
let mut v = IValue::new(1337u64);
assert_eq!(format!("{:?}", v).as_str(), "1337");
v.iset(1338);
assert_eq!(format!("{:?}", v).as_str(), "1338");
let mut d = IDebug::new("hello".to_string());
assert_eq!(format!("{:?}", d).as_str(), "\"hello\"");
d.iset("hello, world".to_string());
assert_eq!(format!("{:?}", d).as_str(), "\"hello, world\"");
}
#[test]
fn iowned_display() {
let mut v = IValue::new(1337u64);
assert_eq!(format!("{}", v).as_str(), "1337");
v.iset(1338);
assert_eq!(format!("{}", v).as_str(), "1338");
let mut d = IDebug::new("hello".to_string());
assert_eq!(format!("{}", d).as_str(), "hello");
d.iset("hello, world".to_string());
assert_eq!(format!("{}", d).as_str(), "hello, world");
}
#[fasync::run_until_stalled(test)]
async fn iowned_composite() -> Result<(), AttachError> {
let inspector = Inspector::new();
let mut yak = PowerYak::default();
yak.iattach(inspector.root(), "my_yak")?;
assert_inspect_tree!(inspector, root: { my_yak: {
name: "",
age: "0",
size: "",
ty: "Arabian",
counter: 0u64,
last_words: "",
}});
yak.name.iset("Lil Sebastian".to_string());
yak.age.lock().expect("could not lock mutex").iset(23);
yak.size.lock().await.iset("small".to_string());
yak.ty.borrow_mut().iset(Horse::Icelandic);
yak.counter.add(1337);
yak.last_words.write().set("good bye, friends");
yak.bday();
assert_inspect_tree!(inspector, root: { my_yak: {
name: "Lil Sebastian",
age: "24",
size: "small",
ty: "Icelandic",
counter: 1337u64,
last_words: "good bye, friends",
}});
std::mem::drop(yak);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn derive_inspect_nested_interior_mut() -> Result<(), AttachError> {
let inspector = Inspector::new();
let mut yak_mut = AutoYak::new("Sebastian".to_string());
yak_mut.iattach(inspector.root(), "my_yak")?;
// Ensure only a reference is necessary, since AutoYak utilizes interior mutability
let yak = &yak_mut;
assert_inspect_tree!(inspector, root: { my_yak: {
name: "Sebastian",
horse_type: "Icelandic",
child: {
age: 0u64,
},
}});
yak.host_bday().await;
assert_inspect_tree!(inspector, root: { my_yak: {
name: "Sebastian",
horse_type: "Icelandic",
child: {
age: 1u64,
},
}});
std::mem::drop(yak_mut);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn derive_inspect_forward() -> Result<(), AttachError> {
let inspector = Inspector::new();
let mut yak = AutoYakWrapper::from(AutoYak::new("Sebastian".to_string()));
yak.iattach(inspector.root(), "my_yak")?;
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Sebastian",
horse_type: "Icelandic",
child: {
age: 0u64,
},
},
wrapper_data: "some data",
});
std::mem::drop(yak);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn derive_inspect_nodeless() -> Result<(), AttachError> {
let inspector = Inspector::new();
let mut yak = NodeLessYak::new("Sebastian".to_string(), 2);
yak.iattach(inspector.root(), "ignored")?;
assert_inspect_tree!(inspector, root: {
name: "Sebastian",
age: 2u64,
});
std::mem::drop(yak);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[test]
fn derive_inspect_generic() -> Result<(), AttachError> {
let inspector = Inspector::new();
let age = 4u16;
let mut yak = GenericYak {
_age: &age,
special_ability: IValue::new("monomorphization".to_string()),
inspect_node: Node::default(),
};
yak.iattach(inspector.root(), "generic_yak")?;
assert_inspect_tree!(inspector, root: { generic_yak: {
special_ability: "monomorphization",
}});
yak.special_ability.iset("lifetime parameterization".to_string());
assert_inspect_tree!(inspector, root: { generic_yak: {
special_ability: "lifetime parameterization",
}});
std::mem::drop(yak);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn with_inspect_mutable() -> Result<(), AttachError> {
let inspector = Inspector::new();
let mut yakling = inner::AutoYakling::default().with_inspect(inspector.root(), "my_yak")?;
let mut happy = IValue::new(false).with_inspect(inspector.root(), "happy")?;
assert_inspect_tree!(inspector, root: {
my_yak: {
age: 0u64,
},
happy: false,
});
yakling.bday();
happy.iset(true);
assert_inspect_tree!(inspector, root: {
my_yak: {
age: 1u64,
},
happy: true,
});
std::mem::drop(yakling);
std::mem::drop(happy);
assert_inspect_tree!(inspector, root: {});
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn with_inspect_interior_mutability() -> Result<(), AttachError> {
let inspector = Inspector::new();
let yak = AutoYakWrapper::from(AutoYak::new("Sebastian".to_string()))
.with_inspect(inspector.root(), "my_yak")?;
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Sebastian",
horse_type: "Icelandic",
child: {
age: 0u64,
},
},
wrapper_data: "some data",
});
yak.inner.lock().host_bday().await;
assert_inspect_tree!(inspector, root: {
my_yak: {
name: "Sebastian",
horse_type: "Icelandic",
child: {
age: 1u64,
},
},
wrapper_data: "some data",
});
std::mem::drop(yak);
assert_inspect_tree!(inspector, root: {});
Ok(())
}