Skip to main content

nextest_runner/reporter/
imp.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Prints out and aggregates test execution statuses.
5//!
6//! The main structure in this module is [`TestReporter`].
7
8use super::{
9    DisplayConfig, DisplayerKind, FinalStatusLevel, MaxProgressRunning, StatusLevel,
10    TestOutputDisplay,
11    displayer::{DisplayReporter, DisplayReporterBuilder},
12};
13use crate::{
14    config::core::EvaluatableProfile,
15    errors::WriteEventError,
16    helpers::progress::ShowTerminalProgress,
17    list::TestList,
18    record::{ShortestRunIdPrefix, StoreSizes},
19    redact::Redactor,
20    reporter::{
21        aggregator::EventAggregator, displayer::ShowProgress, events::*,
22        structured::StructuredReporter,
23    },
24    write_str::WriteStr,
25};
26use std::time::Duration;
27
28/// Statistics returned by the reporter after a test run completes.
29#[derive(Clone, Debug, Default)]
30pub struct ReporterStats {
31    /// The sizes of the recording written to disk (compressed and uncompressed), or `None` if
32    /// recording was not enabled or an error occurred.
33    pub recording_sizes: Option<StoreSizes>,
34    /// Information captured from the `RunFinished` event.
35    pub run_finished: Option<RunFinishedInfo>,
36}
37
38/// Information captured from the `RunFinished` event.
39///
40/// This struct groups together data that is always available together: if we
41/// receive a `RunFinished` event, we have both the stats and elapsed time.
42#[derive(Clone, Copy, Debug)]
43pub struct RunFinishedInfo {
44    /// Statistics about the run.
45    pub stats: RunFinishedStats,
46    /// Total elapsed time for the run.
47    pub elapsed: Duration,
48    /// The number of tests that were outstanding but not seen during this rerun.
49    ///
50    /// This is `None` if this was not a rerun. A value of `Some(0)` means all
51    /// outstanding tests from the rerun chain were seen during this run (and
52    /// either passed or failed).
53    pub outstanding_not_seen_count: Option<usize>,
54}
55
56/// Output destination for the reporter.
57///
58/// This is usually a terminal, but can be a writer for paged output or an
59/// in-memory buffer for tests.
60pub enum ReporterOutput<'a> {
61    /// Produce output on the terminal (stderr).
62    ///
63    /// If the terminal isn't piped, produce output to a progress bar.
64    Terminal,
65
66    /// Write output to a `WriteStr` implementation (e.g., for pager support or
67    /// an in-memory buffer for tests).
68    Writer {
69        /// The writer to use for output.
70        writer: &'a mut (dyn WriteStr + Send),
71        /// Whether to use unicode characters for output.
72        ///
73        /// The caller should determine this based on the actual output
74        /// destination (e.g., by checking `supports_unicode::on()` for the
75        /// appropriate stream).
76        use_unicode: bool,
77    },
78}
79
80/// Test reporter builder.
81#[derive(Debug, Default)]
82pub struct ReporterBuilder {
83    no_capture: bool,
84    should_colorize: bool,
85    failure_output: Option<TestOutputDisplay>,
86    success_output: Option<TestOutputDisplay>,
87    status_level: Option<StatusLevel>,
88    final_status_level: Option<FinalStatusLevel>,
89
90    verbose: bool,
91    show_progress: ShowProgress,
92    no_output_indent: bool,
93    max_progress_running: MaxProgressRunning,
94    redactor: Redactor,
95}
96
97impl ReporterBuilder {
98    /// Sets no-capture mode.
99    ///
100    /// In this mode, `failure_output` and `success_output` will be ignored, and `status_level`
101    /// will be at least [`StatusLevel::Pass`].
102    pub fn set_no_capture(&mut self, no_capture: bool) -> &mut Self {
103        self.no_capture = no_capture;
104        self
105    }
106
107    /// Set to true if the reporter should colorize output.
108    pub fn set_colorize(&mut self, should_colorize: bool) -> &mut Self {
109        self.should_colorize = should_colorize;
110        self
111    }
112
113    /// Sets the conditions under which test failures are output.
114    pub fn set_failure_output(&mut self, failure_output: TestOutputDisplay) -> &mut Self {
115        self.failure_output = Some(failure_output);
116        self
117    }
118
119    /// Sets the conditions under which test successes are output.
120    pub fn set_success_output(&mut self, success_output: TestOutputDisplay) -> &mut Self {
121        self.success_output = Some(success_output);
122        self
123    }
124
125    /// Sets the kinds of statuses to output.
126    pub fn set_status_level(&mut self, status_level: StatusLevel) -> &mut Self {
127        self.status_level = Some(status_level);
128        self
129    }
130
131    /// Sets the kinds of statuses to output at the end of the run.
132    pub fn set_final_status_level(&mut self, final_status_level: FinalStatusLevel) -> &mut Self {
133        self.final_status_level = Some(final_status_level);
134        self
135    }
136
137    /// Sets verbose output.
138    pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
139        self.verbose = verbose;
140        self
141    }
142
143    /// Sets the way of displaying progress.
144    pub fn set_show_progress(&mut self, show_progress: ShowProgress) -> &mut Self {
145        self.show_progress = show_progress;
146        self
147    }
148
149    /// Set to true to disable indentation of captured test output.
150    pub fn set_no_output_indent(&mut self, no_output_indent: bool) -> &mut Self {
151        self.no_output_indent = no_output_indent;
152        self
153    }
154
155    /// Sets the maximum number of running tests to display in the progress bar.
156    ///
157    /// When more tests are running than this limit, only the first N tests are shown
158    /// with a summary line indicating how many more tests are running.
159    pub fn set_max_progress_running(
160        &mut self,
161        max_progress_running: MaxProgressRunning,
162    ) -> &mut Self {
163        self.max_progress_running = max_progress_running;
164        self
165    }
166
167    /// Sets the redactor for snapshot testing.
168    pub fn set_redactor(&mut self, redactor: Redactor) -> &mut Self {
169        self.redactor = redactor;
170        self
171    }
172}
173
174impl ReporterBuilder {
175    /// Creates a new test reporter.
176    pub fn build<'a>(
177        &self,
178        test_list: &TestList,
179        profile: &EvaluatableProfile<'a>,
180        show_term_progress: ShowTerminalProgress,
181        output: ReporterOutput<'a>,
182        structured_reporter: StructuredReporter<'a>,
183    ) -> Reporter<'a> {
184        let aggregator = EventAggregator::new(test_list.mode(), profile);
185
186        let display_reporter = DisplayReporterBuilder {
187            mode: test_list.mode(),
188            default_filter: profile.default_filter().clone(),
189            display_config: DisplayConfig {
190                show_progress: self.show_progress,
191                no_capture: self.no_capture,
192                status_level: self.status_level,
193                final_status_level: self.final_status_level,
194                profile_status_level: profile.status_level(),
195                profile_final_status_level: profile.final_status_level(),
196            },
197            run_count: test_list.run_count(),
198            success_output: self.success_output,
199            failure_output: self.failure_output,
200            should_colorize: self.should_colorize,
201            verbose: self.verbose,
202            no_output_indent: self.no_output_indent,
203            max_progress_running: self.max_progress_running,
204            show_term_progress,
205            displayer_kind: DisplayerKind::Live,
206            redactor: self.redactor.clone(),
207        }
208        .build(output);
209
210        Reporter {
211            display_reporter,
212            structured_reporter,
213            metadata_reporter: aggregator,
214            run_finished: None,
215        }
216    }
217}
218
219/// Functionality to report test results to stderr, JUnit, and/or structured,
220/// machine-readable results to stdout.
221pub struct Reporter<'a> {
222    /// Used to display results to standard error.
223    display_reporter: DisplayReporter<'a>,
224    /// Used to aggregate events for JUnit reports written to disk
225    metadata_reporter: EventAggregator<'a>,
226    /// Used to emit test events in machine-readable format(s) to stdout
227    structured_reporter: StructuredReporter<'a>,
228    /// Information captured from the RunFinished event.
229    run_finished: Option<RunFinishedInfo>,
230}
231
232impl<'a> Reporter<'a> {
233    /// Report a test event.
234    pub fn report_event(&mut self, event: ReporterEvent<'a>) -> Result<(), WriteEventError> {
235        match event {
236            ReporterEvent::Tick => {
237                self.tick();
238                Ok(())
239            }
240            ReporterEvent::Test(event) => self.write_event(event),
241        }
242    }
243
244    /// Mark the reporter done.
245    ///
246    /// Returns statistics about the test run, including the size of the
247    /// recording if recording was enabled.
248    pub fn finish(mut self) -> ReporterStats {
249        self.display_reporter.finish();
250        let recording_sizes = self.structured_reporter.finish();
251        ReporterStats {
252            recording_sizes,
253            run_finished: self.run_finished,
254        }
255    }
256
257    /// Sets the unique prefix for the run ID.
258    ///
259    /// This is used to highlight the unique prefix portion of the run ID
260    /// in the `RunStarted` output when a recording session is active.
261    pub fn set_run_id_unique_prefix(&mut self, prefix: ShortestRunIdPrefix) {
262        self.display_reporter.set_run_id_unique_prefix(prefix);
263    }
264
265    // ---
266    // Helper methods
267    // ---
268
269    /// Tick the reporter, updating displayed state.
270    fn tick(&mut self) {
271        self.display_reporter.tick();
272    }
273
274    /// Report this test event to the given writer.
275    fn write_event(&mut self, event: Box<TestEvent<'a>>) -> Result<(), WriteEventError> {
276        // Capture run finished info before passing to reporters.
277        if let TestEventKind::RunFinished {
278            run_stats,
279            elapsed,
280            outstanding_not_seen,
281            ..
282        } = &event.kind
283        {
284            self.run_finished = Some(RunFinishedInfo {
285                stats: *run_stats,
286                elapsed: *elapsed,
287                outstanding_not_seen_count: outstanding_not_seen.as_ref().map(|t| t.total_not_seen),
288            });
289        }
290
291        // TODO: write to all of these even if one of them fails?
292        self.display_reporter.write_event(&event)?;
293        self.structured_reporter.write_event(&event)?;
294        self.metadata_reporter.write_event(event)?;
295        Ok(())
296    }
297}