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