common/
testing.rs

1use crate::color::{spec_bold_color, spec_color};
2use crate::utils::str_from_utf8;
3use std::fmt;
4use std::io::{Error, ErrorKind, Result, Write};
5use termcolor::{Color, ColorSpec, WriteColor};
6
7pub fn unpack_io_error(error: Error) -> (ErrorKind, String) {
8    (error.kind(), error.to_string())
9}
10
11#[derive(Default)]
12pub struct ColoredOuput {
13    spec: ColorSpec,
14    chunks: Vec<OutputChunk>,
15}
16
17impl ColoredOuput {
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    pub fn chunks(&self) -> &Vec<OutputChunk> {
23        &self.chunks
24    }
25}
26
27impl Write for ColoredOuput {
28    fn write(&mut self, buf: &[u8]) -> Result<usize> {
29        let spec = &self.spec;
30        let value = str_from_utf8(buf)?;
31
32        if let Some(chunk) = self.chunks.last_mut().filter(|chunk| &chunk.spec == spec) {
33            chunk.value += value;
34        } else {
35            self.chunks.push(OutputChunk {
36                spec: self.spec.clone(),
37                value: value.into(),
38            })
39        }
40
41        Ok(buf.len())
42    }
43
44    fn flush(&mut self) -> Result<()> {
45        Ok(())
46    }
47}
48
49impl WriteColor for ColoredOuput {
50    fn supports_color(&self) -> bool {
51        true
52    }
53
54    fn set_color(&mut self, spec: &ColorSpec) -> Result<()> {
55        self.spec = spec.clone();
56        Ok(())
57    }
58
59    fn reset(&mut self) -> Result<()> {
60        self.spec = ColorSpec::new();
61        Ok(())
62    }
63}
64
65#[derive(PartialEq, Clone)]
66pub struct OutputChunk {
67    pub spec: ColorSpec,
68    pub value: String,
69}
70
71impl OutputChunk {
72    pub fn plain(value: &str) -> Self {
73        Self {
74            spec: ColorSpec::new(),
75            value: value.into(),
76        }
77    }
78
79    pub fn color(color: Color, value: &str) -> Self {
80        Self {
81            spec: spec_color(color),
82            value: value.into(),
83        }
84    }
85
86    pub fn bold_color(color: Color, value: &str) -> Self {
87        Self {
88            spec: spec_bold_color(color),
89            value: value.into(),
90        }
91    }
92}
93
94impl fmt::Debug for OutputChunk {
95    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(fmt, "OutputChunk::")?;
97
98        match (self.spec.fg(), self.spec.bold()) {
99            (None, _) => write!(fmt, "plain(")?,
100            (Some(color), true) => write!(fmt, "bold_color(Color::{:?}, ", color)?,
101            (Some(color), false) => write!(fmt, "color(Color::{:?}, ", color)?,
102        }
103
104        write!(fmt, "{:?})", self.value.replace("\n", "\\n"))
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use ntest::*;
112
113    #[test]
114    fn unpack_io_error() {
115        assert_eq!(
116            super::unpack_io_error(Error::new(ErrorKind::Other, "test")),
117            (ErrorKind::Other, "test".into())
118        );
119    }
120
121    mod colored_output {
122        use super::*;
123
124        #[test]
125        fn supports_color() {
126            assert_true!(ColoredOuput::new().supports_color());
127        }
128
129        #[test]
130        fn write() {
131            let mut output = ColoredOuput::new();
132
133            write!(output, "a").unwrap();
134            write!(output, "b").unwrap();
135            output.set_color(&spec_color(Color::Red)).unwrap();
136            write!(output, "c").unwrap();
137            write!(output, "d").unwrap();
138            output.set_color(&spec_bold_color(Color::Blue)).unwrap();
139            write!(output, "e").unwrap();
140            write!(output, "f").unwrap();
141            output.reset().unwrap();
142            write!(output, "g").unwrap();
143            output.flush().unwrap();
144
145            assert_eq!(
146                output.chunks,
147                &[
148                    OutputChunk::plain("ab"),
149                    OutputChunk::color(Color::Red, "cd"),
150                    OutputChunk::bold_color(Color::Blue, "ef"),
151                    OutputChunk::plain("g"),
152                ]
153            );
154        }
155    }
156
157    mod output_chunk {
158        use super::*;
159        use test_case::test_case;
160
161        #[test_case(OutputChunk::plain("ab"),                   ColorSpec::new(),             "ab" ; "plain")]
162        #[test_case(OutputChunk::color(Color::Red, "cd"),       spec_color(Color::Red),       "cd" ; "color")]
163        #[test_case(OutputChunk::bold_color(Color::Blue, "ef"), spec_bold_color(Color::Blue), "ef" ; "bold color")]
164        fn create(chunk: OutputChunk, spec: ColorSpec, value: &str) {
165            assert_eq!(chunk.spec, spec);
166            assert_eq!(chunk.value, value);
167        }
168
169        #[test_case(OutputChunk::plain("a\nb"),                   r#"OutputChunk::plain("a\\nb")"#                   ; "plain")]
170        #[test_case(OutputChunk::color(Color::Red, "c\nd"),       r#"OutputChunk::color(Color::Red, "c\\nd")"#       ; "color")]
171        #[test_case(OutputChunk::bold_color(Color::Blue, "e\nf"), r#"OutputChunk::bold_color(Color::Blue, "e\\nf")"# ; "bold color")]
172        fn debug(chunk: OutputChunk, result: &str) {
173            assert_eq!(format!("{:?}", chunk), result);
174        }
175    }
176}