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