Skip to content

Commit 7ac0397

Browse files
authored
Merge pull request #53 from pnkfelix/issue-34-add-ice-mode
Add --regress=regression-type command line option.
2 parents 8f11321 + ff8a075 commit 7ac0397

File tree

1 file changed

+152
-18
lines changed

1 file changed

+152
-18
lines changed

src/main.rs

Lines changed: 152 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,
@@ -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

Comments
 (0)