summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/fix.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/fix.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/fix.rs1855
1 files changed, 1855 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/testsuite/fix.rs b/src/tools/cargo/tests/testsuite/fix.rs
new file mode 100644
index 000000000..54a021c03
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/fix.rs
@@ -0,0 +1,1855 @@
+//! Tests for the `cargo fix` command.
+
+use cargo::core::Edition;
+use cargo_test_support::compare::assert_match_exact;
+use cargo_test_support::git::{self, init};
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::{Dependency, Package};
+use cargo_test_support::tools;
+use cargo_test_support::{basic_manifest, is_nightly, project};
+
+#[cargo_test]
+fn do_not_fix_broken_builds() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ let mut x = 3;
+ drop(x);
+ }
+
+ pub fn foo2() {
+ let _x: u32 = "a";
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_status(101)
+ .with_stderr_contains("[ERROR] could not compile `foo` (lib) due to previous error")
+ .run();
+ assert!(p.read_file("src/lib.rs").contains("let mut x = 3;"));
+}
+
+#[cargo_test]
+fn fix_broken_if_requested() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ fn foo(a: &u32) -> u32 { a + 1 }
+ pub fn bar() {
+ foo(1);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --broken-code")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+}
+
+#[cargo_test]
+fn broken_fixes_backed_out() {
+ // This works as follows:
+ // - Create a `rustc` shim (the "foo" project) which will pretend that the
+ // verification step fails.
+ // - There is an empty build script so `foo` has `OUT_DIR` to track the steps.
+ // - The first "check", `foo` creates a file in OUT_DIR, and it completes
+ // successfully with a warning diagnostic to remove unused `mut`.
+ // - rustfix removes the `mut`.
+ // - The second "check" to verify the changes, `foo` swaps out the content
+ // with something that fails to compile. It creates a second file so it
+ // won't do anything in the third check.
+ // - cargo fix discovers that the fix failed, and it backs out the changes.
+ // - The third "check" is done to display the original diagnostics of the
+ // original code.
+ let p = project()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file(
+ "foo/src/main.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::io::Write;
+ use std::path::{Path, PathBuf};
+ use std::process::{self, Command};
+
+ fn main() {
+ // Ignore calls to things like --print=file-names and compiling build.rs.
+ // Also compatible for rustc invocations with `@path` argfile.
+ let is_lib_rs = env::args_os()
+ .map(PathBuf::from)
+ .flat_map(|p| if let Some(p) = p.to_str().unwrap_or_default().strip_prefix("@") {
+ fs::read_to_string(p).unwrap().lines().map(PathBuf::from).collect()
+ } else {
+ vec![p]
+ })
+ .any(|l| l == Path::new("src/lib.rs"));
+ if is_lib_rs {
+ let path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let first = path.join("first");
+ let second = path.join("second");
+ if first.exists() && !second.exists() {
+ fs::write("src/lib.rs", b"not rust code").unwrap();
+ fs::File::create(&second).unwrap();
+ } else {
+ fs::File::create(&first).unwrap();
+ }
+ }
+
+ let status = Command::new("rustc")
+ .args(env::args().skip(1))
+ .status()
+ .expect("failed to run rustc");
+ process::exit(status.code().unwrap_or(2));
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = 'bar'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file("bar/build.rs", "fn main() {}")
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() {
+ let mut x = 3;
+ drop(x);
+ }
+ "#,
+ )
+ .build();
+
+ // Build our rustc shim
+ p.cargo("build").cwd("foo").run();
+
+ // Attempt to fix code, but our shim will always fail the second compile
+ p.cargo("fix --allow-no-vcs --lib")
+ .cwd("bar")
+ .env("__CARGO_FIX_YOLO", "1")
+ .env("RUSTC", p.root().join("foo/target/debug/foo"))
+ .with_stderr_contains(
+ "warning: failed to automatically apply fixes suggested by rustc \
+ to crate `bar`\n\
+ \n\
+ after fixes were automatically applied the compiler reported \
+ errors within these files:\n\
+ \n \
+ * src/lib.rs\n\
+ \n\
+ This likely indicates a bug in either rustc or cargo itself,\n\
+ and we would appreciate a bug report! You're likely to see \n\
+ a number of compiler warnings after this message which cargo\n\
+ attempted to fix but failed. If you could open an issue at\n\
+ [..]\n\
+ quoting the full output of this command we'd be very appreciative!\n\
+ Note that you may be able to make some more progress in the near-term\n\
+ fixing code with the `--broken-code` flag\n\
+ \n\
+ The following errors were reported:\n\
+ error: expected one of `!` or `::`, found `rust`\n\
+ ",
+ )
+ .with_stderr_contains("Original diagnostics will follow.")
+ .with_stderr_contains("[WARNING] variable does not need to be mutable")
+ .with_stderr_does_not_contain("[..][FIXED][..]")
+ .run();
+
+ // Make sure the fix which should have been applied was backed out
+ assert!(p.read_file("bar/src/lib.rs").contains("let mut x = 3;"));
+}
+
+#[cargo_test]
+fn fix_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = 'bar' }
+
+ [workspace]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ extern crate bar;
+
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs -p foo -p bar")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr_unordered(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[FIXED] bar/src/lib.rs (1 fix)
+[CHECKING] foo v0.1.0 ([..])
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn do_not_fix_non_relevant_deps() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = '../bar' }
+
+ [workspace]
+ "#,
+ )
+ .file("foo/src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .cwd("foo")
+ .run();
+
+ assert!(p.read_file("bar/src/lib.rs").contains("mut"));
+}
+
+#[cargo_test]
+fn prepare_for_2018() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![allow(unused)]
+
+ mod foo {
+ pub const FOO: &str = "fooo";
+ }
+
+ mod bar {
+ use ::foo::FOO;
+ }
+
+ fn main() {
+ let x = ::foo::FOO;
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] [..]
+";
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+
+ println!("{}", p.read_file("src/lib.rs"));
+ assert!(p.read_file("src/lib.rs").contains("use crate::foo::FOO;"));
+ assert!(p
+ .read_file("src/lib.rs")
+ .contains("let x = crate::foo::FOO;"));
+}
+
+#[cargo_test]
+fn local_paths() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ use test::foo;
+
+ mod test {
+ pub fn foo() {}
+ }
+
+ pub fn f() {
+ foo();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .with_stdout("")
+ .run();
+
+ println!("{}", p.read_file("src/lib.rs"));
+ assert!(p.read_file("src/lib.rs").contains("use crate::test::foo;"));
+}
+
+#[cargo_test]
+fn upgrade_extern_crate() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = '2018'
+
+ [workspace]
+
+ [dependencies]
+ bar = { path = 'bar' }
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![warn(rust_2018_idioms)]
+ extern crate bar;
+
+ use bar::bar;
+
+ pub fn foo() {
+ ::bar::bar();
+ bar();
+ }
+ "#,
+ )
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "pub fn bar() {}")
+ .build();
+
+ let stderr = "\
+[CHECKING] bar v0.1.0 ([..])
+[CHECKING] foo v0.1.0 ([..])
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+ println!("{}", p.read_file("src/lib.rs"));
+ assert!(!p.read_file("src/lib.rs").contains("extern crate"));
+}
+
+#[cargo_test]
+fn specify_rustflags() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #![allow(unused)]
+
+ mod foo {
+ pub const FOO: &str = "fooo";
+ }
+
+ fn main() {
+ let x = ::foo::FOO;
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.0.1 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn no_changes_necessary() {
+ let p = project().file("src/lib.rs", "").build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn fixes_extra_mut() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn fixes_two_missing_ampersands() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ let mut y = 3;
+ x + y
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn tricky() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() -> u32 {
+ let mut x = 3; let mut y = 3;
+ x + y
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo v0.0.1 ([..])
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] [..]
+";
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr(stderr)
+ .with_stdout("")
+ .run();
+}
+
+#[cargo_test]
+fn preserve_line_endings() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "fn add(a: &u32) -> u32 { a + 1 }\r\n\
+ pub fn foo() -> u32 { let mut x = 3; add(&x) }\r\n\
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+ assert!(p.read_file("src/lib.rs").contains("\r\n"));
+}
+
+#[cargo_test]
+fn fix_deny_warnings() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "#![deny(warnings)]
+ pub fn foo() { let mut x = 3; drop(x); }
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+}
+
+#[cargo_test]
+fn fix_deny_warnings_but_not_others() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ #![deny(unused_mut)]
+
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+
+ pub fn bar() {
+ #[allow(unused_mut)]
+ let mut _y = 4;
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+ assert!(!p.read_file("src/lib.rs").contains("let mut x = 3;"));
+ assert!(p.read_file("src/lib.rs").contains("let mut _y = 4;"));
+}
+
+#[cargo_test]
+fn fix_two_files() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "
+ pub mod bar;
+
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+ ",
+ )
+ .file(
+ "src/bar.rs",
+ "
+ pub fn foo() -> u32 {
+ let mut x = 3;
+ x
+ }
+
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stderr_contains("[FIXED] src/bar.rs (1 fix)")
+ .with_stderr_contains("[FIXED] src/lib.rs (1 fix)")
+ .run();
+ assert!(!p.read_file("src/lib.rs").contains("let mut x = 3;"));
+ assert!(!p.read_file("src/bar.rs").contains("let mut x = 3;"));
+}
+
+#[cargo_test]
+fn fixes_missing_ampersand() {
+ let p = project()
+ .file("src/main.rs", "fn main() { let mut x = 3; drop(x); }")
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() { let mut x = 3; drop(x); }
+
+ #[test]
+ pub fn foo2() { let mut x = 3; drop(x); }
+ "#,
+ )
+ .file(
+ "tests/a.rs",
+ r#"
+ #[test]
+ pub fn foo() { let mut x = 3; drop(x); }
+ "#,
+ )
+ .file("examples/foo.rs", "fn main() { let mut x = 3; drop(x); }")
+ .file("build.rs", "fn main() { let mut x = 3; drop(x); }")
+ .build();
+
+ p.cargo("fix --all-targets --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
+ .with_stderr_contains("[FIXED] build.rs (1 fix)")
+ // Don't assert number of fixes for this one, as we don't know if we're
+ // fixing it once or twice! We run this all concurrently, and if we
+ // compile (and fix) in `--test` mode first, we get two fixes. Otherwise
+ // we'll fix one non-test thing, and then fix another one later in
+ // test mode.
+ .with_stderr_contains("[FIXED] src/lib.rs[..]")
+ .with_stderr_contains("[FIXED] src/main.rs (1 fix)")
+ .with_stderr_contains("[FIXED] examples/foo.rs (1 fix)")
+ .with_stderr_contains("[FIXED] tests/a.rs (1 fix)")
+ .with_stderr_contains("[FINISHED] [..]")
+ .run();
+ p.cargo("check").run();
+ p.cargo("test").run();
+}
+
+#[cargo_test]
+fn fix_features() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [features]
+ bar = []
+
+ [workspace]
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #[cfg(feature = "bar")]
+ pub fn foo() -> u32 { let mut x = 3; x }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs").run();
+ p.cargo("check").run();
+ p.cargo("fix --features bar --allow-no-vcs").run();
+ p.cargo("check --features bar").run();
+}
+
+#[cargo_test]
+fn shows_warnings() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ "#[deprecated] fn bar() {} pub fn foo() { let _ = bar(); }",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_contains("[..]warning: use of deprecated[..]")
+ .run();
+}
+
+#[cargo_test]
+fn warns_if_no_vcs_detected() {
+ let p = project().file("src/lib.rs", "pub fn foo() {}").build();
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "error: no VCS found for this package and `cargo fix` can potentially perform \
+ destructive changes; if you'd like to suppress this error pass `--allow-no-vcs`\
+ ",
+ )
+ .run();
+ p.cargo("fix --allow-no-vcs").run();
+}
+
+#[cargo_test]
+fn warns_about_dirty_working_directory() {
+ let p = git::new("foo", |p| p.file("src/lib.rs", "pub fn foo() {}"));
+
+ p.change_file("src/lib.rs", "");
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the working directory of this package has uncommitted changes, \
+and `cargo fix` can potentially perform destructive changes; if you'd \
+like to suppress this error pass `--allow-dirty`, `--allow-staged`, or \
+commit the changes to these files:
+
+ * src/lib.rs (dirty)
+
+
+",
+ )
+ .run();
+ p.cargo("fix --allow-dirty").run();
+}
+
+#[cargo_test]
+fn warns_about_staged_working_directory() {
+ let (p, repo) = git::new_repo("foo", |p| p.file("src/lib.rs", "pub fn foo() {}"));
+
+ p.change_file("src/lib.rs", "pub fn bar() {}");
+ git::add(&repo);
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the working directory of this package has uncommitted changes, \
+and `cargo fix` can potentially perform destructive changes; if you'd \
+like to suppress this error pass `--allow-dirty`, `--allow-staged`, or \
+commit the changes to these files:
+
+ * src/lib.rs (staged)
+
+
+",
+ )
+ .run();
+ p.cargo("fix --allow-staged").run();
+}
+
+#[cargo_test]
+fn errors_about_untracked_files() {
+ let mut git_project = project().at("foo");
+ git_project = git_project.file("src/lib.rs", "pub fn foo() {}");
+ let p = git_project.build();
+ let _ = init(&p.root());
+
+ p.cargo("fix")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the working directory of this package has uncommitted changes, \
+and `cargo fix` can potentially perform destructive changes; if you'd \
+like to suppress this error pass `--allow-dirty`, `--allow-staged`, or \
+commit the changes to these files:
+
+ * Cargo.toml (dirty)
+ * src/ (dirty)
+
+
+",
+ )
+ .run();
+ p.cargo("fix --allow-dirty").run();
+}
+
+#[cargo_test]
+fn does_not_warn_about_clean_working_directory() {
+ let p = git::new("foo", |p| p.file("src/lib.rs", "pub fn foo() {}"));
+ p.cargo("fix").run();
+}
+
+#[cargo_test]
+fn does_not_warn_about_dirty_ignored_files() {
+ let p = git::new("foo", |p| {
+ p.file("src/lib.rs", "pub fn foo() {}")
+ .file(".gitignore", "bar\n")
+ });
+
+ p.change_file("bar", "");
+
+ p.cargo("fix").run();
+}
+
+#[cargo_test]
+fn fix_all_targets_by_default() {
+ let p = project()
+ .file("src/lib.rs", "pub fn foo() { let mut x = 3; drop(x); }")
+ .file("tests/foo.rs", "pub fn foo() { let mut x = 3; drop(x); }")
+ .build();
+ p.cargo("fix --allow-no-vcs")
+ .env("__CARGO_FIX_YOLO", "1")
+ .run();
+ assert!(!p.read_file("src/lib.rs").contains("let mut x"));
+ assert!(!p.read_file("tests/foo.rs").contains("let mut x"));
+}
+
+#[cargo_test]
+fn prepare_for_unstable() {
+ // During the period where a new edition is coming up, but not yet stable,
+ // this test will verify that it cannot be migrated to on stable. If there
+ // is no next edition, it does nothing.
+ let next = match Edition::LATEST_UNSTABLE {
+ Some(next) => next,
+ None => {
+ eprintln!("Next edition is currently not available, skipping test.");
+ return;
+ }
+ };
+ let latest_stable = Edition::LATEST_STABLE;
+ let prev = latest_stable.previous().unwrap();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "{}"
+ "#,
+ latest_stable
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ // -j1 to make the error more deterministic (otherwise there can be
+ // multiple errors since they run in parallel).
+ p.cargo("fix --edition --allow-no-vcs -j1")
+ .with_stderr(&format_args!("\
+[CHECKING] foo [..]
+[WARNING] `src/lib.rs` is on the latest edition, but trying to migrate to edition {next}.
+Edition {next} is unstable and not allowed in this release, consider trying the nightly release channel.
+
+If you are trying to migrate from the previous edition ({prev}), the
+process requires following these steps:
+
+1. Start with `edition = \"{prev}\"` in `Cargo.toml`
+2. Run `cargo fix --edition`
+3. Modify `Cargo.toml` to set `edition = \"{latest_stable}\"`
+4. Run `cargo build` or `cargo test` to verify the fixes worked
+
+More details may be found at
+https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html
+
+[FINISHED] [..]
+", next=next, latest_stable=latest_stable, prev=prev))
+ .run();
+
+ if !is_nightly() {
+ // The rest of this test is fundamentally always nightly.
+ return;
+ }
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_stderr(&format!(
+ "\
+[CHECKING] foo [..]
+[MIGRATING] src/lib.rs from {latest_stable} edition to {next}
+[FINISHED] [..]
+",
+ latest_stable = latest_stable,
+ next = next,
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn prepare_for_latest_stable() {
+ // This is the stable counterpart of prepare_for_unstable.
+ let latest_stable = Edition::LATEST_STABLE;
+ let previous = latest_stable.previous().unwrap();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '{}'
+ "#,
+ previous
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(&format!(
+ "\
+[CHECKING] foo [..]
+[MIGRATING] src/lib.rs from {} edition to {}
+[FINISHED] [..]
+",
+ previous, latest_stable
+ ))
+ .run();
+}
+
+#[cargo_test(nightly, reason = "fundamentally always nightly")]
+fn prepare_for_already_on_latest_unstable() {
+ // During the period where a new edition is coming up, but not yet stable,
+ // this test will check what happens if you are already on the latest. If
+ // there is no next edition, it does nothing.
+ let next_edition = match Edition::LATEST_UNSTABLE {
+ Some(next) => next,
+ None => {
+ eprintln!("Next edition is currently not available, skipping test.");
+ return;
+ }
+ };
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ cargo-features = ["edition{}"]
+
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '{}'
+ "#,
+ next_edition, next_edition
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .masquerade_as_nightly_cargo(&["always_nightly"])
+ .with_stderr_contains("[CHECKING] foo [..]")
+ .with_stderr_contains(&format!(
+ "\
+[WARNING] `src/lib.rs` is already on the latest edition ({next_edition}), unable to migrate further
+",
+ next_edition = next_edition
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn prepare_for_already_on_latest_stable() {
+ // Stable counterpart of prepare_for_already_on_latest_unstable.
+ if Edition::LATEST_UNSTABLE.is_some() {
+ eprintln!("This test cannot run while the latest edition is unstable, skipping.");
+ return;
+ }
+ let latest_stable = Edition::LATEST_STABLE;
+ let p = project()
+ .file(
+ "Cargo.toml",
+ &format!(
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '{}'
+ "#,
+ latest_stable
+ ),
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr_contains("[CHECKING] foo [..]")
+ .with_stderr_contains(&format!(
+ "\
+[WARNING] `src/lib.rs` is already on the latest edition ({latest_stable}), unable to migrate further
+",
+ latest_stable = latest_stable
+ ))
+ .run();
+}
+
+#[cargo_test]
+fn fix_overlapping() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo<T>() {}
+ pub struct A;
+
+ pub mod bar {
+ pub fn baz() {
+ ::foo::<::A>();
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --edition --lib")
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FIXED] src/lib.rs (2 fixes)
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ let contents = p.read_file("src/lib.rs");
+ println!("{}", contents);
+ assert!(contents.contains("crate::foo::<crate::A>()"));
+}
+
+#[cargo_test]
+fn fix_idioms() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ edition = '2018'
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ use std::any::Any;
+ pub fn foo() {
+ let _x: Box<Any> = Box::new(3);
+ }
+ "#,
+ )
+ .build();
+
+ let stderr = "\
+[CHECKING] foo [..]
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+";
+ p.cargo("fix --edition-idioms --allow-no-vcs")
+ .with_stderr(stderr)
+ .run();
+
+ assert!(p.read_file("src/lib.rs").contains("Box<dyn Any>"));
+}
+
+#[cargo_test]
+fn idioms_2015_ok() {
+ let p = project().file("src/lib.rs", "").build();
+
+ p.cargo("fix --edition-idioms --allow-no-vcs").run();
+}
+
+#[cargo_test]
+fn shows_warnings_on_second_run_without_changes() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ pub fn foo() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_contains("[..]warning: use of deprecated[..]")
+ .run();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_contains("[..]warning: use of deprecated[..]")
+ .run();
+}
+
+#[cargo_test]
+fn shows_warnings_on_second_run_without_changes_on_multiple_targets() {
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ pub fn foo() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ fn main() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "tests/foo.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ #[test]
+ fn foo_test() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "tests/bar.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ #[test]
+ fn foo_test() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .file(
+ "examples/fooxample.rs",
+ r#"
+ #[deprecated]
+ fn bar() {}
+
+ fn main() {
+ let _ = bar();
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --all-targets")
+ .with_stderr_contains(" --> examples/fooxample.rs:6:29")
+ .with_stderr_contains(" --> src/lib.rs:6:29")
+ .with_stderr_contains(" --> src/main.rs:6:29")
+ .with_stderr_contains(" --> tests/bar.rs:7:29")
+ .with_stderr_contains(" --> tests/foo.rs:7:29")
+ .run();
+
+ p.cargo("fix --allow-no-vcs --all-targets")
+ .with_stderr_contains(" --> examples/fooxample.rs:6:29")
+ .with_stderr_contains(" --> src/lib.rs:6:29")
+ .with_stderr_contains(" --> src/main.rs:6:29")
+ .with_stderr_contains(" --> tests/bar.rs:7:29")
+ .with_stderr_contains(" --> tests/foo.rs:7:29")
+ .run();
+}
+
+#[cargo_test]
+fn doesnt_rebuild_dependencies() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = 'bar' }
+
+ [workspace]
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --allow-no-vcs -p foo")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[CHECKING] bar v0.1.0 ([..])
+[CHECKING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("fix --allow-no-vcs -p foo")
+ .env("__CARGO_FIX_YOLO", "1")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.1.0 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn does_not_crash_with_rustc_wrapper() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .env("RUSTC_WRAPPER", tools::echo_wrapper())
+ .run();
+ p.build_dir().rm_rf();
+ p.cargo("fix --allow-no-vcs --verbose")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .run();
+}
+
+#[cargo_test]
+fn uses_workspace_wrapper_and_primary_wrapper_override() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --allow-no-vcs --verbose")
+ .env("RUSTC_WORKSPACE_WRAPPER", tools::echo_wrapper())
+ .with_stderr_contains("WRAPPER CALLED: rustc src/lib.rs --crate-name foo [..]")
+ .run();
+}
+
+#[cargo_test]
+fn only_warn_for_relevant_crates() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ a = { path = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "a/src/lib.rs",
+ "
+ pub fn foo() {}
+ pub mod bar {
+ use foo;
+ pub fn baz() { foo() }
+ }
+ ",
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs --edition")
+ .with_stderr(
+ "\
+[CHECKING] a v0.1.0 ([..])
+[CHECKING] foo v0.1.0 ([..])
+[MIGRATING] src/lib.rs from 2015 edition to 2018
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn fix_to_broken_code() {
+ let p = project()
+ .file(
+ "foo/Cargo.toml",
+ r#"
+ [package]
+ name = 'foo'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file(
+ "foo/src/main.rs",
+ r#"
+ use std::env;
+ use std::fs;
+ use std::io::Write;
+ use std::path::{Path, PathBuf};
+ use std::process::{self, Command};
+
+ fn main() {
+ // Ignore calls to things like --print=file-names and compiling build.rs.
+ // Also compatible for rustc invocations with `@path` argfile.
+ let is_lib_rs = env::args_os()
+ .map(PathBuf::from)
+ .flat_map(|p| if let Some(p) = p.to_str().unwrap_or_default().strip_prefix("@") {
+ fs::read_to_string(p).unwrap().lines().map(PathBuf::from).collect()
+ } else {
+ vec![p]
+ })
+ .any(|l| l == Path::new("src/lib.rs"));
+ if is_lib_rs {
+ let path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ let path = path.join("foo");
+ if path.exists() {
+ panic!()
+ } else {
+ fs::File::create(&path).unwrap();
+ }
+ }
+
+ let status = Command::new("rustc")
+ .args(env::args().skip(1))
+ .status()
+ .expect("failed to run rustc");
+ process::exit(status.code().unwrap_or(2));
+ }
+ "#,
+ )
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = 'bar'
+ version = '0.1.0'
+ [workspace]
+ "#,
+ )
+ .file("bar/build.rs", "fn main() {}")
+ .file("bar/src/lib.rs", "pub fn foo() { let mut x = 3; drop(x); }")
+ .build();
+
+ // Build our rustc shim
+ p.cargo("build").cwd("foo").run();
+
+ // Attempt to fix code, but our shim will always fail the second compile
+ p.cargo("fix --allow-no-vcs --broken-code")
+ .cwd("bar")
+ .env("RUSTC", p.root().join("foo/target/debug/foo"))
+ .with_status(101)
+ .with_stderr_contains("[WARNING] failed to automatically apply fixes [..]")
+ .run();
+
+ assert_eq!(
+ p.read_file("bar/src/lib.rs"),
+ "pub fn foo() { let x = 3; drop(x); }"
+ );
+}
+
+#[cargo_test]
+fn fix_with_common() {
+ let p = project()
+ .file("src/lib.rs", "")
+ .file(
+ "tests/t1.rs",
+ "mod common; #[test] fn t1() { common::try(); }",
+ )
+ .file(
+ "tests/t2.rs",
+ "mod common; #[test] fn t2() { common::try(); }",
+ )
+ .file("tests/common/mod.rs", "pub fn try() {}")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs").run();
+
+ assert_eq!(p.read_file("tests/common/mod.rs"), "pub fn r#try() {}");
+}
+
+#[cargo_test]
+fn fix_in_existing_repo_weird_ignore() {
+ // Check that ignore doesn't ignore the repo itself.
+ let p = git::new("foo", |project| {
+ project
+ .file("src/lib.rs", "")
+ .file(".gitignore", "foo\ninner\nCargo.lock\ntarget\n")
+ .file("inner/file", "")
+ });
+
+ p.cargo("fix").run();
+ // This is questionable about whether it is the right behavior. It should
+ // probably be checking if any source file for the current project is
+ // ignored.
+ p.cargo("fix")
+ .cwd("inner")
+ .with_stderr_contains("[ERROR] no VCS found[..]")
+ .with_status(101)
+ .run();
+ p.cargo("fix").cwd("src").run();
+}
+
+#[cargo_test]
+fn fix_color_message() {
+ // Check that color appears in diagnostics.
+ let p = project()
+ .file("src/lib.rs", "std::compile_error!{\"color test\"}")
+ .build();
+
+ p.cargo("fix --allow-no-vcs --color=always")
+ .with_stderr_contains("[..]\x1b[[..]")
+ .with_status(101)
+ .run();
+
+ p.cargo("fix --allow-no-vcs --color=never")
+ .with_stderr_contains("error: color test")
+ .with_stderr_does_not_contain("[..]\x1b[[..]")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn edition_v2_resolver_report() {
+ // Show a report if the V2 resolver shows differences.
+ Package::new("common", "1.0.0")
+ .feature("f1", &[])
+ .feature("dev-feat", &[])
+ .add_dep(Dependency::new("opt_dep", "1.0").optional(true))
+ .publish();
+ Package::new("opt_dep", "1.0.0").publish();
+
+ Package::new("bar", "1.0.0")
+ .add_dep(
+ Dependency::new("common", "1.0")
+ .target("cfg(whatever)")
+ .enable_features(&["f1"]),
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [dependencies]
+ common = "1.0"
+ bar = "1.0"
+
+ [build-dependencies]
+ common = { version = "1.0", features = ["opt_dep"] }
+
+ [dev-dependencies]
+ common = { version="1.0", features=["dev-feat"] }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr_unordered("\
+[UPDATING] [..]
+[DOWNLOADING] crates ...
+[DOWNLOADED] common v1.0.0 [..]
+[DOWNLOADED] bar v1.0.0 [..]
+[DOWNLOADED] opt_dep v1.0.0 [..]
+note: Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo.
+This may cause some dependencies to be built with fewer features enabled than previously.
+More information about the resolver changes may be found at https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html
+When building the following dependencies, the given features will no longer be used:
+
+ common v1.0.0 removed features: dev-feat, f1, opt_dep
+ common v1.0.0 (as host dependency) removed features: dev-feat, f1
+
+The following differences only apply when building with dev-dependencies:
+
+ common v1.0.0 removed features: f1, opt_dep
+
+[CHECKING] opt_dep v1.0.0
+[CHECKING] common v1.0.0
+[CHECKING] bar v1.0.0
+[CHECKING] foo v0.1.0 [..]
+[MIGRATING] src/lib.rs from 2018 edition to 2021
+[FINISHED] [..]
+")
+ .run();
+}
+
+#[cargo_test]
+fn rustfix_handles_multi_spans() {
+ // Checks that rustfix handles a single diagnostic with multiple
+ // suggestion spans (non_fmt_panic in this case).
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ panic!(format!("hey"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs").run();
+ assert!(p.read_file("src/lib.rs").contains(r#"panic!("hey");"#));
+}
+
+#[cargo_test]
+fn fix_edition_2021() {
+ // Can migrate 2021, even when lints are allowed.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ #![allow(ellipsis_inclusive_range_patterns)]
+
+ pub fn f() -> bool {
+ let x = 123;
+ match x {
+ 0...100 => true,
+ _ => false,
+ }
+ }
+ "#,
+ )
+ .build();
+ p.cargo("fix --edition --allow-no-vcs")
+ .with_stderr(
+ "\
+[CHECKING] foo v0.1.0 [..]
+[MIGRATING] src/lib.rs from 2018 edition to 2021
+[FIXED] src/lib.rs (1 fix)
+[FINISHED] [..]
+",
+ )
+ .run();
+ assert!(p.read_file("src/lib.rs").contains(r#"0..=100 => true,"#));
+}
+
+#[cargo_test]
+fn fix_shared_cross_workspace() {
+ // Fixing a file that is shared between multiple packages in the same workspace.
+ // Make sure two processes don't try to fix the same file at the same time.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["foo", "bar"]
+ "#,
+ )
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file("foo/src/lib.rs", "pub mod shared;")
+ // This will fix both unused and bare trait.
+ .file("foo/src/shared.rs", "pub fn fixme(x: Box<&Fn() -> ()>) {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
+ .file(
+ "bar/src/lib.rs",
+ r#"
+ #[path="../../foo/src/shared.rs"]
+ pub mod shared;
+ "#,
+ )
+ .build();
+
+ // The output here can be either of these two, depending on who runs first:
+ // [FIXED] bar/src/../../foo/src/shared.rs (2 fixes)
+ // [FIXED] foo/src/shared.rs (2 fixes)
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_unordered(
+ "\
+[CHECKING] foo v0.1.0 [..]
+[CHECKING] bar v0.1.0 [..]
+[FIXED] [..]foo/src/shared.rs (2 fixes)
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ assert_match_exact(
+ "pub fn fixme(_x: Box<&dyn Fn() -> ()>) {}",
+ &p.read_file("foo/src/shared.rs"),
+ );
+}
+
+#[cargo_test]
+fn abnormal_exit() {
+ // rustc fails unexpectedly after applying fixes, should show some error information.
+ //
+ // This works with a proc-macro that runs three times:
+ // - First run (collect diagnostics pass): writes a file, exits normally.
+ // - Second run (verify diagnostics work): it detects the presence of the
+ // file, removes the file, and aborts the process.
+ // - Third run (collecting messages to display): file not found, exits normally.
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pm = {path="pm"}
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn f() {
+ let mut x = 1;
+ pm::crashme!();
+ }
+ "#,
+ )
+ .file(
+ "pm/Cargo.toml",
+ r#"
+ [package]
+ name = "pm"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "pm/src/lib.rs",
+ r#"
+ use proc_macro::TokenStream;
+ #[proc_macro]
+ pub fn crashme(_input: TokenStream) -> TokenStream {
+ // Use a file to succeed on the first pass, and fail on the second.
+ let p = std::env::var_os("ONCE_PATH").unwrap();
+ let check_path = std::path::Path::new(&p);
+ if check_path.exists() {
+ eprintln!("I'm not a diagnostic.");
+ std::fs::remove_file(check_path).unwrap();
+ std::process::abort();
+ } else {
+ std::fs::write(check_path, "").unwrap();
+ "".parse().unwrap()
+ }
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --lib --allow-no-vcs")
+ .env(
+ "ONCE_PATH",
+ paths::root().join("proc-macro-run-once").to_str().unwrap(),
+ )
+ .with_stderr_contains(
+ "[WARNING] failed to automatically apply fixes suggested by rustc to crate `foo`",
+ )
+ .with_stderr_contains("I'm not a diagnostic.")
+ // "signal: 6, SIGABRT: process abort signal" on some platforms
+ .with_stderr_contains("rustc exited abnormally: [..]")
+ .with_stderr_contains("Original diagnostics will follow.")
+ .run();
+}
+
+#[cargo_test]
+fn fix_with_run_cargo_in_proc_macros() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ edition = "2018"
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ use proc_macro::*;
+
+ #[proc_macro]
+ pub fn foo(_input: TokenStream) -> TokenStream {
+ let output = std::process::Command::new(env!("CARGO"))
+ .args(&["metadata", "--format-version=1"])
+ .output()
+ .unwrap();
+ eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap());
+ println!("{}", std::str::from_utf8(&output.stdout).unwrap());
+ "".parse().unwrap()
+ }
+ "#,
+ )
+ .file(
+ "src/bin/main.rs",
+ r#"
+ use foo::foo;
+
+ fn main() {
+ foo!("bar")
+ }
+ "#,
+ )
+ .build();
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_does_not_contain("error: could not find .rs file in rustc args")
+ .run();
+}
+
+#[cargo_test]
+fn non_edition_lint_migration() {
+ // Migrating to a new edition where a non-edition lint causes problems.
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
+ .file(
+ "src/lib.rs",
+ r#"
+ // This is only used in a test.
+ // To be correct, this should be gated on #[cfg(test)], but
+ // sometimes people don't do that. If the unused_imports
+ // lint removes this, then the unittest will fail to compile.
+ use std::str::from_utf8;
+
+ pub mod foo {
+ pub const FOO: &[u8] = &[102, 111, 111];
+ }
+
+ #[test]
+ fn example() {
+ assert_eq!(
+ from_utf8(::foo::FOO), Ok("foo")
+ );
+ }
+ "#,
+ )
+ .build();
+ // Check that it complains about an unused import.
+ p.cargo("check --lib")
+ .with_stderr_contains("[..]unused_imports[..]")
+ .with_stderr_contains("[..]std::str::from_utf8[..]")
+ .run();
+ p.cargo("fix --edition --allow-no-vcs").run();
+ let contents = p.read_file("src/lib.rs");
+ // Check it does not remove the "unused" import.
+ assert!(contents.contains("use std::str::from_utf8;"));
+ // Check that it made the edition migration.
+ assert!(contents.contains("from_utf8(crate::foo::FOO)"));
+}
+
+// For rust-lang/cargo#9857
+#[cargo_test]
+fn fix_in_dependency() {
+ Package::new("bar", "1.0.0")
+ .file(
+ "src/lib.rs",
+ r#"
+ #[macro_export]
+ macro_rules! m {
+ ($i:tt) => {
+ let $i = 1;
+ };
+ }
+ "#,
+ )
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ r#"
+ pub fn foo() {
+ bar::m!(abc);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("fix --allow-no-vcs")
+ .with_stderr_does_not_contain("[FIXED] [..]")
+ .run();
+}