Skip to main content

diskann_benchmark_runner/utils/
fmt.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::{
7    collections::HashMap,
8    fmt::{Display, Write},
9};
10
11/// A 2-d table for formatting properly spaced values in a table.
12pub struct Table {
13    // The number of columns is implicitly described by the number of entries in `header`.
14    header: Box<[Box<dyn Display>]>,
15    body: HashMap<(usize, usize), Box<dyn Display>>,
16    nrows: usize,
17}
18
19impl Table {
20    pub fn new<I>(header: I, nrows: usize) -> Self
21    where
22        I: IntoIterator<Item: Display + 'static>,
23    {
24        fn as_dyn_display<T: Display + 'static>(x: T) -> Box<dyn Display> {
25            Box::new(x)
26        }
27
28        let header: Box<[_]> = header.into_iter().map(as_dyn_display).collect();
29        Self {
30            header,
31            body: HashMap::new(),
32            nrows,
33        }
34    }
35
36    pub fn nrows(&self) -> usize {
37        self.nrows
38    }
39
40    pub fn ncols(&self) -> usize {
41        self.header.len()
42    }
43
44    pub fn insert<T>(&mut self, item: T, row: usize, col: usize) -> bool
45    where
46        T: Display + 'static,
47    {
48        self.check_bounds(row, col);
49        self.body.insert((row, col), Box::new(item)).is_some()
50    }
51
52    pub fn get(&self, row: usize, col: usize) -> Option<&dyn Display> {
53        self.check_bounds(row, col);
54        self.body.get(&(row, col)).map(|x| &**x)
55    }
56
57    pub fn row(&mut self, row: usize) -> Row<'_> {
58        self.check_bounds(row, 0);
59        Row::new(self, row)
60    }
61
62    #[expect(clippy::panic, reason = "table interfaces are bounds checked")]
63    fn check_bounds(&self, row: usize, col: usize) {
64        if row >= self.nrows() {
65            panic!("row {} is out of bounds (max {})", row, self.nrows());
66        }
67        if col >= self.ncols() {
68            panic!("col {} is out of bounds (max {})", col, self.ncols());
69        }
70    }
71}
72
73pub struct Row<'a> {
74    table: &'a mut Table,
75    row: usize,
76}
77
78impl<'a> Row<'a> {
79    // A **private** constructor assuming that `row` is inbounds.
80    fn new(table: &'a mut Table, row: usize) -> Self {
81        Self { table, row }
82    }
83
84    /// Insert a value into the specified column of this row.
85    pub fn insert<T>(&mut self, item: T, col: usize) -> bool
86    where
87        T: Display + 'static,
88    {
89        self.table.insert(item, self.row, col)
90    }
91}
92
93impl Display for Table {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        const SEP: &str = ",   ";
96
97        // Compute the maximum width of each column.
98        struct Count(usize);
99
100        impl Write for Count {
101            fn write_str(&mut self, s: &str) -> std::fmt::Result {
102                self.0 += s.len();
103                Ok(())
104            }
105        }
106
107        fn formatted_size<T>(x: &T) -> usize
108        where
109            T: Display + ?Sized,
110        {
111            let mut buf = Count(0);
112            match write!(&mut buf, "{}", x) {
113                // Return the number of bytes "written",
114                Ok(()) => buf.0,
115                Err(_) => 0,
116            }
117        }
118
119        let mut widths: Vec<usize> = self.header.iter().map(formatted_size).collect();
120        for row in 0..self.nrows() {
121            for (col, width) in widths.iter_mut().enumerate() {
122                if let Some(v) = self.body.get(&(row, col)) {
123                    *width = (*width).max(formatted_size(v))
124                }
125            }
126        }
127
128        let header_width: usize = widths.iter().sum::<usize>() + (widths.len() - 1) * SEP.len();
129
130        let mut buf = String::new();
131        // Print the header.
132        std::iter::zip(widths.iter(), self.header.iter())
133            .enumerate()
134            .try_for_each(|(col, (width, head))| {
135                buf.clear();
136                write!(buf, "{}", head)?;
137                write!(f, "{:>width$}", buf)?;
138                if col + 1 != self.ncols() {
139                    write!(f, "{}", SEP)?;
140                }
141                Ok(())
142            })?;
143
144        // Banner
145        write!(f, "\n{:=>header_width$}\n", "")?;
146
147        // Write out each row.
148        for row in 0..self.nrows() {
149            for (col, width) in widths.iter_mut().enumerate() {
150                match self.body.get(&(row, col)) {
151                    Some(v) => {
152                        buf.clear();
153                        write!(buf, "{}", v)?;
154                        write!(f, "{:>width$}", buf)?;
155                    }
156                    None => write!(f, "{:>width$}", "")?,
157                }
158                if col + 1 != self.ncols() {
159                    write!(f, "{}", SEP)?;
160                } else {
161                    writeln!(f)?;
162                }
163            }
164        }
165        Ok(())
166    }
167}
168
169////////////
170// Banner //
171////////////
172
173pub(crate) struct Banner<'a>(&'a str);
174
175impl<'a> Banner<'a> {
176    pub(crate) fn new(message: &'a str) -> Self {
177        Self(message)
178    }
179}
180
181impl std::fmt::Display for Banner<'_> {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        let st = format!("# {} #", self.0);
184        let len = st.len();
185        writeln!(f, "{:#>len$}", "")?;
186        writeln!(f, "{}", st)?;
187        writeln!(f, "{:#>len$}", "")?;
188        Ok(())
189    }
190}
191
192////////////
193// Indent //
194////////////
195
196/// Indents each line of a string by a fixed number of spaces.
197///
198/// Each line is prefixed with `spaces` spaces.
199///
200/// # Examples
201///
202/// ```
203/// use diskann_benchmark_runner::utils::fmt::Indent;
204///
205/// let indented = Indent::new("hello\nworld", 4).to_string();
206/// assert_eq!(indented, "    hello\n    world");
207/// ```
208#[derive(Debug, Clone, Copy)]
209pub struct Indent<'a> {
210    string: &'a str,
211    spaces: usize,
212}
213
214impl<'a> Indent<'a> {
215    /// Create a new [`Indent`] that will prefix each line of `string` with `spaces` spaces.
216    pub fn new(string: &'a str, spaces: usize) -> Self {
217        Self { string, spaces }
218    }
219}
220
221impl std::fmt::Display for Indent<'_> {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        let spaces = self.spaces;
224
225        // We don't know ahead of time how many lines there are. To avoid appending a
226        // newline at the end of the last print, we instead add a newline to line `N-1` when
227        // writing out line `N`.
228        let mut first = true;
229        self.string.lines().try_for_each(|ln| {
230            if first {
231                first = false;
232                write!(f, "{: >spaces$}{}", "", ln)
233            } else {
234                write!(f, "\n{: >spaces$}{}", "", ln)
235            }
236        })
237    }
238}
239
240/////////////
241// Delimit //
242/////////////
243
244/// Formats an iterator with a delimiter between items and optional overrides for
245/// the final delimiter and pair formatting.
246///
247/// This is a single-use wrapper: the iterator is consumed on the first call to [`Display::fmt`].
248/// Subsequent calls will print `<missing>`.
249///
250/// Use [`Delimit::with_last`] to change the delimiter before the final item
251/// (e.g., `", and "`), which is useful for natural-language lists like
252/// `"a, b, and c"`.
253///
254/// Use [`Delimit::with_pair`] to change formatting when there are only two items.
255///
256/// # Examples
257///
258/// ```
259/// use diskann_benchmark_runner::utils::fmt::Delimit;
260///
261/// let d = Delimit::new(["a", "b", "c"], ", ").with_last(", and ");
262/// assert_eq!(d.to_string(), "a, b, and c");
263///
264/// let d = Delimit::new(["a", "b"], ", ").with_last(", and ");
265/// assert_eq!(d.to_string(), "a, and b");
266///
267/// let d = Delimit::new(["a", "b"], ", ")
268///     .with_last(", and ")
269///     .with_pair(" and ");
270/// assert_eq!(d.to_string(), "a and b");
271/// ```
272pub struct Delimit<'a, I> {
273    itr: std::cell::Cell<Option<I>>,
274    delimiter: &'a str,
275    last: &'a str,
276    pair: Option<&'a str>,
277}
278
279impl<'a, I> Delimit<'a, I> {
280    /// Create a new [`Delimit`] from an iterable and a delimiter.
281    ///
282    /// By default, the same delimiter is used between every item. Use
283    /// [`Self::with_last`] and [`Self::with_pair`] to opt into special handling
284    /// before the final item or for pairs.
285    pub fn new(itr: impl IntoIterator<IntoIter = I>, delimiter: &'a str) -> Self {
286        Self {
287            itr: std::cell::Cell::new(Some(itr.into_iter())),
288            delimiter,
289            last: delimiter,
290            pair: None,
291        }
292    }
293
294    /// Use `last` before the final item when formatting three or more items.
295    pub fn with_last(mut self, last: &'a str) -> Self {
296        self.last = last;
297        self
298    }
299
300    /// Use `pair` when formatting exactly two items.
301    pub fn with_pair(mut self, pair: &'a str) -> Self {
302        self.pair = Some(pair);
303        self
304    }
305}
306
307impl<I> std::fmt::Display for Delimit<'_, I>
308where
309    I: Iterator<Item: std::fmt::Display>,
310{
311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        let Some(mut itr) = self.itr.take() else {
313            return write!(f, "<missing>");
314        };
315
316        let mut count = 0;
317        let mut current = if let Some(item) = itr.next() {
318            item
319        } else {
320            // Empty iterator
321            return Ok(());
322        };
323
324        loop {
325            match itr.next() {
326                None => {
327                    // "current" is the last item. If it is also the first, we write it
328                    // directly.
329                    //
330                    // Otherwise, we check if we've just emitted a single item so far and
331                    // use `pair` if available. Otherwise, we try `last` and finally
332                    // `delimiter`.
333                    let delimiter = if count == 0 {
334                        ""
335                    } else if count == 1 {
336                        self.pair.unwrap_or(self.last)
337                    } else {
338                        self.last
339                    };
340
341                    return write!(f, "{}{}", delimiter, current);
342                }
343                Some(next) => {
344                    // There is at least one item next. We print "current" and move on.
345                    let delimiter = if count == 0 { "" } else { self.delimiter };
346
347                    write!(f, "{}{}", delimiter, current)?;
348                    count += 1;
349                    current = next;
350                }
351            }
352        }
353    }
354}
355
356///////////
357// Quote //
358///////////
359
360/// Wraps a value in double quotes when displayed.
361///
362/// # Examples
363///
364/// ```
365/// use diskann_benchmark_runner::utils::fmt::Quote;
366///
367/// assert_eq!(Quote("hello").to_string(), "\"hello\"");
368/// assert_eq!(Quote(42).to_string(), "\"42\"");
369/// ```
370#[derive(Debug, Clone, Copy)]
371pub struct Quote<T>(pub T);
372
373impl<T> std::fmt::Display for Quote<T>
374where
375    T: std::fmt::Display,
376{
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        write!(f, "\"{}\"", self.0)
379    }
380}
381
382//////////////
383// KeyValue //
384//////////////
385
386enum MaybeLazy<'a> {
387    Lazy(&'a dyn std::fmt::Display),
388    Debug(&'a dyn std::fmt::Debug),
389    Eager(String),
390}
391
392impl std::fmt::Display for MaybeLazy<'_> {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        match self {
395            Self::Lazy(lazy) => write!(f, "{}", lazy),
396            Self::Debug(debug) => write!(f, "{:?}", debug),
397            Self::Eager(s) => f.write_str(s),
398        }
399    }
400}
401
402impl std::fmt::Debug for MaybeLazy<'_> {
403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404        struct AsDisplay<'a>(&'a dyn std::fmt::Display);
405        impl std::fmt::Debug for AsDisplay<'_> {
406            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407                self.0.fmt(f)
408            }
409        }
410
411        match self {
412            Self::Lazy(o) => {
413                let as_display = AsDisplay(o);
414                f.debug_tuple("MaybeLazy::Lazy").field(&as_display).finish()
415            }
416            Self::Debug(o) => f.debug_tuple("MaybeLazy::Debug").field(o).finish(),
417            Self::Eager(s) => f.debug_tuple("MaybeLazy::Eager").field(s).finish(),
418        }
419    }
420}
421
422/// Display a dynamic list of key-value pairs in a YAML-like style.
423///
424/// Keys are left-aligned and single-line values are aligned into a common column
425/// just past the longest key. A value that renders to multiple lines (for example
426/// a nested [`KeyValue`] or any other multi-line block) is placed on the lines
427/// following its key, indented by two spaces. This keeps nested structures visibly
428/// subordinate to their key regardless of whether the value is itself a key-value
429/// list or an opaque block.
430///
431/// # Examples
432///
433/// ```
434/// use diskann_benchmark_runner::utils::fmt::KeyValue;
435///
436/// let mut kv = KeyValue::new();
437/// kv.push("a", &1);
438/// kv.push("hello", &"world");
439///
440/// let expected = "a:     1\nhello: world";
441///
442/// assert_eq!(kv.to_string(), expected);
443/// ```
444///
445/// Multi-line values are indented beneath their key:
446///
447/// ```
448/// use diskann_benchmark_runner::utils::fmt::KeyValue;
449///
450/// let mut inner = KeyValue::new();
451/// inner.push("x", &1);
452/// inner.push("yy", &2);
453/// let inner = inner.to_string();
454///
455/// let mut kv = KeyValue::new();
456/// kv.push("name", &"example");
457/// kv.push("nested", &inner);
458///
459/// let expected = "name:   example\nnested:\n  x:  1\n  yy: 2";
460///
461/// assert_eq!(kv.to_string(), expected);
462/// ```
463#[derive(Debug, Default)]
464pub struct KeyValue<'a> {
465    kv: Vec<(&'a str, Nesting, MaybeLazy<'a>)>,
466    max_key_length: usize,
467}
468
469#[derive(Debug, Clone, Copy)]
470enum Nesting {
471    Auto,
472    Forced,
473}
474
475impl<'a> KeyValue<'a> {
476    /// Create a new empty [`KeyValue`] formatter.
477    pub fn new() -> Self {
478        Self {
479            kv: Vec::new(),
480            max_key_length: 0,
481        }
482    }
483
484    /// Push the key-value pair to `self` for formatting.
485    pub fn push(&mut self, key: &'a str, value: &'a dyn std::fmt::Display) {
486        self.max_key_length = self.max_key_length.max(key.len());
487        self.kv.push((key, Nesting::Auto, MaybeLazy::Lazy(value)))
488    }
489
490    /// Push the key-value pair to `self` for formatting via [`std::fmt::Debug`].
491    pub fn push_debug(&mut self, key: &'a str, value: &'a dyn std::fmt::Debug) {
492        self.max_key_length = self.max_key_length.max(key.len());
493        self.kv.push((key, Nesting::Auto, MaybeLazy::Debug(value)))
494    }
495
496    /// Push the key-value pair to `self` for formatting - eagerly formatting `value`.
497    pub fn push_eager<D>(&mut self, key: &'a str, value: D)
498    where
499        D: std::fmt::Display,
500    {
501        self.max_key_length = self.max_key_length.max(key.len());
502        self.kv
503            .push((key, Nesting::Auto, MaybeLazy::Eager(value.to_string())))
504    }
505
506    /// Push the key-value pair to `self` for formatting - eagerly formatting `value`.
507    pub fn push_eager_nested<D>(&mut self, key: &'a str, value: D)
508    where
509        D: std::fmt::Display,
510    {
511        self.max_key_length = self.max_key_length.max(key.len());
512        self.kv
513            .push((key, Nesting::Forced, MaybeLazy::Eager(value.to_string())))
514    }
515
516    /// Return the number of pending key-value pairs.
517    pub fn len(&self) -> usize {
518        self.kv.len()
519    }
520
521    /// Return `true` if there are no key-value pairs.
522    pub fn is_empty(&self) -> bool {
523        self.kv.is_empty()
524    }
525}
526
527impl std::fmt::Display for KeyValue<'_> {
528    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529        let width = self.max_key_length;
530        let mut prefix = "";
531        for (k, nesting, v) in self.kv.iter() {
532            let rendered = v.to_string();
533            let nest = match nesting {
534                Nesting::Forced => true,
535                Nesting::Auto => rendered.contains('\n'),
536            };
537
538            if nest {
539                write!(f, "{}{}:\n{}", prefix, k, Indent::new(&rendered, 2))?
540            } else {
541                // Left-align the key and pad so that all single-line values line up in a
542                // column one space past the longest key's colon.
543                let pad = (width + 1).saturating_sub(k.len());
544                write!(f, "{}{}:{:pad$}{rendered}", prefix, k, "")?;
545            }
546            prefix = "\n";
547        }
548        Ok(())
549    }
550}
551
552///////////
553// Tests //
554///////////
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    #[test]
561    fn test_banner() {
562        let b = Banner::new("hello world");
563        let s = b.to_string();
564
565        let expected = "###############\n\
566                        # hello world #\n\
567                        ###############\n";
568
569        assert_eq!(s, expected);
570
571        let b = Banner::new("");
572        let s = b.to_string();
573
574        let expected = "####\n\
575                        #  #\n\
576                        ####\n";
577
578        assert_eq!(s, expected);
579
580        let b = Banner::new("foo");
581        let s = b.to_string();
582
583        let expected = "#######\n\
584                        # foo #\n\
585                        #######\n";
586
587        assert_eq!(s, expected);
588    }
589
590    #[test]
591    fn test_format() {
592        // One column
593        {
594            let headers = ["h 0"];
595            let mut table = Table::new(headers, 3);
596            table.insert("a", 0, 0);
597            table.insert("hello world", 1, 0);
598            table.insert(62, 2, 0);
599
600            let s = table.to_string();
601            let expected = r#"
602        h 0
603===========
604          a
605hello world
606         62
607"#;
608            assert_eq!(s, expected.strip_prefix('\n').unwrap());
609        }
610
611        // Two columns
612        {
613            let headers = ["a really really long header", "h1"];
614            let mut table = Table::new(headers, 3);
615            table.insert("a", 0, 0);
616            table.insert("b", 0, 1);
617
618            table.insert("hello world", 1, 0);
619            table.insert("hello world version 2", 1, 1);
620
621            table.insert(7, 2, 0);
622            table.insert("bar", 2, 1);
623
624            let s = table.to_string();
625            let expected = r#"
626a really really long header,                      h1
627====================================================
628                          a,                       b
629                hello world,   hello world version 2
630                          7,                     bar
631"#;
632            assert_eq!(s, expected.strip_prefix('\n').unwrap());
633        }
634    }
635
636    #[test]
637    fn test_row_api() {
638        let mut table = Table::new(["a", "b", "c"], 2);
639        let mut row = table.row(0);
640        row.insert(1, 0);
641        row.insert("long", 1);
642        row.insert("s", 2);
643
644        let mut row = table.row(1);
645        row.insert("string", 0);
646        row.insert(2, 1);
647        row.insert(3, 2);
648
649        let s = table.to_string();
650
651        let expected = r#"
652     a,      b,   c
653===================
654     1,   long,   s
655string,      2,   3
656"#;
657        assert_eq!(s, expected.strip_prefix('\n').unwrap());
658    }
659
660    #[test]
661    fn missing_values() {
662        let mut table = Table::new(["a", "loong", "c"], 1);
663        let mut row = table.row(0);
664        row.insert("string", 0);
665        row.insert("string", 2);
666
667        let s = table.to_string();
668        let expected = r#"
669     a,   loong,        c
670=========================
671string,        ,   string
672"#;
673        assert_eq!(s, expected.strip_prefix('\n').unwrap());
674    }
675
676    #[test]
677    #[should_panic(expected = "row 3 is out of bounds (max 2)")]
678    fn test_panic_row() {
679        let mut table = Table::new([1, 2, 3], 2);
680        let _ = table.row(3);
681    }
682
683    #[test]
684    #[should_panic(expected = "col 3 is out of bounds (max 2)")]
685    fn test_panic_col() {
686        let mut table = Table::new([1, 2], 1);
687        let mut row = table.row(0);
688        row.insert(1, 3);
689    }
690
691    #[test]
692    fn test_indent_single_line() {
693        let s = Indent::new("hello", 4).to_string();
694        assert_eq!(s, "    hello");
695    }
696
697    #[test]
698    fn test_indent_multi_line() {
699        let s = Indent::new("hello\nworld\nfoo", 2).to_string();
700        assert_eq!(s, "  hello\n  world\n  foo");
701    }
702
703    #[test]
704    fn test_indent_zero_spaces() {
705        let s = Indent::new("hello\nworld", 0).to_string();
706        assert_eq!(s, "hello\nworld");
707    }
708
709    #[test]
710    fn test_indent_empty_string() {
711        let s = Indent::new("", 4).to_string();
712        assert_eq!(s, "");
713    }
714
715    #[test]
716    fn test_delimit_empty() {
717        let d = Delimit::new(std::iter::empty::<&str>(), ", ");
718        assert_eq!(d.to_string(), "");
719    }
720
721    #[test]
722    fn test_delimit_single_item() {
723        let d = Delimit::new(["a"], ", ").with_last(", and ");
724        assert_eq!(d.to_string(), "a");
725    }
726
727    #[test]
728    fn test_delimit_two_items_with_last() {
729        let d = Delimit::new(["a", "b"], ", ").with_last(", and ");
730        assert_eq!(d.to_string(), "a, and b");
731    }
732
733    #[test]
734    fn test_delimit_two_items_with_pair() {
735        let d = Delimit::new(["a", "b"], ", ")
736            .with_last(", and ")
737            .with_pair(" and ");
738        assert_eq!(d.to_string(), "a and b");
739    }
740
741    #[test]
742    fn test_delimit_three_items_with_last() {
743        let d = Delimit::new(["a", "b", "c"], ", ")
744            .with_last(", and ")
745            .with_pair(" and ");
746        assert_eq!(d.to_string(), "a, b, and c");
747    }
748
749    #[test]
750    fn test_delimit_without_last() {
751        let d = Delimit::new(["x", "y", "z"], " | ");
752        assert_eq!(d.to_string(), "x | y | z");
753    }
754
755    #[test]
756    fn test_delimit_second_display_prints_missing() {
757        let d = Delimit::new(["a", "b"], ", ");
758        assert_eq!(d.to_string(), "a, b");
759        assert_eq!(d.to_string(), "<missing>");
760    }
761
762    #[test]
763    fn test_quote() {
764        assert_eq!(Quote("hello").to_string(), "\"hello\"");
765    }
766
767    #[test]
768    fn test_quote_with_integer() {
769        assert_eq!(Quote(42).to_string(), "\"42\"");
770    }
771
772    #[test]
773    fn test_delimit_with_quote() {
774        let d = Delimit::new(["topk", "range"].iter().map(Quote), ", ")
775            .with_last(", and ")
776            .with_pair(" and ");
777        assert_eq!(d.to_string(), "\"topk\" and \"range\"");
778    }
779
780    //----------//
781    // KeyValue //
782    //----------//
783
784    struct Snoop;
785
786    impl std::fmt::Display for Snoop {
787        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
788            write!(f, "display")
789        }
790    }
791
792    impl std::fmt::Debug for Snoop {
793        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794            write!(f, "debug")
795        }
796    }
797
798    // Strip a preceding newline if it exists.
799    fn process(x: &str) -> &str {
800        let x = x.strip_prefix('\n').unwrap_or(x);
801        x.strip_suffix('\n').unwrap_or(x)
802    }
803
804    #[test]
805    fn test_key_value_empty() {
806        let kv = KeyValue::new();
807        assert_eq!(kv.to_string(), "");
808    }
809
810    #[test]
811    fn test_key_value_single_pair() {
812        let mut kv = KeyValue::new();
813        kv.push("a", &1);
814        assert_eq!(kv.to_string(), "a: 1");
815    }
816
817    #[test]
818    fn test_key_value_aligns_values() {
819        let mut kv = KeyValue::new();
820        kv.push("a", &1);
821        kv.push("hello", &"world");
822        kv.push("display", &Snoop);
823        kv.push_debug("debug", &Snoop);
824        let expected = process(
825            r#"
826a:       1
827hello:   world
828display: display
829debug:   debug
830"#,
831        );
832        assert_eq!(kv.to_string(), expected);
833    }
834
835    #[test]
836    fn test_key_value_push_eager() {
837        let mut kv = KeyValue::new();
838        kv.push_eager("a", 1);
839        kv.push_eager("hello", "world");
840
841        let expected = process(
842            r#"
843a:     1
844hello: world
845"#,
846        );
847
848        assert_eq!(kv.to_string(), expected);
849    }
850
851    #[test]
852    fn test_key_value_multiline_value_is_indented() {
853        let mut inner = KeyValue::new();
854        inner.push("x", &1);
855        inner.push("yy", &2);
856        let inner = inner.to_string();
857
858        let mut kv = KeyValue::new();
859        kv.push("name", &"example");
860        kv.push("nested", &inner);
861        kv.push("another line", &1);
862
863        let expected = process(
864            r#"
865name:         example
866nested:
867  x:  1
868  yy: 2
869another line: 1
870"#,
871        );
872
873        assert_eq!(kv.to_string(), expected);
874    }
875
876    #[test]
877    fn maybe_lazy_debug() {
878        let x = MaybeLazy::Lazy(&Snoop);
879        let dbg = format!("{:?}", x);
880        assert_eq!(dbg, "MaybeLazy::Lazy(display)");
881
882        let x = MaybeLazy::Debug(&Snoop);
883        let dbg = format!("{:?}", x);
884        assert_eq!(dbg, "MaybeLazy::Debug(debug)");
885
886        let x = MaybeLazy::Eager("hello".into());
887        let dbg = format!("{:?}", x);
888        assert_eq!(dbg, "MaybeLazy::Eager(\"hello\")");
889    }
890}