Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

WIP: Edition mode #95

Closed
wants to merge 6 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -27,9 +27,9 @@ log = "0.4.1"
duct = "0.8.2"
env_logger = "0.5.0-rc.1"
log = "0.4.1"
pretty_assertions = "0.4.1"
tempdir = "0.3.5"
proptest = "0.7.0"
difference = "2.0.0"

[workspace]
members = [
48 changes: 48 additions & 0 deletions cargo-fix/tests/all/edition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use super::project;

#[test]
#[ignore = "Requires custom rustc build"]
fn fix_edition_lints() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["edition"]
[package]
name = "foo"
version = "0.1.0"
rust = "2018"
[workspace]
"#,
)
.file(
"src/lib.rs",
r#"
#![allow(unused)]
#[warn(rust_2018_migration)]
mod private_mod {
pub const FOO: &str = "BAR";
}
fn main() {}
"#,
)
.build();

let stderr = "\
[CHECKING] foo v0.1.0 (CWD)
[FIXING] src/lib.rs (1 fix)
[FINISHED] dev [unoptimized + debuginfo]
";
p.expect_cmd("cargo-fix fix")
.stdout("")
.stderr(stderr)
.run();

assert!(p.read("src/lib.rs").contains(r#"crate const FOO: &str = "BAR";"#));
}
1 change: 1 addition & 0 deletions cargo-fix/tests/all/main.rs
Original file line number Diff line number Diff line change
@@ -321,3 +321,4 @@ mod dependencies;
mod smoke;
mod subtargets;
mod warnings;
mod edition;
File renamed without changes.
8 changes: 8 additions & 0 deletions tests/edition/pub_crate.fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![allow(unused)]
#[warn(rust_2018_migration)]

mod private_mod {
crate const FOO: &str = "BAR";
}

fn main() {}
108 changes: 108 additions & 0 deletions tests/edition/pub_crate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"message": "unreachable `pub` item",
"code": {
"code": "unreachable_pub",
"explanation": null
},
"level": "warning",
"spans": [
{
"file_name": "./tests/edition/pub_crate.rs",
"byte_start": 70,
"byte_end": 98,
"line_start": 5,
"line_end": 5,
"column_start": 5,
"column_end": 33,
"is_primary": true,
"text": [
{
"text": " pub const FOO: &str = \"BAR\";",
"highlight_start": 5,
"highlight_end": 33
}
],
"label": null,
"suggested_replacement": null,
"expansion": null
}
],
"children": [
{
"message": "lint level defined here",
"code": null,
"level": "note",
"spans": [
{
"file_name": "./tests/edition/pub_crate.rs",
"byte_start": 25,
"byte_end": 44,
"line_start": 2,
"line_end": 2,
"column_start": 8,
"column_end": 27,
"is_primary": true,
"text": [
{
"text": "#[warn(rust_2018_migration)]",
"highlight_start": 8,
"highlight_end": 27
}
],
"label": null,
"suggested_replacement": null,
"expansion": null
}
],
"children": [],
"rendered": null
},
{
"message": "#[warn(unreachable_pub)] implied by #[warn(rust_2018_migration)]",
"code": null,
"level": "note",
"spans": [],
"children": [],
"rendered": null
},
{
"message": "or consider exporting it for use by other crates",
"code": null,
"level": "help",
"spans": [],
"children": [],
"rendered": null
},
{
"message": "consider restricting its visibility",
"code": null,
"level": "help",
"spans": [
{
"file_name": "./tests/edition/pub_crate.rs",
"byte_start": 70,
"byte_end": 73,
"line_start": 5,
"line_end": 5,
"column_start": 5,
"column_end": 8,
"is_primary": true,
"text": [
{
"text": " pub const FOO: &str = \"BAR\";",
"highlight_start": 5,
"highlight_end": 8
}
],
"label": null,
"suggested_replacement": "crate",
"suggestion_applicability": "MachineApplicable",
"expansion": null
}
],
"children": [],
"rendered": null
}
],
"rendered": "warning: unreachable `pub` item\n --> ./tests/edition/pub_crate.rs:5:5\n |\n5 | pub const FOO: &str = \"BAR\";\n | ---^^^^^^^^^^^^^^^^^^^^^^^^^\n | |\n | help: consider restricting its visibility: `crate`\n |\nnote: lint level defined here\n --> ./tests/edition/pub_crate.rs:2:8\n |\n2 | #[warn(rust_2018_migration)]\n | ^^^^^^^^^^^^^^^^^^^\n = note: #[warn(unreachable_pub)] implied by #[warn(rust_2018_migration)]\n = help: or consider exporting it for use by other crates\n\n"
}
8 changes: 8 additions & 0 deletions tests/edition/pub_crate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![allow(unused)]
#[warn(rust_2018_migration)]

