blob: 3667cf08b45f4d0d6b4a65a495ee495d75542719 [file] [log] [blame]
use tracing::{
callsite,
callsite::Callsite,
field::{self, Field, Value, Visit},
metadata::Kind,
};
use std::{collections::HashMap, fmt};
#[derive(Default, Debug, Eq, PartialEq)]
pub struct Expect {
fields: HashMap<String, MockValue>,
only: bool,
}
#[derive(Debug)]
pub struct MockField {
name: String,
value: MockValue,
}
#[derive(Debug, Eq, PartialEq)]
pub enum MockValue {
I64(i64),
U64(u64),
Bool(bool),
Str(String),
Debug(String),
Any,
}
pub fn mock<K>(name: K) -> MockField
where
String: From<K>,
{
MockField {
name: name.into(),
value: MockValue::Any,
}
}
impl MockField {
/// Expect a field with the given name and value.
pub fn with_value(self, value: &dyn Value) -> Self {
Self {
value: MockValue::from(value),
..self
}
}
pub fn and(self, other: MockField) -> Expect {
Expect {
fields: HashMap::new(),
only: false,
}
.and(self)
.and(other)
}
pub fn only(self) -> Expect {
Expect {
fields: HashMap::new(),
only: true,
}
.and(self)
}
}
impl Into<Expect> for MockField {
fn into(self) -> Expect {
Expect {
fields: HashMap::new(),
only: false,
}
.and(self)
}
}
impl Expect {
pub fn and(mut self, field: MockField) -> Self {
self.fields.insert(field.name, field.value);
self
}
/// Indicates that no fields other than those specified should be expected.
pub fn only(self) -> Self {
Self { only: true, ..self }
}
fn compare_or_panic(&mut self, name: &str, value: &dyn Value, ctx: &str) {
let value = value.into();
match self.fields.remove(name) {
Some(MockValue::Any) => {}
Some(expected) => assert!(
expected == value,
"\nexpected `{}` to contain:\n\t`{}{}`\nbut got:\n\t`{}{}`",
ctx,
name,
expected,
name,
value
),
None if self.only => panic!(
"\nexpected `{}` to contain only:\n\t`{}`\nbut got:\n\t`{}{}`",
ctx, self, name, value
),
_ => {}
}
}
pub fn checker(&mut self, ctx: String) -> CheckVisitor<'_> {
CheckVisitor { expect: self, ctx }
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
}
impl fmt::Display for MockValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MockValue::I64(v) => write!(f, "i64 = {:?}", v),
MockValue::U64(v) => write!(f, "u64 = {:?}", v),
MockValue::Bool(v) => write!(f, "bool = {:?}", v),
MockValue::Str(v) => write!(f, "&str = {:?}", v),
MockValue::Debug(v) => write!(f, "&fmt::Debug = {:?}", v),
MockValue::Any => write!(f, "_ = _"),
}
}
}
pub struct CheckVisitor<'a> {
expect: &'a mut Expect,
ctx: String,
}
impl<'a> Visit for CheckVisitor<'a> {
fn record_i64(&mut self, field: &Field, value: i64) {
self.expect
.compare_or_panic(field.name(), &value, &self.ctx[..])
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.expect
.compare_or_panic(field.name(), &value, &self.ctx[..])
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.expect
.compare_or_panic(field.name(), &value, &self.ctx[..])
}
fn record_str(&mut self, field: &Field, value: &str) {
self.expect
.compare_or_panic(field.name(), &value, &self.ctx[..])
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.expect
.compare_or_panic(field.name(), &field::debug(value), &self.ctx)
}
}
impl<'a> CheckVisitor<'a> {
pub fn finish(self) {
assert!(
self.expect.fields.is_empty(),
"{}missing {}",
self.expect,
self.ctx
);
}
}
impl<'a> From<&'a dyn Value> for MockValue {
fn from(value: &'a dyn Value) -> Self {
struct MockValueBuilder {
value: Option<MockValue>,
}
impl Visit for MockValueBuilder {
fn record_i64(&mut self, _: &Field, value: i64) {
self.value = Some(MockValue::I64(value));
}
fn record_u64(&mut self, _: &Field, value: u64) {
self.value = Some(MockValue::U64(value));
}
fn record_bool(&mut self, _: &Field, value: bool) {
self.value = Some(MockValue::Bool(value));
}
fn record_str(&mut self, _: &Field, value: &str) {
self.value = Some(MockValue::Str(value.to_owned()));
}
fn record_debug(&mut self, _: &Field, value: &dyn fmt::Debug) {
self.value = Some(MockValue::Debug(format!("{:?}", value)));
}
}
let fake_field = callsite!(name: "fake", kind: Kind::EVENT, fields: fake_field)
.metadata()
.fields()
.field("fake_field")
.unwrap();
let mut builder = MockValueBuilder { value: None };
value.record(&fake_field, &mut builder);
builder
.value
.expect("finish called before a value was recorded")
}
}
impl fmt::Display for Expect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fields ")?;
let entries = self
.fields
.iter()
.map(|(k, v)| (field::display(k), field::display(v)));
f.debug_map().entries(entries).finish()
}
}