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 pub 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 /// Gets this output as a lossy UTF-8 string.
59 #[inline]
60 pub fn as_str_lossy(&self) -> &str {
61 let s = self
62 .as_str
63 .get_or_init(|| match String::from_utf8_lossy(&self.buf) {
64 // A borrowed string from `from_utf8_lossy` is always valid UTF-8. We can't store
65 // the `Cow` directly because that would be a self-referential struct. (Well, we
66 // could via a library like ouroboros, but that's really unnecessary.)
67 Cow::Borrowed(_) => None,
68 Cow::Owned(s) => Some(s.into_boxed_str()),
69 });
70
71 match s {
72 Some(s) => s,
73 // SAFETY: Immediately above, we've established that `None` means `buf` is valid UTF-8.
74 None => unsafe { std::str::from_utf8_unchecked(&self.buf) },
75 }
76 }
77
78 /// Iterates over lines in this output.
79 #[inline]
80 pub fn lines(&self) -> Lines<'_> {
81 self.buf.lines()
82 }
83
84 /// Returns true if the output is empty.
85 #[inline]
86 pub fn is_empty(&self) -> bool {
87 self.buf.is_empty()
88 }
89}
90
91/// The result of executing a child process: either that the process was run and
92/// at least some output was captured, or that the process could not be started
93/// at all.
94#[derive(Clone, Debug)]
95pub enum ChildExecutionOutput {
96 /// The process was run and the output was captured.
97 Output {
98 /// If the process has finished executing, the final state it is in.
99 ///
100 /// `None` means execution is currently in progress.
101 result: Option<ExecutionResult>,
102
103 /// The captured output.
104 output: ChildOutput,
105
106 /// Errors that occurred while waiting on the child process or parsing
107 /// its output.
108 errors: Option<ErrorList<ChildError>>,
109 },
110
111 /// There was a failure to start the process.
112 StartError(ChildStartError),
113}
114
115impl ChildExecutionOutput {
116 /// Returns true if there are any errors in this output.
117 pub(crate) fn has_errors(&self) -> bool {
118 match self {
119 ChildExecutionOutput::Output { errors, result, .. } => {
120 if errors.is_some() {
121 return true;
122 }
123 if let Some(result) = result {
124 return !result.is_success();
125 }
126
127 false
128 }
129 ChildExecutionOutput::StartError(_) => true,
130 }
131 }
132}
133
134/// The output of a child process: stdout and/or stderr.
135///
136/// Part of [`ChildExecutionOutput`], and can be used independently as well.
137#[derive(Clone, Debug)]
138pub enum ChildOutput {
139 /// The output was split into stdout and stderr.
140 Split(ChildSplitOutput),
141
142 /// The output was combined into stdout and stderr.
143 Combined {
144 /// The captured output.
145 output: ChildSingleOutput,
146 },
147}
148
149/// The output of a child process (test or setup script) with split stdout and stderr.
150///
151/// One of the variants of [`ChildOutput`].
152#[derive(Clone, Debug)]
153pub struct ChildSplitOutput {
154 /// The captured stdout, or `None` if the output was not captured.
155 pub stdout: Option<ChildSingleOutput>,
156
157 /// The captured stderr, or `None` if the output was not captured.
158 pub stderr: Option<ChildSingleOutput>,
159}