@@ -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 time:: Duration ,
6264} ;
@@ -1027,13 +1029,19 @@ fn non_zero_duration(input: &str) -> Result<Duration, String> {
10271029#[ derive( Debug , Default , Args ) ]
10281030#[ command( next_help_heading = "Reporter options" ) ]
10291031struct ReporterOpts {
1030- /// Output stdout and stderr on failure
1032+ /// Output stdout and/or stderr on failure
1033+ ///
1034+ /// Takes the form of: '{value}' or 'stdout={value}' or 'stdout={value},stderr={value}'
1035+ /// where {value} is one of: 'immediate', 'immediate-final', 'final', 'never'
10311036 #[ arg( long, value_enum, value_name = "WHEN" , env = "NEXTEST_FAILURE_OUTPUT" ) ]
1032- failure_output : Option < TestOutputDisplayOpt > ,
1037+ failure_output : Option < TestOutputDisplayStreamsOpt > ,
10331038
1034- /// Output stdout and stderr on success
1039+ /// Output stdout and/or stderr on success
1040+ ///
1041+ /// Takes the form of: '{value}' or 'stdout={value}' or 'stdout={value},stderr={value}'
1042+ /// where {value} is one of: 'immediate', 'immediate-final', 'final', 'never'
10351043 #[ arg( long, value_enum, value_name = "WHEN" , env = "NEXTEST_SUCCESS_OUTPUT" ) ]
1036- success_output : Option < TestOutputDisplayOpt > ,
1044+ success_output : Option < TestOutputDisplayStreamsOpt > ,
10371045
10381046 // status_level does not conflict with --no-capture because pass vs skip still makes sense.
10391047 /// Test statuses to output
@@ -1152,6 +1160,68 @@ impl ReporterOpts {
11521160 }
11531161}
11541162
1163+ #[ derive( Debug , Clone , Copy ) ]
1164+ struct TestOutputDisplayStreamsOpt {
1165+ stdout : Option < TestOutputDisplayOpt > ,
1166+ stderr : Option < TestOutputDisplayOpt > ,
1167+ }
1168+
1169+ impl FromStr for TestOutputDisplayStreamsOpt {
1170+ type Err = String ;
1171+
1172+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
1173+ // expected input has three forms
1174+ // - "{value}": where value is one of [immediate, immediate-final, final, never]
1175+ // - "{stream}={value}": where {stream} is one of [stdout, stderr]
1176+ // - "{stream}={value},{stream=value}": where the two {stream} keys cannot be the same
1177+ let ( stdout, stderr) = if let Some ( ( left, right) ) = s. split_once ( ',' ) {
1178+ // the "{stream}={value},{stream=value}" case
1179+ let left = left
1180+ . split_once ( '=' )
1181+ . map ( |l| ( l. 0 , TestOutputDisplayOpt :: from_str ( l. 1 , false ) ) ) ;
1182+ let right = right
1183+ . split_once ( '=' )
1184+ . map ( |r| ( r. 0 , TestOutputDisplayOpt :: from_str ( r. 1 , false ) ) ) ;
1185+ match ( left, right) {
1186+ ( Some ( ( "stderr" , Ok ( stderr) ) ) , Some ( ( "stdout" , Ok ( stdout) ) ) ) => ( Some ( stdout) , Some ( stderr) ) ,
1187+ ( Some ( ( "stdout" , Ok ( stdout) ) ) , Some ( ( "stderr" , Ok ( stderr) ) ) ) => ( Some ( stdout) , Some ( stderr) ) ,
1188+ ( Some ( ( stream @ "stdout" | stream @ "stderr" , Err ( _) ) ) , _) => return Err ( format ! ( "\n unrecognized setting for {stream}: [possible values: immediate, immediate-final, final, never]" ) ) ,
1189+ ( _, Some ( ( stream @ "stdout" | stream @ "stderr" , Err ( _) ) ) ) => return Err ( format ! ( "\n unrecognized setting for {stream}: [possible values: immediate, immediate-final, final, never]" ) ) ,
1190+ ( Some ( ( "stdout" , _) ) , Some ( ( "stdout" , _) ) ) => return Err ( "\n stdout specified twice" . to_string ( ) ) ,
1191+ ( Some ( ( "stderr" , _) ) , Some ( ( "stderr" , _) ) ) => return Err ( "\n stderr specified twice" . to_string ( ) ) ,
1192+ ( Some ( ( stream, _) ) , Some ( ( "stdout" | "stderr" , _) ) ) => return Err ( format ! ( "\n unrecognized output stream '{stream}': [possible values: stdout, stderr]" ) ) ,
1193+ ( Some ( ( "stdout" | "stderr" , _) ) , Some ( ( stream, _) ) ) => return Err ( format ! ( "\n unrecognized output stream '{stream}': [possible values: stdout, stderr]" ) ) ,
1194+ ( _, _) => return Err ( "\n [possible values: immediate, immediate-final, final, never], or specify one or both output streams: stdout={}, stderr={}, stdout={},stderr={}" . to_string ( ) ) ,
1195+ }
1196+ } else if let Some ( ( stream, right) ) = s. split_once ( '=' ) {
1197+ // the "{stream}={value}" case
1198+ let value = TestOutputDisplayOpt :: from_str ( right, false ) ;
1199+ match ( stream, value) {
1200+ ( "stderr" , Ok ( stderr) ) => ( None , Some ( stderr) ) ,
1201+ ( "stdout" , Ok ( stdout) ) => ( Some ( stdout) , None ) ,
1202+ ( "stdout" | "stderr" , Err ( _) ) => return Err ( format ! ( "\n unrecognized setting for {stream}: [possible values: immediate, immediate-final, final, never]" ) ) ,
1203+ ( _, _) => return Err ( "\n unrecognized output stream, possible values: [stdout={}, stderr={}, stdout={},stderr={}]" . to_string ( ) )
1204+ }
1205+ } else if let Ok ( value) = TestOutputDisplayOpt :: from_str ( s, false ) {
1206+ // the "{value}" case
1207+ ( Some ( value) , Some ( value) )
1208+ } else {
1209+ // did not recognize one of the three cases
1210+ return Err ( "\n [possible values: immediate, immediate-final, final, never], or specify one or both output streams: stdout={}, stderr={}, stdout={},stderr={}" . to_string ( ) ) ;
1211+ } ;
1212+ Ok ( Self { stdout, stderr } )
1213+ }
1214+ }
1215+
1216+ impl From < TestOutputDisplayStreamsOpt > for TestOutputDisplayStreams {
1217+ fn from ( value : TestOutputDisplayStreamsOpt ) -> Self {
1218+ Self {
1219+ stdout : value. stdout . map ( TestOutputDisplay :: from) ,
1220+ stderr : value. stderr . map ( TestOutputDisplay :: from) ,
1221+ }
1222+ }
1223+ }
1224+
11551225#[ derive( Clone , Copy , Debug , ValueEnum ) ]
11561226enum TestOutputDisplayOpt {
11571227 Immediate ,
0 commit comments