mod private_mod {
pub const FOO: &str = "BAR";
}

fn main() {}
158 changes: 0 additions & 158 deletions tests/everything.rs

This file was deleted.

2 changes: 2 additions & 0 deletions tests/everything/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.recorded.json
*.recorded.rs
File renamed without changes.
6 changes: 3 additions & 3 deletions tests/fixtures/E0178.json → tests/everything/E0178.json
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
"level": "error",
"spans": [
{
"file_name": "./tests/fixtures/E0178.rs",
"file_name": "./tests/everything/E0178.rs",
"byte_start": 60,
"byte_end": 74,
"line_start": 6,
@@ -34,7 +34,7 @@
"level": "help",
"spans": [
{
"file_name": "./tests/fixtures/E0178.rs",
"file_name": "./tests/everything/E0178.rs",
"byte_start": 60,
"byte_end": 74,
"line_start": 6,
@@ -58,7 +58,7 @@
"rendered": null
}
],
"rendered": "error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`\n --> ./tests/fixtures/E0178.rs:6:8\n |\n6 | w: &'a Foo + Send,\n | ^^^^^^^^^^^^^^ help: try adding parentheses: `&'a (Foo + Send)`\n\nIf you want more information on this error, try using \"rustc --explain E0178\"\n"
"rendered": "error[E0178]: expected a path on the left-hand side of `+`, not `&'a Foo`\n --> ./tests/everything/E0178.rs:6:8\n |\n6 | w: &'a Foo + Send,\n | ^^^^^^^^^^^^^^ help: try adding parentheses: `&'a (Foo + Send)`\n\nIf you want more information on this error, try using \"rustc --explain E0178\"\n"
}
{
"message": "aborting due to previous error",
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
"level": "error",
"spans": [
{
"file_name": "./tests/fixtures/closure-immutable-outer-variable.rs",
"file_name": "./tests/everything/closure-immutable-outer-variable.rs",
"byte_start": 615,
"byte_end": 624,
"line_start": 19,
@@ -34,7 +34,7 @@
"level": "help",
"spans": [
{
"file_name": "./tests/fixtures/closure-immutable-outer-variable.rs",
"file_name": "./tests/everything/closure-immutable-outer-variable.rs",
"byte_start": 580,
"byte_end": 581,
"line_start": 18,
@@ -58,7 +58,7 @@
"rendered": null
}
],
"rendered": "error[E0594]: cannot assign to captured outer variable in an `FnMut` closure\n --> ./tests/fixtures/closure-immutable-outer-variable.rs:19:26\n |\n18 | let y = true;\n | - help: consider making `y` mutable: `mut y`\n19 | foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable\n | ^^^^^^^^^\n\nIf you want more information on this error, try using \"rustc --explain E0594\"\n"
"rendered": "error[E0594]: cannot assign to captured outer variable in an `FnMut` closure\n --> ./tests/everything/closure-immutable-outer-variable.rs:19:26\n |\n18 | let y = true;\n | - help: consider making `y` mutable: `mut y`\n19 | foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable\n | ^^^^^^^^^\n\nIf you want more information on this error, try using \"rustc --explain E0594\"\n"
}
{
"message": "aborting due to previous error",
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
"level": "error",
"spans": [
{
"file_name": "./tests/fixtures/lt-generic-comp.rs",
"file_name": "./tests/everything/lt-generic-comp.rs",
"byte_start": 49,
"byte_end": 50,
"line_start": 4,
@@ -24,7 +24,7 @@
"expansion": null
},
{
"file_name": "./tests/fixtures/lt-generic-comp.rs",
"file_name": "./tests/everything/lt-generic-comp.rs",
"byte_start": 47,
"byte_end": 48,
"line_start": 4,
@@ -51,7 +51,7 @@
"level": "help",
"spans": [
{
"file_name": "./tests/fixtures/lt-generic-comp.rs",
"file_name": "./tests/everything/lt-generic-comp.rs",
"byte_start": 38,
"byte_end": 46,
"line_start": 4,
@@ -75,7 +75,7 @@
"rendered": null
}
],
"rendered": "error: `<` is interpreted as a start of generic arguments for `u32`, not a comparison\n --> ./tests/fixtures/lt-generic-comp.rs:4:17\n |\n4 | if x as u32 < 4 {\n | -------- ^ - interpreted as generic arguments\n | | |\n | | not interpreted as comparison\n | help: try comparing the cast value: `(x as u32)`\n\n"
"rendered": "error: `<` is interpreted as a start of generic arguments for `u32`, not a comparison\n --> ./tests/everything/lt-generic-comp.rs:4:17\n |\n4 | if x as u32 < 4 {\n | -------- ^ - interpreted as generic arguments\n | | |\n | | not interpreted as comparison\n | help: try comparing the cast value: `(x as u32)`\n\n"
}
{
"message": "aborting due to previous error",
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
"level": "error",
"spans": [
{
"file_name": "./tests/fixtures/str-lit-type-mismatch.rs",
"file_name": "./tests/everything/str-lit-type-mismatch.rs",
"byte_start": 499,
"byte_end": 504,
"line_start": 13,
@@ -42,7 +42,7 @@
"level": "help",
"spans": [
{
"file_name": "./tests/fixtures/str-lit-type-mismatch.rs",
"file_name": "./tests/everything/str-lit-type-mismatch.rs",
"byte_start": 499,
"byte_end": 504,
"line_start": 13,
@@ -66,7 +66,7 @@
"rendered": null
}
],
"rendered": "error[E0308]: mismatched types\n --> ./tests/fixtures/str-lit-type-mismatch.rs:13:20\n |\n13 | let x: &[u8] = \"foo\"; //~ ERROR mismatched types\n | ^^^^^\n | |\n | expected slice, found str\n | help: consider adding a leading `b`: `b\"foo\"`\n |\n = note: expected type `&[u8]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
"rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:13:20\n |\n13 | let x: &[u8] = \"foo\"; //~ ERROR mismatched types\n | ^^^^^\n | |\n | expected slice, found str\n | help: consider adding a leading `b`: `b\"foo\"`\n |\n = note: expected type `&[u8]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
}
{
"message": "mismatched types",
@@ -77,7 +77,7 @@
"level": "error",
"spans": [
{
"file_name": "./tests/fixtures/str-lit-type-mismatch.rs",
"file_name": "./tests/everything/str-lit-type-mismatch.rs",
"byte_start": 555,
"byte_end": 561,
"line_start": 14,
@@ -112,7 +112,7 @@
"level": "help",
"spans": [
{
"file_name": "./tests/fixtures/str-lit-type-mismatch.rs",
"file_name": "./tests/everything/str-lit-type-mismatch.rs",
"byte_start": 555,
"byte_end": 561,
"line_start": 14,
@@ -136,7 +136,7 @@
"rendered": null
}
],
"rendered": "error[E0308]: mismatched types\n --> ./tests/fixtures/str-lit-type-mismatch.rs:14:23\n |\n14 | let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected array of 4 elements, found str\n | help: consider adding a leading `b`: `b\"baaa\"`\n |\n = note: expected type `&[u8; 4]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
"rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:14:23\n |\n14 | let y: &[u8; 4] = \"baaa\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected array of 4 elements, found str\n | help: consider adding a leading `b`: `b\"baaa\"`\n |\n = note: expected type `&[u8; 4]`\n found type `&'static str`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
}
{
"message": "mismatched types",
@@ -147,7 +147,7 @@
"level": "error",
"spans": [
{
"file_name": "./tests/fixtures/str-lit-type-mismatch.rs",
"file_name": "./tests/everything/str-lit-type-mismatch.rs",
"byte_start": 608,
"byte_end": 614,
"line_start": 15,
@@ -182,7 +182,7 @@
"level": "help",
"spans": [
{
"file_name": "./tests/fixtures/str-lit-type-mismatch.rs",
"file_name": "./tests/everything/str-lit-type-mismatch.rs",
"byte_start": 608,
"byte_end": 614,
"line_start": 15,
@@ -206,7 +206,7 @@
"rendered": null
}
],
"rendered": "error[E0308]: mismatched types\n --> ./tests/fixtures/str-lit-type-mismatch.rs:15:19\n |\n15 | let z: &str = b\"foo\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected str, found array of 3 elements\n | help: consider removing the leading `b`: `\"foo\"`\n |\n = note: expected type `&str`\n found type `&'static [u8; 3]`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
"rendered": "error[E0308]: mismatched types\n --> ./tests/everything/str-lit-type-mismatch.rs:15:19\n |\n15 | let z: &str = b\"foo\"; //~ ERROR mismatched types\n | ^^^^^^\n | |\n | expected str, found array of 3 elements\n | help: consider removing the leading `b`: `\"foo\"`\n |\n = note: expected type `&str`\n found type `&'static [u8; 3]`\n\nIf you want more information on this error, try using \"rustc --explain E0308\"\n"
}
{
"message": "aborting due to 3 previous errors",
2 changes: 2 additions & 0 deletions tests/migration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.recorded.json
*.recorded.rs
14 changes: 14 additions & 0 deletions tests/migration/absolute_path_starting_with_module.fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![allow(unused)]
#![feature(crate_in_paths)]
#![warn(absolute_path_starting_with_module)]
// #![warn(rust_2018_migration)]

mod foo {
use crate::bar::Bar;
}

pub mod bar {
pub struct Bar;
}

fn main() {}
108 changes: 108 additions & 0 deletions tests/migration/absolute_path_starting_with_module.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"message": "Absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition",
"code": {
"code": "absolute_path_starting_with_module",
"explanation": null
},
"level": "warning",
"spans": [
{
"file_name": "./tests/migration/absolute_path_starting_with_module.rs",
"byte_start": 143,
"byte_end": 153,
"line_start": 7,
"line_end": 7,
"column_start": 9,
"column_end": 19,
"is_primary": true,
"text": [
{
"text": " use ::bar::Bar;",
"highlight_start": 9,
"highlight_end": 19
}
],
"label": null,
"suggested_replacement": null,
"expansion": null
}
],
"children": [
{
"message": "lint level defined here",
"code": null,
"level": "note",
"spans": [
{
"file_name": "./tests/migration/absolute_path_starting_with_module.rs",
"byte_start": 54,
"byte_end": 88,
"line_start": 3,
"line_end": 3,
"column_start": 9,
"column_end": 43,
"is_primary": true,
"text": [
{
"text": "#![warn(absolute_path_starting_with_module)]",
"highlight_start": 9,
"highlight_end": 43
}
],
"label": null,
"suggested_replacement": null,
"expansion": null
}
],
"children": [],
"rendered": null
},
{
"message": "this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition!",
"code": null,
"level": "warning",
"spans": [],
"children": [],
"rendered": null
},
{
"message": "for more information, see issue TBD",
"code": null,
"level": "note",
"spans": [],
"children": [],
"rendered": null
},
{
"message": "use `crate`",
"code": null,
"level": "help",
"spans": [
{
"file_name": "./tests/migration/absolute_path_starting_with_module.rs",
"byte_start": 143,
"byte_end": 153,
"line_start": 7,
"line_end": 7,
"column_start": 9,
"column_end": 19,
"is_primary": true,
"text": [
{
"text": " use ::bar::Bar;",
"highlight_start": 9,
"highlight_end": 19
}
],
"label": null,
"suggested_replacement": "crate::bar::Bar",
"suggestion_applicability": "MachineApplicable",
"expansion": null
}
],
"children": [],
"rendered": null
}
],
"rendered": "warning: Absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition\n --> ./tests/migration/absolute_path_starting_with_module.rs:7:9\n |\n7 | use ::bar::Bar;\n | ^^^^^^^^^^ help: use `crate`: `crate::bar::Bar`\n |\nnote: lint level defined here\n --> ./tests/migration/absolute_path_starting_with_module.rs:3:9\n |\n3 | #![warn(absolute_path_starting_with_module)]\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition!\n = note: for more information, see issue TBD\n\n"
}
14 changes: 14 additions & 0 deletions tests/migration/absolute_path_starting_with_module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![allow(unused)]
#![feature(crate_in_paths)]
#![warn(absolute_path_starting_with_module)]
// #![warn(rust_2018_migration)]

mod foo {
use ::bar::Bar;
}

pub mod bar {
pub struct Bar;
}

fn main() {}
246 changes: 246 additions & 0 deletions tests/parse_and_replace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#![cfg(not(windows))] // TODO: should fix these tests on Windows

extern crate duct;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate rustfix;
extern crate serde_json;
extern crate tempdir;
#[macro_use]
extern crate failure;
extern crate difference;

use std::ffi::OsString;
use std::{env, fs};
use std::path::{Path, PathBuf};
use std::collections::HashSet;
use std::process::Output;

use failure::{Error, ResultExt};
use tempdir::TempDir;

use rustfix::apply_suggestions;

mod fixmode {
pub const EVERYTHING: &str = "yolo";
pub const MIGRATION: &str = "migration";
pub const EDITION: &str = "edition";
}

mod settings {
// can be set as env var to debug
pub const CHECK_JSON: &str = "RUSTFIX_TEST_CHECK_JSON";
pub const RECORD_JSON: &str = "RUSTFIX_TEST_RECORD_JSON";
pub const RECORD_FIXED_RUST: &str = "RUSTFIX_TEST_RECORD_FIXED_RUST";

// set automatically
pub const MODE: &str = "RUSTFIX_MODE";
}

fn compile(file: &Path, mode: &str) -> Result<Output, Error> {
let tmp = TempDir::new("rustfix-tests")?;

let mut args: Vec<OsString> = vec![
file.into(),
"--error-format=pretty-json".into(),
"-Zunstable-options".into(),
"--emit=metadata".into(),
"--crate-name=rustfix_test".into(),
"-Zsuggestion-applicability".into(),
"--out-dir".into(),
tmp.path().into(),
];

if mode == fixmode::EDITION {
args.push("--edition=2018".into());
}

let res = duct::cmd("rustc", &args)
.env("CLIPPY_DISABLE_DOCS_LINKS", "true")
.stdout_capture()
.stderr_capture()
.unchecked()
.run()?;

Ok(res)
}

fn compile_and_get_json_errors(file: &Path, mode: &str) -> Result<String, Error> {
let res = compile(file, mode)?;
let stderr = String::from_utf8(res.stderr)?;

match res.status.code() {
Some(0) | Some(1) | Some(101) => Ok(stderr),
_ => Err(format_err!("failed with status {:?}: {}", res.status.code(), stderr)),
}
}

fn compiles_without_errors(file: &Path, mode: &str) -> Result<(), Error> {
let res = compile(file, mode)?;

match res.status.code() {
Some(0) => Ok(()),
_ => {
info!(
"file {:?} failed to compile:\n{}",
file,
String::from_utf8(res.stderr)?
);
Err(format_err!(
"failed with status {:?} (`env RUST_LOG=parse_and_replace=info` for more info)",
res.status.code(),
))
}
}
}

fn read_file(path: &Path) -> Result<String, Error> {
use std::io::Read;

let mut buffer = String::new();
let mut file = fs::File::open(path)?;
file.read_to_string(&mut buffer)?;
Ok(buffer)
}

fn diff(expected: &str, actual: &str) -> String {
use std::fmt::Write;
use difference::{Changeset, Difference};

let mut res = String::new();
let changeset = Changeset::new(expected.trim(), actual.trim(), "\n");

let mut different = false;
for diff in changeset.diffs {
let (prefix, diff) = match diff {
Difference::Same(_) => continue,
Difference::Add(add) => ("+", add),
Difference::Rem(rem) => ("-", rem),
};
if !different {
write!(&mut res, "differences found (+ == actual, - == expected):\n");
different = true;
}
for diff in diff.lines() {
writeln!(&mut res, "{} {}", prefix, diff);
}
}
if different {
write!(&mut res, "");
}

res
}

fn test_rustfix_with_file<P: AsRef<Path>>(file: P, mode: &str) -> Result<(), Error> {
let file: &Path = file.as_ref();
let json_file = file.with_extension("json");
let fixed_file = file.with_extension("fixed.rs");

debug!("next up: {:?}", file);
let code = read_file(file)
.context(format!("could not read {}", file.display()))?;
let errors = compile_and_get_json_errors(file, mode)
.context(format!("could compile {}", file.display()))?;
let suggestions = rustfix::get_suggestions_from_json(&errors, &HashSet::new())
.context("could not load suggestions")?;

if std::env::var(settings::RECORD_JSON).is_ok() {
use std::io::Write;
let mut recorded_json = fs::File::create(&file.with_extension("recorded.json"))
.context(format!("could not create recorded.json for {}", file.display()))?;
recorded_json.write_all(errors.as_bytes())?;
}

if std::env::var(settings::CHECK_JSON).is_ok() {
let expected_json = read_file(&json_file)
.context(format!("could not load json fixtures for {}", file.display()))?;;
let expected_suggestions = rustfix::get_suggestions_from_json(&expected_json, &HashSet::new())
.context("could not load expected suggesitons")?;

ensure!(
expected_suggestions == suggestions,
"got unexpected suggestions from clippy:\n{}",
diff(&format!("{:?}", expected_suggestions), &format!("{:?}", suggestions))
);
}

let fixed = apply_suggestions(&code, &suggestions)
.context(format!("could not apply suggestions to {}", file.display()))?;

if std::env::var(settings::RECORD_FIXED_RUST).is_ok() {
use std::io::Write;
let mut recorded_rust = fs::File::create(&file.with_extension("recorded.rs"))?;
recorded_rust.write_all(fixed.as_bytes())?;
}

let expected_fixed = read_file(&fixed_file)
.context(format!("could read fixed file for {}", file.display()))?;
ensure!(
fixed.trim() == expected_fixed.trim(),
"file {} doesn't look fixed:\n{}", file.display(), diff(fixed.trim(), expected_fixed.trim())
);

compiles_without_errors(&fixed_file, mode)?;

Ok(())
}

fn get_fixture_files(p: &str) -> Result<Vec<PathBuf>, Error> {
Ok(fs::read_dir(&p)?
.into_iter()
.map(|e| e.unwrap().path())
.filter(|p| p.is_file())
.filter(|p| {
let x = p.to_string_lossy();
x.ends_with(".rs") && !x.ends_with(".fixed.rs") && !x.ends_with(".recorded.rs")
})
.collect())
}

fn assert_fixtures(dir: &str, mode: &str) {
let files = get_fixture_files(&dir)
.context(format!("couldn't load dir `{}`", dir))
.unwrap();
let mut failures = 0;

for file in &files {
if let Err(err) = test_rustfix_with_file(file, mode) {
println!("failed: {}", file.display());
warn!("{}", err);
for cause in err.causes().skip(1) {
info!("\tcaused by: {}", cause);
}
failures += 1;
}
info!("passed: {:?}", file);
}

if failures > 0 {
panic!(
"{} out of {} fixture asserts failed\n\
(run with `env RUST_LOG=parse_and_replace=info` to get more details)",
failures, files.len(),
);
}
}

#[test]
fn everything() {
let _ = env_logger::try_init();
assert_fixtures("./tests/everything", fixmode::EVERYTHING);
}

#[test]
fn migration() {
let _ = env_logger::try_init();
assert_fixtures("./tests/migration", fixmode::MIGRATION);
}

#[test]
#[ignore = "Requires custom rustc build"]
fn edition() {
let _ = env_logger::try_init();
assert_fixtures("./tests/edition", fixmode::EDITION);
}