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}