1use crate::metrics::Metrics;
6use crate::puppet::DiffType;
7use anyhow::{bail, format_err, Error};
8use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
9use base64::engine::Engine as _;
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("Missing child".into(), |n| {
227 n.to_string(prefix, parsed_data, false)
228 })
229 })
230 .collect::<Vec<_>>();
231 let mut properties = vec![];
232 for FormattedEntries { nodes: mut n, properties: mut p } in
233 root.properties.iter().map(|v| {
234 parsed_data.properties.get(v).map_or(
235 FormattedEntries {
236 nodes: vec![],
237 properties: vec!["Missing property".into()],
238 },
239 |p| p.format_entries(prefix),
240 )
241 })
242 {
243 nodes.append(&mut n);
244 properties.append(&mut p);
245 }
246 FormattedEntries { nodes, properties }
247 }
248 },
249 _ => FormattedEntries { nodes: vec![], properties: vec![self.to_string(prefix)] },
251 }
252 }
253}
254
255impl Node {
256 fn to_string(&self, prefix: &str, tree: &Data, hide_root: bool) -> String {
260 let sub_prefix = format!("{prefix}> ");
261 let mut nodes = vec![];
262 for node_id in self.children.iter() {
263 nodes.push(
264 tree.nodes
265 .get(node_id)
266 .map_or("Missing child".into(), |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(
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 if !parent.children.remove(&id) {
762 bail!(
768 "Internal error! Parent {} didn't know about this child {}",
769 node.parent,
770 id
771 );
772 }
773 }
774 } else {
775 return Err(format_err!("Delete of nonexistent node {}", id));
776 }
777 Ok(())
778 }
779
780 fn make_tombstone_node(&mut self, id: BlockIndex) -> Result<(), Error> {
781 if id == BlockIndex::ROOT {
782 return Err(format_err!("Internal error! Do not try to delete node 0."));
783 }
784 if let Some(node) = self.nodes.remove(&id) {
785 for child in node.children.clone().iter() {
786 self.make_tombstone_node(*child)?;
787 }
788 for property in node.properties.clone().iter() {
789 self.make_tombstone_property(*property)?;
790 }
791 } else {
792 return Err(format_err!("Internal error! Tried to tombstone nonexistent node {}", id));
793 }
794 self.tombstone_nodes.insert(id);
795 Ok(())
796 }
797
798 fn make_tombstone_property(&mut self, id: BlockIndex) -> Result<(), Error> {
799 if self.properties.remove(&id).is_none() {
800 return Err(format_err!(
801 "Internal error! Tried to tombstone nonexistent property {}",
802 id
803 ));
804 }
805 self.tombstone_properties.insert(id);
806 Ok(())
807 }
808
809 fn add_property(
810 &mut self,
811 parent: BlockIndex,
812 id: BlockIndex,
813 name: &str,
814 payload: Payload,
815 ) -> Result<(), Error> {
816 if let Some(node) = self.nodes.get_mut(&parent) {
817 node.properties.insert(id);
818 } else {
819 return Err(format_err!("Parent {} of property {} not found", parent, id));
820 }
821 if self.tombstone_properties.contains(&id) {
822 return Err(format_err!("Tried to create implicitly deleted property {}", id));
823 }
824 let property = Property { parent, id, name: name.into(), payload };
825 if self.properties.insert(id, property).is_some() {
826 return Err(format_err!("Property insert called on existing id {}", id));
827 }
828 Ok(())
829 }
830
831 fn delete_property(&mut self, id: BlockIndex) -> Result<(), Error> {
832 if self.tombstone_properties.remove(&id) {
833 return Ok(());
834 }
835 if let Some(property) = self.properties.remove(&id) {
836 if let Some(node) = self.nodes.get_mut(&property.parent) {
837 if !node.properties.remove(&id) {
838 bail!(
839 "Internal error! Property {}'s parent {} didn't have it as child",
840 id,
841 property.parent
842 );
843 }
844 } else {
845 bail!(
846 "Internal error! Property {}'s parent {} doesn't exist on delete",
847 id,
848 property.parent
849 );
850 }
851 } else {
852 return Err(format_err!("Delete of nonexistent property {}", id));
853 }
854 Ok(())
855 }
856
857 fn modify_number(
858 &mut self,
859 id: BlockIndex,
860 value: &validate::Value,
861 op: Op,
862 ) -> Result<(), Error> {
863 if let Some(property) = self.properties.get_mut(&id) {
864 match (&property, value) {
865 (Property { payload: Payload::Int(old), .. }, Value::IntT(value)) => {
866 property.payload = Payload::Int((op.int)(*old, *value));
867 }
868 (Property { payload: Payload::Uint(old), .. }, Value::UintT(value)) => {
869 property.payload = Payload::Uint((op.uint)(*old, *value));
870 }
871 (Property { payload: Payload::Double(old), .. }, Value::DoubleT(value)) => {
872 property.payload = Payload::Double((op.double)(*old, *value));
873 }
874 unexpected => {
875 return Err(format_err!("Bad types {:?} trying to set number", unexpected))
876 }
877 }
878 } else {
879 return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
880 }
881 Ok(())
882 }
883
884 fn modify_array(
885 &mut self,
886 id: BlockIndex,
887 index64: u64,
888 value: &validate::Value,
889 op: Op,
890 ) -> Result<(), Error> {
891 if let Some(mut property) = self.properties.get_mut(&id) {
892 let index = index64 as usize;
893 let array_len = match &property {
895 Property { payload: Payload::IntArray(numbers, ArrayFormat::Default), .. } => {
896 numbers.len()
897 }
898 Property { payload: Payload::UintArray(numbers, ArrayFormat::Default), .. } => {
899 numbers.len()
900 }
901 Property {
902 payload: Payload::DoubleArray(numbers, ArrayFormat::Default), ..
903 } => numbers.len(),
904 Property { payload: Payload::StringArray(values), .. } => values.len(),
905 unexpected => {
906 return Err(format_err!(
907 "Bad types {:?} trying to set number of elements",
908 unexpected
909 ))
910 }
911 };
912 if index >= array_len {
913 return Ok(());
914 }
915 match (&mut property, value) {
916 (Property { payload: Payload::IntArray(numbers, _), .. }, Value::IntT(value)) => {
917 numbers[index] = (op.int)(numbers[index], *value);
918 }
919 (Property { payload: Payload::UintArray(numbers, _), .. }, Value::UintT(value)) => {
920 numbers[index] = (op.uint)(numbers[index], *value);
921 }
922 (
923 Property { payload: Payload::DoubleArray(numbers, _), .. },
924 Value::DoubleT(value),
925 ) => {
926 numbers[index] = (op.double)(numbers[index], *value);
927 }
928 (Property { payload: Payload::StringArray(values), .. }, Value::StringT(value)) => {
929 values[index] = (op.string)(values[index].clone(), value.clone());
930 }
931 unexpected => {
932 return Err(format_err!("Type mismatch {:?} trying to set value", unexpected))
933 }
934 }
935 } else {
936 return Err(format_err!("Tried to {} number on nonexistent property {}", op.name, id));
937 }
938 Ok(())
939 }
940
941 fn set_string(&mut self, id: BlockIndex, value: &String) -> Result<(), Error> {
942 if let Some(property) = self.properties.get_mut(&id) {
943 match &property {
944 Property { payload: Payload::String(_), .. } => {
945 property.payload = Payload::String(value.to_owned())
946 }
947 unexpected => {
948 return Err(format_err!("Bad type {:?} trying to set string", unexpected))
949 }
950 }
951 } else {
952 return Err(format_err!("Tried to set string on nonexistent property {}", id));
953 }
954 Ok(())
955 }
956
957 fn set_bytes(&mut self, id: BlockIndex, value: &Vec<u8>) -> Result<(), Error> {
958 if let Some(property) = self.properties.get_mut(&id) {
959 match &property {
960 Property { payload: Payload::Bytes(_), .. } => {
961 property.payload = Payload::Bytes(value.to_owned())
962 }
963 unexpected => {
964 return Err(format_err!("Bad type {:?} trying to set bytes", unexpected))
965 }
966 }
967 } else {
968 return Err(format_err!("Tried to set bytes on nonexistent property {}", id));
969 }
970 Ok(())
971 }
972
973 fn set_bool(&mut self, id: BlockIndex, value: bool) -> Result<(), Error> {
974 if let Some(property) = self.properties.get_mut(&id) {
975 match &property {
976 Property { payload: Payload::Bool(_), .. } => {
977 property.payload = Payload::Bool(value)
978 }
979 unexpected => {
980 return Err(format_err!("Bad type {:?} trying to set bool", unexpected))
981 }
982 }
983 } else {
984 return Err(format_err!("Tried to set bool on nonexistent property {}", id));
985 }
986 Ok(())
987 }
988
989 fn add_lazy_node(
990 &mut self,
991 parent: BlockIndex,
992 id: BlockIndex,
993 name: &str,
994 disposition: &validate::LinkDisposition,
995 actions: &Vec<validate::Action>,
996 ) -> Result<(), Error> {
997 let mut parsed_data = Data::new();
998 parsed_data.apply_multiple(actions)?;
999 self.add_property(
1000 parent,
1001 id,
1002 name,
1003 Payload::Link {
1004 disposition: match disposition {
1005 validate::LinkDisposition::Child => LinkNodeDisposition::Child,
1006 validate::LinkDisposition::Inline => LinkNodeDisposition::Inline,
1007 },
1008 parsed_data,
1009 },
1010 )?;
1011 Ok(())
1012 }
1013
1014 fn clone_generic(&self) -> Self {
1019 let mut clone = self.clone();
1020
1021 let mut to_remove = vec![];
1022 let mut names = HashSet::new();
1023
1024 clone.properties = clone
1025 .properties
1026 .into_iter()
1027 .filter_map(|(id, mut v)| {
1028 if !names.insert((v.parent, v.name.clone())) {
1034 to_remove.push((v.parent, id));
1035 None
1036 } else {
1037 v.payload = v.payload.to_generic();
1038 Some((id, v))
1039 }
1040 })
1041 .collect::<HashMap<BlockIndex, Property>>();
1042
1043 for (parent, id) in to_remove {
1045 if clone.nodes.contains_key(&parent) {
1046 clone.nodes.get_mut(&parent).unwrap().properties.remove(&id);
1047 }
1048 }
1049
1050 clone
1051 }
1052
1053 pub fn compare_to_json(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1058 self.clone_generic().compare(other, diff_type)
1059 }
1060
1061 pub fn compare(&self, other: &Data, diff_type: DiffType) -> Result<(), Error> {
1065 let self_string = self.to_string();
1066 let other_string = other.to_string();
1067
1068 let difference::Changeset { diffs, distance, .. } =
1069 difference::Changeset::new(&self_string, &other_string, "\n");
1070
1071 if distance == 0 {
1072 Ok(())
1073 } else {
1074 let diff_lines = diffs
1075 .into_iter()
1076 .flat_map(|diff| {
1077 let (prefix, val) = match diff {
1078 difference::Difference::Same(val) => (" same", val),
1080 difference::Difference::Add(val) => ("other", val),
1081 difference::Difference::Rem(val) => ("local", val),
1082 };
1083 val.split("\n").map(|line| format!("{prefix}: {line:?}")).collect::<Vec<_>>()
1084 })
1085 .collect::<Vec<_>>();
1086
1087 match diff_type {
1088 DiffType::Full => Err(format_err!(
1089 "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}",
1090 self_string,
1091 other_string
1092 )),
1093 DiffType::Diff => {
1094 Err(format_err!("Trees differ:\n-- DIFF --\n{}", diff_lines.join("\n")))
1095 }
1096 DiffType::Both => Err(format_err!(
1097 "Trees differ:\n-- LOCAL --\n{}\n-- OTHER --\n{}\n-- DIFF --\n{}",
1098 self_string,
1099 other_string,
1100 diff_lines.join("\n")
1101 )),
1102 }
1103 }
1104 }
1105
1106 pub fn new() -> Data {
1109 let mut ret = Data {
1110 nodes: HashMap::new(),
1111 properties: HashMap::new(),
1112 tombstone_nodes: HashSet::new(),
1113 tombstone_properties: HashSet::new(),
1114 };
1115 ret.nodes.insert(
1116 BlockIndex::ROOT,
1117 Node {
1118 name: ROOT_NAME.into(),
1119 parent: BlockIndex::ROOT,
1120 children: HashSet::new(),
1121 properties: HashSet::new(),
1122 },
1123 );
1124 ret
1125 }
1126
1127 fn build(nodes: HashMap<BlockIndex, Node>, properties: HashMap<BlockIndex, Property>) -> Data {
1128 Data {
1129 nodes,
1130 properties,
1131 tombstone_nodes: HashSet::new(),
1132 tombstone_properties: HashSet::new(),
1133 }
1134 }
1135
1136 fn apply_multiple(&mut self, actions: &Vec<validate::Action>) -> Result<(), Error> {
1137 for action in actions {
1138 self.apply(action)?;
1139 }
1140 Ok(())
1141 }
1142
1143 pub fn remove_tree(&mut self, name: &str) {
1145 if self.is_empty() {
1146 return;
1147 }
1148 let mut target_node_id = None;
1149 let root = &self.nodes[&BlockIndex::ROOT];
1150 for child_id in &root.children {
1151 if let Some(node) = self.nodes.get(child_id) {
1152 if node.name == name {
1153 target_node_id = Some(*child_id);
1154 break;
1155 }
1156 }
1157 }
1158 if let Some(id) = target_node_id {
1159 self.delete_node(id).unwrap();
1160 }
1161 }
1162
1163 pub fn is_empty(&self) -> bool {
1165 if !self.nodes.contains_key(&BlockIndex::ROOT) {
1166 return true;
1168 }
1169
1170 if self.properties.len() == 0 {
1173 return true;
1174 }
1175
1176 let root = &self.nodes[&BlockIndex::ROOT];
1178 root.children.is_subset(&self.tombstone_nodes) && root.properties.len() == 0
1179 }
1180}
1181
1182impl fmt::Display for Data {
1183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1184 let s = if let Some(node) = self.nodes.get(&BlockIndex::ROOT) {
1185 node.to_string("", self, false)
1186 } else {
1187 "No root node; internal error\n".to_owned()
1188 };
1189 write!(f, "{s}",)
1190 }
1191}
1192
1193impl From<DiagnosticsHierarchy> for Data {
1194 fn from(hierarchy: DiagnosticsHierarchy) -> Self {
1195 let mut nodes = HashMap::new();
1196 let mut properties = HashMap::new();
1197
1198 nodes.insert(
1199 0.into(),
1200 Node {
1201 name: hierarchy.name.clone(),
1202 parent: 0.into(),
1203 children: HashSet::new(),
1204 properties: HashSet::new(),
1205 },
1206 );
1207
1208 let mut queue = vec![(0.into(), &hierarchy)];
1209 let mut next_id = BlockIndex::new(1);
1210
1211 while let Some((id, value)) = queue.pop() {
1212 for node in value.children.iter() {
1213 let child_id = next_id;
1214 next_id += 1;
1215 nodes.insert(
1216 child_id,
1217 Node {
1218 name: node.name.clone(),
1219 parent: id,
1220 children: HashSet::new(),
1221 properties: HashSet::new(),
1222 },
1223 );
1224 nodes.get_mut(&id).expect("parent must exist").children.insert(child_id);
1225 queue.push((child_id, node));
1226 }
1227 for property in value.properties.iter() {
1228 let prop_id = next_id;
1229 next_id += 1;
1230
1231 let (name, payload) = match property.clone() {
1232 iProperty::String(n, v) => (n, Payload::String(v).to_generic()),
1233 iProperty::Bytes(n, v) => (n, Payload::Bytes(v).to_generic()),
1234 iProperty::Int(n, v) => (n, Payload::Int(v).to_generic()),
1235 iProperty::Uint(n, v) => (n, Payload::Uint(v).to_generic()),
1236 iProperty::Double(n, v) => (n, Payload::Double(v).to_generic()),
1237 iProperty::Bool(n, v) => (n, Payload::Bool(v).to_generic()),
1238 iProperty::IntArray(n, content) => (n, content.to_generic()),
1239 iProperty::UintArray(n, content) => (n, content.to_generic()),
1240 iProperty::DoubleArray(n, content) => (n, content.to_generic()),
1241 iProperty::StringList(n, content) => {
1242 (n, Payload::StringArray(content).to_generic())
1243 }
1244 };
1245
1246 properties.insert(prop_id, Property { name, id: prop_id, parent: id, payload });
1247 nodes.get_mut(&id).expect("parent must exist").properties.insert(prop_id);
1248 }
1249 }
1250
1251 Data {
1252 nodes,
1253 properties,
1254 tombstone_nodes: HashSet::new(),
1255 tombstone_properties: HashSet::new(),
1256 }
1257 }
1258}
1259
1260#[cfg(test)]
1261mod tests {
1262 use super::*;
1263 use crate::*;
1264 use fidl_diagnostics_validate::{ValueType, ROOT_ID};
1265 use fuchsia_inspect::reader::ArrayContent as iArrayContent;
1266 use inspect_format::BlockType;
1267 use num_derive::{FromPrimitive, ToPrimitive};
1268
1269 #[fuchsia::test]
1270 fn test_basic_data_strings() -> Result<(), Error> {
1271 let mut info = Data::new();
1272 assert_eq!(info.to_string(), "root ->");
1273
1274 info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "foo"))?;
1275 assert_eq!(info.to_string(), "root ->\n> foo ->");
1276
1277 info.apply(&delete_node!( id: 1 ))?;
1278 assert_eq!(info.to_string(), "root ->");
1279
1280 Ok(())
1281 }
1282
1283 const EXPECTED_HIERARCHY: &str = r#"root ->
1284> double: GenericNumber("2.5")
1285> int: GenericNumber("-5")
1286> string: String("value")
1287> uint: GenericNumber("10")
1288> child ->
1289> > bytes: String("b64:AQI=")
1290> > grandchild ->
1291> > > double_a: GenericArray(["0.5", "1"])
1292> > > double_eh: GenericExponentialHistogram(["0.5", "0.5", "2", "1", "2", "3"])
1293> > > double_lh: GenericLinearHistogram(["0.5", "0.5", "1", "2", "3"])
1294> > > int_a: GenericArray(["-1", "-2"])
1295> > > int_eh: GenericExponentialHistogram(["-1", "1", "2", "1", "2", "3"])
1296> > > int_lh: GenericLinearHistogram(["-1", "1", "1", "2", "3"])
1297> > > uint_a: GenericArray(["1", "2"])
1298> > > uint_eh: GenericExponentialHistogram(["1", "1", "2", "1", "2", "3"])
1299> > > uint_lh: GenericLinearHistogram(["1", "1", "1", "2", "3"])"#;
1300
1301 #[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
1304 enum ArrayType {
1305 Int = 4,
1306 Uint = 5,
1307 Double = 6,
1308 }
1309
1310 #[fuchsia::test]
1311 fn test_parse_hierarchy() -> Result<(), Error> {
1312 let hierarchy = DiagnosticsHierarchy {
1313 name: "root".to_string(),
1314 properties: vec![
1315 iProperty::String("string".to_string(), "value".to_string()),
1316 iProperty::Uint("uint".to_string(), 10u64),
1317 iProperty::Int("int".to_string(), -5i64),
1318 iProperty::Double("double".to_string(), 2.5f64),
1319 ],
1320 children: vec![DiagnosticsHierarchy {
1321 name: "child".to_string(),
1322 properties: vec![iProperty::Bytes("bytes".to_string(), vec![1u8, 2u8])],
1323 children: vec![DiagnosticsHierarchy {
1324 name: "grandchild".to_string(),
1325 properties: vec![
1326 iProperty::UintArray(
1327 "uint_a".to_string(),
1328 iArrayContent::Values(vec![1, 2]),
1329 ),
1330 iProperty::IntArray(
1331 "int_a".to_string(),
1332 iArrayContent::Values(vec![-1i64, -2i64]),
1333 ),
1334 iProperty::DoubleArray(
1335 "double_a".to_string(),
1336 iArrayContent::Values(vec![0.5, 1.0]),
1337 ),
1338 iProperty::UintArray(
1339 "uint_lh".to_string(),
1340 iArrayContent::new(vec![1, 1, 1, 2, 3], ArrayFormat::LinearHistogram)
1341 .unwrap(),
1342 ),
1343 iProperty::IntArray(
1344 "int_lh".to_string(),
1345 iArrayContent::new(
1346 vec![-1i64, 1, 1, 2, 3],
1347 ArrayFormat::LinearHistogram,
1348 )
1349 .unwrap(),
1350 ),
1351 iProperty::DoubleArray(
1352 "double_lh".to_string(),
1353 iArrayContent::new(
1354 vec![0.5, 0.5, 1.0, 2.0, 3.0],
1355 ArrayFormat::LinearHistogram,
1356 )
1357 .unwrap(),
1358 ),
1359 iProperty::UintArray(
1360 "uint_eh".to_string(),
1361 iArrayContent::new(
1362 vec![1, 1, 2, 1, 2, 3],
1363 ArrayFormat::ExponentialHistogram,
1364 )
1365 .unwrap(),
1366 ),
1367 iProperty::IntArray(
1368 "int_eh".to_string(),
1369 iArrayContent::new(
1370 vec![-1i64, 1, 2, 1, 2, 3],
1371 ArrayFormat::ExponentialHistogram,
1372 )
1373 .unwrap(),
1374 ),
1375 iProperty::DoubleArray(
1376 "double_eh".to_string(),
1377 iArrayContent::new(
1378 vec![0.5, 0.5, 2.0, 1.0, 2.0, 3.0],
1379 ArrayFormat::ExponentialHistogram,
1380 )
1381 .unwrap(),
1382 ),
1383 ],
1384 children: vec![],
1385 missing: vec![],
1386 }],
1387 missing: vec![],
1388 }],
1389 missing: vec![],
1390 };
1391
1392 let data: Data = hierarchy.into();
1393 assert_eq!(EXPECTED_HIERARCHY, data.to_string());
1394
1395 Ok(())
1396 }
1397
1398 #[fuchsia::test]
1400 fn test_creation_deletion() -> Result<(), Error> {
1401 let mut info = Data::new();
1402 assert!(!info.to_string().contains("child ->"));
1403 info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
1404 assert!(info.to_string().contains("child ->"));
1405
1406 info.apply(&create_node!(parent: 1, id: 2, name: "grandchild"))?;
1407 assert!(
1408 info.to_string().contains("grandchild ->") && info.to_string().contains("child ->")
1409 );
1410
1411 info.apply(
1412 &create_numeric_property!(parent: ROOT_ID, id: 3, name: "int-42", value: Value::IntT(-42)),
1413 )?;
1414
1415 assert!(info.to_string().contains("int-42: Int(-42)")); info.apply(&create_string_property!(parent: 1, id: 4, name: "stringfoo", value: "foo"))?;
1417 assert_eq!(
1418 info.to_string(),
1419 "root ->\n> int-42: Int(-42)\n> child ->\
1420 \n> > stringfoo: String(\"foo\")\n> > grandchild ->"
1421 );
1422
1423 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 5, name: "uint", value: Value::UintT(1024)))?;
1424 assert!(info.to_string().contains("uint: Uint(1024)"));
1425
1426 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 6, name: "frac", value: Value::DoubleT(0.5)))?;
1427 assert!(info.to_string().contains("frac: Double(0.5)"));
1428
1429 info.apply(
1430 &create_bytes_property!(parent: ROOT_ID, id: 7, name: "bytes", value: vec!(1u8, 2u8)),
1431 )?;
1432 assert!(info.to_string().contains("bytes: Bytes([1, 2])"));
1433
1434 info.apply(&create_array_property!(parent: ROOT_ID, id: 8, name: "i_ntarr", slots: 1, type: ValueType::Int))?;
1435 assert!(info.to_string().contains("i_ntarr: IntArray([0], Default)"));
1436
1437 info.apply(&create_array_property!(parent: ROOT_ID, id: 9, name: "u_intarr", slots: 2, type: ValueType::Uint))?;
1438 assert!(info.to_string().contains("u_intarr: UintArray([0, 0], Default)"));
1439
1440 info.apply(&create_array_property!(parent: ROOT_ID, id: 10, name: "dblarr", slots: 3, type: ValueType::Double))?;
1441 assert!(info.to_string().contains("dblarr: DoubleArray([0.0, 0.0, 0.0], Default)"));
1442
1443 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 11, name: "ILhist", floor: 12,
1444 step_size: 3, buckets: 2, type: IntT))?;
1445 assert!(info
1446 .to_string()
1447 .contains("ILhist: IntArray([12, 3, 0, 0, 0, 0], LinearHistogram)"));
1448
1449 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 12, name: "ULhist", floor: 34,
1450 step_size: 5, buckets: 2, type: UintT))?;
1451 assert!(info
1452 .to_string()
1453 .contains("ULhist: UintArray([34, 5, 0, 0, 0, 0], LinearHistogram)"));
1454
1455 info.apply(
1456 &create_linear_histogram!(parent: ROOT_ID, id: 13, name: "DLhist", floor: 56.0,
1457 step_size: 7.0, buckets: 2, type: DoubleT),
1458 )?;
1459 assert!(info
1460 .to_string()
1461 .contains("DLhist: DoubleArray([56.0, 7.0, 0.0, 0.0, 0.0, 0.0], LinearHistogram)"));
1462
1463 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 14, name: "IEhist",
1464 floor: 12, initial_step: 3, step_multiplier: 5, buckets: 2, type: IntT))?;
1465 assert!(info
1466 .to_string()
1467 .contains("IEhist: IntArray([12, 3, 5, 0, 0, 0, 0], ExponentialHistogram)"));
1468
1469 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 15, name: "UEhist",
1470 floor: 34, initial_step: 9, step_multiplier: 6, buckets: 2, type: UintT))?;
1471 assert!(info
1472 .to_string()
1473 .contains("UEhist: UintArray([34, 9, 6, 0, 0, 0, 0], ExponentialHistogram)"));
1474
1475 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 16, name: "DEhist",
1476 floor: 56.0, initial_step: 27.0, step_multiplier: 7.0, buckets: 2, type: DoubleT))?;
1477 assert!(info.to_string().contains(
1478 "DEhist: DoubleArray([56.0, 27.0, 7.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1479 ));
1480
1481 info.apply(&create_bool_property!(parent: ROOT_ID, id: 17, name: "bool", value: true))?;
1482 assert!(info.to_string().contains("bool: Bool(true)"));
1483
1484 info.apply(&delete_property!(id: 3))?;
1485 assert!(!info.to_string().contains("int-42") && info.to_string().contains("stringfoo"));
1486 info.apply(&delete_property!(id: 4))?;
1487 assert!(!info.to_string().contains("stringfoo"));
1488 info.apply(&delete_property!(id: 5))?;
1489 assert!(!info.to_string().contains("uint"));
1490 info.apply(&delete_property!(id: 6))?;
1491 assert!(!info.to_string().contains("frac"));
1492 info.apply(&delete_property!(id: 7))?;
1493 assert!(!info.to_string().contains("bytes"));
1494 info.apply(&delete_property!(id: 8))?;
1495 assert!(!info.to_string().contains("i_ntarr"));
1496 info.apply(&delete_property!(id: 9))?;
1497 assert!(!info.to_string().contains("u_intarr"));
1498 info.apply(&delete_property!(id: 10))?;
1499 assert!(!info.to_string().contains("dblarr"));
1500 info.apply(&delete_property!(id: 11))?;
1501 assert!(!info.to_string().contains("ILhist"));
1502 info.apply(&delete_property!(id: 12))?;
1503 assert!(!info.to_string().contains("ULhist"));
1504 info.apply(&delete_property!(id: 13))?;
1505 assert!(!info.to_string().contains("DLhist"));
1506 info.apply(&delete_property!(id: 14))?;
1507 assert!(!info.to_string().contains("IEhist"));
1508 info.apply(&delete_property!(id: 15))?;
1509 assert!(!info.to_string().contains("UEhist"));
1510 info.apply(&delete_property!(id: 16))?;
1511 assert!(!info.to_string().contains("DEhist"));
1512 info.apply(&delete_property!(id: 17))?;
1513 assert!(!info.to_string().contains("bool"));
1514 info.apply(&delete_node!(id:2))?;
1515 assert!(!info.to_string().contains("grandchild") && info.to_string().contains("child"));
1516 info.apply(&delete_node!( id: 1 ))?;
1517 assert_eq!(info.to_string(), "root ->");
1518 Ok(())
1519 }
1520
1521 #[fuchsia::test]
1522 fn test_basic_int_ops() -> Result<(), Error> {
1523 let mut info = Data::new();
1524 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1525 value: Value::IntT(-42)))?;
1526 assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_ok());
1527 assert!(info.to_string().contains("value: Int(-39)"));
1528 assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1529 assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1530 assert!(info.to_string().contains("value: Int(-39)"));
1531 assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_ok());
1532 assert!(info.to_string().contains("value: Int(-44)"));
1533 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1534 assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1535 assert!(info.to_string().contains("value: Int(-44)"));
1536 assert!(info.apply(&set_number!(id: 3, value: Value::IntT(22))).is_ok());
1537 assert!(info.to_string().contains("value: Int(22)"));
1538 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1539 assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1540 assert!(info.to_string().contains("value: Int(22)"));
1541 Ok(())
1542 }
1543
1544 #[fuchsia::test]
1545 fn test_array_string_ops() -> Result<(), Error> {
1546 let mut info = Data::new();
1547 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1548 type: ValueType::String))?;
1549 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1550 assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1551 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1552 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1553 assert!(info.to_string().contains(r#"value: StringArray(["", "", ""]"#));
1554 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1555 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1556 assert!(info
1557 .apply(&array_subtract!(id: 3, index: 2,
1558 value: Value::DoubleT(5.0)))
1559 .is_err());
1560 assert!(info.to_string().contains(r#"value: StringArray(["", "", ""])"#));
1561 assert!(info
1562 .apply(&array_set!(id: 3, index: 1, value: Value::StringT("data".into())))
1563 .is_ok());
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!(info
1589 .apply(&array_subtract!(id: 3, index: 2,
1590 value: Value::DoubleT(5.0)))
1591 .is_err());
1592 assert!(info.to_string().contains("value: IntArray([0, 3, -5], Default)"));
1593 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(22))).is_ok());
1594 assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1595 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(23))).is_err());
1596 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1597 assert!(info.to_string().contains("value: IntArray([0, 22, -5], Default)"));
1598 Ok(())
1599 }
1600
1601 #[fuchsia::test]
1602 fn test_linear_int_ops() -> Result<(), Error> {
1603 let mut info = Data::new();
1604 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1605 floor: 4, step_size: 2, buckets: 2, type: IntT))?;
1606 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1607 assert!(info.apply(&insert!(id: 3, value: Value::IntT(4))).is_ok());
1608 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1609 assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1610 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1611 assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1612 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1613 assert!(info.apply(&insert!(id: 3, value: Value::IntT(8))).is_ok());
1614 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1615 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1616 assert!(info.to_string().contains("value: IntArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1617 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1618 assert!(info.to_string().contains("value: IntArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1619 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1620 assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1621 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1622 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1623 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1624 assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 1, 2], LinearHistogram)"));
1625 assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(7), count: 4)).is_ok());
1626 assert!(info.to_string().contains("value: IntArray([4, 2, 2, 2, 5, 2], LinearHistogram)"));
1627 Ok(())
1628 }
1629
1630 #[fuchsia::test]
1631 fn test_exponential_int_ops() -> Result<(), Error> {
1632 let mut info = Data::new();
1633 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1635 floor: 5, initial_step: 2,
1636 step_multiplier: 4, buckets: 2, type: IntT))?;
1637 assert!(info
1638 .to_string()
1639 .contains("value: IntArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)"));
1640 assert!(info.apply(&insert!(id: 3, value: Value::IntT(5))).is_ok());
1641 assert!(info
1642 .to_string()
1643 .contains("value: IntArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)"));
1644 assert!(info.apply(&insert!(id: 3, value: Value::IntT(6))).is_ok());
1645 assert!(info
1646 .to_string()
1647 .contains("value: IntArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)"));
1648 assert!(info.apply(&insert!(id: 3, value: Value::IntT(7))).is_ok());
1649 assert!(info
1650 .to_string()
1651 .contains("value: IntArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)"));
1652 assert!(info.apply(&insert!(id: 3, value: Value::IntT(13))).is_ok());
1653 assert!(info
1654 .to_string()
1655 .contains("value: IntArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)"));
1656 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MAX))).is_ok());
1657 assert!(info
1658 .to_string()
1659 .contains("value: IntArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)"));
1660 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_ok());
1661 assert!(info
1662 .to_string()
1663 .contains("value: IntArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)"));
1664 assert!(info.apply(&insert!(id: 3, value: Value::IntT(i64::MIN))).is_ok());
1665 assert!(info
1666 .to_string()
1667 .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)"));
1668 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1669 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1670 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(222))).is_err());
1671 assert!(info
1672 .to_string()
1673 .contains("value: IntArray([5, 2, 4, 2, 2, 1, 2], ExponentialHistogram)"));
1674 assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(12), count: 4)).is_ok());
1675 assert!(info
1676 .to_string()
1677 .contains("value: IntArray([5, 2, 4, 2, 2, 5, 2], ExponentialHistogram)"));
1678 Ok(())
1679 }
1680
1681 #[fuchsia::test]
1682 fn test_array_out_of_bounds_nop() -> Result<(), Error> {
1683 let mut info = Data::new();
1685 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1686 type: ValueType::Int))?;
1687 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_ok());
1688 assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1689 assert!(info.apply(&array_add!(id: 3, index: 3, value: Value::IntT(3))).is_ok());
1690 assert!(info.apply(&array_add!(id: 3, index: 6, value: Value::IntT(3))).is_ok());
1691 assert!(info.apply(&array_add!(id: 3, index: 12345, value: Value::IntT(3))).is_ok());
1692 assert!(info.to_string().contains("value: IntArray([0, 3, 0], Default)"));
1693 Ok(())
1694 }
1695
1696 #[fuchsia::test]
1697 fn test_basic_uint_ops() -> Result<(), Error> {
1698 let mut info = Data::new();
1699 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1700 value: Value::UintT(42)))?;
1701 assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_ok());
1702 assert!(info.to_string().contains("value: Uint(45)"));
1703 assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1704 assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_err());
1705 assert!(info.to_string().contains("value: Uint(45)"));
1706 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_ok());
1707 assert!(info.to_string().contains("value: Uint(40)"));
1708 assert!(info.apply(&subtract_number!(id: 3, value: Value::IntT(5))).is_err());
1709 assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_err());
1710 assert!(info.to_string().contains("value: Uint(40)"));
1711 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(22))).is_ok());
1712 assert!(info.to_string().contains("value: Uint(22)"));
1713 assert!(info.apply(&set_number!(id: 3, value: Value::IntT(23))).is_err());
1714 assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(24.0))).is_err());
1715 assert!(info.to_string().contains("value: Uint(22)"));
1716 Ok(())
1717 }
1718
1719 #[fuchsia::test]
1720 fn test_array_uint_ops() -> Result<(), Error> {
1721 let mut info = Data::new();
1722 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1723 type: ValueType::Uint))?;
1724 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_ok());
1725 assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1726 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1727 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_err());
1728 assert!(info.to_string().contains("value: UintArray([0, 3, 0], Default)"));
1729 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(22))).is_ok());
1730 assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1731 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1732 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(24.0))).is_err());
1733 assert!(info.to_string().contains("value: UintArray([0, 22, 0], Default)"));
1734 assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::UintT(5))).is_ok());
1735 assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1736 assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::IntT(5))).is_err());
1737 assert!(info.apply(&array_subtract!(id: 3, index: 1, value: Value::DoubleT(5.0))).is_err());
1738 assert!(info.to_string().contains("value: UintArray([0, 17, 0], Default)"));
1739 Ok(())
1740 }
1741
1742 #[fuchsia::test]
1743 fn test_linear_uint_ops() -> Result<(), Error> {
1744 let mut info = Data::new();
1745 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1746 floor: 4, step_size: 2, buckets: 2, type: UintT))?;
1747 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 0, 0, 0], LinearHistogram)"));
1748 assert!(info.apply(&insert!(id: 3, value: Value::UintT(4))).is_ok());
1749 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 1, 0, 0], LinearHistogram)"));
1750 assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1751 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 0, 0], LinearHistogram)"));
1752 assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1753 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 0], LinearHistogram)"));
1754 assert!(info.apply(&insert!(id: 3, value: Value::UintT(8))).is_ok());
1755 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 1], LinearHistogram)"));
1756 assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1757 assert!(info.to_string().contains("value: UintArray([4, 2, 0, 2, 1, 2], LinearHistogram)"));
1758 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1759 assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1760 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1761 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1762 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1763 assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 1, 2], LinearHistogram)"));
1764 assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(7), count: 4)).is_ok());
1765 assert!(info.to_string().contains("value: UintArray([4, 2, 1, 2, 5, 2], LinearHistogram)"));
1766 Ok(())
1767 }
1768
1769 #[fuchsia::test]
1770 fn test_exponential_uint_ops() -> Result<(), Error> {
1771 let mut info = Data::new();
1772 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1774 floor: 5, initial_step: 2,
1775 step_multiplier: 4, buckets: 2, type: UintT))?;
1776 assert!(info
1777 .to_string()
1778 .contains("value: UintArray([5, 2, 4, 0, 0, 0, 0], ExponentialHistogram)"));
1779 assert!(info.apply(&insert!(id: 3, value: Value::UintT(5))).is_ok());
1780 assert!(info
1781 .to_string()
1782 .contains("value: UintArray([5, 2, 4, 0, 1, 0, 0], ExponentialHistogram)"));
1783 assert!(info.apply(&insert!(id: 3, value: Value::UintT(6))).is_ok());
1784 assert!(info
1785 .to_string()
1786 .contains("value: UintArray([5, 2, 4, 0, 2, 0, 0], ExponentialHistogram)"));
1787 assert!(info.apply(&insert!(id: 3, value: Value::UintT(7))).is_ok());
1788 assert!(info
1789 .to_string()
1790 .contains("value: UintArray([5, 2, 4, 0, 2, 1, 0], ExponentialHistogram)"));
1791 assert!(info.apply(&insert!(id: 3, value: Value::UintT(13))).is_ok());
1792 assert!(info
1793 .to_string()
1794 .contains("value: UintArray([5, 2, 4, 0, 2, 1, 1], ExponentialHistogram)"));
1795 assert!(info.apply(&insert!(id: 3, value: Value::UintT(u64::MAX))).is_ok());
1796 assert!(info
1797 .to_string()
1798 .contains("value: UintArray([5, 2, 4, 0, 2, 1, 2], ExponentialHistogram)"));
1799 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_ok());
1800 assert!(info
1801 .to_string()
1802 .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)"));
1803 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1804 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_err());
1805 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::UintT(222))).is_err());
1806 assert!(info
1807 .to_string()
1808 .contains("value: UintArray([5, 2, 4, 1, 2, 1, 2], ExponentialHistogram)"));
1809 assert!(info.apply(&insert_multiple!(id: 3, value: Value::UintT(12), count: 4)).is_ok());
1810 assert!(info
1811 .to_string()
1812 .contains("value: UintArray([5, 2, 4, 1, 2, 5, 2], ExponentialHistogram)"));
1813 Ok(())
1814 }
1815
1816 #[fuchsia::test]
1817 fn test_basic_double_ops() -> Result<(), Error> {
1818 let mut info = Data::new();
1819 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
1820 value: Value::DoubleT(42.0)))?;
1821 assert!(info.apply(&add_number!(id: 3, value: Value::DoubleT(3.0))).is_ok());
1822 assert!(info.to_string().contains("value: Double(45.0)"));
1823 assert!(info.apply(&add_number!(id: 3, value: Value::IntT(3))).is_err());
1824 assert!(info.apply(&add_number!(id: 3, value: Value::UintT(3))).is_err());
1825 assert!(info.to_string().contains("value: Double(45.0)"));
1826 assert!(info.apply(&subtract_number!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1827 assert!(info.to_string().contains("value: Double(40.0)"));
1828 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1829 assert!(info.apply(&subtract_number!(id: 3, value: Value::UintT(5))).is_err());
1830 assert!(info.to_string().contains("value: Double(40.0)"));
1831 assert!(info.apply(&set_number!(id: 3, value: Value::DoubleT(22.0))).is_ok());
1832 assert!(info.to_string().contains("value: Double(22.0)"));
1833 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(23))).is_err());
1834 assert!(info.apply(&set_number!(id: 3, value: Value::UintT(24))).is_err());
1835 assert!(info.to_string().contains("value: Double(22.0)"));
1836 Ok(())
1837 }
1838
1839 #[fuchsia::test]
1840 fn test_array_double_ops() -> Result<(), Error> {
1841 let mut info = Data::new();
1842 info.apply(&create_array_property!(parent: ROOT_ID, id: 3, name: "value", slots: 3,
1843 type: ValueType::Double))?;
1844 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::DoubleT(3.0))).is_ok());
1845 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1846 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::IntT(3))).is_err());
1847 assert!(info.apply(&array_add!(id: 3, index: 1, value: Value::UintT(3))).is_err());
1848 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, 0.0], Default)"));
1849 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::DoubleT(5.0))).is_ok());
1850 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1851 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::IntT(5))).is_err());
1852 assert!(info.apply(&array_subtract!(id: 3, index: 2, value: Value::UintT(5))).is_err());
1853 assert!(info.to_string().contains("value: DoubleArray([0.0, 3.0, -5.0], Default)"));
1854 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(22.0))).is_ok());
1855 assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1856 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(23))).is_err());
1857 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::IntT(24))).is_err());
1858 assert!(info.to_string().contains("value: DoubleArray([0.0, 22.0, -5.0], Default)"));
1859 Ok(())
1860 }
1861
1862 #[fuchsia::test]
1863 fn test_linear_double_ops() -> Result<(), Error> {
1864 let mut info = Data::new();
1865 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 3, name: "value",
1866 floor: 4.0, step_size: 0.5, buckets: 2, type: DoubleT))?;
1867 assert!(info
1868 .to_string()
1869 .contains("value: DoubleArray([4.0, 0.5, 0.0, 0.0, 0.0, 0.0], LinearHistogram)"));
1870 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.0))).is_ok());
1871 assert!(info
1872 .to_string()
1873 .contains("value: DoubleArray([4.0, 0.5, 0.0, 1.0, 0.0, 0.0], LinearHistogram)"));
1874 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.25))).is_ok());
1875 assert!(info
1876 .to_string()
1877 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 0.0, 0.0], LinearHistogram)"));
1878 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(4.75))).is_ok());
1879 assert!(info
1880 .to_string()
1881 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 0.0], LinearHistogram)"));
1882 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.1))).is_ok());
1883 assert!(info
1884 .to_string()
1885 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 1.0], LinearHistogram)"));
1886 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1887 assert!(info
1888 .to_string()
1889 .contains("value: DoubleArray([4.0, 0.5, 0.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1890 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1891 assert!(info
1892 .to_string()
1893 .contains("value: DoubleArray([4.0, 0.5, 1.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1894 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1895 assert!(info
1896 .to_string()
1897 .contains("value: DoubleArray([4.0, 0.5, 2.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1898 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1899 assert!(info
1900 .to_string()
1901 .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1902 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1903 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1904 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1905 assert!(info
1906 .to_string()
1907 .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 1.0, 2.0], LinearHistogram)"));
1908 assert!(info.apply(&insert_multiple!(id: 3, value: Value::DoubleT(4.5), count: 4)).is_ok());
1909 assert!(info
1910 .to_string()
1911 .contains("value: DoubleArray([4.0, 0.5, 3.0, 2.0, 5.0, 2.0], LinearHistogram)"));
1912 Ok(())
1913 }
1914
1915 #[fuchsia::test]
1916 fn test_exponential_double_ops() -> Result<(), Error> {
1917 let mut info = Data::new();
1918 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 3, name: "value",
1920 floor: 5.0, initial_step: 2.0,
1921 step_multiplier: 4.0, buckets: 3, type: DoubleT))?;
1922 assert!(info.to_string().contains(
1923 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1924 ));
1925 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(5.0))).is_ok());
1926 assert!(info.to_string().contains(
1927 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 1.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1928 ));
1929 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(6.9))).is_ok());
1930 assert!(info.to_string().contains(
1931 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 0.0, 0.0, 0.0], ExponentialHistogram)"
1932 ));
1933 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(7.1))).is_ok());
1934 assert!(info.to_string().contains(
1935 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 1.0, 0.0, 0.0], ExponentialHistogram)"
1936 ));
1937 assert!(info
1938 .apply(&insert_multiple!(id: 3, value: Value::DoubleT(12.9), count: 4))
1939 .is_ok());
1940 assert!(info.to_string().contains(
1941 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 0.0, 0.0], ExponentialHistogram)"
1942 ));
1943 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(13.1))).is_ok());
1944 assert!(info.to_string().contains(
1945 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 1.0, 0.0], ExponentialHistogram)"
1946 ));
1947 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(36.9))).is_ok());
1948 assert!(info.to_string().contains(
1949 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 0.0], ExponentialHistogram)"
1950 ));
1951 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(37.1))).is_ok());
1952 assert!(info.to_string().contains(
1953 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 1.0], ExponentialHistogram)"
1954 ));
1955 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MAX))).is_ok());
1956 assert!(info.to_string().contains(
1957 "value: DoubleArray([5.0, 2.0, 4.0, 0.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1958 ));
1959 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN_POSITIVE))).is_ok());
1960 assert!(info.to_string().contains(
1961 "value: DoubleArray([5.0, 2.0, 4.0, 1.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1962 ));
1963 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(f64::MIN))).is_ok());
1964 assert!(info.to_string().contains(
1965 "value: DoubleArray([5.0, 2.0, 4.0, 2.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1966 ));
1967 assert!(info.apply(&insert!(id: 3, value: Value::DoubleT(0.0))).is_ok());
1968 assert!(info.to_string().contains(
1969 "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1970 ));
1971 assert!(info.apply(&insert!(id: 3, value: Value::IntT(0))).is_err());
1972 assert!(info.apply(&insert!(id: 3, value: Value::UintT(0))).is_err());
1973 assert!(info.apply(&array_set!(id: 3, index: 1, value: Value::DoubleT(222.0))).is_err());
1974 assert!(info.to_string().contains(
1975 "value: DoubleArray([5.0, 2.0, 4.0, 3.0, 2.0, 5.0, 2.0, 2.0], ExponentialHistogram)"
1976 ));
1977 Ok(())
1978 }
1979
1980 #[fuchsia::test]
1981 fn test_basic_vector_ops() -> Result<(), Error> {
1982 let mut info = Data::new();
1983 info.apply(&create_string_property!(parent: ROOT_ID, id: 3, name: "value",
1984 value: "foo"))?;
1985 assert!(info.to_string().contains("value: String(\"foo\")"));
1986 assert!(info.apply(&set_string!(id: 3, value: "bar")).is_ok());
1987 assert!(info.to_string().contains("value: String(\"bar\")"));
1988 assert!(info.apply(&set_bytes!(id: 3, value: vec!(3u8))).is_err());
1989 assert!(info.to_string().contains("value: String(\"bar\")"));
1990 info.apply(&create_bytes_property!(parent: ROOT_ID, id: 4, name: "bvalue",
1991 value: vec!(1u8, 2u8)))?;
1992 assert!(info.to_string().contains("bvalue: Bytes([1, 2])"));
1993 assert!(info.apply(&set_bytes!(id: 4, value: vec!(3u8, 4u8))).is_ok());
1994 assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
1995 assert!(info.apply(&set_string!(id: 4, value: "baz")).is_err());
1996 assert!(info.to_string().contains("bvalue: Bytes([3, 4])"));
1997 Ok(())
1998 }
1999
2000 #[fuchsia::test]
2001 fn test_basic_lazy_node_ops() -> Result<(), Error> {
2002 let mut info = Data::new();
2003 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))]))?;
2004 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))]))?;
2005
2006 assert_eq!(
2008 info.to_string(),
2009 "root ->\n> inline_bytes: Bytes([3, 4])\n> child ->\n> > child_bytes: Bytes([3, 4])"
2010 );
2011
2012 info.apply_lazy(&delete_lazy_node!(id: 1))?;
2013 assert_eq!(info.to_string(), "root ->\n> inline_bytes: Bytes([3, 4])");
2015
2016 Ok(())
2017 }
2018
2019 #[fuchsia::test]
2020 fn test_illegal_node_actions() -> Result<(), Error> {
2021 let mut info = Data::new();
2022 assert!(info.apply(&create_node!(parent: 42, id: 1, name: "child")).is_err());
2024 info = Data::new();
2026 info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "child"))?;
2027 assert!(info.apply(&create_node!(parent: ROOT_ID, id: 1, name: "another_child")).is_err());
2028 info = Data::new();
2030 assert!(info.apply(&delete_node!(id: ROOT_ID)).is_err());
2031 info = Data::new();
2033 assert!(info.apply(&delete_node!(id: 333)).is_err());
2034 Ok(())
2035 }
2036
2037 #[fuchsia::test]
2038 fn test_illegal_property_actions() -> Result<(), Error> {
2039 let mut info = Data::new();
2040 assert!(info
2042 .apply(
2043 &create_numeric_property!(parent: 42, id: 1, name: "answer", value: Value::IntT(42))
2044 )
2045 .is_err());
2046 info = Data::new();
2048 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "answer",
2049 value: Value::IntT(42)))?;
2050 assert!(info
2051 .apply(&create_numeric_property!(parent: ROOT_ID, id: 1, name: "another_answer",
2052 value: Value::IntT(7)))
2053 .is_err());
2054 info = Data::new();
2056 assert!(info.apply(&delete_property!(id: 1)).is_err());
2057 info = Data::new();
2059 info.apply(&create_numeric_property!(parent: ROOT_ID, id: 3, name: "value",
2060 value: Value::IntT(42)))?;
2061 info.apply(&create_array_property!(parent: ROOT_ID, id: 4, name: "array", slots: 2,
2062 type: ValueType::Int))?;
2063 info.apply(&create_linear_histogram!(parent: ROOT_ID, id: 5, name: "lin",
2064 floor: 5, step_size: 2,
2065 buckets: 2, type: IntT))?;
2066 info.apply(&create_exponential_histogram!(parent: ROOT_ID, id: 6, name: "exp",
2067 floor: 5, initial_step: 2,
2068 step_multiplier: 2, buckets: 2, type: IntT))?;
2069 assert!(info.apply(&set_number!(id: 3, value: Value::IntT(5))).is_ok());
2070 assert!(info.apply(&array_set!(id: 4, index: 0, value: Value::IntT(5))).is_ok());
2071 assert!(info.apply(&insert!(id: 5, value: Value::IntT(2))).is_ok());
2072 assert!(info.apply(&insert!(id: 6, value: Value::IntT(2))).is_ok());
2073 assert!(info.apply(&insert_multiple!(id: 5, value: Value::IntT(2), count: 3)).is_ok());
2074 assert!(info.apply(&insert_multiple!(id: 6, value: Value::IntT(2), count: 3)).is_ok());
2075 assert!(info.apply(&set_number!(id: 4, value: Value::IntT(5))).is_err());
2076 assert!(info.apply(&set_number!(id: 5, value: Value::IntT(5))).is_err());
2077 assert!(info.apply(&set_number!(id: 6, value: Value::IntT(5))).is_err());
2078 assert!(info.apply(&array_set!(id: 3, index: 0, value: Value::IntT(5))).is_err());
2079 assert!(info.apply(&array_set!(id: 5, index: 0, value: Value::IntT(5))).is_err());
2080 assert!(info.apply(&array_set!(id: 6, index: 0, value: Value::IntT(5))).is_err());
2081 assert!(info.apply(&insert!(id: 3, value: Value::IntT(2))).is_err());
2082 assert!(info.apply(&insert!(id: 4, value: Value::IntT(2))).is_err());
2083 assert!(info.apply(&insert_multiple!(id: 3, value: Value::IntT(2), count: 3)).is_err());
2084 assert!(info.apply(&insert_multiple!(id: 4, value: Value::IntT(2), count: 3)).is_err());
2085 Ok(())
2086 }
2087
2088 #[fuchsia::test]
2089 fn test_enum_values() {
2090 assert_eq!(BlockType::IntValue.to_isize().unwrap(), ArrayType::Int.to_isize().unwrap());
2091 assert_eq!(BlockType::UintValue.to_isize().unwrap(), ArrayType::Uint.to_isize().unwrap());
2092 assert_eq!(
2093 BlockType::DoubleValue.to_isize().unwrap(),
2094 ArrayType::Double.to_isize().unwrap()
2095 );
2096 }
2097
2098 #[fuchsia::test]
2099 fn test_create_node_checks() {
2100 let mut data = Data::new();
2101 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2102 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "second")).is_ok());
2103 assert!(data.apply(&create_node!(parent: 1, id: 3, name: "child")).is_ok());
2104 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "double")).is_err());
2105 let mut data = Data::new();
2106 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "orphan")).is_err());
2107 }
2108
2109 #[fuchsia::test]
2110 fn test_delete_node_checks() {
2111 let mut data = Data::new();
2112 assert!(data.apply(&delete_node!(id: 0)).is_err());
2113 let mut data = Data::new();
2114 data.apply(&create_node!(parent: 0, id: 1, name: "first")).ok();
2115 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2116 assert!(data.apply(&delete_node!(id: 1)).is_err());
2117 }
2118
2119 #[fuchsia::test]
2120 fn test_node_tombstoning() {
2122 let mut data = Data::new();
2124 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2125 assert!(data
2126 .apply(&create_numeric_property!(parent: 1, id: 2,
2127 name: "answer", value: Value::IntT(42)))
2128 .is_ok());
2129 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2130 assert!(data.apply(&delete_property!(id: 2)).is_ok());
2131 assert!(data.apply(&delete_property!(id: 2)).is_err());
2132 let mut data = Data::new();
2134 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2135 assert!(data
2136 .apply(&create_numeric_property!(parent: 1, id: 2,
2137 name: "answer", value: Value::IntT(42)))
2138 .is_ok());
2139 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2140 assert!(data.apply(&delete_property!(id: 2)).is_ok());
2141 assert!(data
2142 .apply(&create_numeric_property!(parent: 0, id: 2,
2143 name: "root_answer", value: Value::IntT(42)))
2144 .is_ok());
2145 let mut data = Data::new();
2147 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2148 assert!(data
2149 .apply(&create_numeric_property!(parent: 1, id: 2,
2150 name: "answer", value: Value::IntT(42)))
2151 .is_ok());
2152 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2153 assert!(data
2154 .apply(&create_numeric_property!(parent: 0, id: 2,
2155 name: "root_answer", value: Value::IntT(42)))
2156 .is_err());
2157 }
2158
2159 #[fuchsia::test]
2160 fn test_property_tombstoning() {
2161 let mut data = Data::new();
2164 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2165 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2166 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2167 assert!(data.apply(&delete_node!(id: 2)).is_ok());
2168 assert!(data.apply(&delete_node!(id: 2)).is_err());
2169 let mut data = Data::new();
2171 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2172 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2173 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2174 assert!(data.apply(&delete_node!(id: 2)).is_ok());
2175 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_ok());
2176 let mut data = Data::new();
2178 assert!(data.apply(&create_node!(parent: 0, id: 1, name: "first")).is_ok());
2179 assert!(data.apply(&create_node!(parent: 1, id: 2, name: "second")).is_ok());
2180 assert!(data.apply(&delete_node!(id: 1)).is_ok());
2181 assert!(data.apply(&create_node!(parent: 0, id: 2, name: "new_root_second")).is_err());
2182 }
2183
2184 const DIFF_STRING: &str = r#"-- DIFF --
2185 same: "root ->"
2186 same: "> node ->"
2187local: "> > prop1: String(\"foo\")"
2188other: "> > prop1: String(\"bar\")""#;
2189
2190 const FULL_STRING: &str = r#"-- LOCAL --
2191root ->
2192> node ->
2193> > prop1: String("foo")
2194-- OTHER --
2195root ->
2196> node ->
2197> > prop1: String("bar")"#;
2198
2199 #[fuchsia::test]
2200 fn diff_modes_work() -> Result<(), Error> {
2201 let mut local = Data::new();
2202 let mut remote = Data::new();
2203 local.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2204 local.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "foo"))?;
2205 remote.apply(&create_node!(parent: 0, id: 1, name: "node"))?;
2206 remote.apply(&create_string_property!(parent: 1, id: 2, name: "prop1", value: "bar"))?;
2207 match local.compare(&remote, DiffType::Diff) {
2208 Err(error) => {
2209 let error_string = format!("{error:?}");
2210 assert_eq!("Trees differ:\n".to_string() + DIFF_STRING, error_string);
2211 }
2212 _ => return Err(format_err!("Didn't get failure")),
2213 }
2214 match local.compare(&remote, DiffType::Full) {
2215 Err(error) => {
2216 let error_string = format!("{error:?}");
2217 assert_eq!("Trees differ:\n".to_string() + FULL_STRING, error_string);
2218 }
2219 _ => return Err(format_err!("Didn't get failure")),
2220 }
2221 match local.compare(&remote, DiffType::Both) {
2222 Err(error) => {
2223 let error_string = format!("{error:?}");
2224 assert_eq!(["Trees differ:", FULL_STRING, DIFF_STRING].join("\n"), error_string);
2225 }
2226 _ => return Err(format_err!("Didn't get failure")),
2227 }
2228 Ok(())
2229 }
2230}