@@ -31,7 +31,7 @@ use std::env;
3131use std:: ffi:: OsString ;
3232use std:: fmt;
3333use std:: fs;
34- use std:: io:: { self , Read } ;
34+ use std:: io:: { self , Read , Write } ;
3535use std:: path:: { Path , PathBuf } ;
3636use std:: process:: { self , Command , Stdio } ;
3737use std:: str:: FromStr ;
@@ -87,6 +87,16 @@ fn get_commits(start: &str, end: &str) -> Result<Vec<git::Commit>, Error> {
8787 --end 866a713258915e6cbb212d135f751a6a8c9e1c0a --test-dir ../my_project/ --prompt -- build
8888 ```" ) ]
8989struct Opts {
90+ #[ structopt(
91+ long = "regress" ,
92+ default_value = "error" ,
93+ help = "Customize what is treated as regression." ,
94+ long_help = "Customize what is treated as regression. \
95+ Values include `--regress=error`, `--regress=non-error`, \
96+ `--regress=ice`, and `--regress=success`."
97+ ) ]
98+ regress : String ,
99+
90100 #[ structopt(
91101 short = "a" , long = "alt" , help = "Download the alt build instead of normal build"
92102 ) ]
@@ -442,6 +452,7 @@ enum InstallError {
442452 Move ( #[ cause] io:: Error ) ,
443453}
444454
455+ #[ derive( Debug ) ]
445456enum TestOutcome {
446457 Baseline ,
447458 Regressed ,
@@ -495,14 +506,20 @@ impl Toolchain {
495506 fn test ( & self , cfg : & Config ) -> TestOutcome {
496507 let outcome = if cfg. args . prompt {
497508 loop {
498- let status = self . run_test ( cfg) ;
509+ let output = self . run_test ( cfg) ;
510+ let status = output. status ;
499511
500512 eprintln ! ( "\n \n {} finished with exit code {:?}." , self , status. code( ) ) ;
501513 eprintln ! ( "please select an action to take:" ) ;
502514
515+ let default_choice = match cfg. default_outcome_of_output ( output) {
516+ TestOutcome :: Regressed => 0 ,
517+ TestOutcome :: Baseline => 1 ,
518+ } ;
519+
503520 match Select :: new ( )
504521 . items ( & [ "mark regressed" , "mark baseline" , "retry" ] )
505- . default ( 0 )
522+ . default ( default_choice )
506523 . interact ( )
507524 . unwrap ( )
508525 {
@@ -513,17 +530,120 @@ impl Toolchain {
513530 }
514531 }
515532 } else {
516- if self . run_test ( cfg) . success ( ) {
517- TestOutcome :: Baseline
518- } else {
519- TestOutcome :: Regressed
520- }
533+ let output = self . run_test ( cfg) ;
534+ cfg. default_outcome_of_output ( output)
521535 } ;
522536
523537 outcome
524538 }
539+ }
540+
541+ impl Config {
542+ fn default_outcome_of_output ( & self , output : process:: Output ) -> TestOutcome {
543+ let status = output. status ;
544+ let stdout_utf8 = String :: from_utf8_lossy ( & output. stdout ) . to_string ( ) ;
545+ let stderr_utf8 = String :: from_utf8_lossy ( & output. stderr ) . to_string ( ) ;
546+
547+ debug ! ( "status: {:?} stdout: {:?} stderr: {:?}" , status, stdout_utf8, stderr_utf8) ;
548+
549+ let saw_ice = || -> bool {
550+ stderr_utf8. contains ( "error: internal compiler error" )
551+ } ;
552+
553+ let input = ( self . output_processing_mode ( ) , status. success ( ) ) ;
554+ let result = match input {
555+ ( OutputProcessingMode :: RegressOnErrorStatus , true ) => TestOutcome :: Baseline ,
556+ ( OutputProcessingMode :: RegressOnErrorStatus , false ) => TestOutcome :: Regressed ,
557+
558+ ( OutputProcessingMode :: RegressOnSuccessStatus , true ) => TestOutcome :: Regressed ,
559+ ( OutputProcessingMode :: RegressOnSuccessStatus , false ) => TestOutcome :: Baseline ,
560+
561+ ( OutputProcessingMode :: RegressOnIceAlone , _) => {
562+ if saw_ice ( ) { TestOutcome :: Regressed } else { TestOutcome :: Baseline }
563+ }
564+
565+ ( OutputProcessingMode :: RegressOnNonCleanError , true ) => TestOutcome :: Regressed ,
566+ ( OutputProcessingMode :: RegressOnNonCleanError , false ) => {
567+ if saw_ice ( ) { TestOutcome :: Regressed } else { TestOutcome :: Baseline }
568+ }
569+ } ;
570+ debug ! ( "default_outcome_of_output: input: {:?} result: {:?}" , input, result) ;
571+ result
572+ }
525573
526- fn run_test ( & self , cfg : & Config ) -> process:: ExitStatus {
574+ fn output_processing_mode ( & self ) -> OutputProcessingMode {
575+ match self . args . regress . as_str ( ) {
576+ "error" => OutputProcessingMode :: RegressOnErrorStatus ,
577+ "non-error" => OutputProcessingMode :: RegressOnNonCleanError ,
578+ "ice" => OutputProcessingMode :: RegressOnIceAlone ,
579+ "success" => OutputProcessingMode :: RegressOnSuccessStatus ,
580+ setting => panic ! ( "Unknown --regress setting: {:?}" , setting) ,
581+ }
582+ }
583+ }
584+
585+ #[ derive( Copy , Clone , PartialEq , Eq , Debug , StructOpt ) ]
586+ /// Customize what is treated as regression.
587+ enum OutputProcessingMode {
588+ /// `RegressOnErrorStatus`: Marks test outcome as `Regressed` if and only if
589+ /// the `rustc` process reports a non-success status. This corresponds to
590+ /// when `rustc` has an internal compiler error (ICE) or when it detects an
591+ /// error in the input program.
592+ ///
593+ /// This covers the most common use case for `cargo-bisect-rustc` and is
594+ /// thus the default setting.
595+ ///
596+ /// You explicitly opt into this seting via `--regress=error`.
597+ RegressOnErrorStatus ,
598+
599+ /// `RegressOnSuccessStatus`: Marks test outcome as `Regressed` if and only
600+ /// if the `rustc` process reports a success status. This corresponds to
601+ /// when `rustc` believes it has successfully compiled the program. This
602+ /// covers the use case for when you want to bisect to see when a bug was
603+ /// fixed.
604+ ///
605+ /// You explicitly opt into this seting via `--regress=success`.
606+ RegressOnSuccessStatus ,
607+
608+ /// `RegressOnIceAlone`: Marks test outcome as `Regressed` if and only if
609+ /// the `rustc` process issues a diagnostic indicating that an internal
610+ /// compiler error (ICE) occurred. This covers the use case for when you
611+ /// want to bisect to see when an ICE was introduced pon a codebase that is
612+ /// meant to produce a clean error.
613+ ///
614+ /// You explicitly opt into this seting via `--regress=ice`.
615+ RegressOnIceAlone ,
616+
617+ /// `RegressOnNonCleanError`: Marks test outcome as `Baseline` if and only
618+ /// if the `rustc` process reports error status and does not issue any
619+ /// diagnostic indicating that an internal compiler error (ICE) occurred.
620+ /// This is the use case if the regression is a case where an ill-formed
621+ /// program has stopped being properly rejected by the compiler.
622+ ///
623+ /// (The main difference between this case and `RegressOnSuccessStatus` is
624+ /// the handling of ICE: `RegressOnSuccessStatus` assumes that ICE should be
625+ /// considered baseline; `RegressOnNonCleanError` assumes ICE should be
626+ /// considered a sign of a regression.)
627+ ///
628+ /// You explicitly opt into this seting via `--regress=non-error`.
629+ RegressOnNonCleanError ,
630+
631+ }
632+
633+ impl OutputProcessingMode {
634+ fn must_process_stderr ( & self ) -> bool {
635+ match self {
636+ OutputProcessingMode :: RegressOnErrorStatus |
637+ OutputProcessingMode :: RegressOnSuccessStatus => false ,
638+
639+ OutputProcessingMode :: RegressOnNonCleanError |
640+ OutputProcessingMode :: RegressOnIceAlone => true ,
641+ }
642+ }
643+ }
644+
645+ impl Toolchain {
646+ fn run_test ( & self , cfg : & Config ) -> process:: Output {
527647 if !cfg. args . preserve_target {
528648 let _ = fs:: remove_dir_all (
529649 cfg. args
@@ -550,21 +670,35 @@ impl Toolchain {
550670 } ;
551671 cmd. current_dir ( & cfg. args . test_dir ) ;
552672 cmd. env ( "CARGO_TARGET_DIR" , format ! ( "target-{}" , self . rustup_name( ) ) ) ;
553- if cfg. args . emit_cargo_output ( ) || cfg. args . prompt {
554- cmd. stdout ( Stdio :: inherit ( ) ) ;
555- cmd. stderr ( Stdio :: inherit ( ) ) ;
673+
674+ // let `cmd` capture stderr for us to process afterward.
675+ let must_capture_output = cfg. output_processing_mode ( ) . must_process_stderr ( ) ;
676+ let emit_output = cfg. args . emit_cargo_output ( ) || cfg. args . prompt ;
677+
678+ let default_stdio = || if must_capture_output {
679+ Stdio :: piped ( )
680+ } else if emit_output {
681+ Stdio :: inherit ( )
556682 } else {
557- cmd. stdout ( Stdio :: null ( ) ) ;
558- cmd. stderr ( Stdio :: null ( ) ) ;
559- }
560- let status = match cmd. status ( ) {
561- Ok ( status) => status,
683+ Stdio :: null ( )
684+ } ;
685+
686+ cmd. stdout ( default_stdio ( ) ) ;
687+ cmd. stderr ( default_stdio ( ) ) ;
688+
689+ let output = match cmd. output ( ) {
690+ Ok ( output) => output,
562691 Err ( err) => {
563692 panic ! ( "failed to run {:?}: {:?}" , cmd, err) ;
564693 }
565694 } ;
566695
567- status
696+ // if we captured the stdout above but still need to emit it, then do so now
697+ if must_capture_output && emit_output {
698+ io:: stdout ( ) . write_all ( & output. stdout ) . unwrap ( ) ;
699+ io:: stderr ( ) . write_all ( & output. stderr ) . unwrap ( ) ;
700+ }
701+ output
568702 }
569703
570704 fn install ( & self , client : & Client , dl_params : & DownloadParams ) -> Result < ( ) , InstallError > {
0 commit comments