Skip to content

Commit 013807c

Browse files
chore: begin to port #1707 (comment) to main
1 parent c527ae4 commit 013807c

File tree

10 files changed

+430
-109
lines changed

10 files changed

+430
-109
lines changed

cargo-nextest/src/dispatch.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use nextest_runner::{
3737
redact::Redactor,
3838
reporter::{
3939
FinalStatusLevel, ReporterBuilder, StatusLevel, TestOutputDisplay, TestOutputErrorSlice,
40+
displayer::TestOutputDisplayStreams,
4041
events::{FinalRunStats, RunStatsFailureKind},
4142
highlight_end, structured,
4243
},
@@ -57,6 +58,7 @@ use std::{
5758
env::VarError,
5859
fmt,
5960
io::{Cursor, Write},
61+
str::FromStr,
6062
sync::{Arc, OnceLock},
6163
};
6264
use swrite::{SWrite, swrite};
@@ -973,13 +975,19 @@ enum MessageFormat {
973975
#[derive(Debug, Default, Args)]
974976
#[command(next_help_heading = "Reporter options")]
975977
struct ReporterOpts {
976-
/// Output stdout and stderr on failure
978+
/// Output stdout and/or stderr on failure
979+
///
980+
/// Takes the form of: '{value}' or 'stdout={value}' or 'stdout={value},stderr={value}'
981+
/// where {value} is one of: 'immediate', 'immediate-final', 'final', 'never'
977982
#[arg(long, value_enum, value_name = "WHEN", env = "NEXTEST_FAILURE_OUTPUT")]
978-
failure_output: Option<TestOutputDisplayOpt>,
983+
failure_output: Option<TestOutputDisplayStreamsOpt>,
979984

980-
/// Output stdout and stderr on success
985+
/// Output stdout and/or stderr on success
986+
///
987+
/// Takes the form of: '{value}' or 'stdout={value}' or 'stdout={value},stderr={value}'
988+
/// where {value} is one of: 'immediate', 'immediate-final', 'final', 'never'
981989
#[arg(long, value_enum, value_name = "WHEN", env = "NEXTEST_SUCCESS_OUTPUT")]
982-
success_output: Option<TestOutputDisplayOpt>,
990+
success_output: Option<TestOutputDisplayStreamsOpt>,
983991

984992
// status_level does not conflict with --no-capture because pass vs skip still makes sense.
985993
/// Test statuses to output
@@ -1098,6 +1106,68 @@ impl ReporterOpts {
10981106
}
10991107
}
11001108

1109+
#[derive(Debug, Clone, Copy)]
1110+
struct TestOutputDisplayStreamsOpt {
1111+
stdout: Option<TestOutputDisplayOpt>,
1112+
stderr: Option<TestOutputDisplayOpt>,
1113+
}
1114+
1115+
impl FromStr for TestOutputDisplayStreamsOpt {
1116+
type Err = String;
1117+
1118+
fn from_str(s: &str) -> Result<Self, Self::Err> {
1119+
// expected input has three forms
1120+
// - "{value}": where value is one of [immediate, immediate-final, final, never]
1121+
// - "{stream}={value}": where {stream} is one of [stdout, stderr]
1122+
// - "{stream}={value},{stream=value}": where the two {stream} keys cannot be the same
1123+
let (stdout, stderr) = if let Some((left, right)) = s.split_once(',') {
1124+
// the "{stream}={value},{stream=value}" case
1125+
let left = left
1126+
.split_once('=')
1127+
.map(|l| (l.0, TestOutputDisplayOpt::from_str(l.1, false)));
1128+
let right = right
1129+
.split_once('=')
1130+
.map(|r| (r.0, TestOutputDisplayOpt::from_str(r.1, false)));
1131+
match (left, right) {
1132+
(Some(("stderr", Ok(stderr))), Some(("stdout", Ok(stdout)))) => (Some(stdout), Some(stderr)),
1133+
(Some(("stdout", Ok(stdout))), Some(("stderr", Ok(stderr)))) => (Some(stdout), Some(stderr)),
1134+
(Some((stream @ "stdout" | stream @ "stderr", Err(_))), _) => return Err(format!("\n unrecognized setting for {stream}: [possible values: immediate, immediate-final, final, never]")),
1135+
(_, Some((stream @ "stdout" | stream @ "stderr", Err(_)))) => return Err(format!("\n unrecognized setting for {stream}: [possible values: immediate, immediate-final, final, never]")),
1136+
(Some(("stdout", _)), Some(("stdout", _))) => return Err("\n stdout specified twice".to_string()),
1137+
(Some(("stderr", _)), Some(("stderr", _))) => return Err("\n stderr specified twice".to_string()),
1138+
(Some((stream, _)), Some(("stdout" | "stderr", _))) => return Err(format!("\n unrecognized output stream '{stream}': [possible values: stdout, stderr]")),
1139+
(Some(("stdout" | "stderr", _)), Some((stream, _))) => return Err(format!("\n unrecognized output stream '{stream}': [possible values: stdout, stderr]")),
1140+
(_, _) => return Err("\n [possible values: immediate, immediate-final, final, never], or specify one or both output streams: stdout={}, stderr={}, stdout={},stderr={}".to_string()),
1141+
}
1142+
} else if let Some((stream, right)) = s.split_once('=') {
1143+
// the "{stream}={value}" case
1144+
let value = TestOutputDisplayOpt::from_str(right, false);
1145+
match (stream, value) {
1146+
("stderr", Ok(stderr)) => (None, Some(stderr)),
1147+
("stdout", Ok(stdout)) => (Some(stdout), None),
1148+
("stdout" | "stderr", Err(_)) => return Err(format!("\n unrecognized setting for {stream}: [possible values: immediate, immediate-final, final, never]")),
1149+
(_, _) => return Err("\n unrecognized output stream, possible values: [stdout={}, stderr={}, stdout={},stderr={}]".to_string())
1150+
}
1151+
} else if let Ok(value) = TestOutputDisplayOpt::from_str(s, false) {
1152+
// the "{value}" case
1153+
(Some(value), Some(value))
1154+
} else {
1155+
// did not recognize one of the three cases
1156+
return Err("\n [possible values: immediate, immediate-final, final, never], or specify one or both output streams: stdout={}, stderr={}, stdout={},stderr={}".to_string());
1157+
};
1158+
Ok(Self { stdout, stderr })
1159+
}
1160+
}
1161+
1162+
impl From<TestOutputDisplayStreamsOpt> for TestOutputDisplayStreams {
1163+
fn from(value: TestOutputDisplayStreamsOpt) -> Self {
1164+
Self {
1165+
stdout: value.stdout.map(TestOutputDisplay::from),
1166+
stderr: value.stderr.map(TestOutputDisplay::from),
1167+
}
1168+
}
1169+
}
1170+
11011171
#[derive(Clone, Copy, Debug, ValueEnum)]
11021172
enum TestOutputDisplayOpt {
11031173
Immediate,

nextest-runner/src/config/core/imp.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ use crate::{
2828
helpers::plural,
2929
list::TestList,
3030
platform::BuildPlatforms,
31-
reporter::{FinalStatusLevel, StatusLevel, TestOutputDisplay},
31+
reporter::{
32+
FinalStatusLevel, StatusLevel, TestOutputDisplay, displayer::TestOutputDisplayStreams,
33+
},
3234
};
3335
use camino::{Utf8Path, Utf8PathBuf};
3436
use config::{
@@ -1049,14 +1051,14 @@ impl<'cfg> EvaluatableProfile<'cfg> {
10491051
}
10501052

10511053
/// Returns the failure output config for this profile.
1052-
pub fn failure_output(&self) -> TestOutputDisplay {
1054+
pub fn failure_output(&self) -> TestOutputDisplayStreams {
10531055
self.custom_profile
10541056
.and_then(|profile| profile.failure_output)
10551057
.unwrap_or(self.default_profile.failure_output)
10561058
}
10571059

10581060
/// Returns the failure output config for this profile.
1059-
pub fn success_output(&self) -> TestOutputDisplay {
1061+
pub fn success_output(&self) -> TestOutputDisplayStreams {
10601062
self.custom_profile
10611063
.and_then(|profile| profile.success_output)
10621064
.unwrap_or(self.default_profile.success_output)
@@ -1225,8 +1227,8 @@ pub(in crate::config) struct DefaultProfileImpl {
12251227
retries: RetryPolicy,
12261228
status_level: StatusLevel,
12271229
final_status_level: FinalStatusLevel,
1228-
failure_output: TestOutputDisplay,
1229-
success_output: TestOutputDisplay,
1230+
failure_output: TestOutputDisplayStreams,
1231+
success_output: TestOutputDisplayStreams,
12301232
max_fail: MaxFail,
12311233
slow_timeout: SlowTimeout,
12321234
global_timeout: GlobalTimeout,
@@ -1314,9 +1316,9 @@ pub(in crate::config) struct CustomProfileImpl {
13141316
#[serde(default)]
13151317
final_status_level: Option<FinalStatusLevel>,
13161318
#[serde(default)]
1317-
failure_output: Option<TestOutputDisplay>,
1319+
failure_output: Option<TestOutputDisplayStreams>,
13181320
#[serde(default)]
1319-
success_output: Option<TestOutputDisplay>,
1321+
success_output: Option<TestOutputDisplayStreams>,
13201322
#[serde(
13211323
default,
13221324
rename = "fail-fast",

nextest-runner/src/config/overrides/imp.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
ConfigCompileError, ConfigCompileErrorKind, ConfigCompileSection, ConfigParseErrorKind,
1818
},
1919
platform::BuildPlatforms,
20-
reporter::TestOutputDisplay,
20+
reporter::{TestOutputDisplay, displayer::TestOutputDisplayStreams},
2121
};
2222
use guppy::graph::cargo::BuildPlatform;
2323
use nextest_filtering::{
@@ -109,8 +109,8 @@ pub struct TestSettings<'p, Source = ()> {
109109
slow_timeout: (SlowTimeout, Source),
110110
leak_timeout: (LeakTimeout, Source),
111111
test_group: (TestGroup, Source),
112-
success_output: (TestOutputDisplay, Source),
113-
failure_output: (TestOutputDisplay, Source),
112+
success_output: (TestOutputDisplayStreams, Source),
113+
failure_output: (TestOutputDisplayStreams, Source),
114114
junit_store_success_output: (bool, Source),
115115
junit_store_failure_output: (bool, Source),
116116
}
@@ -217,12 +217,12 @@ impl<'p> TestSettings<'p> {
217217
}
218218

219219
/// Returns the success output setting for this test.
220-
pub fn success_output(&self) -> TestOutputDisplay {
220+
pub fn success_output(&self) -> TestOutputDisplayStreams {
221221
self.success_output.0
222222
}
223223

224224
/// Returns the failure output setting for this test.
225-
pub fn failure_output(&self) -> TestOutputDisplay {
225+
pub fn failure_output(&self) -> TestOutputDisplayStreams {
226226
self.failure_output.0
227227
}
228228

@@ -704,8 +704,8 @@ pub(in crate::config) struct ProfileOverrideData {
704704
slow_timeout: Option<SlowTimeout>,
705705
leak_timeout: Option<LeakTimeout>,
706706
pub(in crate::config) test_group: Option<TestGroup>,
707-
success_output: Option<TestOutputDisplay>,
708-
failure_output: Option<TestOutputDisplay>,
707+
success_output: Option<TestOutputDisplayStreams>,
708+
failure_output: Option<TestOutputDisplayStreams>,
709709
junit: DeserializedJunitOutput,
710710
}
711711

@@ -947,9 +947,9 @@ pub(in crate::config) struct DeserializedOverride {
947947
#[serde(default)]
948948
test_group: Option<TestGroup>,
949949
#[serde(default)]
950-
success_output: Option<TestOutputDisplay>,
950+
success_output: Option<TestOutputDisplayStreams>,
951951
#[serde(default)]
952-
failure_output: Option<TestOutputDisplay>,
952+
failure_output: Option<TestOutputDisplayStreams>,
953953
#[serde(default)]
954954
junit: DeserializedJunitOutput,
955955
}
@@ -1122,8 +1122,14 @@ mod tests {
11221122
}
11231123
);
11241124
assert_eq!(overrides.test_group(), &test_group("my-group"));
1125-
assert_eq!(overrides.success_output(), TestOutputDisplay::Never);
1126-
assert_eq!(overrides.failure_output(), TestOutputDisplay::Final);
1125+
assert_eq!(
1126+
overrides.success_output(),
1127+
TestOutputDisplayStreams::create_never()
1128+
);
1129+
assert_eq!(
1130+
overrides.failure_output(),
1131+
TestOutputDisplayStreams::create_final()
1132+
);
11271133
// For clarity.
11281134
#[expect(clippy::bool_assert_comparison)]
11291135
{
@@ -1173,9 +1179,12 @@ mod tests {
11731179
assert_eq!(overrides.test_group(), &test_group("my-group"));
11741180
assert_eq!(
11751181
overrides.success_output(),
1176-
TestOutputDisplay::ImmediateFinal
1182+
TestOutputDisplayStreams::create_immediate_final()
1183+
);
1184+
assert_eq!(
1185+
overrides.failure_output(),
1186+
TestOutputDisplayStreams::create_final()
11771187
);
1178-
assert_eq!(overrides.failure_output(), TestOutputDisplay::Final);
11791188
// For clarity.
11801189
#[expect(clippy::bool_assert_comparison)]
11811190
{

0 commit comments

Comments
 (0)