inspect_validator/
results.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::metrics::Metrics;
6use super::puppet::DiffType;
7use fidl_diagnostics_validate::*;
8use serde::Serialize;
9use std::collections::HashSet;
10
11#[derive(Serialize, Debug, Default)]
12pub struct Results {
13    messages: Vec<String>,
14    unimplemented: HashSet<String>,
15    failed: bool,
16    metrics: Vec<TrialMetrics>,
17    pub diff_type: DiffType,
18}
19
20pub trait Summary {
21    fn summary(&self) -> String;
22}
23
24impl Summary for Value {
25    fn summary(&self) -> String {
26        match self {
27            Self::IntT(_) => "Int",
28            Self::UintT(_) => "Uint",
29            Self::DoubleT(_) => "Double",
30            Self::StringT(_) => "String",
31            _ => "Unknown",
32        }
33        .to_string()
34    }
35}
36
37impl Summary for ValueType {
38    fn summary(&self) -> String {
39        match self {
40            ValueType::Int => "Int",
41            ValueType::Uint => "Uint",
42            ValueType::Double => "Double",
43            ValueType::String => "String",
44            _ => "Unknown",
45        }
46        .to_string()
47    }
48}
49
50impl Summary for Action {
51    fn summary(&self) -> String {
52        match self {
53            Action::CreateNode(_) => "CreateNode".to_string(),
54            Action::DeleteNode(_) => "DeleteNode".to_string(),
55            Action::CreateNumericProperty(CreateNumericProperty { value, .. }) => {
56                format!("CreateProperty({})", value.summary())
57            }
58            Action::CreateBytesProperty(_) => "CreateProperty(Bytes)".to_string(),
59            Action::CreateStringProperty(_) => "CreateProperty(String)".to_string(),
60            Action::CreateBoolProperty(_) => "CreateProperty(Bool)".to_string(),
61            Action::DeleteProperty(_) => "DeleteProperty".to_string(),
62            Action::SetBytes(_) => "Set(Bytes)".to_string(),
63            Action::SetString(_) => "Set(String)".to_string(),
64            Action::SetBool(_) => "Set(Bool)".to_string(),
65            Action::AddNumber(AddNumber { value, .. }) => format!("Add({})", value.summary()),
66            Action::SubtractNumber(SubtractNumber { value, .. }) => {
67                format!("Subtract({})", value.summary())
68            }
69            Action::SetNumber(SetNumber { value, .. }) => format!("Set({})", value.summary()),
70            Action::CreateArrayProperty(CreateArrayProperty { value_type, .. }) => {
71                format!("CreateArrayProperty({})", value_type.summary())
72            }
73            Action::ArraySet(ArraySet { value, .. }) => format!("ArraySet({})", value.summary()),
74            Action::ArrayAdd(ArrayAdd { value, .. }) => format!("ArrayAdd({})", value.summary()),
75            Action::ArraySubtract(ArraySubtract { value, .. }) => {
76                format!("ArraySubtract({})", value.summary())
77            }
78            Action::CreateLinearHistogram(CreateLinearHistogram { floor, .. }) => {
79                format!("CreateLinearHistogram({})", floor.summary())
80            }
81            Action::CreateExponentialHistogram(CreateExponentialHistogram { floor, .. }) => {
82                format!("CreateExponentialHistogram({})", floor.summary())
83            }
84            Action::Insert(Insert { value, .. }) => format!("Insert({})", value.summary()),
85            Action::InsertMultiple(InsertMultiple { value, .. }) => {
86                format!("InsertMultiple({})", value.summary())
87            }
88            _ => "Unknown".to_string(),
89        }
90    }
91}
92
93impl Summary for LazyAction {
94    fn summary(&self) -> String {
95        match self {
96            LazyAction::CreateLazyNode(_) => "CreateLazyNode".to_string(),
97            LazyAction::DeleteLazyNode(_) => "DeleteLazyNode".to_string(),
98            _ => "Unknown".to_string(),
99        }
100    }
101}
102
103#[derive(Serialize, Debug)]
104struct TrialMetrics {
105    step_index: usize,
106    trial_name: String,
107    metrics: Metrics,
108    step_name: String,
109}
110
111impl Results {
112    pub fn new() -> Results {
113        Results::default()
114    }
115
116    pub fn error(&mut self, message: String) {
117        self.log(message);
118        self.failed = true;
119    }
120
121    pub fn log(&mut self, message: String) {
122        self.messages.push(message);
123    }
124
125    pub fn unimplemented<T: Summary>(&mut self, puppet_name: &str, action: &T) {
126        self.unimplemented.insert(format!("{}: {}", puppet_name, action.summary()));
127    }
128
129    pub fn remember_metrics(
130        &mut self,
131        metrics: Metrics,
132        trial_name: &str,
133        step_index: usize,
134        step_name: &str,
135    ) {
136        self.metrics.push(TrialMetrics {
137            metrics,
138            trial_name: trial_name.into(),
139            step_index,
140            step_name: step_name.into(),
141        });
142    }
143
144    pub fn to_json(&self) -> String {
145        match serde_json::to_string(self) {
146            Ok(string) => string,
147            Err(e) => format!("{{error: \"Converting to json: {:?}\"}}", e),
148        }
149    }
150
151    fn print_pretty_metric(metric: &TrialMetrics) {
152        println!(
153            "Trial: '{}' Step {}: '{}' Blocks: {} Size: {}",
154            metric.trial_name,
155            metric.step_index,
156            metric.step_name,
157            metric.metrics.block_count,
158            metric.metrics.size
159        );
160        println!("Count\tHeader\tData\tTotal\tData %\tType");
161        for (name, statistics) in metric.metrics.block_statistics.iter() {
162            println!(
163                "{}\t{}\t{}\t{}\t{}\t{}",
164                statistics.count,
165                statistics.header_bytes,
166                statistics.data_bytes,
167                statistics.total_bytes,
168                statistics.data_percent,
169                name
170            );
171        }
172        println!();
173    }
174
175    pub fn print_pretty_text(&self) {
176        if self.failed {
177            println!("FAILED, sorry about that.");
178        } else {
179            println!("SUCCESS on all tests!");
180        }
181        for message in self.messages.iter() {
182            println!("{}", message);
183        }
184        if !self.unimplemented.is_empty() {
185            println!("\nUnimplemented:");
186            for info in self.unimplemented.iter() {
187                println!("  {}", info);
188            }
189        }
190        if !self.metrics.is_empty() {
191            println!("\nMetrics:");
192            for metric in self.metrics.iter() {
193                Self::print_pretty_metric(metric);
194            }
195        }
196    }
197
198    pub fn failed(&self) -> bool {
199        self.failed
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::*;
207
208    #[fuchsia::test]
209    fn error_result_fails_and_outputs() {
210        let mut results = Results::new();
211        assert!(!results.failed());
212        results.error("Oops!".to_string());
213        assert!(results.failed());
214        assert!(results.to_json().contains("Oops!"));
215    }
216
217    #[fuchsia::test]
218    fn log_result_does_not_fail_and_outputs() {
219        let mut results = Results::new();
220        assert!(!results.failed());
221        results.log("Harmless message!".to_string());
222        assert!(!results.failed());
223        assert!(results.to_json().contains("Harmless message!"));
224    }
225
226    #[fuchsia::test]
227    fn unimplemented_does_not_error() {
228        let mut results = Results::new();
229        results.unimplemented("foo", &delete_node!(id:17));
230        assert!(!results.failed());
231    }
232
233    #[fuchsia::test]
234    fn unimplemented_does_not_duplicate() {
235        let mut results = Results::new();
236        results.unimplemented("foo", &delete_node!(id:17));
237        assert!(results.to_json().split("DeleteNode").collect::<Vec<_>>().len() == 2);
238        // Adding a second instance of the same command and puppet name doesn't increase reports.
239        results.unimplemented("foo", &delete_node!(id:123));
240        assert!(results.to_json().split("DeleteNode").collect::<Vec<_>>().len() == 2);
241        // But adding the same command for a different puppet does increase reports.
242        results.unimplemented("bar", &delete_node!(id:123));
243        assert!(results.to_json().split("DeleteNode").collect::<Vec<_>>().len() == 3);
244    }
245
246    #[fuchsia::test]
247    fn unimplemented_renders_everything() {
248        let mut results = Results::new();
249        results.unimplemented("foo", &create_node!(parent: 42, id:42, name: "bar"));
250        assert!(results.to_json().contains("foo: CreateNode"));
251        results.unimplemented("foo", &delete_node!(id:42));
252        assert!(results.to_json().contains("foo: DeleteNode"));
253        results.unimplemented(
254            "foo",
255            &create_numeric_property!(parent:42, id:42, name: "bar", value: Value::IntT(42)),
256        );
257        assert!(results.to_json().contains("foo: CreateProperty(Int)"));
258        results.unimplemented(
259            "foo",
260            &create_numeric_property!(parent:42, id:42, name: "bar", value: Value::UintT(42)),
261        );
262        assert!(results.to_json().contains("foo: CreateProperty(Uint)"));
263        results.unimplemented(
264            "foo",
265            &create_numeric_property!(parent:42, id:42, name: "bar", value: Value::DoubleT(42.0)),
266        );
267        assert!(results.to_json().contains("foo: CreateProperty(Double)"));
268        results.unimplemented(
269            "foo",
270            &create_bytes_property!(parent:42, id:42, name: "bar", value: vec![42]),
271        );
272        assert!(results.to_json().contains("foo: CreateProperty(Bytes)"));
273        results.unimplemented(
274            "foo",
275            &create_string_property!(parent:42, id:42, name: "bar", value: "bar"),
276        );
277        assert!(results.to_json().contains("foo: CreateProperty(String)"));
278        results.unimplemented("foo", &set_string!(id:42, value: "bar"));
279        assert!(results.to_json().contains("foo: Set(String)"));
280        results.unimplemented("foo", &set_bytes!(id:42, value: vec![42]));
281        assert!(results.to_json().contains("foo: Set(Bytes)"));
282        results.unimplemented("foo", &set_number!(id:42, value: Value::IntT(42)));
283        assert!(results.to_json().contains("foo: Set(Int)"));
284        results.unimplemented("foo", &set_number!(id:42, value: Value::UintT(42)));
285        assert!(results.to_json().contains("foo: Set(Uint)"));
286        results.unimplemented("foo", &set_number!(id:42, value: Value::DoubleT(42.0)));
287        assert!(results.to_json().contains("foo: Set(Double)"));
288        results.unimplemented("foo", &add_number!(id:42, value: Value::IntT(42)));
289        assert!(results.to_json().contains("foo: Add(Int)"));
290        results.unimplemented("foo", &add_number!(id:42, value: Value::UintT(42)));
291        assert!(results.to_json().contains("foo: Add(Uint)"));
292        results.unimplemented("foo", &add_number!(id:42, value: Value::DoubleT(42.0)));
293        assert!(results.to_json().contains("foo: Add(Double)"));
294        results.unimplemented("foo", &subtract_number!(id:42, value: Value::IntT(42)));
295        assert!(results.to_json().contains("foo: Subtract(Int)"));
296        results.unimplemented("foo", &subtract_number!(id:42, value: Value::UintT(42)));
297        assert!(results.to_json().contains("foo: Subtract(Uint)"));
298        results.unimplemented("foo", &subtract_number!(id:42, value: Value::DoubleT(42.0)));
299        assert!(results.to_json().contains("foo: Subtract(Double)"));
300        results.unimplemented("foo", &delete_property!(id:42));
301        assert!(results.to_json().contains("foo: DeleteProperty"));
302
303        results.unimplemented("foo", &create_array_property!(parent: 42, id:42, name: "foo", slots: 42, type: ValueType::Uint));
304        assert!(results.to_json().contains("foo: CreateArrayProperty(Uint)"));
305        results.unimplemented("foo", &array_set!(id:42, index: 42, value: Value::UintT(42)));
306        assert!(results.to_json().contains("foo: ArraySet(Uint)"));
307        results.unimplemented("foo", &array_add!(id:42, index: 42, value: Value::UintT(42)));
308        assert!(results.to_json().contains("foo: ArrayAdd(Uint)"));
309        results.unimplemented("foo", &array_subtract!(id:42, index:42, value:Value::UintT(42)));
310        assert!(results.to_json().contains("foo: ArraySubtract(Uint)"));
311
312        results.unimplemented(
313            "foo",
314            &create_linear_histogram!(parent: 42, id:42, name: "foo", floor: 42, step_size: 42,
315                                buckets: 42, type: IntT),
316        );
317        assert!(results.to_json().contains("foo: CreateLinearHistogram(Int)"));
318        results.unimplemented("foo", &create_exponential_histogram!(parent: 42, id:42, name: "foo", floor: 42, initial_step: 42,
319                                step_multiplier: 42, buckets: 42, type: UintT));
320        assert!(results.to_json().contains("foo: CreateExponentialHistogram(Uint)"));
321        results.unimplemented("foo", &insert!(id:42, value:Value::UintT(42)));
322        assert!(results.to_json().contains("foo: Insert(Uint)"));
323        results.unimplemented("foo", &insert_multiple!(id:42, value:Value::UintT(42), count: 42));
324        assert!(results.to_json().contains("foo: InsertMultiple(Uint)"));
325
326        assert!(!results.to_json().contains("42"));
327        assert!(!results.to_json().contains("bar"));
328        assert!(!results.to_json().contains("Unknown"));
329    }
330
331    #[fuchsia::test]
332    fn metric_remembering() {
333        let mut results = Results::new();
334        let mut metrics = metrics::Metrics::new();
335        let sample = metrics::BlockMetrics::sample_for_test("MyBlock".to_owned(), 8, 4, 16);
336        // NotUsed should set data size (the "4" parameter) to 0.
337        metrics.record(&sample, metrics::BlockStatus::NotUsed);
338        // Recording the same sample twice should double all the values.
339        metrics.record(&sample, metrics::BlockStatus::Used);
340        metrics.record(&sample, metrics::BlockStatus::Used);
341        results.remember_metrics(metrics, "FooTrial", 42, "BarStep");
342        let json = results.to_json();
343        assert!(json
344            .contains("\"metrics\":[{\"step_index\":42,\"trial_name\":\"FooTrial\",\"metrics\":"));
345        assert!(json.contains("\"step_name\":\"BarStep\""));
346        assert!(json.contains(
347            "\"MyBlock(UNUSED)\":{\"count\":1,\"header_bytes\":8,\"data_bytes\":0,\"total_bytes\":16,\"data_percent\":0}"), "{}", json);
348        assert!(json.contains(
349            "\"MyBlock\":{\"count\":2,\"header_bytes\":16,\"data_bytes\":8,\"total_bytes\":32,\"data_percent\":25}"), "{}", json);
350    }
351}