1use diagnostics_data::{InspectData, InspectHandleName, InspectMetadata};
6use diagnostics_hierarchy::{
7 ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram, Property,
8};
9use either::Either;
10use nom::HexDisplay;
11use num_traits::{Bounded, Zero};
12use std::fmt;
13use std::ops::{Add, AddAssign, Mul, MulAssign};
14
15const INDENT: usize = 2;
16const HEX_DISPLAY_CHUNK_SIZE: usize = 16;
17
18pub fn format<W>(w: &mut W, path: &str, diagnostics_hierarchy: DiagnosticsHierarchy) -> fmt::Result
19where
20 W: fmt::Write,
21{
22 writeln!(w, "{path}:")?;
23 output_hierarchy(w, &diagnostics_hierarchy, 1)
24}
25
26pub fn output_schema<W>(w: &mut W, schema: &InspectData) -> fmt::Result
27where
28 W: fmt::Write,
29{
30 let InspectData {
31 moniker,
32 metadata: InspectMetadata { errors, name, component_url, timestamp, escrowed },
33 payload,
34 data_source: _,
35 version: _,
36 } = schema;
37
38 writeln!(w, "{moniker}:")?;
39 writeln!(w, " metadata:")?;
40 if let Some(errors) = &errors {
41 let errors = errors.join(", ");
42 writeln!(w, " errors = {errors}")?;
43 }
44
45 match &name {
46 InspectHandleName::Filename(f) => {
47 writeln!(w, " filename = {f}")?;
48 }
49 InspectHandleName::Name(n) => {
50 writeln!(w, " name = {n}")?;
51 }
52 }
53
54 writeln!(w, " component_url = {component_url}")?;
55 writeln!(w, " timestamp = {}", timestamp.into_nanos())?;
56
57 if *escrowed {
58 writeln!(w, " escrowed = true")?;
59 }
60
61 match &payload {
62 Some(hierarchy) => {
63 writeln!(w, " payload:")?;
64 output_hierarchy(w, hierarchy, 2)
65 }
66 None => writeln!(w, " payload: null"),
67 }
68}
69
70fn output_hierarchy<W>(
71 w: &mut W,
72 diagnostics_hierarchy: &DiagnosticsHierarchy,
73 indent: usize,
74) -> fmt::Result
75where
76 W: fmt::Write,
77{
78 let name_indent = " ".repeat(INDENT * indent);
79 let value_indent = " ".repeat(INDENT * (indent + 1));
80 let array_broken_line_indent = " ".repeat(INDENT * (indent + 2));
81
82 writeln!(w, "{}{}:", name_indent, diagnostics_hierarchy.name)?;
83
84 for property in &diagnostics_hierarchy.properties {
85 match property {
86 Property::String(name, value) => writeln!(w, "{value_indent}{name} = {value}")?,
87 Property::Int(name, value) => writeln!(w, "{value_indent}{name} = {value}")?,
88 Property::Uint(name, value) => writeln!(w, "{value_indent}{name} = {value}")?,
89 Property::Double(name, value) => writeln!(w, "{value_indent}{name} = {value:.6}")?,
90 Property::Bytes(name, array) => {
91 let byte_str = array.to_hex(HEX_DISPLAY_CHUNK_SIZE);
92 writeln!(w, "{}{} = Binary:\n{}", value_indent, name, byte_str.trim())?;
93 }
94 Property::Bool(name, value) => writeln!(w, "{value_indent}{name} = {value}")?,
95 Property::IntArray(name, array) => output_array(w, &value_indent, name, array)?,
96 Property::UintArray(name, array) => output_array(w, &value_indent, name, array)?,
97 Property::DoubleArray(name, array) => output_array(w, &value_indent, name, array)?,
98 Property::StringList(name, list) => {
99 let max_line_length = 100;
100 let length_of_brackets = 2;
101 let length_of_comma_space = 2;
102 let total_len = list
103 .iter()
104 .fold(length_of_brackets, |acc, v| acc + v.len() + length_of_comma_space);
105 if total_len < max_line_length {
106 writeln!(w, "{value_indent}{name} = {list:?}")?;
107 } else {
108 writeln!(w, "{value_indent}{name} = [")?;
109 for v in list {
110 writeln!(w, r#"{array_broken_line_indent}"{v}","#)?;
111 }
112 writeln!(w, "{value_indent}]")?;
113 }
114 }
115 }
116 }
117
118 for child in &diagnostics_hierarchy.children {
119 output_hierarchy(w, child, indent + 1)?;
120 }
121 Ok(())
122}
123
124trait FromUsize {
125 fn from_usize(n: usize) -> Self;
126}
127
128impl FromUsize for i64 {
129 fn from_usize(n: usize) -> Self {
130 i64::try_from(n).unwrap()
131 }
132}
133
134impl FromUsize for u64 {
135 fn from_usize(n: usize) -> Self {
136 u64::try_from(n).unwrap()
137 }
138}
139
140impl FromUsize for f64 {
141 fn from_usize(n: usize) -> Self {
142 u64::try_from(n).unwrap() as f64
143 }
144}
145
146trait ExponentialBucketBound {
147 fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self;
148}
149
150impl ExponentialBucketBound for i64 {
151 fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self {
152 floor + initial_step * i64::pow(step_multiplier, index as u32)
153 }
154}
155
156impl ExponentialBucketBound for u64 {
157 fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self {
158 floor + initial_step * u64::pow(step_multiplier, index as u32)
159 }
160}
161
162impl ExponentialBucketBound for f64 {
163 fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self {
164 floor + initial_step * f64::powi(step_multiplier, (index as u64) as i32)
165 }
166}
167
168struct ExponentialBucketBoundsArgs<T> {
169 floor: T,
170 initial_step: T,
171 step_multiplier: T,
172 index: usize,
173 size: usize,
174}
175
176fn exponential_bucket_bounds<T>(args: ExponentialBucketBoundsArgs<T>) -> (T, T)
177where
178 T: Bounded + Add<Output = T> + ExponentialBucketBound + Copy,
179{
180 let ExponentialBucketBoundsArgs { floor, initial_step, step_multiplier, index, size } = args;
181 match index {
182 0 => (T::min_value(), floor),
183 1 if size == 2 => (floor, T::max_value()),
184 1 => (floor, floor + initial_step),
185 index if index == size - 1 => {
186 (T::bound(floor, initial_step, step_multiplier, index - 2), T::max_value())
187 }
188 _ => (
189 T::bound(floor, initial_step, step_multiplier, index - 2),
190 T::bound(floor, initial_step, step_multiplier, index - 1),
191 ),
192 }
193}
194
195struct LinearBucketBoundsArgs<T> {
196 floor: T,
197 step: T,
198 index: usize,
199 size: usize,
200}
201
202fn linear_bucket_bounds<T>(args: LinearBucketBoundsArgs<T>) -> (T, T)
203where
204 T: Bounded + Add<Output = T> + Mul<Output = T> + FromUsize + Copy,
205{
206 let LinearBucketBoundsArgs { floor, step, index, size } = args;
207 match index {
208 0 => (T::min_value(), floor),
209 index if index == size - 1 => (floor + step * T::from_usize(index - 1), T::max_value()),
210 _ => (floor + step * T::from_usize(index - 1), floor + step * T::from_usize(index)),
211 }
212}
213
214struct OutputHistogramArgs<'a, T, F> {
215 indent: &'a str,
216 name: &'a str,
217 histogram_type: &'a str,
218 counts: &'a [T],
219 indexes: Option<&'a [usize]>,
220 size: usize,
221 bound_calculator: F,
222}
223
224fn output_histogram<T, F, W>(w: &mut W, args: OutputHistogramArgs<'_, T, F>) -> fmt::Result
225where
226 W: fmt::Write,
227 T: NumberFormat + fmt::Display + PartialOrd + Zero,
228 F: Fn(usize) -> (T, T),
229{
230 let OutputHistogramArgs {
231 indent,
232 name,
233 histogram_type,
234 counts,
235 indexes,
236 size,
237 bound_calculator,
238 } = args;
239 let value_indent = format!("{}{}", indent, " ".repeat(INDENT));
240 write!(
241 w,
242 "{indent}{name}:\n\
243 {value_indent}type = {histogram_type}\n\
244 {value_indent}size = {size}\n\
245 {value_indent}buckets = ["
246 )?;
247 let mut nonzero_counts = match indexes {
248 None => Either::Left(counts.iter().enumerate().filter(|(_, count)| **count > T::zero())),
249 Some(indexes) => Either::Right(
250 indexes
251 .iter()
252 .zip(counts)
253 .filter(|(_, count)| **count > T::zero())
254 .map(|(u, t)| (*u, t)),
255 ),
256 }
257 .peekable();
258 while let Some((index, count)) = nonzero_counts.next() {
259 let (low_bound, high_bound) = bound_calculator(index);
260 write!(w, "[{},{})={}", low_bound.format(), high_bound.format(), count)?;
261 if nonzero_counts.peek().is_some() {
262 write!(w, ", ")?;
263 }
264 }
265 writeln!(w, "]")
266}
267
268fn output_array<T, W>(
269 w: &mut W,
270 value_indent: &str,
271 name: &str,
272 array: &ArrayContent<T>,
273) -> fmt::Result
274where
275 W: fmt::Write,
276 T: AddAssign
277 + MulAssign
278 + Copy
279 + Add<Output = T>
280 + fmt::Display
281 + NumberFormat
282 + Bounded
283 + Mul<Output = T>
284 + ExponentialBucketBound
285 + FromUsize
286 + PartialOrd
287 + Zero,
288{
289 match array {
290 ArrayContent::Values(values) => {
291 write!(w, "{value_indent}{name} = [")?;
292 for (i, value) in values.iter().enumerate() {
293 write!(w, "{value}")?;
294 if i < values.len() - 1 {
295 w.write_str(", ")?;
296 }
297 }
298 writeln!(w, "]")
299 }
300 ArrayContent::LinearHistogram(LinearHistogram { floor, step, counts, indexes, size }) => {
301 let bucket_bounder = |index| {
302 linear_bucket_bounds::<T>(LinearBucketBoundsArgs {
303 floor: *floor,
304 step: *step,
305 index,
306 size: *size,
307 })
308 };
309 output_histogram(
310 w,
311 OutputHistogramArgs {
312 indent: value_indent,
313 name,
314 histogram_type: "linear",
315 counts,
316 indexes: indexes.as_deref(),
317 size: *size,
318 bound_calculator: bucket_bounder,
319 },
320 )
321 }
322 ArrayContent::ExponentialHistogram(ExponentialHistogram {
323 floor,
324 initial_step,
325 step_multiplier,
326 counts,
327 indexes,
328 size,
329 }) => {
330 let bucket_bounder = |index| {
331 exponential_bucket_bounds::<T>(ExponentialBucketBoundsArgs {
332 floor: *floor,
333 initial_step: *initial_step,
334 step_multiplier: *step_multiplier,
335 index,
336 size: *size,
337 })
338 };
339 output_histogram(
340 w,
341 OutputHistogramArgs {
342 indent: value_indent,
343 name,
344 histogram_type: "exponential",
345 counts,
346 indexes: indexes.as_deref(),
347 size: *size,
348 bound_calculator: bucket_bounder,
349 },
350 )
351 }
352 }
353}
354
355trait NumberFormat {
356 fn format(&self) -> String;
357}
358
359impl NumberFormat for i64 {
360 fn format(&self) -> String {
361 match *self {
362 i64::MAX => "<max>".to_string(),
363 std::i64::MIN => "<min>".to_string(),
364 x => format!("{x}"),
365 }
366 }
367}
368
369impl NumberFormat for u64 {
370 fn format(&self) -> String {
371 match *self {
372 u64::MAX => "<max>".to_string(),
373 x => format!("{x}"),
374 }
375 }
376}
377
378impl NumberFormat for usize {
379 fn format(&self) -> String {
380 match *self {
381 std::usize::MAX => "<max>".to_string(),
382 x => format!("{x}"),
383 }
384 }
385}
386
387impl NumberFormat for f64 {
388 fn format(&self) -> String {
389 if *self == f64::MAX || *self == f64::INFINITY {
390 "inf".to_string()
391 } else if *self == f64::MIN || *self == f64::NEG_INFINITY {
392 "-inf".to_string()
393 } else {
394 format!("{self}")
395 }
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use diagnostics_data::{InspectDataBuilder, Timestamp};
403 use test_case::test_case;
404
405 #[fuchsia::test]
406 fn test_array_line_breaks() {
407 let h = DiagnosticsHierarchy {
408 name: "test".into(),
409 properties: vec![Property::StringList(
410 "short_array".to_owned(),
411 vec!["short".into(), "array".into()],
412 )],
413 children: vec![],
414 missing: vec![],
415 };
416 let mut buf = String::new();
417 output_hierarchy(&mut buf, &h, 2).unwrap();
418
419 let expected = r#" test:
420 short_array = ["short", "array"]
421"#;
422 assert_eq!(expected, buf);
423
424 let long_array = vec![
426 "12345678".into(),
427 "12345678".into(),
428 "12345678".into(),
429 "12345678".into(),
430 "12345678".into(),
431 "12345678".into(),
432 "12345678".into(),
433 "12345678".into(),
434 "12345678".into(),
435 "12345678".into(),
436 "12345678".into(),
437 "12345678".into(),
438 "12345678".into(),
439 ];
440
441 let h = DiagnosticsHierarchy {
442 name: "test".into(),
443 properties: vec![Property::StringList("long_array".to_owned(), long_array)],
444 children: vec![],
445 missing: vec![],
446 };
447 let mut buf = String::new();
448 output_hierarchy(&mut buf, &h, 2).unwrap();
449
450 let expected = r#" test:
451 long_array = [
452 "12345678",
453 "12345678",
454 "12345678",
455 "12345678",
456 "12345678",
457 "12345678",
458 "12345678",
459 "12345678",
460 "12345678",
461 "12345678",
462 "12345678",
463 "12345678",
464 "12345678",
465 ]
466"#;
467 assert_eq!(expected, buf);
468 }
469
470 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 0, size: 4 }, (i64::MIN, -10))]
471 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 1, size: 4 }, (-10, -8))]
472 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 2, size: 4 }, (-8, -6))]
473 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 3, size: 4 }, (-6, i64::MAX))]
474 #[fuchsia::test]
475 fn test_linear_bucket_bounds_i64(args: LinearBucketBoundsArgs<i64>, bounds: (i64, i64)) {
476 assert_eq!(linear_bucket_bounds(args), bounds);
478 }
479
480 #[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 0, size: 4 }, (0, 0))]
482 #[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 1, size: 4 }, (0, 2))]
483 #[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 2, size: 4 }, (2, 4))]
484 #[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 3, size: 4 }, (4, u64::MAX))]
485 #[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 0, size: 4 }, (0, 1))]
486 #[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 1, size: 4 }, (1, 3))]
487 #[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 2, size: 4 }, (3, 5))]
488 #[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 3, size: 4 }, (5, u64::MAX))]
489 #[fuchsia::test]
490 fn test_linear_bucket_bounds_u64(args: LinearBucketBoundsArgs<u64>, bounds: (u64, u64)) {
491 assert_eq!(linear_bucket_bounds(args), bounds);
492 }
493
494 #[test_case(
495 LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 0, size: 4 },
496 (f64::MIN, -0.5)
497 )]
498 #[test_case(LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 1, size: 4 }, (-0.5, 0.0))]
499 #[test_case(LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 2, size: 4 }, (0.0, 0.5))]
500 #[test_case(
501 LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 3, size: 4 },
502 (0.5, f64::MAX)
503 )]
504 #[fuchsia::test]
505 fn test_linear_bucket_bounds_f64(args: LinearBucketBoundsArgs<f64>, bounds: (f64, f64)) {
506 assert_eq!(linear_bucket_bounds(args), bounds);
507 }
508
509 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 0, size: 3 }, (i64::MIN, -10))]
512 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 1, size: 3 }, (-10, -8))]
513 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 2, size: 3 }, (-8, i64::MAX))]
514 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 0, size: 2 }, (i64::MIN, -10))]
515 #[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 1, size: 2 }, (-10, i64::MAX))]
516 #[fuchsia::test]
517 fn test_linear_small_bucket_bounds(args: LinearBucketBoundsArgs<i64>, bounds: (i64, i64)) {
518 assert_eq!(linear_bucket_bounds(args), bounds);
520 }
521
522 #[test_case(
524 ExponentialBucketBoundsArgs {
525 floor: -10,
526 initial_step: 2,
527 step_multiplier: 3,
528 index: 0,
529 size: 5 },
530 (i64::MIN, -10)
531 )]
532 #[test_case(
533 ExponentialBucketBoundsArgs {
534 floor: -10,
535 initial_step: 2,
536 step_multiplier: 3,
537 index: 1,
538 size: 5 },
539 (-10, -8)
540 )]
541 #[test_case(
542 ExponentialBucketBoundsArgs {
543 floor: -10,
544 initial_step: 2,
545 step_multiplier: 3,
546 index: 2,
547 size: 5
548 },
549 (-8, -4)
550 )]
551 #[test_case(
552 ExponentialBucketBoundsArgs {
553 floor: -10,
554 initial_step: 2,
555 step_multiplier: 3,
556 index: 3,
557 size: 5
558 },
559 (-4, 8)
560 )]
561 #[test_case(
562 ExponentialBucketBoundsArgs {
563 floor: -10,
564 initial_step: 2,
565 step_multiplier: 3,
566 index: 4,
567 size: 5
568 },
569 (8, i64::MAX)
570 )]
571 #[fuchsia::test]
572 fn test_exponential_bucket_bounds_i64(
573 args: ExponentialBucketBoundsArgs<i64>,
574 bounds: (i64, i64),
575 ) {
576 assert_eq!(exponential_bucket_bounds(args), bounds);
577 }
578
579 #[test_case(
581 ExponentialBucketBoundsArgs {
582 floor: 0,
583 initial_step: 2,
584 step_multiplier: 3,
585 index: 0,
586 size: 5
587 },
588 (0, 0)
589 )]
590 #[test_case(
591 ExponentialBucketBoundsArgs {
592 floor: 0,
593 initial_step: 2,
594 step_multiplier: 3,
595 index: 1,
596 size: 5
597 },
598 (0, 2)
599 )]
600 #[test_case(
601 ExponentialBucketBoundsArgs {
602 floor: 0,
603 initial_step: 2,
604 step_multiplier: 3,
605 index: 2,
606 size: 5
607 },
608 (2, 6)
609 )]
610 #[test_case(
611 ExponentialBucketBoundsArgs {
612 floor: 0,
613 initial_step: 2,
614 step_multiplier: 3,
615 index: 3,
616 size: 5
617 },
618 (6, 18)
619 )]
620 #[test_case(
621 ExponentialBucketBoundsArgs {
622 floor: 0,
623 initial_step: 2,
624 step_multiplier: 3,
625 index: 4,
626 size: 5 },
627 (18, u64::MAX)
628 )]
629 #[test_case(
630 ExponentialBucketBoundsArgs {
631 floor: 1,
632 initial_step: 2,
633 step_multiplier: 3,
634 index: 0,
635 size: 5
636 },
637 (0, 1)
638 )]
639 #[test_case(
640 ExponentialBucketBoundsArgs {
641 floor: 1,
642 initial_step: 2,
643 step_multiplier: 3,
644 index: 1,
645 size: 5
646 },
647 (1, 3)
648 )]
649 #[test_case(
650 ExponentialBucketBoundsArgs {
651 floor: 1,
652 initial_step: 2,
653 step_multiplier: 3,
654 index: 2,
655 size: 5
656 },
657 (3, 7)
658 )]
659 #[test_case(
660 ExponentialBucketBoundsArgs {
661 floor: 1,
662 initial_step: 2,
663 step_multiplier: 3,
664 index: 3,
665 size: 5
666 },
667 (7, 19)
668 )]
669 #[test_case(
670 ExponentialBucketBoundsArgs {
671 floor: 1,
672 initial_step: 2,
673 step_multiplier: 3,
674 index: 4,
675 size: 5
676 },
677 (19, u64::MAX)
678 )]
679 #[fuchsia::test]
680 fn test_exponential_bucket_bounds_u64(
681 args: ExponentialBucketBoundsArgs<u64>,
682 bounds: (u64, u64),
683 ) {
684 assert_eq!(exponential_bucket_bounds(args), bounds);
685 }
686
687 #[test_case(
689 ExponentialBucketBoundsArgs {
690 floor: -0.5,
691 initial_step: 0.5,
692 step_multiplier: 3.0,
693 index: 0,
694 size: 5 },
695 (f64::MIN, -0.5)
696 )]
697 #[test_case(
698 ExponentialBucketBoundsArgs {
699 floor: -0.5,
700 initial_step: 0.5,
701 step_multiplier: 3.0,
702 index: 1,
703 size: 5 },
704 (-0.5, 0.0)
705 )]
706 #[test_case(
707 ExponentialBucketBoundsArgs {
708 floor: -0.5,
709 initial_step: 0.5,
710 step_multiplier: 3.0,
711 index: 2,
712 size: 5 },
713 (0.0, 1.0)
714 )]
715 #[test_case(
716 ExponentialBucketBoundsArgs {
717 floor: -0.5,
718 initial_step: 0.5,
719 step_multiplier: 3.0,
720 index: 3,
721 size: 5 },
722 (1.0, 4.0)
723 )]
724 #[test_case(
725 ExponentialBucketBoundsArgs {
726 floor: -0.5,
727 initial_step: 0.5,
728 step_multiplier: 3.0,
729 index: 4,
730 size: 5 },
731 (4.0, f64::MAX)
732 )]
733 #[fuchsia::test]
734 fn test_exponential_bucket_bounds_f64(
735 args: ExponentialBucketBoundsArgs<f64>,
736 bounds: (f64, f64),
737 ) {
738 assert_eq!(exponential_bucket_bounds(args), bounds);
739 }
740
741 #[test_case(
744 ExponentialBucketBoundsArgs {
745 floor: -10,
746 initial_step: 2,
747 step_multiplier: 3,
748 index: 0,
749 size: 4
750 },
751 (i64::MIN, -10)
752 )]
753 #[test_case(
754 ExponentialBucketBoundsArgs {
755 floor: -10,
756 initial_step: 2,
757 step_multiplier: 3,
758 index: 1,
759 size: 4
760 },
761 (-10, -8)
762 )]
763 #[test_case(
764 ExponentialBucketBoundsArgs {
765 floor: -10,
766 initial_step: 2,
767 step_multiplier: 3,
768 index: 2,
769 size: 4
770 },
771 (-8, -4)
772 )]
773 #[test_case(
774 ExponentialBucketBoundsArgs {
775 floor: -10,
776 initial_step: 2,
777 step_multiplier: 3,
778 index: 3,
779 size: 4
780 },
781 (-4, i64::MAX)
782 )]
783 #[test_case(
784 ExponentialBucketBoundsArgs {
785 floor: -10,
786 initial_step: 2,
787 step_multiplier: 3,
788 index: 0,
789 size: 3
790 },
791 (i64::MIN, -10)
792 )]
793 #[test_case(
794 ExponentialBucketBoundsArgs {
795 floor: -10,
796 initial_step: 2,
797 step_multiplier: 3,
798 index: 1,
799 size: 3
800 },
801 (-10, -8)
802 )]
803 #[test_case(
804 ExponentialBucketBoundsArgs {
805 floor: -10,
806 initial_step: 2,
807 step_multiplier: 3,
808 index: 2,
809 size: 3
810 },
811 (-8, i64::MAX)
812 )]
813 #[test_case(
814 ExponentialBucketBoundsArgs {
815 floor: -10,
816 initial_step: 2,
817 step_multiplier: 3,
818 index: 0,
819 size: 2
820 },
821 (i64::MIN, -10)
822 )]
823 #[test_case(
824 ExponentialBucketBoundsArgs {
825 floor: -10,
826 initial_step: 2,
827 step_multiplier: 3,
828 index: 1,
829 size: 2
830 },
831 (-10, i64::MAX)
832 )]
833 #[fuchsia::test]
834 fn test_exponential_small_bucket_bounds(
835 args: ExponentialBucketBoundsArgs<i64>,
836 bounds: (i64, i64),
837 ) {
838 assert_eq!(exponential_bucket_bounds(args), bounds);
839 }
840
841 #[fuchsia::test]
842 fn render_escrowed_data() {
843 let data = InspectDataBuilder::new(
844 "a/b/c/d".try_into().unwrap(),
845 "test-url",
846 Timestamp::from_nanos(123456i64),
847 )
848 .escrowed(true)
849 .build();
850 let mut buf = String::new();
851 output_schema(&mut buf, &data).unwrap();
852 let expected = r#"a/b/c/d:
853 metadata:
854 name = root
855 component_url = test-url
856 timestamp = 123456
857 escrowed = true
858 payload: null
859"#;
860 assert_eq!(expected, buf);
861 }
862}