mirror of
https://github.com/rust-lang/cargo
synced 2024-10-30 22:57:17 +00:00
e7eaa51909
This changes `cargo fix` so that it keeps track of the output so that it doesn't need to run the final "show the output" step.
510 lines
16 KiB
Rust
510 lines
16 KiB
Rust
//! Tests for the `cargo fix` command, specifically targeting the logic around
|
|
//! running rustc multiple times to apply and verify fixes.
|
|
//!
|
|
//! These tests use a replacement of rustc ("rustc-fix-shim") which emits JSON
|
|
//! messages based on what the test is exercising. It uses an environment
|
|
//! variable RUSTC_FIX_SHIM_SEQUENCE which determines how it should behave
|
|
//! based on how many times `rustc` has run. It keeps track of how many times
|
|
//! rustc has run in a local file.
|
|
//!
|
|
//! For example, a sequence of `[Step::OneFix, Step::Error]` will emit one
|
|
//! suggested fix the first time `rustc` is run, and then the next time it is
|
|
//! run it will generate an error.
|
|
//!
|
|
//! The [`expect_fix_runs_rustc_n_times`] function handles setting everything
|
|
//! up, and verifying the results.
|
|
|
|
use cargo_test_support::{basic_manifest, paths, project, tools, Execs};
|
|
use std::path::PathBuf;
|
|
use std::sync::{Mutex, OnceLock};
|
|
|
|
/// The action that the `rustc` shim should take in the current sequence of
|
|
/// events.
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
#[repr(u8)]
|
|
enum Step {
|
|
/// Exits with success with no messages.
|
|
SuccessNoOutput = b'0',
|
|
/// Emits one suggested fix.
|
|
///
|
|
/// The suggested fix involves updating the number of the first line
|
|
/// comment which starts as `// fix-count 0`.
|
|
OneFix = b'1',
|
|
/// Emits two suggested fixes which overlap, which rustfix can only apply
|
|
/// one of them, and fails for the other.
|
|
///
|
|
/// The suggested fix is the same as `Step::OneFix`, it just shows up
|
|
/// twice.
|
|
TwoFixOverlapping = b'2',
|
|
/// Generates a warning without a suggestion.
|
|
Warning = b'w',
|
|
/// Generates an error message with no suggestion.
|
|
Error = b'e',
|
|
/// Emits one suggested fix and an error.
|
|
OneFixError = b'f',
|
|
}
|
|
|
|
/// Verifies `cargo fix` behavior based on the given sequence of behaviors for
|
|
/// `rustc`.
|
|
///
|
|
/// - `sequence` is the sequence of behaviors for each call to `rustc`.
|
|
/// If rustc is called more often than the number of steps, then it will panic.
|
|
/// - `extra_execs` a callback that allows extra customization of the [`Execs`].
|
|
/// - `expected_stderr` is the expected output from cargo.
|
|
/// - `expected_lib_rs` is the expected contents of `src/lib.rs` after the
|
|
/// fixes have been applied. The file starts out with the content `//
|
|
/// fix-count 0`, and the number increases based on which suggestions are
|
|
/// applied.
|
|
fn expect_fix_runs_rustc_n_times(
|
|
sequence: &[Step],
|
|
extra_execs: impl FnOnce(&mut Execs),
|
|
expected_stderr: &str,
|
|
expected_lib_rs: &str,
|
|
) {
|
|
let rustc = rustc_for_cargo_fix();
|
|
let p = project().file("src/lib.rs", "// fix-count 0").build();
|
|
|
|
let sequence_vec: Vec<_> = sequence.iter().map(|x| *x as u8).collect();
|
|
let sequence_str = std::str::from_utf8(&sequence_vec).unwrap();
|
|
|
|
let mut execs = p.cargo("fix --allow-no-vcs --lib");
|
|
execs
|
|
.env("RUSTC", &rustc)
|
|
.env("RUSTC_FIX_SHIM_SEQUENCE", sequence_str)
|
|
.with_stderr(expected_stderr);
|
|
extra_execs(&mut execs);
|
|
execs.run();
|
|
let lib_rs = p.read_file("src/lib.rs");
|
|
assert_eq!(expected_lib_rs, lib_rs);
|
|
let count: usize = p.read_file("rustc-fix-shim-count").parse().unwrap();
|
|
assert_eq!(sequence.len(), count);
|
|
}
|
|
|
|
/// Returns the path to the rustc replacement executable.
|
|
fn rustc_for_cargo_fix() -> PathBuf {
|
|
static FIX_SHIM: OnceLock<Mutex<Option<PathBuf>>> = OnceLock::new();
|
|
|
|
let mut lock = FIX_SHIM.get_or_init(|| Default::default()).lock().unwrap();
|
|
if let Some(path) = &*lock {
|
|
return path.clone();
|
|
}
|
|
|
|
let p = project()
|
|
.at(paths::global_root().join("rustc-fix-shim"))
|
|
.file("Cargo.toml", &basic_manifest("rustc-fix-shim", "1.0.0"))
|
|
.file(
|
|
"src/main.rs",
|
|
r##"
|
|
fn main() {
|
|
if std::env::var_os("CARGO_PKG_NAME").is_none() {
|
|
// Handle things like rustc -Vv
|
|
let r = std::process::Command::new("rustc")
|
|
.args(std::env::args_os().skip(1))
|
|
.status();
|
|
std::process::exit(r.unwrap().code().unwrap_or(2));
|
|
}
|
|
|
|
// Keep track of which step in the sequence that needs to run.
|
|
let successful_count = std::fs::read_to_string("rustc-fix-shim-count")
|
|
.map(|c| c.parse().unwrap())
|
|
.unwrap_or(0);
|
|
std::fs::write("rustc-fix-shim-count", format!("{}", successful_count + 1)).unwrap();
|
|
// The sequence tells us which behavior we should have.
|
|
let seq = std::env::var("RUSTC_FIX_SHIM_SEQUENCE").unwrap();
|
|
if successful_count >= seq.len() {
|
|
panic!("rustc called too many times count={}, \
|
|
make sure to update the Step sequence", successful_count);
|
|
}
|
|
match seq.as_bytes()[successful_count] {
|
|
b'0' => return,
|
|
b'1' => {
|
|
output_suggestion(successful_count + 1);
|
|
}
|
|
b'2' => {
|
|
output_suggestion(successful_count + 1);
|
|
output_suggestion(successful_count + 2);
|
|
}
|
|
b'w' => {
|
|
output_message("warning", successful_count + 1);
|
|
}
|
|
b'e' => {
|
|
output_message("error", successful_count + 1);
|
|
std::process::exit(1);
|
|
}
|
|
b'f' => {
|
|
output_suggestion(successful_count + 1);
|
|
output_message("error", successful_count + 2);
|
|
std::process::exit(1);
|
|
}
|
|
_ => panic!("unexpected sequence"),
|
|
}
|
|
}
|
|
|
|
fn output_suggestion(count: usize) {
|
|
let json = format!(
|
|
r#"{{
|
|
"$message_type": "diagnostic",
|
|
"message": "rustc fix shim comment {count}",
|
|
"code": null,
|
|
"level": "warning",
|
|
"spans":
|
|
[
|
|
{{
|
|
"file_name": "src/lib.rs",
|
|
"byte_start": 13,
|
|
"byte_end": 14,
|
|
"line_start": 1,
|
|
"line_end": 1,
|
|
"column_start": 14,
|
|
"column_end": 15,
|
|
"is_primary": true,
|
|
"text":
|
|
[
|
|
{{
|
|
"text": "// fix-count 0",
|
|
"highlight_start": 14,
|
|
"highlight_end": 15
|
|
}}
|
|
],
|
|
"label": "increase this number",
|
|
"suggested_replacement": null,
|
|
"suggestion_applicability": null,
|
|
"expansion": null
|
|
}}
|
|
],
|
|
"children":
|
|
[
|
|
{{
|
|
"message": "update the number here",
|
|
"code": null,
|
|
"level": "help",
|
|
"spans":
|
|
[
|
|
{{
|
|
"file_name": "src/lib.rs",
|
|
"byte_start": 13,
|
|
"byte_end": 14,
|
|
"line_start": 1,
|
|
"line_end": 1,
|
|
"column_start": 14,
|
|
"column_end": 15,
|
|
"is_primary": true,
|
|
"text":
|
|
[
|
|
{{
|
|
"text": "// fix-count 0",
|
|
"highlight_start": 14,
|
|
"highlight_end": 15
|
|
}}
|
|
],
|
|
"label": null,
|
|
"suggested_replacement": "{count}",
|
|
"suggestion_applicability": "MachineApplicable",
|
|
"expansion": null
|
|
}}
|
|
],
|
|
"children": [],
|
|
"rendered": null
|
|
}}
|
|
],
|
|
"rendered": "rustc fix shim comment {count}"
|
|
}}"#,
|
|
)
|
|
.replace("\n", "");
|
|
eprintln!("{json}");
|
|
}
|
|
|
|
fn output_message(level: &str, count: usize) {
|
|
let json = format!(
|
|
r#"{{
|
|
"$message_type": "diagnostic",
|
|
"message": "rustc fix shim {level} count={count}",
|
|
"code": null,
|
|
"level": "{level}",
|
|
"spans":
|
|
[
|
|
{{
|
|
"file_name": "src/lib.rs",
|
|
"byte_start": 0,
|
|
"byte_end": 0,
|
|
"line_start": 1,
|
|
"line_end": 1,
|
|
"column_start": 1,
|
|
"column_end": 1,
|
|
"is_primary": true,
|
|
"text":
|
|
[
|
|
{{
|
|
"text": "// fix-count 0",
|
|
"highlight_start": 1,
|
|
"highlight_end": 4
|
|
}}
|
|
],
|
|
"label": "forced error",
|
|
"suggested_replacement": null,
|
|
"suggestion_applicability": null,
|
|
"expansion": null
|
|
}}
|
|
],
|
|
"children": [],
|
|
"rendered": "rustc fix shim {level} count={count}"
|
|
}}"#,
|
|
)
|
|
.replace("\n", "");
|
|
eprintln!("{json}");
|
|
}
|
|
"##,
|
|
)
|
|
.build();
|
|
p.cargo("build").run();
|
|
let path = p.bin("rustc-fix-shim");
|
|
*lock = Some(path.clone());
|
|
path
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn fix_no_suggestions() {
|
|
// No suggested fixes.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::SuccessNoOutput],
|
|
|_execs| {},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 0",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn fix_one_suggestion() {
|
|
// One suggested fix, with a successful verification, no output.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::OneFix, Step::SuccessNoOutput],
|
|
|_execs| {},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
[FIXED] src/lib.rs (1 fix)
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 1",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn fix_one_overlapping() {
|
|
// Two suggested fixes, where one fails, then the next step returns no suggestions.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::TwoFixOverlapping, Step::SuccessNoOutput],
|
|
|_execs| {},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
[FIXED] src/lib.rs (1 fix)
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 2",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn fix_overlapping_max() {
|
|
// Rustc repeatedly spits out suggestions that overlap, which should hit
|
|
// the limit of 4 attempts. It should show the output from the 5th attempt.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[
|
|
Step::TwoFixOverlapping,
|
|
Step::TwoFixOverlapping,
|
|
Step::TwoFixOverlapping,
|
|
Step::TwoFixOverlapping,
|
|
Step::TwoFixOverlapping,
|
|
],
|
|
|_execs| {},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
warning: error applying suggestions to `src/lib.rs`
|
|
|
|
The full error message was:
|
|
|
|
> cannot replace slice of data that was already replaced
|
|
|
|
This likely indicates a bug in either rustc or cargo itself,
|
|
and we would appreciate a bug report! You're likely to see
|
|
a number of compiler warnings after this message which cargo
|
|
attempted to fix but failed. If you could open an issue at
|
|
https://github.com/rust-lang/rust/issues
|
|
quoting the full output of this command we'd be very appreciative!
|
|
Note that you may be able to make some more progress in the near-term
|
|
fixing code with the `--broken-code` flag
|
|
|
|
[FIXED] src/lib.rs (4 fixes)
|
|
rustc fix shim comment 5
|
|
rustc fix shim comment 6
|
|
warning: `foo` (lib) generated 2 warnings (run `cargo fix --lib -p foo` to apply 2 suggestions)
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 5",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn fix_verification_failed() {
|
|
// One suggested fix, with an error in the verification step.
|
|
// This should cause `cargo fix` to back out the changes.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::OneFix, Step::Error],
|
|
|_execs| {},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
warning: failed to automatically apply fixes suggested by rustc to crate `foo`
|
|
|
|
after fixes were automatically applied the compiler reported errors within these files:
|
|
|
|
* src/lib.rs
|
|
|
|
This likely indicates a bug in either rustc or cargo itself,
|
|
and we would appreciate a bug report! You're likely to see
|
|
a number of compiler warnings after this message which cargo
|
|
attempted to fix but failed. If you could open an issue at
|
|
https://github.com/rust-lang/rust/issues
|
|
quoting the full output of this command we'd be very appreciative!
|
|
Note that you may be able to make some more progress in the near-term
|
|
fixing code with the `--broken-code` flag
|
|
|
|
The following errors were reported:
|
|
rustc fix shim error count=2
|
|
Original diagnostics will follow.
|
|
|
|
rustc fix shim comment 1
|
|
warning: `foo` (lib) generated 1 warning (run `cargo fix --lib -p foo` to apply 1 suggestion)
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 0",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn fix_verification_failed_clippy() {
|
|
// This is the same as `fix_verification_failed_clippy`, except it checks
|
|
// the error message has the customization for the clippy URL and
|
|
// subcommand.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::OneFix, Step::Error],
|
|
|execs| {
|
|
execs.env("RUSTC_WORKSPACE_WRAPPER", tools::wrapped_clippy_driver());
|
|
},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
warning: failed to automatically apply fixes suggested by rustc to crate `foo`
|
|
|
|
after fixes were automatically applied the compiler reported errors within these files:
|
|
|
|
* src/lib.rs
|
|
|
|
This likely indicates a bug in either rustc or cargo itself,
|
|
and we would appreciate a bug report! You're likely to see
|
|
a number of compiler warnings after this message which cargo
|
|
attempted to fix but failed. If you could open an issue at
|
|
https://github.com/rust-lang/rust-clippy/issues
|
|
quoting the full output of this command we'd be very appreciative!
|
|
Note that you may be able to make some more progress in the near-term
|
|
fixing code with the `--broken-code` flag
|
|
|
|
The following errors were reported:
|
|
rustc fix shim error count=2
|
|
Original diagnostics will follow.
|
|
|
|
rustc fix shim comment 1
|
|
warning: `foo` (lib) generated 1 warning (run `cargo clippy --fix --lib -p foo` to apply 1 suggestion)
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 0",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn warnings() {
|
|
// Only emits warnings.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::Warning],
|
|
|_execs| {},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
rustc fix shim warning count=1
|
|
warning: `foo` (lib) generated 1 warning
|
|
[FINISHED] [..]
|
|
",
|
|
"// fix-count 0",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn starts_with_error() {
|
|
// The source code doesn't compile to start with.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::Error],
|
|
|execs| {
|
|
execs.with_status(101);
|
|
},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
rustc fix shim error count=1
|
|
error: could not compile `foo` (lib) due to 1 previous error
|
|
",
|
|
"// fix-count 0",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn broken_code_no_suggestions() {
|
|
// --broken-code with no suggestions
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::Error],
|
|
|execs| {
|
|
execs.arg("--broken-code").with_status(101);
|
|
},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
rustc fix shim error count=1
|
|
error: could not compile `foo` (lib) due to 1 previous error
|
|
",
|
|
"// fix-count 0",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn broken_code_one_suggestion() {
|
|
// --broken-code where there is an error and a suggestion.
|
|
expect_fix_runs_rustc_n_times(
|
|
&[Step::OneFixError, Step::Error],
|
|
|execs| {
|
|
execs.arg("--broken-code").with_status(101);
|
|
},
|
|
"\
|
|
[CHECKING] foo [..]
|
|
warning: failed to automatically apply fixes suggested by rustc to crate `foo`
|
|
|
|
after fixes were automatically applied the compiler reported errors within these files:
|
|
|
|
* src/lib.rs
|
|
|
|
This likely indicates a bug in either rustc or cargo itself,
|
|
and we would appreciate a bug report! You're likely to see
|
|
a number of compiler warnings after this message which cargo
|
|
attempted to fix but failed. If you could open an issue at
|
|
https://github.com/rust-lang/rust/issues
|
|
quoting the full output of this command we'd be very appreciative!
|
|
Note that you may be able to make some more progress in the near-term
|
|
fixing code with the `--broken-code` flag
|
|
|
|
The following errors were reported:
|
|
rustc fix shim error count=2
|
|
Original diagnostics will follow.
|
|
|
|
rustc fix shim comment 1
|
|
rustc fix shim error count=2
|
|
warning: `foo` (lib) generated 1 warning
|
|
error: could not compile `foo` (lib) due to 1 previous error; 1 warning emitted
|
|
",
|
|
"// fix-count 1",
|
|
);
|
|
}
|