libtest2_harness/notify/
summary.rs

1use super::event::CaseMessage;
2use super::Event;
3use super::MessageKind;
4use super::FAILED;
5use super::OK;
6
7#[derive(Default, Clone, Debug)]
8pub(crate) struct Summary {
9    num_run: usize,
10    /// Number of tests and benchmarks that were filtered out (either by the
11    /// filter-in pattern or by `--skip` arguments).
12    num_filtered_out: usize,
13
14    status: std::collections::HashMap<String, CaseStatus>,
15    elapsed_s: Option<super::Elapsed>,
16}
17
18impl Summary {
19    pub(crate) fn get_kind(&self, name: &str) -> Option<MessageKind> {
20        let status = self.status.get(name)?;
21        find_run_status(status)
22    }
23
24    pub(crate) fn write_start(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
25        let s = if self.num_run == 1 { "" } else { "s" };
26
27        writeln!(writer)?;
28        writeln!(writer, "running {} test{s}", self.num_run)?;
29        Ok(())
30    }
31
32    pub(crate) fn write_complete(&self, writer: &mut dyn ::std::io::Write) -> std::io::Result<()> {
33        let mut num_passed = 0;
34        let mut num_failed = 0;
35        let mut num_ignored = 0;
36        let mut failures = std::collections::BTreeMap::new();
37        for (name, case_status) in &self.status {
38            let mut status = find_run_status(case_status);
39            if !case_status.started {
40                // Even override `Ignored`
41                status = Some(MessageKind::Error);
42                failures.insert(name, Some("test found that never started"));
43            }
44            if !case_status.completed {
45                // Even override `Ignored`
46                status = Some(MessageKind::Error);
47                failures.insert(name, Some("test never completed"));
48            }
49            match status {
50                Some(MessageKind::Ignored) => num_ignored += 1,
51                Some(MessageKind::Error) => {
52                    num_failed += 1;
53                    for event in &case_status.messages {
54                        if Some(event.kind) == status {
55                            failures.insert(name, event.message.as_deref());
56                        }
57                    }
58                }
59                None => num_passed += 1,
60            }
61        }
62
63        let has_failed = 0 < num_failed;
64
65        let (summary, summary_style) = if has_failed {
66            ("FAILED", FAILED)
67        } else {
68            ("ok", OK)
69        };
70        let num_filtered_out = self.num_filtered_out;
71        let elapsed_s = self.elapsed_s;
72
73        if has_failed {
74            writeln!(writer)?;
75            writeln!(writer, "failures:")?;
76            writeln!(writer)?;
77
78            // Print messages of all tests
79            for (name, msg) in &failures {
80                if let Some(msg) = msg {
81                    writeln!(writer, "---- {name} ----")?;
82                    writeln!(writer, "{msg}")?;
83                    writeln!(writer)?;
84                }
85            }
86
87            // Print summary list of failed tests
88            writeln!(writer)?;
89            writeln!(writer, "failures:")?;
90            for name in failures.keys() {
91                writeln!(writer, "    {name}")?;
92            }
93        }
94        writeln!(writer)?;
95        let finished = if let Some(elapsed_s) = elapsed_s {
96            format!("; finished in {elapsed_s}")
97        } else {
98            "".to_owned()
99        };
100        writeln!(
101                    writer,
102                    "test result: {summary_style}{summary}{summary_style:#}. {num_passed} passed; {num_failed} failed; {num_ignored} ignored; \
103                        {num_filtered_out} filtered out{finished}",
104                )?;
105        writeln!(writer)?;
106
107        Ok(())
108    }
109}
110
111impl super::Notifier for Summary {
112    fn notify(&mut self, event: Event) -> std::io::Result<()> {
113        match event {
114            Event::DiscoverStart(_) => {}
115            Event::DiscoverCase(inner) => {
116                if inner.selected {
117                    self.num_run += 1;
118                } else {
119                    self.num_filtered_out += 1;
120                }
121            }
122            Event::DiscoverComplete(_) => {}
123            Event::RunStart(_) => {}
124            Event::CaseStart(inner) => {
125                self.status.entry(inner.name).or_default().started = true;
126            }
127            Event::CaseMessage(inner) => {
128                self.status
129                    .entry(inner.name.clone())
130                    .or_default()
131                    .messages
132                    .push(inner);
133            }
134            Event::CaseComplete(inner) => {
135                self.status.entry(inner.name).or_default().completed = true;
136            }
137            Event::RunComplete(inner) => {
138                self.elapsed_s = inner.elapsed_s;
139            }
140        }
141        Ok(())
142    }
143}
144
145fn find_run_status(case_status: &CaseStatus) -> Option<MessageKind> {
146    let mut status = None;
147    for event in &case_status.messages {
148        status = status.max(Some(event.kind));
149    }
150    status
151}
152
153#[derive(Default, Clone, Debug)]
154struct CaseStatus {
155    messages: Vec<CaseMessage>,
156    started: bool,
157    completed: bool,
158}