nextest_runner/reporter/structured/imp.rs
1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Functionality for emitting structured, machine readable output in different
5//! formats.
6
7use super::{LibtestReporter, RecordReporter};
8use crate::{
9 errors::WriteEventError,
10 record::{RecordOpts, StoreSizes},
11 reporter::events::TestEvent,
12};
13use nextest_metadata::TestListSummary;
14use std::sync::Arc;
15
16/// A reporter for structured, machine-readable formats.
17///
18/// This reporter can emit output in multiple formats simultaneously:
19/// - Libtest-compatible JSON to stdout.
20/// - Recording to disk for later inspection.
21#[derive(Default)]
22pub struct StructuredReporter<'a> {
23 /// Libtest-compatible output written to stdout.
24 libtest: Option<LibtestReporter<'a>>,
25 /// Recording reporter for writing to disk.
26 record: Option<RecordReporter<'a>>,
27}
28
29impl<'a> StructuredReporter<'a> {
30 /// Creates a new `StructuredReporter`.
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 /// Sets libtest output for the `StructuredReporter`.
36 pub fn set_libtest(&mut self, libtest: LibtestReporter<'a>) -> &mut Self {
37 self.libtest = Some(libtest);
38 self
39 }
40
41 /// Sets the record reporter for the `StructuredReporter`.
42 pub fn set_record(&mut self, record: RecordReporter<'a>) -> &mut Self {
43 self.record = Some(record);
44 self
45 }
46
47 /// Writes metadata to the record reporter, if configured.
48 ///
49 /// This should be called once at the beginning of a test run.
50 pub fn write_meta(
51 &self,
52 cargo_metadata_json: Arc<String>,
53 test_list: TestListSummary,
54 opts: RecordOpts,
55 ) {
56 if let Some(record) = &self.record {
57 record.write_meta(cargo_metadata_json, test_list, opts);
58 }
59 }
60
61 /// Writes a test event to all configured reporters.
62 #[inline]
63 pub(crate) fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
64 if let Some(libtest) = &mut self.libtest {
65 libtest.write_event(event)?;
66 }
67 if let Some(record) = &self.record {
68 // Clone the event for the record reporter since it runs in a separate thread.
69 record.write_event(event.clone());
70 }
71 Ok(())
72 }
73
74 /// Finishes writing to all configured reporters.
75 ///
76 /// Returns the sizes of the recording (compressed and uncompressed), or `None` if recording
77 /// was not enabled or an error occurred.
78 ///
79 /// This should be called at the end of a test run to ensure all data is flushed.
80 pub fn finish(self) -> Option<StoreSizes> {
81 if let Some(record) = self.record {
82 match record.finish() {
83 Ok(sizes) => Some(sizes),
84 Err(error) => {
85 tracing::error!("error finishing run recording: {error}");
86 None
87 }
88 }
89 } else {
90 None
91 }
92 }
93}