Skip to main content

nextest_runner/
test_output.rs

1//! Utilities for capture output from tests run in a child process
2
3use crate::{
4    errors::{ChildError, ChildStartError, ErrorList},
5    reporter::events::ExecutionResult,
6};
7use bstr::{ByteSlice, Lines};
8use bytes::Bytes;
9use std::{borrow::Cow, sync::OnceLock};
10
11/// The strategy used to capture test executable output
12#[derive(Copy, Clone, PartialEq, Default, Debug)]
13pub enum CaptureStrategy {
14    /// Captures `stdout` and `stderr` separately
15    ///
16    /// * pro: output from `stdout` and `stderr` can be identified and easily split
17    /// * con: ordering between the streams cannot be guaranteed
18    #[default]
19    Split,
20    /// Captures `stdout` and `stderr` in a single stream
21    ///
22    /// * pro: output is guaranteed to be ordered as it would in a terminal emulator
23    /// * con: distinction between `stdout` and `stderr` is lost, all output is attributed to `stdout`
24    Combined,
25    /// Output is not captured
26    ///
27    /// This mode is used when using --no-capture, causing nextest to execute
28    /// tests serially without capturing output
29    None,
30}
31
32/// A single output for a test or setup script: standard output, standard error, or a combined
33/// buffer.
34///
35/// This is a wrapper around a [`Bytes`] that provides some convenience methods.
36#[derive(Clone, Debug)]
37pub struct ChildSingleOutput {
38    /// The raw output buffer.
39    buf: Bytes,
40
41    /// A string representation of the output, computed on first access.
42    ///
43    /// `None` means the output is valid UTF-8.
44    as_str: OnceLock<Option<Box<str>>>,
45}
46
47impl From<Bytes> for ChildSingleOutput {
48    #[inline]
49    fn from(buf: Bytes) -> Self {
50        Self {
51            buf,
52            as_str: OnceLock::new(),
53        }
54    }
55}
56
57impl ChildSingleOutput {
58    /// Returns the raw output buffer.
59    #[inline]
60    pub fn buf(&self) -> &Bytes {
61        &self.buf
62    }
63
64    /// Gets this output as a lossy UTF-8 string.
65    #[inline]
66    pub fn as_str_lossy(&self) -> &str {
67        let s = self
68            .as_str
69            .get_or_init(|| match String::from_utf8_lossy(&self.buf) {
70                // A borrowed string from `from_utf8_lossy` is always valid UTF-8. We can't store
71                // the `Cow` directly because that would be a self-referential struct. (Well, we
72                // could via a library like ouroboros, but that's really unnecessary.)
73                Cow::Borrowed(_) => None,
74                Cow::Owned(s) => Some(s.into_boxed_str()),
75            });
76
77        match s {
78            Some(s) => s,
79            // SAFETY: Immediately above, we've established that `None` means `buf` is valid UTF-8.
80            None => unsafe { std::str::from_utf8_unchecked(&self.buf) },
81        }
82    }
83
84    /// Iterates over lines in this output.
85    #[inline]
86    pub fn lines(&self) -> Lines<'_> {
87        self.buf.lines()
88    }
89
90    /// Returns true if the output is empty.
91    #[inline]
92    pub fn is_empty(&self) -> bool {
93        self.buf.is_empty()
94    }
95}
96
97/// The result of executing a child process: either that the process was run and
98/// at least some output was captured, or that the process could not be started
99/// at all.
100#[derive(Clone, Debug)]
101pub enum ChildExecutionOutput {
102    /// The process was run and the output was captured.
103    Output {
104        /// If the process has finished executing, the final state it is in.
105        ///
106        /// `None` means execution is currently in progress.
107        result: Option<ExecutionResult>,
108
109        /// The captured output.
110        output: ChildOutput,
111
112        /// Errors that occurred while waiting on the child process or parsing
113        /// its output.
114        errors: Option<ErrorList<ChildError>>,
115    },
116
117    /// There was a failure to start the process.
118    StartError(ChildStartError),
119}
120
121/// The output of a child process: stdout and/or stderr.
122///
123/// Part of [`ChildExecutionOutput`], and can be used independently as well.
124#[derive(Clone, Debug)]
125pub enum ChildOutput {
126    /// The output was split into stdout and stderr.
127    Split(ChildSplitOutput),
128
129    /// The output was combined into stdout and stderr.
130    Combined {
131        /// The captured output.
132        output: ChildSingleOutput,
133    },
134}
135
136impl ChildOutput {
137    /// Returns the lengths of stdout and stderr in bytes.
138    ///
139    /// Returns `None` for each stream that wasn't captured.
140    pub fn stdout_stderr_len(&self) -> (Option<u64>, Option<u64>) {
141        match self {
142            Self::Split(split) => (
143                split.stdout.as_ref().map(|s| s.buf().len() as u64),
144                split.stderr.as_ref().map(|s| s.buf().len() as u64),
145            ),
146            Self::Combined { output } => (Some(output.buf().len() as u64), None),
147        }
148    }
149}
150
151/// The output of a child process (test or setup script) with split stdout and stderr.
152///
153/// One of the variants of [`ChildOutput`].
154#[derive(Clone, Debug)]
155pub struct ChildSplitOutput {
156    /// The captured stdout, or `None` if the output was not captured.
157    pub stdout: Option<ChildSingleOutput>,
158
159    /// The captured stderr, or `None` if the output was not captured.
160    pub stderr: Option<ChildSingleOutput>,
161}