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