@@ -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} ;
6264use swrite:: { SWrite , swrite} ;
@@ -973,13 +975,19 @@ enum MessageFormat {
973975#[ derive( Debug , Default , Args ) ]
974976#[ command( next_help_heading = "Reporter options" ) ]
975977struct 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 ) ]
11021172enum TestOutputDisplayOpt {
11031173 Immediate ,
0 commit comments