@@ -579,6 +579,7 @@ pub struct Execs {
579
579
expect_stderr_not_contains : Vec < String > ,
580
580
expect_stderr_unordered : Vec < String > ,
581
581
expect_neither_contains : Vec < String > ,
582
+ expect_stderr_with_without : Vec < ( Vec < String > , Vec < String > ) > ,
582
583
expect_json : Option < Vec < Value > > ,
583
584
expect_json_contains_unordered : Vec < Value > ,
584
585
stream_output : bool ,
@@ -696,6 +697,37 @@ impl Execs {
696
697
self
697
698
}
698
699
700
+ /// Verify that a particular line appears in stderr with and without the
701
+ /// given substrings. Exactly one line must match.
702
+ ///
703
+ /// The substrings are matched as `contains`. Example:
704
+ ///
705
+ /// ```no_run
706
+ /// execs.with_stderr_line_without(
707
+ /// &[
708
+ /// "[RUNNING] `rustc --crate-name build_script_build",
709
+ /// "-C opt-level=3",
710
+ /// ],
711
+ /// &["-C debuginfo", "-C incremental"],
712
+ /// )
713
+ /// ```
714
+ ///
715
+ /// This will check that a build line includes `-C opt-level=3` but does
716
+ /// not contain `-C debuginfo` or `-C incremental`.
717
+ ///
718
+ /// Be careful writing the `without` fragments, see note in
719
+ /// `with_stderr_does_not_contain`.
720
+ pub fn with_stderr_line_without < S : ToString > (
721
+ & mut self ,
722
+ with : & [ S ] ,
723
+ without : & [ S ] ,
724
+ ) -> & mut Self {
725
+ let with = with. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
726
+ let without = without. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
727
+ self . expect_stderr_with_without . push ( ( with, without) ) ;
728
+ self
729
+ }
730
+
699
731
/// Verifies the JSON output matches the given JSON.
700
732
/// Typically used when testing cargo commands that emit JSON.
701
733
/// Each separate JSON object should be separated by a blank line.
@@ -830,6 +862,7 @@ impl Execs {
830
862
&& self . expect_stderr_not_contains . is_empty ( )
831
863
&& self . expect_stderr_unordered . is_empty ( )
832
864
&& self . expect_neither_contains . is_empty ( )
865
+ && self . expect_stderr_with_without . is_empty ( )
833
866
&& self . expect_json . is_none ( )
834
867
&& self . expect_json_contains_unordered . is_empty ( )
835
868
{
@@ -1004,6 +1037,10 @@ impl Execs {
1004
1037
}
1005
1038
}
1006
1039
1040
+ for ( with, without) in self . expect_stderr_with_without . iter ( ) {
1041
+ self . match_with_without ( & actual. stderr , with, without) ?;
1042
+ }
1043
+
1007
1044
if let Some ( ref objects) = self . expect_json {
1008
1045
let stdout = str:: from_utf8 ( & actual. stdout )
1009
1046
. map_err ( |_| "stdout was not utf8 encoded" . to_owned ( ) ) ?;
@@ -1063,6 +1100,32 @@ impl Execs {
1063
1100
)
1064
1101
}
1065
1102
1103
+ fn normalize_actual ( & self , description : & str , actual : & [ u8 ] ) -> Result < String , String > {
1104
+ let actual = match str:: from_utf8 ( actual) {
1105
+ Err ( ..) => return Err ( format ! ( "{} was not utf8 encoded" , description) ) ,
1106
+ Ok ( actual) => actual,
1107
+ } ;
1108
+ // Let's not deal with \r\n vs \n on windows...
1109
+ let actual = actual. replace ( "\r " , "" ) ;
1110
+ let actual = actual. replace ( "\t " , "<tab>" ) ;
1111
+ Ok ( actual)
1112
+ }
1113
+
1114
+ fn replace_expected ( & self , expected : & str ) -> String {
1115
+ // Do the template replacements on the expected string.
1116
+ let replaced = match self . process_builder {
1117
+ None => expected. to_string ( ) ,
1118
+ Some ( ref p) => match p. get_cwd ( ) {
1119
+ None => expected. to_string ( ) ,
1120
+ Some ( cwd) => expected. replace ( "[CWD]" , & cwd. display ( ) . to_string ( ) ) ,
1121
+ } ,
1122
+ } ;
1123
+
1124
+ // On Windows, we need to use a wildcard for the drive,
1125
+ // because we don't actually know what it will be.
1126
+ replaced. replace ( "[ROOT]" , if cfg ! ( windows) { r#"[..]:\"# } else { "/" } )
1127
+ }
1128
+
1066
1129
fn match_std (
1067
1130
& self ,
1068
1131
expected : Option < & String > ,
@@ -1072,30 +1135,11 @@ impl Execs {
1072
1135
kind : MatchKind ,
1073
1136
) -> MatchResult {
1074
1137
let out = match expected {
1075
- Some ( out) => {
1076
- // Do the template replacements on the expected string.
1077
- let replaced = match self . process_builder {
1078
- None => out. to_string ( ) ,
1079
- Some ( ref p) => match p. get_cwd ( ) {
1080
- None => out. to_string ( ) ,
1081
- Some ( cwd) => out. replace ( "[CWD]" , & cwd. display ( ) . to_string ( ) ) ,
1082
- } ,
1083
- } ;
1084
-
1085
- // On Windows, we need to use a wildcard for the drive,
1086
- // because we don't actually know what it will be.
1087
- replaced. replace ( "[ROOT]" , if cfg ! ( windows) { r#"[..]:\"# } else { "/" } )
1088
- }
1138
+ Some ( out) => self . replace_expected ( out) ,
1089
1139
None => return Ok ( ( ) ) ,
1090
1140
} ;
1091
1141
1092
- let actual = match str:: from_utf8 ( actual) {
1093
- Err ( ..) => return Err ( format ! ( "{} was not utf8 encoded" , description) ) ,
1094
- Ok ( actual) => actual,
1095
- } ;
1096
- // Let's not deal with `\r\n` vs `\n` on Windows.
1097
- let actual = actual. replace ( "\r " , "" ) ;
1098
- let actual = actual. replace ( "\t " , "<tab>" ) ;
1142
+ let actual = self . normalize_actual ( description, actual) ?;
1099
1143
1100
1144
match kind {
1101
1145
MatchKind :: Exact => {
@@ -1219,6 +1263,47 @@ impl Execs {
1219
1263
}
1220
1264
}
1221
1265
1266
+ fn match_with_without (
1267
+ & self ,
1268
+ actual : & [ u8 ] ,
1269
+ with : & [ String ] ,
1270
+ without : & [ String ] ,
1271
+ ) -> MatchResult {
1272
+ let actual = self . normalize_actual ( "stderr" , actual) ?;
1273
+ let contains = |s, line| {
1274
+ let mut s = self . replace_expected ( s) ;
1275
+ s. insert_str ( 0 , "[..]" ) ;
1276
+ s. push_str ( "[..]" ) ;
1277
+ lines_match ( & s, line)
1278
+ } ;
1279
+ let matches: Vec < & str > = actual
1280
+ . lines ( )
1281
+ . filter ( |line| with. iter ( ) . all ( |with| contains ( with, line) ) )
1282
+ . filter ( |line| !without. iter ( ) . any ( |without| contains ( without, line) ) )
1283
+ . collect ( ) ;
1284
+ match matches. len ( ) {
1285
+ 0 => Err ( format ! (
1286
+ "Could not find expected line in output.\n \
1287
+ With contents: {:?}\n \
1288
+ Without contents: {:?}\n \
1289
+ Actual stderr:\n \
1290
+ {}\n ",
1291
+ with, without, actual
1292
+ ) ) ,
1293
+ 1 => Ok ( ( ) ) ,
1294
+ _ => Err ( format ! (
1295
+ "Found multiple matching lines, but only expected one.\n \
1296
+ With contents: {:?}\n \
1297
+ Without contents: {:?}\n \
1298
+ Matching lines:\n \
1299
+ {}\n ",
1300
+ with,
1301
+ without,
1302
+ matches. join( "\n " )
1303
+ ) ) ,
1304
+ }
1305
+ }
1306
+
1222
1307
fn match_json ( & self , expected : & Value , line : & str ) -> MatchResult {
1223
1308
let actual = match line. parse ( ) {
1224
1309
Err ( e) => return Err ( format ! ( "invalid json, {}:\n `{}`" , e, line) ) ,
@@ -1436,6 +1521,7 @@ pub fn execs() -> Execs {
1436
1521
expect_stderr_not_contains : Vec :: new ( ) ,
1437
1522
expect_stderr_unordered : Vec :: new ( ) ,
1438
1523
expect_neither_contains : Vec :: new ( ) ,
1524
+ expect_stderr_with_without : Vec :: new ( ) ,
1439
1525
expect_json : None ,
1440
1526
expect_json_contains_unordered : Vec :: new ( ) ,
1441
1527
stream_output : false ,
@@ -1529,7 +1615,7 @@ fn substitute_macros(input: &str) -> String {
1529
1615
( "[UNPACKING]" , " Unpacking" ) ,
1530
1616
( "[SUMMARY]" , " Summary" ) ,
1531
1617
( "[FIXING]" , " Fixing" ) ,
1532
- ( "[EXE]" , env:: consts:: EXE_SUFFIX ) ,
1618
+ ( "[EXE]" , env:: consts:: EXE_SUFFIX ) ,
1533
1619
] ;
1534
1620
let mut result = input. to_owned ( ) ;
1535
1621
for & ( pat, subst) in & macros {
0 commit comments