Skip to content

Commit ff8a075

Browse files
committed
Added --regress=ice flag to resolve #34.
Also added some other variants, to resolve #28. Just to spell it out: * --regress=error is the default: an error status (program rejection or ICE) is a regression. * --regress=success is probably what you want for #28, to see where a fix was injected. * --regress=ice is what you want if you're looking for where an ICE was injected. * --regress=non-error is what you want if you know the code should be a static error, but the bug is causing either ICEs or invalid successes to show up.
1 parent 90f0ac8 commit ff8a075

File tree

1 file changed

+137
-18
lines changed

1 file changed

+137
-18
lines changed

src/main.rs

Lines changed: 137 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use std::env;
3131
use std::ffi::OsString;
3232
use std::fmt;
3333
use std::fs;
34-
use std::io::{self, Read};
34+
use std::io::{self, Read, Write};
3535
use std::path::{Path, PathBuf};
3636
use std::process::{self, Command, Stdio};
3737
use 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
```")]
8989
struct 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)]
445456
enum TestOutcome {
446457
Baseline,
447458
Regressed,
@@ -496,18 +507,19 @@ impl Toolchain {
496507
let outcome = if cfg.args.prompt {
497508
loop {
498509
let output = self.run_test(cfg);
499-
let status = output.status();
510+
let status = output.status;
500511

501512
eprintln!("\n\n{} finished with exit code {:?}.", self, status.code());
502513
eprintln!("please select an action to take:");
503514

504-
// FIXME: should this use `Config::default_outcome_of_output`
505-
// for inferring the default below, rather than always
506-
// defaulting to "mark regressed"?
515+
let default_choice = match cfg.default_outcome_of_output(output) {
516+
TestOutcome::Regressed => 0,
517+
TestOutcome::Baseline => 1,
518+
};
507519

508520
match Select::new()
509521
.items(&["mark regressed", "mark baseline", "retry"])
510-
.default(0)
522+
.default(default_choice)
511523
.interact()
512524
.unwrap()
513525
{
@@ -527,12 +539,105 @@ impl Toolchain {
527539
}
528540

529541
impl Config {
530-
fn default_outcome_of_output(&self, process::Output) -> TestOutcome {
531-
let status = output.status();
532-
if status.success() {
533-
TestOutcome::Baseline
534-
} else {
535-
TestOutcome::Regressed
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+
}
573+
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,
536641
}
537642
}
538643
}
@@ -565,20 +670,34 @@ impl Toolchain {
565670
};
566671
cmd.current_dir(&cfg.args.test_dir);
567672
cmd.env("CARGO_TARGET_DIR", format!("target-{}", self.rustup_name()));
568-
if cfg.args.emit_cargo_output() || cfg.args.prompt {
569-
cmd.stdout(Stdio::inherit());
570-
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()
571682
} else {
572-
cmd.stdout(Stdio::null());
573-
cmd.stderr(Stdio::null());
574-
}
683+
Stdio::null()
684+
};
685+
686+
cmd.stdout(default_stdio());
687+
cmd.stderr(default_stdio());
688+
575689
let output = match cmd.output() {
576690
Ok(output) => output,
577691
Err(err) => {
578692
panic!("failed to run {:?}: {:?}", cmd, err);
579693
}
580694
};
581695

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+
}
582701
output
583702
}
584703

0 commit comments

Comments
 (0)