libtest2_harness/notify/
summary.rs1use 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 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 status = Some(MessageKind::Error);
42 failures.insert(name, Some("test found that never started"));
43 }
44 if !case_status.completed {
45 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 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 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}