1use crate::metrics::Metrics;
6use crate::puppet::DiffType;
7use anyhow::{Error, bail, format_err};
8use base64::engine::Engine as _;
9use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
10use diagnostics_hierarchy::{
11 ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram,
12 Property as iProperty,
13};
14use fidl_diagnostics_validate::{self as validate, Value};
15use inspect_format::{ArrayFormat, BlockIndex, LinkNodeDisposition};
16use num_traits::Zero;
17use std::collections::{HashMap, HashSet};
18use std::fmt;
19
20mod scanner;
21pub use scanner::Scanner;
22mod fetch;
23pub use fetch::LazyNode;
24
25#[cfg(test)]
26use num_traits::ToPrimitive;
27
28const ROOT_NAME: &str = "root";
29
30#[derive(Debug, Clone)]
36pub struct Data {
37 nodes: HashMap<BlockIndex, Node>,
38 properties: HashMap<BlockIndex, Property>,
39 tombstone_nodes: HashSet<BlockIndex>,
40 tombstone_properties: HashSet<BlockIndex>,
41}
42
43#[derive(Debug, Clone)]
60pub struct Node {
61 name: String,
62 parent: BlockIndex,
63 children: HashSet<BlockIndex>,
64 properties: HashSet<BlockIndex>,
65}
66
67#[derive(Debug, Clone)]
68pub struct Property {
69 name: String,
70 #[allow(unused)]
72 id: BlockIndex,
73 parent: BlockIndex,
74 payload: Payload,
75}
76
77#[derive(Debug, Clone)]
78enum Payload {
81 String(#[allow(unused)] String),
82 Bytes(Vec<u8>),
83 Int(i64),
84 Uint(u64),
85 Double(f64),
86 Bool(#[allow(unused)] bool),
87 IntArray(Vec<i64>, ArrayFormat),
88 UintArray(Vec<u64>, ArrayFormat),
89 DoubleArray(Vec<f64>, ArrayFormat),
90 StringArray(Vec<String>),
91 Link { disposition: LinkNodeDisposition, parsed_data: Data },
92
93 GenericNumber(#[allow(unused)] String),
97 GenericArray(#[allow(unused)] Vec<String>),
98 GenericLinearHistogram(#[allow(unused)] Vec<String>),
99 GenericExponentialHistogram(#[allow(unused)] Vec<String>),
100}
101
102fn to_string<T: std::fmt::Display>(v: T) -> String {
103 format!("{v}")
104}
105
106fn stringify_list<T: std::fmt::Display>(values: Vec<T>) -> Vec<String> {
107 values.into_iter().map(|x| to_string(x)).collect()
108}
109
110fn stringify_linear_histogram<T: Clone + std::fmt::Display + Zero>(
111 histogram: LinearHistogram<T>,
112) -> Vec<String> {
113 let mut values = vec![T::zero(); histogram.size + 2];
114 values[0] = histogram.floor;
115 values[1] = histogram.step;
116 if let Some(indexes) = histogram.indexes {
117 for (index, count) in indexes.iter().zip(histogram.counts) {
118 values[index + 2] = count.clone();
119 }
120 } else {
121 for (index, count) in histogram.counts.iter().enumerate() {
122 values[index + 2] = count.clone();
123 }
124 }
125 stringify_list(values)
126}
127
128fn stringify_exponential_histogram<T: Clone + std::fmt::Display + Zero>(
129 histogram: ExponentialHistogram<T>,
130) -> Vec<String> {
131 let mut values = vec![T::zero(); histogram.size + 3];
132 values[0] = histogram.floor;
133 values[1] = histogram.initial_step;
134 values[2] = histogram.step_multiplier;
135 if let Some(indexes) = histogram.indexes {
136 for (index, count) in indexes.iter().zip(histogram.counts) {
137 values[index + 3] = count.clone();
138 }
139 } else {
140 for (index, count) in histogram.counts.iter().enumerate() {
141 values[index + 3] = count.clone();
142 }
143 }
144 stringify_list(values)
145}
146
147fn handle_array(values: Vec<String>, format: ArrayFormat) -> Payload {
148 match format {
149 ArrayFormat::LinearHistogram => Payload::GenericLinearHistogram(values),
150 ArrayFormat::ExponentialHistogram => Payload::GenericExponentialHistogram(values),
151 _ => Payload::GenericArray(values),
152 }
153}
154
155trait ToGeneric {
156 fn to_generic(self) -> Payload;
157}
158
159impl ToGeneric for Payload {
160 fn to_generic(self) -> Self {
162 match self {
163 Payload::IntArray(val, format) => handle_array(stringify_list(val), format),
164 Payload::UintArray(val, format) => handle_array(stringify_list(val), format),
165 Payload::DoubleArray(val, format) => handle_array(stringify_list(val), format),
166 Payload::StringArray(val) => handle_array(val, ArrayFormat::Default),
167 Payload::Bytes(val) => Payload::String(format!("b64:{}", BASE64_STANDARD.encode(&val))),
168 Payload::Int(val) => Payload::GenericNumber(to_string(val)),
169 Payload::Uint(val) => Payload::GenericNumber(to_string(val)),
170 Payload::Double(val) => Payload::GenericNumber(to_string(val)),
171 Payload::Link { disposition, parsed_data } => {
172 Payload::Link { disposition, parsed_data: parsed_data.clone_generic() }
173 }
174 val => val,
175 }
176 }
177}
178
179impl<T: std::fmt::Display + Clone + Zero> ToGeneric for ArrayContent<T> {
180 fn to_generic(self) -> Payload {
181 match self {
182 ArrayContent::Values(values) => Payload::GenericArray(stringify_list(values)),
183 ArrayContent::LinearHistogram(histogram) => {
184 Payload::GenericLinearHistogram(stringify_linear_histogram(histogram))
185 }
186 ArrayContent::ExponentialHistogram(histogram) => {
187 Payload::GenericExponentialHistogram(stringify_exponential_histogram(histogram))
188 }
189 }
190 }
191}
192
193struct FormattedEntries {
194 nodes: Vec<String>,
195 properties: Vec<String>,
196}
197
198impl Property {
199 fn to_string(&self, prefix: &str) -> String {
200 format!("{}{}: {:?}", prefix, self.name, &self.payload)
201 }
202
203 fn format_entries(&self, prefix: &str) -> FormattedEntries {
206 match &self.payload {
207 Payload::Link { disposition, parsed_data } => match disposition {
208 LinkNodeDisposition::Child => FormattedEntries {
210 nodes: vec![format!(
211 "{}{} ->\n{}",
212 prefix,
213 self.name,
214 parsed_data.nodes[&0.into()].to_string(prefix, parsed_data, true)
215 )],
216 properties: vec![],
217 },
218 LinkNodeDisposition::Inline => {
221 let root = &parsed_data.nodes[&BlockIndex::ROOT];
222 let mut nodes = root
223 .children
224 .iter()
225 .map(|v| {
226 parsed_data.nodes.get(v).map_or_else(
227 || "Missing child".into(),
228 |n| n.to_string(prefix, parsed_data, false),
229 )
230 })
231 .collect::<Vec<_>>();
232 let mut properties = vec![];
233 for FormattedEntries { nodes: mut n, properties: mut p } in
234 root.properties.iter().map(|v| {
235 parsed_data.properties.get(v).map_or_else(
236 || FormattedEntries {
237 nodes: vec![],
238 properties: vec!["Missing property".into()],
239 },
240 |p| p.format_entries(prefix),
241 )
242 })
243 {
244 nodes.append(&mut n);
245 properties.append(&mut p);
246 }
247 FormattedEntries { nodes, properties }
248 }
249 },
250 _ => FormattedEntries { nodes: vec![], properties: vec![self.to_string(prefix)] },
252 }
253 }
254}
255
256impl Node {
257 fn to_string(&self, prefix: &str, tree: &Data, hide_root: bool) -> String {
261 let sub_prefix = format!("{prefix}> ");
262 let mut nodes = vec![];
263 for node_id in self.children.iter() {
264 nodes.push(tree.nodes.get(node_id).map_or_else(
265 || "Missing child".into(),
266 |n| n.to_string(&sub_prefix, tree, hide_root),
267 ));
268 }
269 let mut properties = vec![];
270
271 for property_id in self.properties.iter() {
272 let FormattedEntries { nodes: mut n, properties: mut p } =
273 tree.properties.get(property_id).map_or_else(
274 || FormattedEntries {
275 nodes: vec![],
276 properties: vec!["Missing property".to_string()],
277 },
278 |p| p.format_entries(&sub_prefix),
279 );
280 properties.append(&mut p);
281 nodes.append(&mut n);
282 }
283
284 nodes.sort_unstable();
285 properties.sort_unstable();
286
287 let mut output_lines = vec![];
288
289 if self.name != ROOT_NAME || !hide_root {
290 output_lines.push(format!("{}{} ->", prefix, self.name));
291 }
292 output_lines.append(&mut properties);
293 output_lines.append(&mut nodes);
294
295 output_lines.join("\n")
296 }
297}
298
299struct Op {
300 int: fn(i64, i64) -> i64,
301 uint: fn(u64, u64) -> u64,
302 double: fn(f64, f64) -> f64,
303 string: fn(String, String) -> String,
304 name: &'static str,
305}
306
307const ADD: Op = Op {
308 int: |a, b| a + b,
309 uint: |a, b| a + b,
310 double: |a, b| a + b,
311 string: |_, _| unreachable!(),
312 name: "add",
313};
314const SUBTRACT: Op = Op {
315 int: |a, b| a - b,
316 uint: |a, b| a - b,
317 double: |a, b| a - b,
318 string: |_, _| unreachable!(),
319 name: "subtract",
320};
321const SET: Op =
322 Op { int: |_a, b| b, uint: |_a, b| b, double: |_a, b| b, string: |_a, b| b, name: "set" };
323
324macro_rules! insert_linear_fn {
325 ($name:ident, $type:ident) => {
326 fn $name(numbers: &mut [$type], value: $type, count: u64) -> Result<(), Error> {
327 let buckets: $type = (numbers.len() as i32 - 4).try_into().unwrap();
328 let floor = numbers[0];
329 let step_size = numbers[1];
330 let index: usize = if value < floor {
331 2
332 } else if value >= floor + buckets * step_size {
333 numbers.len() - 1
334 } else {
335 (((value - floor) / step_size) as $type + 3 as $type) as i32 as usize
336 };
337 numbers[index] += count as $type;
338 Ok(())
339 }
340 };
341}
342
343insert_linear_fn! {insert_linear_i, i64}
344insert_linear_fn! {insert_linear_u, u64}
345insert_linear_fn! {insert_linear_d, f64}
346
347macro_rules! insert_exponential_fn {
356 ($name:ident, $type:ident, $fudge_factor:expr) => {
357 fn $name(numbers: &mut [$type], value: $type, count: u64) -> Result<(), Error> {
358 let buckets = numbers.len() - 5;
359 let floor = numbers[0];
360 let initial_step = numbers[1];
361 let step_multiplier = numbers[2];
362 let index = if value < floor {
363 3
364 } else if value < floor + initial_step {
365 4
366 } else if value
367 >= floor + initial_step * (step_multiplier as f64).powi(buckets as i32 - 1) as $type
368 {
369 numbers.len() - 1
370 } else {
371 ((((value as f64 - floor as f64) / initial_step as f64) as f64).log2()
372 / (step_multiplier as f64 + $fudge_factor).log2())
373 .trunc() as usize
374 + 5
375 };
376 numbers[index] += count as $type;
377 Ok(())
378 }
379 };
380}
381
382insert_exponential_fn! {insert_exponential_i, i64, 0.0000000000000000000001}
383insert_exponential_fn! {insert_exponential_u, u64, 0.0000000000000000000001}
384insert_exponential_fn! {insert_exponential_d, f64, 0.0}
385
386impl Data {
387 pub fn apply(&mut self, action: &validate::Action) -> Result<(), Error> {
391 match action {
392 validate::Action::CreateNode(validate::CreateNode { parent, id, name }) => {
393 self.create_node(parent.into(), id.into(), name)
394 }
395 validate::Action::DeleteNode(validate::DeleteNode { id }) => {
396 self.delete_node(id.into())
397 }
398 validate::Action::CreateNumericProperty(validate::CreateNumericProperty {
399 parent,
400 id,
401 name,
402 value,
403 }) => self.add_property(
404 parent.into(),
405 id.into(),
406 name,
407 match value {
408 validate::Value::IntT(value) => Payload::Int(*value),
409 validate::Value::UintT(value) => Payload::Uint(*value),
410 validate::Value::DoubleT(value) => Payload::Double(*value),
411 unknown => return Err(format_err!("Unknown number type {:?}", unknown)),
412 },
413 ),
414 validate::Action::CreateBytesProperty(validate::CreateBytesProperty {
415 parent,
416 id,
417 name,
418 value,
419 }) => self.add_property(parent.into(), id.into(), name, Payload::Bytes(value.clone())),
420 validate::Action::CreateStringProperty(validate::CreateStringProperty {
421 parent,
422 id,
423 name,
424 value,
425 }) => self.add_property(
426 parent.into(),
427 id.into(),
428 name,
429 Payload::String(value.to_string()),
430 ),
431 validate::Action::CreateBoolProperty(validate::CreateBoolProperty {
432 parent,
433 id,
434 name,
435 value,
436 }) => self.add_property(parent.into(), id.into(), name, Payload::Bool(*value)),
437 validate::Action::DeleteProperty(validate::DeleteProperty { id }) => {
438 self.delete_property(id.into())
439 }
440 validate::Action::SetNumber(validate::SetNumber { id, value }) => {
441 self.modify_number(id.into(), value, SET)
442 }
443 validate::Action::AddNumber(validate::AddNumber { id, value }) => {
444 self.modify_number(id.into(), value, ADD)
445 }
446 validate::Action::SubtractNumber(validate::SubtractNumber { id, value }) => {
447 self.modify_number(id.into(), value, SUBTRACT)
448 }
449 validate::Action::SetBytes(validate::SetBytes { id, value }) => {
450 self.set_bytes(id.into(), value)
451 }
452 validate::Action::SetString(validate::SetString { id, value }) => {
453 self.set_string(id.into(), value)
454 }
455 validate::Action::SetBool(validate::SetBool { id, value }) => {
456 self.set_bool(id.into(), *value)
457 }
458 validate::Action::CreateArrayProperty(validate::CreateArrayProperty {
459 parent,
460 id,
461 name,
462 slots,
463 value_type,
464 }) => self.add_property(
465 parent.into(),
466 id.into(),
467 name,
468 match value_type {
469 validate::ValueType::Int => {
470 Payload::IntArray(vec![0; *slots as usize], ArrayFormat::Default)
471 }
472 validate::ValueType::Uint => {
473 Payload::UintArray(vec![0; *slots as usize], ArrayFormat::Default)
474 }
475 validate::ValueType::Double => {
476 Payload::DoubleArray(vec![0.0; *slots as usize], ArrayFormat::Default)
477 }
478 validate::ValueType::String => {
479 Payload::StringArray(vec![String::new(); *slots as usize])
480 }
481 unknown => return Err(format_err!("unknown value type {unknown:?}")),
482 },
483 ),
484 validate::Action::ArrayAdd(validate::ArrayAdd { id, index, value }) => {
485 self.modify_array(id.into(), *index, value, ADD)
486 }
487 validate::Action::ArraySubtract(validate::ArraySubtract { id, index, value }) => {
488 self.modify_array(id.into(), *index, value, SUBTRACT)
489 }
490 validate::Action::ArraySet(validate::ArraySet { id, index, value }) => {
491 self.modify_array(id.into(), *index, value, SET)
492 }
493 validate::Action::CreateLinearHistogram(validate::CreateLinearHistogram {
494 parent,
495 id,
496 name,
497 floor,
498 step_size,
499 buckets,
500 }) => self.add_property(
501 parent.into(),
502 id.into(),
503 name,
504 match (floor, step_size) {
505 (validate::Value::IntT(floor), validate::Value::IntT(step_size)) => {
506 let mut data = vec![0; *buckets as usize + 4];
507 data[0] = *floor;
508 data[1] = *step_size;
509 Payload::IntArray(data, ArrayFormat::LinearHistogram)
510 }
511 (validate::Value::UintT(floor), validate::Value::UintT(step_size)) => {
512 let mut data = vec![0; *buckets as usize + 4];
513 data[0] = *floor;
514 data[1] = *step_size;
515 Payload::UintArray(data, ArrayFormat::LinearHistogram)
516 }
517 (validate::Value::DoubleT(floor), validate::Value::DoubleT(step_size)) => {
518 let mut data = vec![0.0; *buckets as usize + 4];
519 data[0] = *floor;
520 data[1] = *step_size;
521 Payload::DoubleArray(data, ArrayFormat::LinearHistogram)
522 }
523 unexpected => {
524 return Err(format_err!(
525 "Bad types in CreateLinearHistogram: {:?}",
526 unexpected
527 ));
528 }
529 },
530 ),
531 validate::Action::CreateExponentialHistogram(
532 validate::CreateExponentialHistogram {
533 parent,
534 id,
535 name,
536 floor,
537 initial_step,
538 step_multiplier,
539 buckets,
540 },
541 ) => self.add_property(
542 parent.into(),
543 id.into(),
544 name,
545 match (floor, initial_step, step_multiplier) {
546 (
547 validate::Value::IntT(floor),
548 validate::Value::IntT(initial_step),
549 validate::Value::IntT(step_multiplier),
550 ) => {
551 let mut data = vec![0i64; *buckets as usize + 5];
552 data[0] = *floor;
553 data[1] = *initial_step;
554 data[2] = *step_multiplier;
555 Payload::IntArray(data, ArrayFormat::ExponentialHistogram)
556 }
557 (
558 validate::Value::UintT(floor),
559 validate::Value::UintT(initial_step),
560 validate::Value::UintT(step_multiplier),
561 ) => {
562 let mut data = vec![0u64; *buckets as usize + 5];
563 data[0] = *floor;
564 data[1] = *initial_step;
565 data[2] = *step_multiplier;
566 Payload::UintArray(data, ArrayFormat::ExponentialHistogram)
567 }
568 (
569 validate::Value::DoubleT(floor),
570 validate::Value::DoubleT(initial_step),
571 validate::Value::DoubleT(step_multiplier),
572 ) => {
573 let mut data = vec![0.0f64; *buckets as usize + 5];
574 data[0] = *floor;
575 data[1] = *initial_step;
576 data[2] = *step_multiplier;
577 Payload::DoubleArray(data, ArrayFormat::ExponentialHistogram)
578 }
579 unexpected => {
580 return Err(format_err!(
581 "Bad types in CreateExponentialHistogram: {:?}",
582 unexpected
583 ));
584 }
585 },
586 ),
587 validate::Action::Insert(validate::Insert { id, value }) => {
588 if let Some(mut property) = self.properties.get_mut(&id.into()) {
589 match (&mut property, value) {
590 (
591 Property {
592 payload: Payload::IntArray(numbers, ArrayFormat::LinearHistogram),
593 ..
594 },
595 Value::IntT(value),
596 ) => insert_linear_i(numbers, *value, 1),
597 (
598 Property {
599 payload:
600 Payload::IntArray(numbers, ArrayFormat::ExponentialHistogram),
601 ..
602 },
603 Value::IntT(value),
604 ) => insert_exponential_i(numbers, *value, 1),
605 (
606 Property {
607 payload: Payload::UintArray(numbers, ArrayFormat::LinearHistogram),
608 ..
609 },
610 Value::UintT(value),
611 ) => insert_linear_u(numbers, *value, 1),
612 (
613 Property {
614 payload:
615 Payload::UintArray(numbers, ArrayFormat::ExponentialHistogram),
616 ..
617 },
618 Value::UintT(value),
619 ) => insert_exponential_u(numbers, *value, 1),
620 (
621 Property {
622 payload: Payload::DoubleArray(numbers, ArrayFormat::LinearHistogram),
623 ..
624 },
625 Value::DoubleT(value),
626 ) => insert_linear_d(numbers, *value, 1),
627 (
628 Property {
629 payload:
630 Payload::DoubleArray(numbers, ArrayFormat::ExponentialHistogram),
631 ..
632 },
633 Value::DoubleT(value),
634 ) => insert_exponential_d(numbers, *value, 1),
635 unexpected => {
636 Err(format_err!("Type mismatch {:?} trying to insert", unexpected))
637 }
638 }
639 } else {
640 Err(format_err!("Tried to insert number on nonexistent property {}", id))
641 }
642 }
643 validate::Action::InsertMultiple(validate::InsertMultiple { id, value, count }) => {
644 if let Some(mut property) = self.properties.get_mut(&id.into()) {
645 match (&mut property, value) {
646 (
647 Property {
648 payload: Payload::IntArray(numbers, ArrayFormat::LinearHistogram),
649 ..
650 },
651 Value::IntT(value),
652 ) => insert_linear_i(numbers, *value, *count),
653 (
654 Property {
655 payload:
656 Payload::IntArray(numbers, ArrayFormat::ExponentialHistogram),
657 ..
658 },
659 Value::IntT(value),
660 ) => insert_exponential_i(numbers, *value, *count),
661 (
662 Property {
663 payload: Payload::UintArray(numbers, ArrayFormat::LinearHistogram),
664 ..
665 },
666 Value::UintT(value),
667 ) => insert_linear_u(numbers, *value, *count),
668 (
669 Property {
670 payload:
671 Payload::UintArray(numbers, ArrayFormat::ExponentialHistogram),
672 ..
673 },
674 Value::UintT(value),
675 ) => insert_exponential_u(numbers, *value, *count),
676 (
677 Property {
678 payload: Payload::DoubleArray(numbers, ArrayFormat::LinearHistogram),
679 ..
680 },
681 Value::DoubleT(value),
682 ) => insert_linear_d(numbers, *value, *count),
683 (
684 Property {
685 payload:
686 Payload::DoubleArray(numbers, ArrayFormat::ExponentialHistogram),
687 ..
688 },
689 Value::DoubleT(value),
690 ) => insert_exponential_d(numbers, *value, *count),
691 unexpected => Err(format_err!(
692 "Type mismatch {:?} trying to insert multiple",
693 unexpected
694 )),
695 }
696 } else {
697 Err(format_err!(
698 "Tried to insert_multiple number on nonexistent property {}",
699 id
700 ))
701 }
702 }
703 _ => Err(format_err!("Unknown action {:?}", action)),
704 }
705 }
706
707 pub fn apply_lazy(&mut self, lazy_action: &validate::LazyAction) -> Result<(), Error> {
708 match lazy_action {
709 validate::LazyAction::CreateLazyNode(validate::CreateLazyNode {
710 parent,
711 id,
712 name,
713 disposition,
714 actions,
715 }) => self.add_lazy_node(parent.into(), id.into(), name, disposition, actions),
716 validate::LazyAction::DeleteLazyNode(validate::DeleteLazyNode { id }) => {
717 self.delete_property(id.into())
718 }
719 _ => Err(format_err!("Unknown lazy action {:?}", lazy_action)),
720 }
721 }
722
723 fn create_node(&mut self, parent: BlockIndex, id: BlockIndex, name: &str) -> Result<(), Error> {
724 let node = Node {
725 name: name.to_owned(),
726 parent,
727 children: HashSet::new(),
728 properties: HashSet::new(),
729 };
730 if self.tombstone_nodes.contains(&id) {
731 return Err(format_err!("Tried to create implicitly deleted node {}", id));
732 }
733 if self.nodes.insert(id, node).is_some() {
734 return Err(format_err!("Create called when node already existed at {}", id));
735 }
736 if let Some(parent_node) = self.nodes.get_mut(&parent) {
737 parent_node.children.insert(id);
738 } else {
739 return Err(format_err!("Parent {} of created node {} doesn't exist", parent, id));
740 }
741 Ok(())
742 }
743
744 fn delete_node(&mut self, id: BlockIndex) -> Result<(), Error> {
745 if id == BlockIndex::ROOT {
746 return Err(format_err!("Do not try to delete node 0"));
747 }
748 if self.tombstone_nodes.remove(&id) {
749 return Ok(());
750 }
751 if let Some(node) = self.nodes.remove(&id) {
752 for child in node.children.clone().iter() {
755 self.make_tombstone_node(*child)?;
756 }
757 for property in node.properties.clone().iter() {
758 self.make_tombstone_property(*property)?;
759 }
760 if let Some(parent) = self.nodes.get_mut(&node.parent)
761 && !parent.children.remove(&id)
762 {
763 bail!("Internal error! Parent {} didn't know about this child {}", node.parent, id);
769 }
770 } else {
771 return Err(format_err!("Delete of nonexistent node {}", id));
772 }
773 Ok(())
774 }
775
776 fn make_tombstone_node(&mut self, id: BlockIndex) -> Result<(), Error> {
777 if id == BlockIndex::ROOT {
778 return Err(format_err!("Internal error! Do not try to delete node 0."));
779 }
780 if let Some(node) = self.nodes.remove(&id) {
781 for child in node.children.clone().iter() {
782 self.make_tombstone_node(*child)?;
783 }
784 for property in node.properties.clone().iter() {
785 self.make_tombstone_property(*property)?;
786 }
787 } else {
788 return Err(format_err!("Internal error! Tried to tombstone nonexistent node {}", id));
789 }
790 self.tombstone_nodes.insert(id);
791 Ok(())
792 }
793
794 fn make_tombstone_property(&mut self, id: BlockIndex) -> Result<(), Error> {
795 if self.properties.remove(&id).is_none() {
796 return Err(format_err!(
797 "Internal error! Tried to tombstone nonexistent property {}",
798 id
799 ));
800 }
801 self.tombstone_properties.insert(id);
802 Ok(())
803 }
804
805 fn add_property(
806 &mut self,
807 parent: BlockIndex,
808 id: BlockIndex,
809 name: &str,
810 payload: Payload,
811 ) -> Result<(), Error> {
812 if let Some(node) = self.nodes.get_mut(&parent) {
813 node.properties.insert(id);
814 } else {
815 return Err(format_err!("Parent {} of property {} not found", parent, id));
816 }
817 if self.tombstone_properties.contains(&id) {
818 return Err(format_err!("Tried to create implicitly deleted property {}", id));
819 }
820 let property = Property { parent, id, name: name.into(), payload };
821 if self.properties.insert(id, property).is_some() {
822 return Err(format_err!("Property insert called on existing id {}", id));
823 }
824 Ok(())
825 }
826
827 fn delete_property(&mut self, id: BlockIndex) -> Result<(), Error> {
828 if self.tombstone_properties.remove(&id) {
829 return Ok(());
830 }
831 if let Some(property) = self.properties.remove(&id) {
832 if let Some(node) = self.nodes.get_mut(&property.parent) {
833 if !node.properties.remove(&id) {
834 bail!(
835 "Internal error! Property {}'s parent {} didn't have it as child",
836 id,
837 property.parent
838 );
839 }
840 } else {
841 bail!(
842 "Internal error! Property {}'s parent {} doesn't exist on delete",
843 id,
844 property.parent
845 );
846 }
847 } else {
848 return Err(format_err!("Delete of nonexistent property {}", id));
849 }
850 Ok(())
851 }
852
853 fn modify_number(
854 &mut self,
855 id: BlockIndex,
856 value: &validate::Value,
857 op: Op,
858 ) -> Result<(), Error> {
859 if let Some(property) = self.properties.get_mut(&id) {
860 match (&property, value) {
861 (Property { payload: Payload::Int(old), .. }, Value::IntT(value)) => {
862 property.payload = Payload::Int((op.int)(*old, *value));
863 }
864 (Property { payload: Payload::Uint(old), .. }, Value::UintT(value)) => {
865 property.payload = Payload::Uint((op.uint)(*old, *value));
866 }
867 (Property { payload: Payload::Double(old), .. }, Value::DoubleT(value)) => {
868 property.payload = Payload::Double((op.double)(*old, *value));
869 }
870 unexpected => {
871 return Err(format_err!("Bad types {:?} trying to set number", unexpected));
872 }
873 }
874 } else {
875 return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
876 }
877 Ok(())
878 }
879
880 fn modify_array(
881 &mut self,
882 id: BlockIndex,
883 index64: u64,
884 value: &validate::Value,
885 op: Op,
886 ) -> Result<(), Error> {
887 if let Some(mut property) = self.properties.get_mut(&id) {
888 let index = index64 as usize;
889 let array_len = match &property {
891 Property { payload: Payload::IntArray(numbers, ArrayFormat::Default), .. } => {
892 numbers.len()
893 }
894 Property { payload: Payload::UintArray(numbers, ArrayFormat::Default), .. } => {
895 numbers.len()
896 }
897 Property {
898 payload: Payload::DoubleArray(numbers, ArrayFormat::Default), ..
899 } => numbers.len(),
900 Property { payload: Payload::StringArray(values), .. } => values.len(),
901 unexpected => {
902 return Err(format_err!(
903 "Bad types {:?} trying to set number of elements",
904 unexpected
905 ));
906 }
907 };
908 if index >= array_len {
909 return Ok(());
910 }
911 match (&mut property, value) {
912 (Property { payload: Payload::IntArray(numbers, _), .. }, Value::IntT(value)) => {
913 numbers[index] = (op.int)(numbers[index], *value);
914 }
915 (Property { payload: Payload::UintArray(numbers, _), .. }, Value::UintT(value)) => {
916 numbers[index] = (op.uint)(numbers[index], *value);
917 }
918 (
919 Property { payload: Payload::DoubleArray(numbers, _), .. },
920 Value::DoubleT(value),
921 ) => {
922 numbers[index] = (op.double)(numbers[index], *value);
923 }
924 (Property { payload: Payload::StringArray(values), .. }, Value::StringT(value)) => {
925 values[index] = (op.string)(values[index].clone(), value.clone());
926 }
927 unexpected => {
928 return Err(format_err!("Type mismatch {:?} trying to set value", unexpected));
929 }
930 }
931 } else {
932 return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
933 }
934 Ok(())
935 }
936
937 fn set_string(&mut self, id: BlockIndex, value: &String) -> Result<(), Error> {
938 if let Some(property) = self.properties.get_mut(&id) {
939 match &property {
940 Property { payload: Payload::String(_), .. } => {
941 property.payload = Payload::String(value.to_owned())
942 }
943 unexpected => {
944 return Err(format_err!("Bad type {:?} trying to set string", unexpected));
945 }
946 }
947 } else {
948 return Err(format_err!("Tried to set string on nonexistent property {}", id));
949 }
950 Ok(())
951 }
952
953 fn set_bytes(&mut self, id: BlockIndex, value: &Vec<u8>) -> Result<(), Error> {
954 if let Some(property) = self.properties.get_mut(&id) {
955 match &property {
956 Property { payload: Payload::Bytes(_), .. } => {
957 property.payload = Payload::Bytes(value.to_owned())
958 }
959 unexpected => {
960 return Err(format_err!("Bad type {:?} trying to set bytes", unexpected));
961 }
962 }
963 } else {
964 return Err(format_err!("Tried to set bytes on nonexistent property {}", id));
965 }
966 Ok(())
967 }
968
969 fn set_bool(&mut self, id: BlockIndex, value: bool) -> Result<(), Error> {
970 if let Some(property) = self.properties.get_mut(&id) {
971 match &property {
972 Property { payload: Payload::Bool(_), .. } => {
973 property.payload = Payload::Bool(value)
974 }
975 unexpected => {
976 return Err(format_err!("Bad type {:?} trying to set bool", unexpected));
977 }
978 }
979 } else {
980 return Err(format_err!("Tried to set bool on nonexistent property {}", id));
981 }
982 Ok(())
983 }
984
985 fn add_lazy_node(
986 &mut self,
987 parent: BlockIndex,
988 id: BlockIndex,
989 name: &str,
990 disposition: &validate::LinkDisposition,
991 actions: &Vec<validate::Action>,
992 ) -> Result<(), Error> {
993 let mut parsed_data = Data::new();
994 parsed_data.apply_multiple(actions)?;
995 self.add_property(
996 parent,
997 id,
998 name,
999 Payload::Link {
1000 disposition: match disposition {
1001 validate::LinkDisposition::Child => LinkNodeDisposition::Child,
1002 validate::LinkDisposition::Inline => LinkNodeDisposition::Inline,
1003 },
1004 parsed_data,
1005 },
1006 )?;
1007 Ok(())
1008 }
1009
1010 fn clone_generic(&self) -> Self {
1015 let mut clone = self.clone();
1016
1017 let mut to_remove = vec![];
1018 let mut names = HashSet::new();
1019
1020 clone.properties = clone
1021 .properties
1022 .into_iter()
1023 .filter_map(|(id, mut v)| {
1024 if !names.insert((v.parent, v.name.clone())) {
1030 to_remove.push((v.parent, id));
1031 None
1032 } else {
1033 v.payload = v.payload.to_generic();
1034 Some((id, v))
1035 }
1036 })
1037 .collect::<HashMap<BlockIndex, Property>>();
1038
1039 for (parent, id) in to_remove {
1041 if clone.nodes.contains_key(&parent) {
1042 clone.nodes.get_mut(&parent).unwrap().properties.remove(&id);
1043 }
1044 }
1045
1046 clone
1047 }
1048
1049 pub fn compare_to_json(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1054 self.clone_generic().compare(other, diff_type)
1055 }
1056
1057 pub fn compare(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1061 let self_string = self.to_string();
1062 let other_string = other.to_string();
1063
1064 let difference::Changeset { diffs, distance, .. } =
1065 difference::Changeset::new(&self_string, &other_string, "\n");
1066
1067 if distance == 0 {
1068 Ok(())
1069 } else {
1070 let diff_lines = diffs
1071 .into_iter()
1072 .flat_map(|diff| {
1073 let (prefix, val) = match diff {
1074 difference::Difference::Same(val) => (" same", val),
1076 difference::Difference::Add(val) => ("other", val),
1077 difference::Difference::Rem(val) => ("local", val),
1078 };
1079 val.split("\n").map(|line| format!("{prefix}: {line:?}")).collect::<Vec<_>>()
1080 })
1081 .collect::<Vec<_>>();
1082
1083 match diff_type {
1084 DiffType::Full => Err(format_err!(
1085 "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}",
1086 self_string,
1087 other_string
1088 )),
1089 DiffType::Diff => {
1090 Err(format_err!("Trees differ:\n-- DIFF --\n{}", diff_lines.join("\n")))
1091 }
1092 DiffType::Both => Err(format_err!(
1093 "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}\n-- DIFF --\n{}",
1094 self_string,
1095 other_string,
1096 diff_lines.join("\n")
1097 )),
1098 }
1099 }
1100 }
1101
1102 pub fn new() -> Data {
1105 let mut ret = Data {
1106 nodes: HashMap::new(),
1107 properties: HashMap::new(),
1108 tombstone_nodes: HashSet::new(),
1109 tombstone_properties: HashSet::new(),
1110 };
1111 ret.nodes.insert(
1112 BlockIndex::ROOT,
1113 Node {
1114 name: ROOT_NAME.into(),
1115 parent: BlockIndex::ROOT,
1116 children: HashSet::new(),
1117 properties: HashSet::new(),
1118 },
1119 );
1120 ret
1121 }
1122
1123 fn build(nodes: HashMap<BlockIndex, Node>, properties: HashMap<BlockIndex, Property>) -> Data {
1124 Data {
1125 nodes,
1126 properties,
1127 tombstone_nodes: HashSet::new(),
1128 tombstone_properties: HashSet::new(),
1129 }
1130 }
1131
1132 fn apply_multiple(&mut self, actions: &Vec<validate::Action>) -> Result<(), Error> {
1133 for action in actions {
1134 self.apply(action)?;
1135 }
1136 Ok(())
1137 }
1138
1139 pub fn remove_tree(&mut self, name: &str) {
1141 if self.is_empty() {
1142 return;
1143 }
1144 let mut target_node_id = None;
1145 let root = &self.nodes[&BlockIndex::ROOT];
1146 for child_id in &root.children {
1147 if let Some(node) = self.nodes.get(child_id)
1148 && node.name == name
1149 {
1150 target_node_id = Some(*child_id);
1151 break;
1152 }
1153 }
1154 if let Some(id) = target_node_id {
1155 self.delete_node(id).unwrap();
1156 }
1157 }
1158
1159 pub fn is_empty(&self) -> bool {
1161 if !self.nodes.contains_key(&BlockIndex::ROOT) {
1162 return true;
1164 }
1165
1166 if self.properties.len() == 0 {
1169 return true;
1170 }
1171
1172 let root = &self.nodes[&BlockIndex::ROOT];
1174 root.children.is_subset(&self.tombstone_nodes) && root.properties.len() == 0
1175 }
1176}
1177
1178impl fmt::Display for Data {
1179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1180 let s = if let Some(node) = self.nodes.get(&BlockIndex::ROOT) {
1181 node.to_string("", self, false)
1182 } else {
1183 "No root node; internal error\n".to_owned()
1184 };
1185 write!(f, "{s}",)
1186 }
1187}
1188
1189impl From<DiagnosticsHierarchy> for Data {
1190 fn from(hierarchy: DiagnosticsHierarchy) -> Self {
1191 let mut nodes = HashMap::new();
1192 let mut properties = HashMap::new();
1193
1194 nodes.insert(
1195 0.into(),
1196 Node {
1197 name: hierarchy.name.clone(),
1198 parent: 0.into(),
1199 children: HashSet::new(),
1200 properties: HashSet::new(),
1201 },
1202 );
1203
1204 let mut queue = vec![(0.into(), &hierarchy)];
1205 let mut next_id = BlockIndex::new(1);
1206
1207 while let Some((id, value)) = queue.pop() {
1208 for node in value.children.iter() {
1209 let child_id = next_id;
1210 next_id += 1;
1211 nodes.insert(
1212 child_id,
1213 Node {
1214 name: node.name.clone(),
1215 parent: id,
1216 children: HashSet::new(),
1217 properties: HashSet::new(),
1218 },
1219 );
1220 nodes.get_mut(&id).expect("parent must exist").children.insert(child_id);
1221 queue.push((child_id, node));
1222 }
1223 for property in value.properties.iter() {
1224 let prop_id = next_id;
1225 next_id += 1;
1226
1227 let (name, payload) = match property.clone() {
1228 iProperty::String(n, v) => (n, Payload::String(v).to_generic()),
1229 iProperty::Bytes(n, v) => (n, Payload::Bytes(v).to_generic()),
1230 iProperty::Int(n, v) => (n, Payload::Int(v).to_generic()),
1231 iProperty::Uint(n, v) => (n, Payload::Uint(v).to_generic()),
1232 iProperty::Double(n, v) => (n, Payload::Double(v).to_generic()),
1233 iProperty::Bool(n, v) => (n, Payload::Bool(v).to_generic()),
1234 iProperty::IntArray(n, content) => (n, content.to_generic()),
1235 iProperty::UintArray(n, content) => (n, content.to_generic()),
1236 iProperty::DoubleArray(n, content) => (n, content.to_generic()),
1237 iProperty::StringList(n, content) => {
1238 (n, Payload::StringArray(content).to_generic())
1239 }
1240 };
1241
1242 properties.insert(prop_id, Property { name, id: prop_id, parent: id, payload });
1243 nodes.get_mut(&id).expect("parent must exist").properties.insert(prop_id);
1244 }
1245 }
1246
1247 Data {
1248 nodes,
1249 properties,
1250 tombstone_nodes: HashSet::new(),
1251 tombstone_properties: HashSet::new(),
1252 }
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use super::*;
1259 use crate::*;
1260 use fidl_diagnostics_validate::{ROOT_ID, ValueType};
1261 use fuchsia_inspect::reader::ArrayContent as iArrayContent;
1262 use inspect_format::BlockType;
1263 use num_derive::{FromPrimitive, ToPrimitive};
1264
1265 #[fuchsia::test]
1266 fn test_basic_data_strings() -> Result<(), Error> {
1267 let mut info = Data::new();
1268 assert_eq!(info.to_string(), "root ->");
1269
1270 info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "foo"))?;
1271 assert_eq!(info.to_string(), "root ->\n> foo ->");
1272
1273 info.apply(&delete_node!( id: 1 ))?;
1274 assert_eq!(info.to_string(), "root ->");
1275
1276 Ok(())
1277 }
1278
1279 const EXPECTED_HIERARCHY: &str = r#"root ->
1280> double: GenericNumber("2.5")
1281> int: GenericNumber("-5")
1282> string: String("value")
1283> uint: GenericNumber("10")
1284> child ->
1285> > bytes: String("b64:AQI=")
1286> > grandchild ->
1287> > > double_a: GenericArray(["0.5", "1"])
1288> > > double_eh: GenericExponentialHistogram(["0.5", "0.5", "2", "1", "2", "3"])
1289> > > double_lh: GenericLinearHistogram(["0.5", "0.5", "1", "2", "3"])
1290> > > int_a: GenericArray(["-1", "-2"])
1291> > > int_eh: GenericExponentialHistogram(["-1", "1", "2", "1", "2", "3"])
1292> > > int_lh: GenericLinearHistogram(["-1", "1", "1", "2", "3"])
1293> > > uint_a: GenericArray(["1", "2"])
1294> > > uint_eh: GenericExponentialHistogram(["1", "1", "2", "1", "2", "3"])
1295> > > uint_lh: GenericLinearHistogram(["1", "1", "1", "2", "3"])"#;
1296
1297 #[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
1300 enum ArrayType {
1301 Int = 4,
1302 Uint = 5,
1303 Double = 6,
1304 }
1305
1306 #[fuchsia::test]
1307 fn test_parse_hierarchy() -> Result<(), Error> {
1308 let hierarchy = DiagnosticsHierarchy {
1309 name: "root".to_string(),
1310 properties: vec![
1311 iProperty::String("string".to_string(), "value".to_string()),
1312 iProperty::Uint("uint".to_string(), 10u64),
1313 iProperty::Int("int".to_string(), -5i64),
1314 iProperty::Double("double".to_string(), 2.5f64),
1315 ],
1316 children: vec![DiagnosticsHierarchy {
1317 name: "child".to_string(),
1318 properties: vec![iProperty::Bytes("bytes".to_string(), vec![1u8, 2u8])],
1319 children: vec![DiagnosticsHierarchy {
1320 name: "grandchild".to_string(),
1321 properties: vec![
1322 iProperty::UintArray(
1323 "uint_a".to_string(),
1324 iArrayContent::Values(vec![1, 2]),
1325 ),
1326 iProperty::IntArray(
1327 "int_a".to_string(),
1328 iArrayContent::Values(vec![-1i64, -2i64]),
1329 ),
1330 iProperty::DoubleArray(
1331 "double_a".to_string(),
1332 iArrayContent::Values(vec![0.5, 1.0]),
1333 ),
1334 iProperty::UintArray(
1335 "uint_lh".to_string(),
1336 iArrayContent::new(vec![1, 1, 1, 2, 3], ArrayFormat::LinearHistogram)
1337 .unwrap(),
1338 ),
1339 iProperty::IntArray(
1340 "int_lh".to_string(),
1341 iArrayContent::new(
1342 vec![-1i64, 1, 1, 2, 3],
1343 ArrayFormat::LinearHistogram,
1344 )
1345 .unwrap(),
1346 ),
1347 iProperty::DoubleArray(
1348 "double_lh".to_string(),
1349 iArrayContent::new(
1350 vec![0.5, 0.5, 1.0, 2.0, 3.0],
1351 ArrayFormat::LinearHistogram,
1352 )
1353 .unwrap(),
1354 ),
1355 iProperty::UintArray(
1356 "uint_eh".to_string(),
1357 iArrayContent::new(
1358 vec![1, 1, 2, 1, 2, 3],
1359 ArrayFormat::ExponentialHistogram,
1360 )
1361 .unwrap(),
1362 ),
1363 iProperty::IntArray(
1364 "int_eh".to_string(),
1365 iArrayContent::new(
1366 vec![-1i64, 1, 2, 1, 2, 3],
1367 ArrayFormat::ExponentialHistogram,
1368 )
1369 .unwrap(),
1370 ),
1371 iProperty::DoubleArray(
1372 "double_eh".to_string(),
1373 iArrayContent::new(
1374 vec![0.5, 0.5, 2.0, 1.0, 2.0, 3.0],
1375 ArrayFormat::ExponentialHistogram,
1376 )
1377 .unwrap(),
1378 ),
1379 ],
1380 children: vec![],
1381 missing: vec![],
1382 }],
1383 missing: vec![],
1384 }],
1385 missing: vec![],
1386 };
1387
1388 let data: Data = hierarchy.into();
1389 assert_eq!(EXPECTED_HIERARCHY, data.to_string());
1390
1391 Ok(())
1392 }
1393
1394 #[fuchsia::test]
1396 fn test_creation_deletion() -> Result<(), Error> {
1397 let mut info = Data::new();
1398 assert!(!info.to_string().contains("child ->"));
1399 info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
1400 assert!(info.to_string().contains("child ->"));
1401
1402 info.apply(&create_node!(parent: 1, id: 2, name: "grandchild"))?;
1403 assert!(
1404 info.to_string().contains("grandchild ->") && info.to_string().contains("child ->")
1405 );
1406
1407 info.apply(
1408 &create_numeric_property!(parent: ROOT_ID, id: 3, name: "int-42", value: Value::IntT(-42)),
1409 )?;
1410
1411 assert!(info.to_string().contains("int-42: Int(-42)")); info.apply(&create_string_property!(parent: 1, id: 4, name: "stringfoo", value: "foo"))?;
1413 assert_eq!(
1414 info.to_string(),
1415 "root ->\n> int-42: Int(-42)\n> child ->\
1416 \n> > stringfoo: String(\"foo\")\n> > grandchild ->"
1417 );
1418
1419 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 5, name: "uint", value: Value::UintT(1024)))?;
1420 assert!(info.to_string().contains("uint: Uint(1024)"));
1421
1422 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 6, name: "frac", value: Value::DoubleT(0.5)))?;
1423 assert!(info.to_string().contains("frac: Double(0.5)"));
1424
1425 info.apply(
1426 &create_bytes_property!(parent: ROOT_ID, id: 7, name: "bytes", value: vec!(1u8, 2u8)),
1427 )?;
1428 assert!(info.to_string().contains("bytes: Bytes([1, 2])"));
1429
1430 info.apply(&create_array_property!(parent: ROOT_ID, id: 8, name: "i_ntarr", slots: 1, type: ValueType::Int))?;
1431 assert!(info.to_string().contains("i_ntarr: IntArray([0], Default)"));
1432
1433 info.apply(&create_array_property!(parent: ROOT_ID, id: 9, name: "u_intarr", slots: 2, type: ValueType::Uint))?;
1434 assert!(info.to_string().contains("u_intarr: UintArray([0, 0], Default)"));
1435
1436 info.apply(&create_array_property!(parent: ROOT_ID, id: 10, name: "dblarr", slots: 3, type: ValueType::Double))?;
1437 assert!(info.to_string().contains("dblarr: DoubleArray([0.0, 0.0, 0.0], Default)"));
1438
1439 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 11, name: "ILhist", floor: 12,
1440 step_size: 3, buckets: 2, type: IntT))?;
1441 assert!(
1442 info.to_string().contains("ILhist: IntArray([12, 3, 0, 0, 0, 0], LinearHistogram)")
1443 );
1444
1445 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 12, name: "ULhist", floor: 34,
1446 step_size: 5, buckets: 2, type: UintT))?;
1447 assert!(
1448 info.to_string().contains("ULhist: UintArray([34, 5, 0, 0, 0, 0], LinearHistogram)")
1449 );
1450
1451 info.apply(
1452 &create_linear_histogram!(parent: ROOT_ID, id: 13, name: "DLhist", floor: 56.0,
1453 step_size: 7.0, buckets: 2, type: DoubleT),
1454 )?;
1455 assert!(
1456 info.to_string()
1457 .contains("DLhist: DoubleArray([56.0, 7.0, 0.0, 0.0, 0.0, 0.0], LinearHistogram)")
1458 );
1459
1460 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 14, name: "IEhist",
1461 floor: 12, initial_step: 3, step_multiplier: 5, buckets: 2, type: IntT))?;
1462 assert!(
1463 info.to_string()
1464 .contains("IEhist: IntArray([12, 3, 5, 0, 0, 0, 0], ExponentialHistogram)")
1465 );
1466
1467 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 15, name: "UEhist",
1468 floor: 34, initial_step: 9, step_multiplier: 6, buckets: 2, type: UintT))?;
1469 assert!(
1470 info.to_string()
1471 .contains("UEhist: UintArray([34, 9, 6, 0, 0, 0, 0], ExponentialHistogram)")
1472 );
1473
1474 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 16, name: "DEhist",
1475 floor: 56.0, initial_step: 27.0, step_multiplier: 7.0, buckets: 2, type: DoubleT))?;
1476 assert!(info.to_string().contains(
1477 "DEhist: DoubleArray([56.0, 27.0, 7.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1478 ));
1479
1480 info.apply(&create_bool_property!(parent: ROOT_ID, id: 17, name: "bool", value: true))?;
1481 assert!(info.to_string().contains("bool: Bool(true)"));
1482
1483 info.apply(&delete_property!(id: 3))?;
1484 assert!(!info.to_string().contains("int-42") && info.to_string().contains("stringfoo"));
1485 info.apply(&delete_property!(id: 4))?;
1486 assert!(!info.to_string().contains("stringfoo"));
1487 info.apply(&delete_property!(id: 5))?;
1488 assert!(!info.to_string().contains("uint"));
1489 info.apply(&delete_property!(id: 6))?;
1490 assert!(!info.to_string().contains("frac"));
1491 info.apply(&delete_property!(id: 7))?;
1492 assert!(!info.to_string().contains("bytes"));
1493 info.apply(&delete_property!(id: 8))?;
1494 assert!(!info.to_string().contains("i_ntarr"));
1495 info.apply(&delete_property!(id: 9))?;
1496 assert!(!info.to_string().contains("u_intarr"));
1497 info.apply(&delete_property!(id: 10))?;
1498 assert!(!info.to_string().contains("dblarr"));
1499 info.apply(&delete_property!(id: 11))?;
1500 assert!(!info.to_string().contains("ILhist"));
1501 info.apply(&delete_property!(id: 12))?;
1502 assert!(!info.to_string().contains("ULhist"));
1503 info.apply(&delete_property!(id: 13))?;
1504 assert!(!info.to_string().contains("DLhist"));
1505 info.apply(&delete_property!(id: 14))?;
1506 assert!(!info.to_string().contains("IEhist"));
1507 info.apply(&delete_property!(id: 15))?;
1508 assert!(!info.to_string().contains("UEhist"));
1509 info.apply(&delete_property!(id: 16))?;
1510 assert!(!info.to_string().contains("DEhist"));
1511 info.apply(&delete_property!(id: 17))?;
1512 assert!(!info.to_string().contains("bool"));
1513 info.apply(&delete_node!(id:2))?;
1514 assert!(!info.to_string().contains("grandchild") && info.to_string().contains("child"));
1515 info.apply(&delete_node!( id: 1 ))?;
1516 assert_eq!(info.to_string(), "root ->");
1517 Ok(())
1518 }
1519
1520 #[fuchsia::test]
1521 fn test_basic_int_ops() -> Result<(), Error> {
1522 let mut info = Data::new();
1523 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1524 value: Value::IntT(-42)))?;
1525 assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_ok());
1526 assert!(info.to_string().contains("value: Int(-39)"));
1527 assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1528 assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1529 assert!(info.to_string().contains("value: Int(-39)"));
1530 assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_ok());
1531 assert!(info.to_string().contains("value: Int(-44)"));
1532 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1533 assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1534 assert!(info.to_string().contains("value: Int(-44)"));
1535 assert!(info.apply(&set_number!(id: 3, value: Value::IntT(22))).is_ok());
1536 assert!(info.to_string().contains("value: Int(22)"));
1537 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1538 assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1539 assert!(info.to_string().contains("value: Int(22)"));
1540 Ok(())
1541 }
1542
1543 #[fuchsia::test]
1544 fn test_array_string_ops() -> Result<(), Error> {
1545 let mut info = Data::new();
1546 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1547 type: ValueType::String))?;
1548 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1549 assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1550 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1551 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1552 assert!(info.to_string().contains(r#"value: StringArray(["", "", ""]"#));
1553 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1554 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1555 assert!(
1556 info.apply(&array_subtract!(id: 3, index: 2,
1557 value: Value::DoubleT(5.0)))
1558 .is_err()
1559 );
1560 assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1561 assert!(
1562 info.apply(&array_set!(id: 3, index: 1, value: Value::StringT("data".into()))).is_ok()
1563 );
1564 assert!(info.to_string().contains(r#"value: StringArray(["", "data", ""])"#));
1565 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1566 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1567 assert!(info.to_string().contains(r#"value: StringArray(["", "data", ""])"#));
1568 let long: String = (0..3000).map(|_| ".").collect();
1569 let expected = format!(r#"value: StringArray(["{}", "data", ""])"#, long.clone());
1570 assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::StringT(long))).is_ok());
1571 assert!(info.to_string().contains(&expected));
1572 Ok(())
1573 }
1574
1575 #[fuchsia::test]
1576 fn test_array_int_ops() -> Result<(), Error> {
1577 let mut info = Data::new();
1578 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1579 type: ValueType::Int))?;
1580 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1581 assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1582 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1583 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1584 assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1585 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_ok());
1586 assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1587 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1588 assert!(
1589 info.apply(&array_subtract!(id: 3, index: 2,
1590 value: Value::DoubleT(5.0)))
1591 .is_err()
1592 );
1593 assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1594 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(22))).is_ok());
1595 assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1596 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1597 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1598 assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1599 Ok(())
1600 }
1601
1602 #[fuchsia::test]
1603 fn test_linear_int_ops() -> Result<(), Error> {
1604 let mut info = Data::new();
1605 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1606 floor: 4, step_size: 2, buckets: 2, type: IntT))?;
1607 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1608 assert!(info.apply(&insert!(id: 3, value: Value::IntT(4))).is_ok());
1609 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1610 assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1611 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1612 assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1613 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1614 assert!(info.apply(&insert!(id: 3, value: Value::IntT(8))).is_ok());
1615 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1616 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1617 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1618 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1619 assert!(info.to_string().contains("value: IntArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1620 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1621 assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1622 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1623 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1624 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1625 assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1626 assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(7), count: 4)).is_ok());
1627 assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 5, 2], LinearHistogram)"));
1628 Ok(())
1629 }
1630
1631 #[fuchsia::test]
1632 fn test_exponential_int_ops() -> Result<(), Error> {
1633 let mut info = Data::new();
1634 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1636 floor: 5, initial_step: 2,
1637 step_multiplier: 4, buckets: 2, type: IntT))?;
1638 assert!(
1639 info.to_string()
1640 .contains("value: IntArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)")
1641 );
1642 assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1643 assert!(
1644 info.to_string()
1645 .contains("value: IntArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)")
1646 );
1647 assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1648 assert!(
1649 info.to_string()
1650 .contains("value: IntArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)")
1651 );
1652 assert!(info.apply(&insert!(id: 3, value: Value::IntT(7))).is_ok());
1653 assert!(
1654 info.to_string()
1655 .contains("value: IntArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)")
1656 );
1657 assert!(info.apply(&insert!(id: 3, value: Value::IntT(13))).is_ok());
1658 assert!(
1659 info.to_string()
1660 .contains("value: IntArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)")
1661 );
1662 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1663 assert!(
1664 info.to_string()
1665 .contains("value: IntArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)")
1666 );
1667 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1668 assert!(
1669 info.to_string()
1670 .contains("value: IntArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1671 );
1672 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1673 assert!(
1674 info.to_string()
1675 .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)")
1676 );
1677 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1678 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1679 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1680 assert!(
1681 info.to_string()
1682 .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)")
1683 );
1684 assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(12), count: 4)).is_ok());
1685 assert!(
1686 info.to_string()
1687 .contains("value: IntArray([5, 2, 4, 2, 2, 5, 2], ExponentialHistogram)")
1688 );
1689 Ok(())
1690 }
1691
1692 #[fuchsia::test]
1693 fn test_array_out_of_bounds_nop() -> Result<(), Error> {
1694 let mut info = Data::new();
1696 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1697 type: ValueType::Int))?;
1698 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1699 assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1700 assert!(info.apply(&array_add!(id: 3, index: 3, value: Value::IntT(3))).is_ok());
1701 assert!(info.apply(&array_add!(id: 3, index: 6, value: Value::IntT(3))).is_ok());
1702 assert!(info.apply(&array_add!(id: 3, index: 12345, value: Value::IntT(3))).is_ok());
1703 assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1704 Ok(())
1705 }
1706
1707 #[fuchsia::test]
1708 fn test_basic_uint_ops() -> Result<(), Error> {
1709 let mut info = Data::new();
1710 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1711 value: Value::UintT(42)))?;
1712 assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_ok());
1713 assert!(info.to_string().contains("value: Uint(45)"));
1714 assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1715 assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1716 assert!(info.to_string().contains("value: Uint(45)"));
1717 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_ok());
1718 assert!(info.to_string().contains("value: Uint(40)"));
1719 assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_err());
1720 assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1721 assert!(info.to_string().contains("value: Uint(40)"));
1722 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(22))).is_ok());
1723 assert!(info.to_string().contains("value: Uint(22)"));
1724 assert!(info.apply(&set_number!(id: 3, value: Value::IntT(23))).is_err());
1725 assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1726 assert!(info.to_string().contains("value: Uint(22)"));
1727 Ok(())
1728 }
1729
1730 #[fuchsia::test]
1731 fn test_array_uint_ops() -> Result<(), Error> {
1732 let mut info = Data::new();
1733 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1734 type: ValueType::Uint))?;
1735 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_ok());
1736 assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1737 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1738 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1739 assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1740 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(22))).is_ok());
1741 assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1742 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1743 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1744 assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1745 assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::UintT(5))).is_ok());
1746 assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1747 assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::IntT(5))).is_err());
1748 assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::DoubleT(5.0))).is_err());
1749 assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1750 Ok(())
1751 }
1752
1753 #[fuchsia::test]
1754 fn test_linear_uint_ops() -> Result<(), Error> {
1755 let mut info = Data::new();
1756 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1757 floor: 4, step_size: 2, buckets: 2, type: UintT))?;
1758 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1759 assert!(info.apply(&insert!(id: 3, value: Value::UintT(4))).is_ok());
1760 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1761 assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1762 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1763 assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1764 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1765 assert!(info.apply(&insert!(id: 3, value: Value::UintT(8))).is_ok());
1766 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1767 assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1768 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1769 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1770 assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1771 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1772 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1773 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1774 assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1775 assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(7), count: 4)).is_ok());
1776 assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 5, 2], LinearHistogram)"));
1777 Ok(())
1778 }
1779
1780 #[fuchsia::test]
1781 fn test_exponential_uint_ops() -> Result<(), Error> {
1782 let mut info = Data::new();
1783 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1785 floor: 5, initial_step: 2,
1786 step_multiplier: 4, buckets: 2, type: UintT))?;
1787 assert!(
1788 info.to_string()
1789 .contains("value: UintArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)")
1790 );
1791 assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1792 assert!(
1793 info.to_string()
1794 .contains("value: UintArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)")
1795 );
1796 assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1797 assert!(
1798 info.to_string()
1799 .contains("value: UintArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)")
1800 );
1801 assert!(info.apply(&insert!(id: 3, value: Value::UintT(7))).is_ok());
1802 assert!(
1803 info.to_string()
1804 .contains("value: UintArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)")
1805 );
1806 assert!(info.apply(&insert!(id: 3, value: Value::UintT(13))).is_ok());
1807 assert!(
1808 info.to_string()
1809 .contains("value: UintArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)")
1810 );
1811 assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1812 assert!(
1813 info.to_string()
1814 .contains("value: UintArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)")
1815 );
1816 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1817 assert!(
1818 info.to_string()
1819 .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1820 );
1821 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1822 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1823 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1824 assert!(
1825 info.to_string()
1826 .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)")
1827 );
1828 assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(12), count: 4)).is_ok());
1829 assert!(
1830 info.to_string()
1831 .contains("value: UintArray([5, 2, 4, 1, 2, 5, 2], ExponentialHistogram)")
1832 );
1833 Ok(())
1834 }
1835
1836 #[fuchsia::test]
1837 fn test_basic_double_ops() -> Result<(), Error> {
1838 let mut info = Data::new();
1839 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1840 value: Value::DoubleT(42.0)))?;
1841 assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_ok());
1842 assert!(info.to_string().contains("value: Double(45.0)"));
1843 assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1844 assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1845 assert!(info.to_string().contains("value: Double(45.0)"));
1846 assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1847 assert!(info.to_string().contains("value: Double(40.0)"));
1848 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1849 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1850 assert!(info.to_string().contains("value: Double(40.0)"));
1851 assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(22.0))).is_ok());
1852 assert!(info.to_string().contains("value: Double(22.0)"));
1853 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1854 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(24))).is_err());
1855 assert!(info.to_string().contains("value: Double(22.0)"));
1856 Ok(())
1857 }
1858
1859 #[fuchsia::test]
1860 fn test_array_double_ops() -> Result<(), Error> {
1861 let mut info = Data::new();
1862 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1863 type: ValueType::Double))?;
1864 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_ok());
1865 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1866 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1867 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1868 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1869 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::DoubleT(5.0))).is_ok());
1870 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1871 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1872 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1873 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1874 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(22.0))).is_ok());
1875 assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1876 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1877 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(24))).is_err());
1878 assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1879 Ok(())
1880 }
1881
1882 #[fuchsia::test]
1883 fn test_linear_double_ops() -> Result<(), Error> {
1884 let mut info = Data::new();
1885 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1886 floor: 4.0, step_size: 0.5, buckets: 2, type: DoubleT))?;
1887 assert!(
1888 info.to_string()
1889 .contains("value: DoubleArray([4.0, 0.5, 0.0, 0.0, 0.0, 0.0], LinearHistogram)")
1890 );
1891 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.0))).is_ok());
1892 assert!(
1893 info.to_string()
1894 .contains("value: DoubleArray([4.0, 0.5, 0.0, 1.0, 0.0, 0.0], LinearHistogram)")
1895 );
1896 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.25))).is_ok());
1897 assert!(
1898 info.to_string()
1899 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 0.0, 0.0], LinearHistogram)")
1900 );
1901 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.75))).is_ok());
1902 assert!(
1903 info.to_string()
1904 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 0.0], LinearHistogram)")
1905 );
1906 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.1))).is_ok());
1907 assert!(
1908 info.to_string()
1909 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 1.0], LinearHistogram)")
1910 );
1911 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1912 assert!(
1913 info.to_string()
1914 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 2.0], LinearHistogram)")
1915 );
1916 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1917 assert!(
1918 info.to_string()
1919 .contains("value: DoubleArray([4.0, 0.5, 1.0, 2.0, 1.0, 2.0], LinearHistogram)")
1920 );
1921 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1922 assert!(
1923 info.to_string()
1924 .contains("value: DoubleArray([4.0, 0.5, 2.0, 2.0, 1.0, 2.0], LinearHistogram)")
1925 );
1926 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1927 assert!(
1928 info.to_string()
1929 .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)")
1930 );
1931 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1932 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1933 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1934 assert!(
1935 info.to_string()
1936 .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)")
1937 );
1938 assert!(info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(4.5), count: 4)).is_ok());
1939 assert!(
1940 info.to_string()
1941 .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 5.0, 2.0], LinearHistogram)")
1942 );
1943 Ok(())
1944 }
1945
1946 #[fuchsia::test]
1947 fn test_exponential_double_ops() -> Result<(), Error> {
1948 let mut info = Data::new();
1949 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1951 floor: 5.0, initial_step: 2.0,
1952 step_multiplier: 4.0, buckets: 3, type: DoubleT))?;
1953 assert!(info.to_string().contains(
1954 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1955 ));
1956 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1957 assert!(info.to_string().contains(
1958 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 1.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1959 ));
1960 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(6.9))).is_ok());
1961 assert!(info.to_string().contains(
1962 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1963 ));
1964 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(7.1))).is_ok());
1965 assert!(info.to_string().contains(
1966 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 1.0, 0.0, 0.0], ExponentialHistogram)"
1967 ));
1968 assert!(
1969 info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(12.9), count: 4)).is_ok()
1970 );
1971 assert!(info.to_string().contains(
1972 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 0.0, 0.0], ExponentialHistogram)"
1973 ));
1974 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(13.1))).is_ok());
1975 assert!(info.to_string().contains(
1976 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 1.0, 0.0], ExponentialHistogram)"
1977 ));
1978 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(36.9))).is_ok());
1979 assert!(info.to_string().contains(
1980 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 0.0], ExponentialHistogram)"
1981 ));
1982 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(37.1))).is_ok());
1983 assert!(info.to_string().contains(
1984 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 1.0], ExponentialHistogram)"
1985 ));
1986 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1987 assert!(info.to_string().contains(
1988 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1989 ));
1990 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1991 assert!(info.to_string().contains(
1992 "value: DoubleArray([5.0, 2.0, 4.0, 1.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1993 ));
1994 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1995 assert!(info.to_string().contains(
1996 "value: DoubleArray([5.0, 2.0, 4.0, 2.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1997 ));
1998 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1999 assert!(info.to_string().contains(
2000 "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2001 ));
2002 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
2003 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
2004 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
2005 assert!(info.to_string().contains(
2006 "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
2007 ));
2008 Ok(())
2009 }
2010
2011 #[fuchsia::test]
2012 fn test_basic_vector_ops() -> Result<(), Error> {
2013 let mut info = Data::new();
2014 info.apply(&create_string_property!(parent: ROOT_ID, id: 3, name: "value",
2015 value: "foo"))?;
2016 assert!(info.to_string().contains("value: String(\"foo\")"));
2017 assert!(info.apply(&set_string!(id: 3, value: "bar")).is_ok());
2018 assert!(info.to_string().contains("value: String(\"bar\")"));
2019 assert!(info.apply(&set_bytes!(id: 3, value: vec!(3u8))).is_err());
2020 assert!(info.to_string().contains("value: String(\"bar\")"));
2021 info.apply(&create_bytes_property!(parent: ROOT_ID, id: 4, name: "bvalue",
2022 value: vec!(1u8, 2u8)))?;
2023 assert!(info.to_string().contains("bvalue: Bytes([1, 2])"));
2024 assert!(info.apply(&set_bytes!(id: 4, value: vec!(3u8, 4u8))).is_ok());
2025 assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
2026 assert!(info.apply(&set_string!(id: 4, value: "baz")).is_err());
2027 assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
2028 Ok(())
2029 }
2030
2031 #[fuchsia::test]
2032 fn test_basic_lazy_node_ops() -> Result<(), Error> {
2033 let mut info = Data::new();
2034 info.apply_lazy(&create_lazy_node!(parent: ROOT_ID, id: 1, name: "child", disposition: validate::LinkDisposition::Child, actions: vec![create_bytes_property!(parent: ROOT_ID, id: 1, name: "child_bytes",value: vec!(3u8, 4u8))]))?;
2035 info.apply_lazy(&create_lazy_node!(parent: ROOT_ID, id: 2, name: "inline", disposition: validate::LinkDisposition::Inline, actions: vec![create_bytes_property!(parent: ROOT_ID, id: 1, name: "inline_bytes",value: vec!(3u8, 4u8))]))?;
2036
2037 assert_eq!(
2039 info.to_string(),
2040 "root ->\n> inline_bytes: Bytes([3, 4])\n> child ->\n> > child_bytes: Bytes([3, 4])"
2041 );
2042
2043 info.apply_lazy(&delete_lazy_node!(id: 1))?;
2044 assert_eq!(info.to_string(), "root ->\n> inline_bytes: Bytes([3, 4])");
2046
2047 Ok(())
2048 }
2049
2050 #[fuchsia::test]
2051 fn test_illegal_node_actions() -> Result<(), Error> {
2052 let mut info = Data::new();
2053 assert!(info.apply(&create_node!(parent: 42, id: 1, name: "child")).is_err());
2055 info = Data::new();
2057 info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
2058 assert!(info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "another_child")).is_err());
2059 info = Data::new();
2061 assert!(info.apply(&delete_node!(id: ROOT_ID)).is_err());
2062 info = Data::new();
2064 assert!(info.apply(&delete_node!(id: 333)).is_err());
2065 Ok(())
2066 }
2067
2068 #[fuchsia::test]
2069 fn test_illegal_property_actions() -> Result<(), Error> {
2070 let mut info = Data::new();
2071 assert!(
2073 info.apply(
2074 &create_numeric_property!(parent: 42, id: 1, name: "answer", value: Value::IntT(42))
2075 )
2076 .is_err()
2077 );
2078 info = Data::new();
2080 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "answer",
2081 value: Value::IntT(42)))?;
2082 assert!(
2083 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "another_answer",
2084 value: Value::IntT(7)))
2085 .is_err()
2086 );
2087 info = Data::new();
2089 assert!(info.apply(&delete_property!(id: 1)).is_err());
2090 info = Data::new();
2092 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
2093 value: Value::IntT(42)))?;
2094 info.apply(&create_array_property!(parent: ROOT_ID, id: 4, name: "array", slots: 2,
2095 type: ValueType::Int))?;
2096 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 5, name: "lin",
2097 floor: 5, step_size: 2,
2098 buckets: 2, type: IntT))?;
2099 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 6, name: "exp",
2100 floor: 5, initial_step: 2,
2101 step_multiplier: 2, buckets: 2, type: IntT))?;
2102 assert!(info.apply(&set_number!(id: 3, value: Value::IntT(5))).is_ok());
2103 assert!(info.apply(&array_set!(id: 4, index: 0, value: Value::IntT(5))).is_ok());
2104 assert!(info.apply(&insert!(id: 5, value: Value::IntT(2))).is_ok());
2105 assert!(info.apply(&insert!(id: 6, value: Value::IntT(2))).is_ok());
2106 assert!(info.apply(&insert_multiple!(id: 5, value: Value::IntT(2), count: 3)).is_ok());
2107 assert!(info.apply(&insert_multiple!(id: 6, value: Value::IntT(2), count: 3)).is_ok());
2108 assert!(info.apply(&set_number!(id: 4, value: Value::IntT(5))).is_err());
2109 assert!(info.apply(&set_number!(id: 5, value: Value::IntT(5))).is_err());
2110 assert!(info.apply(&set_number!(id: 6, value: Value::IntT(5))).is_err());
2111 assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::IntT(5))).is_err());
2112 assert!(info.apply(&array_set!(id: 5, index: 0, value: Value::IntT(5))).is_err());
2113 assert!(info.apply(&array_set!(id: 6, index: 0, value: Value::IntT(5))).is_err());
2114 assert!(info.apply(&insert!(id: 3, value: Value::IntT(2))).is_err());
2115 assert!(info.apply(&insert!(id: 4, value: Value::IntT(2))).is_err());
2116 assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(2), count: 3)).is_err());
2117 assert!(info.apply(&insert_multiple!(id: 4, value: Value::IntT(2), count: 3)).is_err());
2118 Ok(())
2119 }
2120
2121 #[fuchsia::test]
2122 fn test_enum_values() {
2123 assert_eq!(BlockType::IntValue.to_isize().unwrap(), ArrayType::Int.to_isize().unwrap());
2124 assert_eq!(BlockType::UintValue.to_isize().unwrap(), ArrayType::Uint.to_isize().unwrap());
2125 assert_eq!(
2126 BlockType::DoubleValue.to_isize().unwrap(),
2127 ArrayType::Double.to_isize().unwrap()
2128 );
2129 }
2130
2131 #[fuchsia::test]
2132 fn test_create_node_checks() {
2133 let mut data = Data::new();
2134 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2135 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "second")).is_ok());
2136 assert!(data.apply(&create_node!(parent: 1, id: 3, name: "child")).is_ok());
2137 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "double")).is_err());
2138 let mut data = Data::new();
2139 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "orphan")).is_err());
2140 }
2141
2142 #[fuchsia::test]
2143 fn test_delete_node_checks() {
2144 let mut data = Data::new();
2145 assert!(data.apply(&delete_node!(id: 0)).is_err());
2146 let mut data = Data::new();
2147 data.apply(&create_node!(parent: 0, id: 1, name: "first")).ok();
2148 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2149 assert!(data.apply(&delete_node!(id: 1)).is_err());
2150 }
2151
2152 #[fuchsia::test]
2153 fn test_node_tombstoning() {
2155 let mut data = Data::new();
2157 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2158 assert!(
2159 data.apply(&create_numeric_property!(parent: 1, id: 2,
2160 name: "answer", value: Value::IntT(42)))
2161 .is_ok()
2162 );
2163 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2164 assert!(data.apply(&delete_property!(id: 2)).is_ok());
2165 assert!(data.apply(&delete_property!(id: 2)).is_err());
2166 let mut data = Data::new();
2168 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2169 assert!(
2170 data.apply(&create_numeric_property!(parent: 1, id: 2,
2171 name: "answer", value: Value::IntT(42)))
2172 .is_ok()
2173 );
2174 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2175 assert!(data.apply(&delete_property!(id: 2)).is_ok());
2176 assert!(
2177 data.apply(&create_numeric_property!(parent: 0, id: 2,
2178 name: "root_answer", value: Value::IntT(42)))
2179 .is_ok()
2180 );
2181 let mut data = Data::new();
2183 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2184 assert!(
2185 data.apply(&create_numeric_property!(parent: 1, id: 2,
2186 name: "answer", value: Value::IntT(42)))
2187 .is_ok()
2188 );
2189 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2190 assert!(
2191 data.apply(&create_numeric_property!(parent: 0, id: 2,
2192 name: "root_answer", value: Value::IntT(42)))
2193 .is_err()
2194 );
2195 }
2196
2197 #[fuchsia::test]
2198 fn test_property_tombstoning() {
2199 let mut data = Data::new();
2202 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2203 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2204 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2205 assert!(data.apply(&delete_node!(id: 2)).is_ok());
2206 assert!(data.apply(&delete_node!(id: 2)).is_err());
2207 let mut data = Data::new();
2209 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2210 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2211 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2212 assert!(data.apply(&delete_node!(id: 2)).is_ok());
2213 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_ok());
2214 let mut data = Data::new();
2216 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2217 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2218 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2219 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_err());
2220 }
2221
2222 const DIFF_STRING: &str = r#"-- DIFF --
2223 same: "root ->"
2224 same: "> node ->"
2225local: "> > prop1: String(\"foo\")"
2226other: "> > prop1: String(\"bar\")""#;
2227
2228 const FULL_STRING: &str = r#"-- LOCAL --
2229root ->
2230> node ->
2231> > prop1: String("foo")
2232-- OTHER --
2233root ->
2234> node ->
2235> > prop1: String("bar")"#;
2236
2237 #[fuchsia::test]
2238 fn diff_modes_work() -> Result<(), Error> {
2239 let mut local = Data::new();
2240 let mut remote = Data::new();
2241 local.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2242 local.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "foo"))?;
2243 remote.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2244 remote.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "bar"))?;
2245 match local.compare(&remote, DiffType::Diff) {
2246 Err(error) => {
2247 let error_string = format!("{error:?}");
2248 assert_eq!("Trees differ:\n".to_string() + DIFF_STRING, error_string);
2249 }
2250 _ => return Err(format_err!("Didn't get failure")),
2251 }
2252 match local.compare(&remote, DiffType::Full) {
2253 Err(error) => {
2254 let error_string = format!("{error:?}");
2255 assert_eq!("Trees differ:\n".to_string() + FULL_STRING, error_string);
2256 }
2257 _ => return Err(format_err!("Didn't get failure")),
2258 }
2259 match local.compare(&remote, DiffType::Both) {
2260 Err(error) => {
2261 let error_string = format!("{error:?}");
2262 assert_eq!(["Trees differ:", FULL_STRING, DIFF_STRING].join("\n"), error_string);
2263 }
2264 _ => return Err(format_err!("Didn't get failure")),
2265 }
2266 Ok(())
2267 }
2268}