]>
Commit | Line | Data |
---|---|---|
f035d41b XL |
1 | use tracing::{ |
2 | callsite, | |
3 | callsite::Callsite, | |
4 | field::{self, Field, Value, Visit}, | |
5 | metadata::Kind, | |
6 | }; | |
7 | ||
8 | use std::{collections::HashMap, fmt}; | |
9 | ||
10 | #[derive(Default, Debug, Eq, PartialEq)] | |
11 | pub struct Expect { | |
12 | fields: HashMap<String, MockValue>, | |
13 | only: bool, | |
14 | } | |
15 | ||
16 | #[derive(Debug)] | |
17 | pub struct MockField { | |
18 | name: String, | |
19 | value: MockValue, | |
20 | } | |
21 | ||
c295e0f8 | 22 | #[derive(Debug)] |
f035d41b | 23 | pub enum MockValue { |
c295e0f8 | 24 | F64(f64), |
f035d41b XL |
25 | I64(i64), |
26 | U64(u64), | |
27 | Bool(bool), | |
28 | Str(String), | |
29 | Debug(String), | |
30 | Any, | |
31 | } | |
32 | ||
c295e0f8 XL |
33 | impl Eq for MockValue {} |
34 | ||
35 | impl PartialEq for MockValue { | |
36 | fn eq(&self, other: &Self) -> bool { | |
37 | use MockValue::*; | |
38 | ||
39 | match (self, other) { | |
40 | (F64(a), F64(b)) => { | |
41 | debug_assert!(!a.is_nan()); | |
42 | debug_assert!(!b.is_nan()); | |
43 | ||
44 | a.eq(b) | |
45 | } | |
46 | (I64(a), I64(b)) => a.eq(b), | |
47 | (U64(a), U64(b)) => a.eq(b), | |
48 | (Bool(a), Bool(b)) => a.eq(b), | |
49 | (Str(a), Str(b)) => a.eq(b), | |
50 | (Debug(a), Debug(b)) => a.eq(b), | |
51 | (Any, _) => true, | |
52 | (_, Any) => true, | |
53 | _ => false, | |
54 | } | |
55 | } | |
56 | } | |
57 | ||
f035d41b XL |
58 | pub fn mock<K>(name: K) -> MockField |
59 | where | |
60 | String: From<K>, | |
61 | { | |
62 | MockField { | |
63 | name: name.into(), | |
64 | value: MockValue::Any, | |
65 | } | |
66 | } | |
67 | ||
c295e0f8 XL |
68 | pub fn msg(message: impl fmt::Display) -> MockField { |
69 | MockField { | |
70 | name: "message".to_string(), | |
71 | value: MockValue::Debug(message.to_string()), | |
72 | } | |
73 | } | |
74 | ||
f035d41b XL |
75 | impl MockField { |
76 | /// Expect a field with the given name and value. | |
77 | pub fn with_value(self, value: &dyn Value) -> Self { | |
78 | Self { | |
79 | value: MockValue::from(value), | |
80 | ..self | |
81 | } | |
82 | } | |
83 | ||
84 | pub fn and(self, other: MockField) -> Expect { | |
85 | Expect { | |
86 | fields: HashMap::new(), | |
87 | only: false, | |
88 | } | |
89 | .and(self) | |
90 | .and(other) | |
91 | } | |
92 | ||
93 | pub fn only(self) -> Expect { | |
94 | Expect { | |
95 | fields: HashMap::new(), | |
96 | only: true, | |
97 | } | |
98 | .and(self) | |
99 | } | |
100 | } | |
101 | ||
17df50a5 XL |
102 | impl From<MockField> for Expect { |
103 | fn from(field: MockField) -> Self { | |
f035d41b XL |
104 | Expect { |
105 | fields: HashMap::new(), | |
106 | only: false, | |
107 | } | |
17df50a5 | 108 | .and(field) |
f035d41b XL |
109 | } |
110 | } | |
111 | ||
112 | impl Expect { | |
113 | pub fn and(mut self, field: MockField) -> Self { | |
114 | self.fields.insert(field.name, field.value); | |
115 | self | |
116 | } | |
117 | ||
118 | /// Indicates that no fields other than those specified should be expected. | |
119 | pub fn only(self) -> Self { | |
120 | Self { only: true, ..self } | |
121 | } | |
122 | ||
c295e0f8 XL |
123 | fn compare_or_panic( |
124 | &mut self, | |
125 | name: &str, | |
126 | value: &dyn Value, | |
127 | ctx: &str, | |
128 | subscriber_name: &str, | |
129 | ) { | |
f035d41b XL |
130 | let value = value.into(); |
131 | match self.fields.remove(name) { | |
132 | Some(MockValue::Any) => {} | |
133 | Some(expected) => assert!( | |
134 | expected == value, | |
c295e0f8 XL |
135 | "\n[{}] expected `{}` to contain:\n\t`{}{}`\nbut got:\n\t`{}{}`", |
136 | subscriber_name, | |
f035d41b XL |
137 | ctx, |
138 | name, | |
139 | expected, | |
140 | name, | |
141 | value | |
142 | ), | |
143 | None if self.only => panic!( | |
c295e0f8 XL |
144 | "[{}]expected `{}` to contain only:\n\t`{}`\nbut got:\n\t`{}{}`", |
145 | subscriber_name, ctx, self, name, value | |
f035d41b XL |
146 | ), |
147 | _ => {} | |
148 | } | |
149 | } | |
150 | ||
c295e0f8 XL |
151 | pub fn checker<'a>(&'a mut self, ctx: &'a str, subscriber_name: &'a str) -> CheckVisitor<'a> { |
152 | CheckVisitor { | |
153 | expect: self, | |
154 | ctx, | |
155 | subscriber_name, | |
156 | } | |
f035d41b XL |
157 | } |
158 | ||
159 | pub fn is_empty(&self) -> bool { | |
160 | self.fields.is_empty() | |
161 | } | |
162 | } | |
163 | ||
164 | impl fmt::Display for MockValue { | |
165 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
166 | match self { | |
c295e0f8 | 167 | MockValue::F64(v) => write!(f, "f64 = {:?}", v), |
f035d41b XL |
168 | MockValue::I64(v) => write!(f, "i64 = {:?}", v), |
169 | MockValue::U64(v) => write!(f, "u64 = {:?}", v), | |
170 | MockValue::Bool(v) => write!(f, "bool = {:?}", v), | |
171 | MockValue::Str(v) => write!(f, "&str = {:?}", v), | |
172 | MockValue::Debug(v) => write!(f, "&fmt::Debug = {:?}", v), | |
173 | MockValue::Any => write!(f, "_ = _"), | |
174 | } | |
175 | } | |
176 | } | |
177 | ||
178 | pub struct CheckVisitor<'a> { | |
179 | expect: &'a mut Expect, | |
c295e0f8 XL |
180 | ctx: &'a str, |
181 | subscriber_name: &'a str, | |
f035d41b XL |
182 | } |
183 | ||
184 | impl<'a> Visit for CheckVisitor<'a> { | |
c295e0f8 XL |
185 | fn record_f64(&mut self, field: &Field, value: f64) { |
186 | self.expect | |
187 | .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name) | |
188 | } | |
189 | ||
f035d41b XL |
190 | fn record_i64(&mut self, field: &Field, value: i64) { |
191 | self.expect | |
c295e0f8 | 192 | .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name) |
f035d41b XL |
193 | } |
194 | ||
195 | fn record_u64(&mut self, field: &Field, value: u64) { | |
196 | self.expect | |
c295e0f8 | 197 | .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name) |
f035d41b XL |
198 | } |
199 | ||
200 | fn record_bool(&mut self, field: &Field, value: bool) { | |
201 | self.expect | |
c295e0f8 | 202 | .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name) |
f035d41b XL |
203 | } |
204 | ||
205 | fn record_str(&mut self, field: &Field, value: &str) { | |
206 | self.expect | |
c295e0f8 | 207 | .compare_or_panic(field.name(), &value, self.ctx, self.subscriber_name) |
f035d41b XL |
208 | } |
209 | ||
210 | fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { | |
c295e0f8 XL |
211 | self.expect.compare_or_panic( |
212 | field.name(), | |
213 | &field::debug(value), | |
214 | self.ctx, | |
215 | self.subscriber_name, | |
216 | ) | |
f035d41b XL |
217 | } |
218 | } | |
219 | ||
220 | impl<'a> CheckVisitor<'a> { | |
221 | pub fn finish(self) { | |
222 | assert!( | |
223 | self.expect.fields.is_empty(), | |
c295e0f8 XL |
224 | "[{}] {}missing {}", |
225 | self.subscriber_name, | |
f035d41b XL |
226 | self.expect, |
227 | self.ctx | |
228 | ); | |
229 | } | |
230 | } | |
231 | ||
232 | impl<'a> From<&'a dyn Value> for MockValue { | |
233 | fn from(value: &'a dyn Value) -> Self { | |
234 | struct MockValueBuilder { | |
235 | value: Option<MockValue>, | |
236 | } | |
237 | ||
238 | impl Visit for MockValueBuilder { | |
c295e0f8 XL |
239 | fn record_f64(&mut self, _: &Field, value: f64) { |
240 | self.value = Some(MockValue::F64(value)); | |
241 | } | |
242 | ||
f035d41b XL |
243 | fn record_i64(&mut self, _: &Field, value: i64) { |
244 | self.value = Some(MockValue::I64(value)); | |
245 | } | |
246 | ||
247 | fn record_u64(&mut self, _: &Field, value: u64) { | |
248 | self.value = Some(MockValue::U64(value)); | |
249 | } | |
250 | ||
251 | fn record_bool(&mut self, _: &Field, value: bool) { | |
252 | self.value = Some(MockValue::Bool(value)); | |
253 | } | |
254 | ||
255 | fn record_str(&mut self, _: &Field, value: &str) { | |
256 | self.value = Some(MockValue::Str(value.to_owned())); | |
257 | } | |
258 | ||
259 | fn record_debug(&mut self, _: &Field, value: &dyn fmt::Debug) { | |
260 | self.value = Some(MockValue::Debug(format!("{:?}", value))); | |
261 | } | |
262 | } | |
263 | ||
264 | let fake_field = callsite!(name: "fake", kind: Kind::EVENT, fields: fake_field) | |
265 | .metadata() | |
266 | .fields() | |
267 | .field("fake_field") | |
268 | .unwrap(); | |
269 | let mut builder = MockValueBuilder { value: None }; | |
270 | value.record(&fake_field, &mut builder); | |
271 | builder | |
272 | .value | |
273 | .expect("finish called before a value was recorded") | |
274 | } | |
275 | } | |
276 | ||
277 | impl fmt::Display for Expect { | |
278 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
279 | write!(f, "fields ")?; | |
280 | let entries = self | |
281 | .fields | |
282 | .iter() | |
283 | .map(|(k, v)| (field::display(k), field::display(v))); | |
284 | f.debug_map().entries(entries).finish() | |
285 | } | |
286 | } |