diff --git a/Cargo.toml b/Cargo.toml index 81231a8..e4b93fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,3 @@ proptest = "0.7.0" difference = "2.0.0" [workspace] -members = [ - "cargo-fix", -] diff --git a/cargo-fix/Cargo.toml b/cargo-fix/Cargo.toml deleted file mode 100644 index ba5d92f..0000000 --- a/cargo-fix/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "cargo-fix" -version = "0.4.1" -authors = [ - "Pascal Hertleif ", - "Alex Crichton ", -] -license = "Apache-2.0/MIT" -description = "Automatically apply the suggestions made by rustc" -repository = "https://github.com/rust-lang-nursery/rustfix" -documentation = "https://docs.rs/rustfix" -readme = "README.md" - -[[bin]] -name = "cargo-fix" -test = false - -[dependencies] -failure = "0.1" -rustfix = { path = "..", version = "0.4" } -serde_json = "1" -log = "0.4" -env_logger = { version = "0.5", default-features = false } -clap = "2.31" -termcolor = "0.3.6" -atty = "0.2.10" -serde_derive = "1.0.45" -serde = "1.0.45" - -[dev-dependencies] -difference = "2" -url = "1" diff --git a/cargo-fix/LICENSE-APACHE b/cargo-fix/LICENSE-APACHE deleted file mode 120000 index 965b606..0000000 --- a/cargo-fix/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/cargo-fix/LICENSE-MIT b/cargo-fix/LICENSE-MIT deleted file mode 120000 index 76219eb..0000000 --- a/cargo-fix/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/cargo-fix/Readme.md b/cargo-fix/Readme.md deleted file mode 100644 index d8dc3f4..0000000 --- a/cargo-fix/Readme.md +++ /dev/null @@ -1,35 +0,0 @@ -# cargo fix - -Run `cargo fix` and get better code. - -[![Build Status](https://travis-ci.org/rust-lang-nursery/rustfix.svg?branch=master)](https://travis-ci.org/rust-lang-nursery/rustfix) -[![Build status](https://ci.appveyor.com/api/projects/status/g8ljreo9ryu3s6ee/branch/master?svg=true)](https://ci.appveyor.com/project/rust-lang-libs/rustfix/branch/master) - -## Installation - -Assuming you have a recent Rust toolchain installed and active, run `cargo install cargo-fix`. - -## Usage - -1. Navigate to a cargo project - 1. Make sure you use version control and have a clean working directory (so you can easily see the changes rustfix makes later on) - 2. Make sure your project compiles -2. Run `cargo fix` -3. ??? (wait for it to compile and fix your code) -4. PROFIT! - -## License - -Licensed under either of - -- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. diff --git a/cargo-fix/src/cli.rs b/cargo-fix/src/cli.rs deleted file mode 100644 index 5dd030b..0000000 --- a/cargo-fix/src/cli.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::env; -use std::io::Write; -use std::process::Command; - -use clap::{App, AppSettings, Arg, SubCommand}; -use failure::{Error, ResultExt}; -use termcolor::{ColorSpec, StandardStream, WriteColor}; - -use super::exit_with; -use diagnostics::{self, log_for_human, output_stream, write_warning, Message}; -use lock; -use vcs::VersionControl; - -static PLEASE_REPORT_THIS_BUG: &str = - "\ - This likely indicates a bug in either rustc or rustfix itself,\n\ - and we would appreciate a bug report! You're likely to see \n\ - a number of compiler warnings after this message which rustfix\n\ - attempted to fix but failed. If you could open an issue at\n\ - https://github.com/rust-lang-nursery/rustfix/issues\n\ - quoting the full output of this command we'd be very appreciative!\n\n\ - "; - -pub fn run() -> Result<(), Error> { - let matches = App::new("Cargo Fix") - .bin_name("cargo") - .subcommand( - SubCommand::with_name("fix") - .version(env!("CARGO_PKG_VERSION")) - .author("The Rust Project Developers") - .about("Automatically apply rustc's suggestions about fixing code") - .setting(AppSettings::TrailingVarArg) - .arg(Arg::with_name("args").multiple(true)) - .arg( - Arg::with_name("broken-code") - .long("broken-code") - .help("Fix code even if it already has compiler errors"), - ) - .arg( - Arg::with_name("edition") - .long("prepare-for") - .help("Fix warnings in preparation of an edition upgrade") - .takes_value(true) - .possible_values(&["2018"]), - ) - .arg( - Arg::with_name("allow-no-vcs") - .long("allow-no-vcs") - .help("Fix code even if a VCS was not detected"), - ) - .arg( - Arg::with_name("allow-dirty") - .long("allow-dirty") - .help("Fix code even if the working directory is dirty"), - ), - ) - .get_matches(); - - let matches = match matches.subcommand() { - ("fix", Some(matches)) => matches, - _ => bail!("unknown CLI arguments passed"), - }; - - if matches.is_present("broken-code") { - env::set_var("__CARGO_FIX_BROKEN_CODE", "1"); - } - - check_version_control(matches)?; - - // Spin up our lock server which our subprocesses will use to synchronize - // fixes. - let _lock_server = lock::Server::new()?.start()?; - - // Spin up our diagnostics server which our subprocesses will use to send - // use their dignostics messages in an ordered way. - let _diagnostics_server = diagnostics::Server::new()?.start(|m, stream| { - if let Err(e) = log_message(&m, stream) { - warn!("failed to log message: {}", e); - } - })?; - - let cargo = env::var_os("CARGO").unwrap_or("cargo".into()); - let mut cmd = Command::new(&cargo); - // TODO: shouldn't hardcode `check` here, we want to allow things like - // `cargo fix bench` or something like that - // - // TODO: somehow we need to force `check` to actually do something here, if - // `cargo check` was previously run it won't actually do anything again. - cmd.arg("check"); - if let Some(args) = matches.values_of("args") { - cmd.args(args); - } - - // Override the rustc compiler as ourselves. That way whenever rustc would - // run we run instead and have an opportunity to inject fixes. - let me = env::current_exe().context("failed to learn about path to current exe")?; - cmd.env("RUSTC", &me).env("__CARGO_FIX_NOW_RUSTC", "1"); - if let Some(rustc) = env::var_os("RUSTC") { - cmd.env("RUSTC_ORIGINAL", rustc); - } - - // Trigger edition-upgrade mode. Currently only supports the 2018 edition. - info!("edition upgrade? {:?}", matches.value_of("edition")); - if let Some("2018") = matches.value_of("edition") { - info!("edition upgrade!"); - let mut rustc_flags = env::var_os("RUSTFLAGS").unwrap_or_else(|| "".into()); - rustc_flags.push(" -W rust-2018-compatibility"); - cmd.env("RUSTFLAGS", &rustc_flags); - } - - // An now execute all of Cargo! This'll fix everything along the way. - // - // TODO: we probably want to do something fancy here like collect results - // from the client processes and print out a summary of what happened. - let status = cmd.status() - .with_context(|e| format!("failed to execute `{}`: {}", cargo.to_string_lossy(), e))?; - exit_with(status); -} - -fn check_version_control(matches: &::clap::ArgMatches) -> Result<(), Error> { - // Useful for tests - if env::var("__CARGO_FIX_IGNORE_VCS").is_ok() { - return Ok(()); - } - - let version_control = VersionControl::new(); - match (version_control.is_present(), version_control.is_dirty()?) { - (true, None) => {} // clean and versioned slate - (false, _) => { - let stream = &mut output_stream(); - - write_warning(stream)?; - stream.set_color(ColorSpec::new().set_bold(true))?; - writeln!(stream, "Could not detect a version control system")?; - stream.reset()?; - writeln!(stream, "You should consider using a VCS so you can easily see and revert rustfix's changes.")?; - - if !matches.is_present("allow-no-vcs") { - bail!("No VCS found, aborting. Overwrite this behavior with `--allow-no-vcs`."); - } - } - (true, Some(output)) => { - let stream = &mut output_stream(); - - write_warning(stream)?; - stream.set_color(ColorSpec::new().set_bold(true))?; - writeln!(stream, "Working directory dirty")?; - stream.reset()?; - writeln!(stream, "Make sure your working directory is clean so you can easily revert rustfix's changes.")?; - - stream.write_all(&output)?; - - if !matches.is_present("allow-dirty") { - bail!("Aborting because of dirty working directory. Overwrite this behavior with `--allow-dirty`."); - } - } - } - - Ok(()) -} - -fn log_message(msg: &Message, stream: &mut StandardStream) -> Result<(), Error> { - use diagnostics::Message::*; - - match *msg { - Fixing { - ref file, - ref fixes, - } => { - log_for_human( - "Fixing", - &format!( - "{name} ({n} {fixes})", - name = file, - n = fixes, - fixes = if *fixes > 1 { "fixes" } else { "fix" }, - ), - stream, - )?; - } - ReplaceFailed { - ref file, - ref message, - } => { - write_warning(stream)?; - stream.set_color(ColorSpec::new().set_bold(true))?; - write!(stream, "error applying suggestions to `{}`\n", file)?; - stream.reset()?; - write!(stream, "The full error message was:\n\n> {}\n\n", message)?; - stream.write(PLEASE_REPORT_THIS_BUG.as_bytes())?; - } - FixFailed { - ref files, - ref krate, - } => { - write_warning(stream)?; - stream.set_color(ColorSpec::new().set_bold(true))?; - if let Some(ref krate) = *krate { - write!( - stream, - "failed to automatically apply fixes suggested by rustc \ - to crate `{}`\n", - krate, - )?; - } else { - write!( - stream, - "failed to automatically apply fixes suggested by rustc\n" - )?; - } - if files.len() > 0 { - write!( - stream, - "\nafter fixes were automatically applied the compiler \ - reported errors within these files:\n\n" - )?; - for file in files { - write!(stream, " * {}\n", file)?; - } - write!(stream, "\n")?; - } - stream.write(PLEASE_REPORT_THIS_BUG.as_bytes())?; - } - } - - stream.reset()?; - stream.flush()?; - Ok(()) -} diff --git a/cargo-fix/src/diagnostics.rs b/cargo-fix/src/diagnostics.rs deleted file mode 100644 index 9a91980..0000000 --- a/cargo-fix/src/diagnostics.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! A small TCP server to handle collection of diagnostics information in a -//! cross-platform way. - -use std::env; -use std::io::{BufReader, Read, Write}; -use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream}; -use std::thread::{self, JoinHandle}; - -use atty; -use failure::{Error, ResultExt}; -use serde_json; -use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; - -static DIAGNOSICS_SERVER_VAR: &str = "__CARGO_FIX_DIAGNOSTICS_SERVER"; - -#[derive(Deserialize, Serialize)] -pub enum Message { - Fixing { - file: String, - fixes: usize, - }, - FixFailed { - files: Vec, - krate: Option, - }, - ReplaceFailed { - file: String, - message: String, - }, -} - -impl Message { - pub fn fixing(file: &str, num: usize) -> Message { - Message::Fixing { - file: file.into(), - fixes: num, - } - } - - pub fn post(&self) -> Result<(), Error> { - let addr = env::var(DIAGNOSICS_SERVER_VAR).context("diagnostics collector misconfigured")?; - let mut client = - TcpStream::connect(&addr).context("failed to connect to parent diagnostics target")?; - - let s = serde_json::to_string(self).context("failed to serialize message")?; - client - .write_all(s.as_bytes()) - .context("failed to write message to diagnostics target")?; - client - .shutdown(Shutdown::Write) - .context("failed to shutdown")?; - - let mut tmp = Vec::new(); - client - .read_to_end(&mut tmp) - .context("failed to receive a disconnect")?; - - Ok(()) - } -} - -pub struct Server { - listener: TcpListener, -} - -pub struct StartedServer { - _addr: SocketAddr, - thread: Option>, -} - -impl Server { - pub fn new() -> Result { - let listener = TcpListener::bind("127.0.0.1:0") - .with_context(|_| "failed to bind TCP listener to manage locking")?; - env::set_var(DIAGNOSICS_SERVER_VAR, listener.local_addr()?.to_string()); - - Ok(Server { listener }) - } - - pub fn start(self, on_message: F) -> Result - where - F: Fn(Message, &mut StandardStream) + Send + 'static, - { - let _addr = self.listener.local_addr()?; - let thread = thread::spawn(move || { - self.run(on_message); - }); - - Ok(StartedServer { - _addr, - thread: Some(thread), - }) - } - - fn run(self, on_message: F) - where - F: Fn(Message, &mut StandardStream), - { - let mut stream = output_stream(); - while let Ok((client, _)) = self.listener.accept() { - let mut client = BufReader::new(client); - match serde_json::from_reader(client) { - Ok(message) => on_message(message, &mut stream), - Err(e) => { - warn!("invalid diagnostics message: {}", e); - } - } - } - } -} - -impl Drop for StartedServer { - fn drop(&mut self) { - drop(self.thread.take().unwrap().join()); - } -} - -pub fn output_stream() -> StandardStream { - let color_choice = if atty::is(atty::Stream::Stderr) { - ColorChoice::Auto - } else { - ColorChoice::Never - }; - StandardStream::stderr(color_choice) -} - -pub fn write_warning(stream: &mut StandardStream) -> Result<(), Error> { - stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Yellow)))?; - write!(stream, "warning")?; - stream.reset()?; - stream.set_color(ColorSpec::new().set_bold(true))?; - write!(stream, ": ")?; - Ok(()) -} - -pub fn log_for_human(kind: &str, msg: &str, stream: &mut StandardStream) -> Result<(), Error> { - stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Cyan)))?; - // Justify to 12 chars just like cargo - write!(stream, "{:>12}", kind)?; - stream.reset()?; - write!(stream, " {}\n", msg)?; - Ok(()) -} diff --git a/cargo-fix/src/lock.rs b/cargo-fix/src/lock.rs deleted file mode 100644 index c3c6d2b..0000000 --- a/cargo-fix/src/lock.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! An implementation of IPC locks, guaranteed to be released if a process dies -//! -//! This module implements a locking server/client where the main `cargo fix` -//! process will start up a server and then all the client processes will -//! connect to it. The main purpose of this file is to enusre that each crate -//! (aka file entry point) is only fixed by one process at a time, currently -//! concurrent fixes can't happen. -//! -//! The basic design here is to use a TCP server which is pretty portable across -//! platforms. For simplicity it just uses threads as well. Clients connect to -//! the main server, inform the server what its name is, and then wait for the -//! server to give it the lock (aka write a byte). - -use std::collections::HashMap; -use std::env; -use std::io::{BufRead, BufReader, Read, Write}; -use std::net::{SocketAddr, TcpListener, TcpStream}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -use std::thread::{self, JoinHandle}; - -use failure::{Error, ResultExt}; - -pub struct Server { - listener: TcpListener, - threads: HashMap, - done: Arc, -} - -pub struct StartedServer { - done: Arc, - addr: SocketAddr, - thread: Option>, -} - -pub struct Client { - _socket: TcpStream, -} - -struct ServerClient { - thread: Option>, - lock: Arc)>>, -} - -impl Server { - pub fn new() -> Result { - let listener = TcpListener::bind("127.0.0.1:0") - .with_context(|_| "failed to bind TCP listener to manage locking")?; - env::set_var("__CARGO_FIX_SERVER", listener.local_addr()?.to_string()); - Ok(Server { - listener, - threads: HashMap::new(), - done: Arc::new(AtomicBool::new(false)), - }) - } - - pub fn start(self) -> Result { - let addr = self.listener.local_addr()?; - let done = self.done.clone(); - let thread = thread::spawn(|| { - self.run(); - }); - Ok(StartedServer { - addr, - thread: Some(thread), - done, - }) - } - - fn run(mut self) { - while let Ok((client, _)) = self.listener.accept() { - if self.done.load(Ordering::SeqCst) { - break; - } - - // Learn the name of our connected client to figure out if it needs - // to wait for another process to release the lock. - let mut client = BufReader::new(client); - let mut name = String::new(); - if client.read_line(&mut name).is_err() { - continue; - } - let client = client.into_inner(); - - // If this "named mutex" is already registered and the thread is - // still going, put it on the queue. Otherwise wait on the previous - // thread and we'll replace it just below. - if let Some(t) = self.threads.get_mut(&name) { - let mut state = t.lock.lock().unwrap(); - if state.0 { - state.1.push(client); - continue; - } - drop(t.thread.take().unwrap().join()); - } - - let lock = Arc::new(Mutex::new((true, vec![client]))); - let lock2 = lock.clone(); - let thread = thread::spawn(move || { - loop { - let mut client = { - let mut state = lock2.lock().unwrap(); - if state.1.len() == 0 { - state.0 = false; - break; - } else { - state.1.remove(0) - } - }; - // Inform this client that it now has the lock and wait for - // it to disconnect by waiting for EOF. - if client.write_all(&[1]).is_err() { - continue; - } - let mut dst = Vec::new(); - drop(client.read_to_end(&mut dst)); - } - }); - - self.threads.insert( - name, - ServerClient { - thread: Some(thread), - lock, - }, - ); - } - } -} - -impl Drop for Server { - fn drop(&mut self) { - for (_, mut client) in self.threads.drain() { - if let Some(thread) = client.thread.take() { - drop(thread.join()); - } - } - } -} - -impl Drop for StartedServer { - fn drop(&mut self) { - self.done.store(true, Ordering::SeqCst); - // Ignore errors here as this is largely best-effort - if TcpStream::connect(&self.addr).is_err() { - return; - } - drop(self.thread.take().unwrap().join()); - } -} - -impl Client { - pub fn lock(name: &str) -> Result { - let addr = env::var("__CARGO_FIX_SERVER") - .map_err(|_| format_err!("locking strategy misconfigured"))?; - let mut client = - TcpStream::connect(&addr).with_context(|_| "failed to connect to parent lock server")?; - client - .write_all(name.as_bytes()) - .and_then(|_| client.write_all(b"\n")) - .with_context(|_| "failed to write to lock server")?; - let mut buf = [0]; - client - .read_exact(&mut buf) - .with_context(|_| "failed to acquire lock")?; - Ok(Client { _socket: client }) - } -} diff --git a/cargo-fix/src/main.rs b/cargo-fix/src/main.rs deleted file mode 100644 index 45e91f4..0000000 --- a/cargo-fix/src/main.rs +++ /dev/null @@ -1,313 +0,0 @@ -extern crate atty; -extern crate clap; -extern crate env_logger; -#[macro_use] -extern crate failure; -#[macro_use] -extern crate log; -extern crate rustfix; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; -extern crate termcolor; - -use std::collections::{BTreeSet, HashMap, HashSet}; -use std::env; -use std::fs::File; -use std::io::{Read, Write}; -use std::path::Path; -use std::process::{self, Command, ExitStatus}; -use std::str; - -use failure::{Error, ResultExt}; -use rustfix::diagnostics::Diagnostic; -use termcolor::{Color, ColorSpec, WriteColor}; - -use diagnostics::{output_stream, Message}; - -mod cli; -mod diagnostics; -mod lock; -mod vcs; - -fn main() { - env_logger::init(); - let result = if env::var("__CARGO_FIX_NOW_RUSTC").is_ok() { - debug!("invoking cargo-fix as rustc wrapper"); - cargo_fix_rustc() - } else { - debug!("invoking cargo-fix as cargo subcommand"); - cli::run() - }; - let err = match result { - Ok(()) => return, - Err(e) => e, - }; - - let stream = &mut output_stream(); - let _ = stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Red))); - write!(stream, "error").unwrap(); - let _ = stream.reset(); - let _ = stream.set_color(ColorSpec::new().set_bold(true)); - writeln!(stream, ": {}", err).unwrap(); - let _ = stream.reset(); - - for cause in err.causes().skip(1) { - eprintln!("\tcaused by: {}", cause); - } - process::exit(102); -} - -fn cargo_fix_rustc() -> Result<(), Error> { - // Try to figure out what we're compiling by looking for a rust-like file - // that exists. - let filename = env::args() - .skip(1) - .filter(|s| s.ends_with(".rs")) - .filter(|s| Path::new(s).exists()) - .next(); - - trace!("cargo-fix as rustc got file {:?}", filename); - let rustc = env::var_os("RUSTC_ORIGINAL").unwrap_or("rustc".into()); - - // Our goal is to fix only the crates that the end user is interested in. - // That's very likely to only mean the crates in the workspace the user is - // working on, not random crates.io crates. - // - // To that end we only actually try to fix things if it looks like we're - // compiling a Rust file and it *doesn't* have an absolute filename. That's - // not the best heuristic but matches what Cargo does today at least. - let mut fixes = FixedCrate::default(); - if let Some(path) = filename { - if !Path::new(&path).is_absolute() { - trace!("start rustfixing {:?}", path); - fixes = rustfix_crate(rustc.as_ref(), &path)?; - } - } - - // Ok now we have our final goal of testing out the changes that we applied. - // If these changes went awry and actually started to cause the crate to - // *stop* compiling then we want to back them out and continue to print - // warnings to the user. - // - // If we didn't actually make any changes then we can immediately exec the - // new rustc, and otherwise we capture the output to hide it in the scenario - // that we have to back it all out. - let mut cmd = Command::new(&rustc); - cmd.args(env::args().skip(1)); - cmd.arg("--cap-lints=warn"); - cmd.arg("--error-format=json"); - if fixes.original_files.len() > 0 { - let output = cmd.output().context("failed to spawn rustc")?; - - if output.status.success() { - for message in fixes.messages.drain(..) { - message.post()?; - } - } - - // If we succeeded then we'll want to commit to the changes we made, if - // any. If stderr is empty then there's no need for the final exec at - // the end, we just bail out here. - if output.status.success() && output.stderr.len() == 0 { - return Ok(()); - } - - // Otherwise if our rustc just failed then that means that we broke the - // user's code with our changes. Back out everything and fall through - // below to recompile again. - if !output.status.success() { - for (k, v) in fixes.original_files { - File::create(&k) - .and_then(|mut f| f.write_all(v.as_bytes())) - .with_context(|_| format!("failed to write file `{}`", k))?; - } - log_failed_fix(&output.stderr)?; - } - } - - let mut cmd = Command::new(&rustc); - cmd.args(env::args().skip(1)); - cmd.arg("--cap-lints=warn"); - exit_with(cmd.status().context("failed to spawn rustc")?); -} - -#[derive(Default)] -struct FixedCrate { - messages: Vec, - original_files: HashMap, -} - -fn rustfix_crate(rustc: &Path, filename: &str) -> Result { - // If not empty, filter by these lints - // - // TODO: Implement a way to specify this - let only = HashSet::new(); - - // First up we want to make sure that each crate is only checked by one - // process at a time. If two invocations concurrently check a crate then - // it's likely to corrupt it. - // - // Currently we do this by assigning the name on our lock to the first - // argument that looks like a Rust file. - let _lock = lock::Client::lock(filename)?; - - let mut cmd = Command::new(&rustc); - cmd.args(env::args().skip(1)); - cmd.arg("--error-format=json").arg("--cap-lints=warn"); - let output = cmd.output() - .with_context(|_| format!("failed to execute `{}`", rustc.display()))?; - - // If rustc didn't succeed for whatever reasons then we're very likely to be - // looking at otherwise broken code. Let's not make things accidentally - // worse by applying fixes where a bug could cause *more* broken code. - // Instead, punt upwards which will reexec rustc over the original code, - // displaying pretty versions of the diagnostics we just read out. - // - // TODO: this should be configurable by the CLI to sometimes proceed to - // attempt to fix broken code. - if !output.status.success() && env::var_os("__CARGO_FIX_BROKEN_CODE").is_none() { - debug!( - "rustfixing `{:?}` failed, rustc exited with {:?}", - filename, - output.status.code() - ); - return Ok(Default::default()); - } - - let fix_mode = env::var_os("__CARGO_FIX_YOLO") - .map(|_| rustfix::Filter::Everything) - .unwrap_or(rustfix::Filter::MachineApplicableOnly); - - // Sift through the output of the compiler to look for JSON messages - // indicating fixes that we can apply. - let stderr = str::from_utf8(&output.stderr).context("failed to parse rustc stderr as utf-8")?; - - let suggestions = stderr.lines() - .filter(|x| !x.is_empty()) - - // Parse each line of stderr ignoring errors as they may not all be json - .filter_map(|line| serde_json::from_str::(line).ok()) - - // From each diagnostic try to extract suggestions from rustc - .filter_map(|diag| rustfix::collect_suggestions(&diag, &only, fix_mode)); - - // Collect suggestions by file so we can apply them one at a time later. - let mut file_map = HashMap::new(); - let mut num_suggestion = 0; - for suggestion in suggestions { - // Make sure we've got a file associated with this suggestion and all - // snippets point to the same location. Right now it's not clear what - // we would do with multiple locations. - let (file_name, range) = match suggestion.snippets.get(0) { - Some(s) => (s.file_name.clone(), s.line_range), - None => { - trace!("rejecting as it has no snippets {:?}", suggestion); - continue; - } - }; - if !suggestion - .snippets - .iter() - .all(|s| s.file_name == file_name && s.line_range == range) - { - trace!("rejecting as it spans multiple files {:?}", suggestion); - continue; - } - - file_map - .entry(file_name) - .or_insert_with(Vec::new) - .push(suggestion); - num_suggestion += 1; - } - - debug!( - "collected {} suggestions for `{}`", - num_suggestion, filename - ); - - let mut original_files = HashMap::with_capacity(file_map.len()); - let mut messages = Vec::new(); - for (file, suggestions) in file_map { - // Attempt to read the source code for this file. If this fails then - // that'd be pretty surprising, so log a message and otherwise keep - // going. - let mut code = String::new(); - if let Err(e) = File::open(&file).and_then(|mut f| f.read_to_string(&mut code)) { - warn!("failed to read `{}`: {}", file, e); - continue; - } - let num_suggestions = suggestions.len(); - debug!("applying {} fixes to {}", num_suggestions, file); - - messages.push(Message::fixing(&file, num_suggestions)); - - match rustfix::apply_suggestions(&code, &suggestions) { - Err(e) => { - diagnostics::Message::ReplaceFailed { - file: file, - message: e.to_string(), - }.post()?; - // TODO: Add flag to decide if we want to continue or bail out - continue; - } - Ok(new_code) => { - File::create(&file) - .and_then(|mut f| f.write_all(new_code.as_bytes())) - .with_context(|_| format!("failed to write file `{}`", file))?; - original_files.insert(file, code); - } - } - } - - Ok(FixedCrate { - messages, - original_files, - }) -} - -fn exit_with(status: ExitStatus) -> ! { - #[cfg(unix)] - { - use std::os::unix::prelude::*; - if let Some(signal) = status.signal() { - eprintln!("child failed with signal `{}`", signal); - process::exit(2); - } - } - process::exit(status.code().unwrap_or(3)); -} - -fn log_failed_fix(stderr: &[u8]) -> Result<(), Error> { - let stderr = str::from_utf8(stderr).context("failed to parse rustc stderr as utf-8")?; - - let diagnostics = stderr - .lines() - .filter(|x| !x.is_empty()) - .filter_map(|line| serde_json::from_str::(line).ok()); - let mut files = BTreeSet::new(); - for diagnostic in diagnostics { - for span in diagnostic.spans.into_iter() { - files.insert(span.file_name); - } - } - let mut krate = None; - let mut prev_dash_dash_krate_name = false; - for arg in env::args() { - if prev_dash_dash_krate_name { - krate = Some(arg.clone()); - } - - if arg == "--crate-name" { - prev_dash_dash_krate_name = true; - } else { - prev_dash_dash_krate_name = false; - } - } - - let files = files.into_iter().collect(); - Message::FixFailed { files, krate }.post()?; - - Ok(()) -} diff --git a/cargo-fix/src/vcs.rs b/cargo-fix/src/vcs.rs deleted file mode 100644 index 85f9d58..0000000 --- a/cargo-fix/src/vcs.rs +++ /dev/null @@ -1,77 +0,0 @@ -use failure::{err_msg, Error, ResultExt}; -use std::env; -use std::process::Command; - -pub enum VersionControl { - Git, - Hg, - Fossil, - Pijul, - None, -} - -impl VersionControl { - pub fn new() -> Self { - if is_in_vcs(".git").is_ok() { - VersionControl::Git - } else if is_in_vcs(".hg").is_ok() { - VersionControl::Hg - } else if is_in_vcs(".pijul").is_ok() { - VersionControl::Pijul - } else if is_in_vcs(".fossil").is_ok() { - VersionControl::Fossil - } else { - VersionControl::None - } - } - - pub fn is_present(&self) -> bool { - match *self { - VersionControl::None => false, - _ => true, - } - } - - /// Check if working tree is dirty - /// - /// # Returns - /// - /// - `Err(error)`: anything went wrong - /// - `Ok(None)`: No changes - /// - `Ok(bytes)`: Changes (bytes are VCS's output) - pub fn is_dirty(&self) -> Result>, Error> { - let (program, args) = match *self { - VersionControl::Git => ("git", "status --short"), - VersionControl::Hg => ("hg", "status"), - VersionControl::Pijul => ("pijul", "status"), - VersionControl::Fossil => ("fossil", "changes"), - VersionControl::None => return Ok(None), - }; - - let output = Command::new(program) - .args(args.split_whitespace()) - .output()? - .stdout; - - if output.is_empty() { - Ok(None) - } else { - Ok(Some(output)) - } - } -} - -fn is_in_vcs(vcs_dir: &str) -> Result<(), Error> { - let mut current_dir = env::current_dir().context("could not find the current directory")?; - - loop { - if current_dir.join(vcs_dir).metadata().is_ok() { - return Ok(()); - } - - current_dir = current_dir - .parent() - .ok_or_else(|| err_msg("could not find the parent directory"))? - .to_owned(); - } -} diff --git a/cargo-fix/tests/all/broken_build.rs b/cargo-fix/tests/all/broken_build.rs deleted file mode 100644 index 70005a9..0000000 --- a/cargo-fix/tests/all/broken_build.rs +++ /dev/null @@ -1,142 +0,0 @@ -use super::project; - -#[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.expect_cmd("cargo-fix fix").status(101).run(); - assert!(p.read("src/lib.rs").contains("let mut x = 3;")); -} - -#[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.expect_cmd("cargo-fix fix --broken-code") - .fix_everything() - .status(0) - .run(); -} - -#[test] -fn broken_fixes_backed_out() { - 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() { - let is_lib_rs = env::args_os() - .map(PathBuf::from) - .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() { - fs::File::create("src/lib.rs") - .unwrap() - .write_all(b"not rust code") - .unwrap(); - } 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", - r#" - pub fn foo() { - let mut x = 3; - drop(x); - } - "#, - ) - .build(); - - // Build our rustc shim - p.expect_cmd("cargo build").cwd("foo").run(); - - // Attempt to fix code, but our shim will always fail the second compile - p.expect_cmd("cargo-fix fix") - .fix_everything() - .cwd("bar") - .env("RUSTC", p.root.join("foo/target/debug/foo")) - .stderr_contains("not rust code") - .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 rustfix itself,\n\ - and we would appreciate a bug report! You're likely to see \n\ - a number of compiler warnings after this message which rustfix\n\ - attempted to fix but failed. If you could open an issue at\n\ - https://github.com/rust-lang-nursery/rustfix/issues\n\ - quoting the full output of this command we'd be very appreciative!\n\n\ - ", - ) - .stderr_not_contains("[FIXING]") - .status(101) - .run(); -} diff --git a/cargo-fix/tests/all/broken_lints.rs b/cargo-fix/tests/all/broken_lints.rs deleted file mode 100644 index 2865b28..0000000 --- a/cargo-fix/tests/all/broken_lints.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Ensure we give good error message when rustfix failes to apply changes -//! -//! TODO: Add rustc shim that outputs wrong suggestions instead of depending on -//! actual rustc bugs! - -// use super::project; - -// #[test] -// fn tell_user_about_broken_lints() { -// let p = project() -// .file( -// "src/lib.rs", -// r#" -// pub fn foo() { -// let mut i = 42; -// } -// "#, -// ) -// .build(); - -// p.expect_cmd("cargo-fix fix") -// .fix_everything() -// .stderr_contains(r"warning: error applying suggestions to `src/lib.rs`") -// .stderr_contains("The full error message was:") -// .stderr_contains("> Could not replace range 56...60 in file -- maybe parts of it were already replaced?") -// .stderr_contains("\ -// This likely indicates a bug in either rustc or rustfix itself,\n\ -// and we would appreciate a bug report! You're likely to see \n\ -// a number of compiler warnings after this message which rustfix\n\ -// attempted to fix but failed. If you could open an issue at\n\ -// https://github.com/rust-lang-nursery/rustfix/issues\n\ -// quoting the full output of this command we'd be very appreciative!\n\n\ -// ") -// .status(0) -// .run(); -// } diff --git a/cargo-fix/tests/all/dependencies.rs b/cargo-fix/tests/all/dependencies.rs deleted file mode 100644 index c0baeab..0000000 --- a/cargo-fix/tests/all/dependencies.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::project; - -#[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", - r#" - [package] - name = "bar" - version = "0.1.0" - "#, - ) - .file( - "bar/src/lib.rs", - r#" - pub fn foo() -> u32 { - let mut x = 3; - x - } - "#, - ) - .build(); - - let stderr = "\ -[CHECKING] bar v0.1.0 (CWD/bar) -[FIXING] bar/src/lib.rs (1 fix) -[CHECKING] foo v0.1.0 (CWD) -[FIXING] src/lib.rs (1 fix) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix") - .fix_everything() - .stdout("") - .stderr(stderr) - .run(); -} - -#[test] -fn do_not_fix_non_relevant_deps() { - let p = project() - .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", - r#" - [package] - name = "bar" - version = "0.1.0" - "#, - ) - .file( - "bar/src/lib.rs", - r#" - pub fn foo() -> u32 { - let mut x = 3; - x - } - "#, - ) - .build(); - - p.expect_cmd("cargo-fix fix").cwd("foo").status(0).run(); - assert!(p.read("bar/src/lib.rs").contains("mut")); -} diff --git a/cargo-fix/tests/all/edition_upgrade.rs b/cargo-fix/tests/all/edition_upgrade.rs deleted file mode 100644 index fbdf4a4..0000000 --- a/cargo-fix/tests/all/edition_upgrade.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Test that we can use cargo-fix to upgrade our code to work with the 2018 -//! edition. -//! -//! We'll trigger the `absolute_path_starting_with_module` lint which should -//! transform a `use ::foo;` where `foo` is local to `use crate::foo;`. - -use super::project; - -#[test] -fn prepare_for_2018() { - let p = project() - .file( - "src/lib.rs", - r#" - #![allow(unused)] - #![feature(rust_2018_preview)] - - mod foo { - pub const FOO: &str = "fooo"; - } - - mod bar { - use ::foo::FOO; - } - - fn main() { - let x = ::foo::FOO; - } - "#, - ) - .build(); - - let stderr = "\ -[CHECKING] foo v0.1.0 (CWD) -[FIXING] src/lib.rs (2 fixes) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix --prepare-for 2018") - .stdout("") - .stderr(stderr) - .run(); - - println!("{}", p.read("src/lib.rs")); - assert!(p.read("src/lib.rs").contains("use crate::foo::FOO;")); - assert!(p.read("src/lib.rs").contains("let x = crate::foo::FOO;")); -} - -#[test] -fn local_paths() { - let p = project() - .file( - "src/lib.rs", - r#" - #![feature(rust_2018_preview)] - - use test::foo; - - mod test { - pub fn foo() {} - } - - pub fn f() { - foo(); - } - "#, - ) - .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 --prepare-for 2018") - .stdout("") - .stderr(stderr) - .run(); - - println!("{}", p.read("src/lib.rs")); - assert!(p.read("src/lib.rs").contains("use crate::test::foo;")); -} - -#[test] -fn local_paths_no_fix() { - let p = project() - .file( - "src/lib.rs", - r#" - use test::foo; - - mod test { - pub fn foo() {} - } - - pub fn f() { - foo(); - } - "#, - ) - .build(); - - let stderr = "\ -[CHECKING] foo v0.1.0 (CWD) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix --prepare-for 2018") - .stdout("") - .stderr(stderr) - .run(); -} - -#[test] -fn upgrade_extern_crate() { - let p = project() - .file( - "Cargo.toml", - r#" - cargo-features = ["edition"] - - [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", - r#" - [package] - name = "bar" - version = "0.1.0" - "#, - ) - .file("bar/src/lib.rs", "pub fn bar() {}") - .build(); - - let stderr = "\ -[CHECKING] bar v0.1.0 (CWD/bar) -[CHECKING] foo v0.1.0 (CWD) -[FIXING] src/lib.rs (1 fix) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix") - .fix_everything() - .stdout("") - .stderr(stderr) - .run(); - - println!("{}", p.read("src/lib.rs")); - assert!(!p.read("src/lib.rs").contains("extern crate")); -} diff --git a/cargo-fix/tests/all/main.rs b/cargo-fix/tests/all/main.rs deleted file mode 100644 index 43f135f..0000000 --- a/cargo-fix/tests/all/main.rs +++ /dev/null @@ -1,363 +0,0 @@ -extern crate difference; -extern crate url; - -use std::env; -use std::ffi::{OsStr, OsString}; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::str; -use std::sync::atomic::*; -use std::time::Instant; - -use difference::{Changeset, Difference}; -use url::Url; - -static CNT: AtomicUsize = ATOMIC_USIZE_INIT; -thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst)); - -struct ProjectBuilder { - files: Vec<(String, String)>, - root: PathBuf, -} - -struct Project { - root: PathBuf, -} - -fn project() -> ProjectBuilder { - ProjectBuilder { - files: Vec::new(), - root: root(), - } -} - -fn root() -> PathBuf { - let idx = IDX.with(|x| *x); - - let mut me = env::current_exe().unwrap(); - me.pop(); // chop off exe name - me.pop(); // chop off `deps` - me.pop(); // chop off `debug` / `release` - me.push("generated-tests"); - me.push(&format!("test{}", idx)); - return me; -} - -impl ProjectBuilder { - fn file(mut self, name: &str, contents: &str) -> ProjectBuilder { - self.files.push((name.to_string(), contents.to_string())); - self - } - - fn use_temp_dir(mut self) -> ProjectBuilder { - let mut temp_dir = env::temp_dir(); - temp_dir.push(&format!("rustfix-generated-test{}", IDX.with(|x| *x))); - - self.root = temp_dir; - self - } - - fn build(mut self) -> Project { - if !self.files.iter().any(|f| f.0.ends_with("Cargo.toml")) { - let manifest = r#" - [package] - name = "foo" - version = "0.1.0" - - [workspace] - "#; - self.files - .push(("Cargo.toml".to_string(), manifest.to_string())); - } - drop(fs::remove_dir_all(&self.root)); - for &(ref file, ref contents) in self.files.iter() { - let dst = self.root.join(file); - fs::create_dir_all(dst.parent().unwrap()).unwrap(); - fs::File::create(&dst) - .unwrap() - .write_all(contents.as_ref()) - .unwrap(); - } - Project { root: self.root } - } -} - -impl Project { - fn expect_cmd<'a>(&'a self, cmd: &'a str) -> ExpectCmd<'a> { - ExpectCmd { - project: self, - cmd: cmd, - env: Vec::new(), - stdout: None, - stdout_contains: Vec::new(), - stderr: None, - stderr_contains: Vec::new(), - stderr_not_contains: Vec::new(), - check_vcs: false, - status: 0, - ran: false, - cwd: None, - } - } - - fn read(&self, path: &str) -> String { - let mut ret = String::new(); - File::open(self.root.join(path)) - .unwrap() - .read_to_string(&mut ret) - .unwrap(); - return ret; - } -} - -impl Drop for Project { - fn drop(&mut self) { - drop(fs::remove_dir_all(&self.root)); - drop(fs::remove_dir(&self.root)); - } -} - -struct ExpectCmd<'a> { - ran: bool, - project: &'a Project, - cmd: &'a str, - env: Vec<(OsString, OsString)>, - stdout: Option, - stdout_contains: Vec, - stderr: Option, - stderr_contains: Vec, - stderr_not_contains: Vec, - check_vcs: bool, - status: i32, - cwd: Option, -} - -impl<'a> ExpectCmd<'a> { - fn status(&mut self, status: i32) -> &mut Self { - self.status = status; - self - } - - fn cwd>(&mut self, path: P) -> &mut Self { - self.cwd = Some(self.project.root.join(path)); - self - } - - fn env, V: AsRef>(&mut self, k: K, v: V) -> &mut Self { - self.env - .push((k.as_ref().to_owned(), v.as_ref().to_owned())); - self - } - - fn fix_everything(&mut self) -> &mut Self { - self.env("__CARGO_FIX_YOLO", "true"); - self - } - - fn stdout(&mut self, s: &str) -> &mut Self { - self.stdout = Some(s.to_string()); - self - } - - fn stderr(&mut self, s: &str) -> &mut Self { - self.stderr = Some(s.to_string()); - self - } - - fn stderr_contains(&mut self, s: &str) -> &mut Self { - self.stderr_contains.push(s.to_string()); - self - } - - fn stderr_not_contains(&mut self, s: &str) -> &mut Self { - self.stderr_not_contains.push(s.to_string()); - self - } - - fn check_vcs(&mut self, b: bool) -> &mut Self { - self.check_vcs = b; - self - } - - fn run(&mut self) { - self.ran = true; - let mut parts = self.cmd.split_whitespace(); - let mut cmd = Command::new(parts.next().unwrap()); - cmd.args(parts); - match self.cwd { - Some(ref p) => { - cmd.current_dir(p); - } - None => { - cmd.current_dir(&self.project.root); - } - } - - for &(ref k, ref v) in self.env.iter() { - cmd.env(k, v); - } - - let mut me = env::current_exe().unwrap(); - me.pop(); // chop off exe name - me.pop(); // chop off `deps` - - let mut new_path = Vec::new(); - new_path.push(me); - new_path.extend(env::split_paths(&env::var_os("PATH").unwrap_or(Default::default()))); - cmd.env("PATH", env::join_paths(&new_path).unwrap()); - - // Don't output log stuff in tests, because it breaks our std{err,out} assertions - cmd.env("RUST_LOG", "warn"); - - if !self.check_vcs { - cmd.env("__CARGO_FIX_IGNORE_VCS", "true"); - } - - println!("\n···················································"); - println!("running {:?}", cmd); - let start = Instant::now(); - let output = match cmd.output() { - Ok(output) => output, - Err(err) => panic!("failed to spawn: {}", err), - }; - let dur = start.elapsed(); - println!( - "dur: {}.{:03}ms", - dur.as_secs(), - dur.subsec_nanos() / 1_000_000 - ); - println!("exit: {}", output.status); - if output.stdout.len() > 0 { - println!("stdout ---\n{}", String::from_utf8_lossy(&output.stdout)); - } - if output.stderr.len() > 0 { - println!("stderr ---\n{}", String::from_utf8_lossy(&output.stderr)); - } - println!("···················································"); - let code = match output.status.code() { - Some(code) => code, - None => panic!("super extra failure: {}", output.status), - }; - if code != self.status { - panic!("expected exit code `{}` got `{}`", self.status, code); - } - self.match_std(&output.stdout, &self.stdout, &self.stdout_contains, &[]); - self.match_std( - &output.stderr, - &self.stderr, - &self.stderr_contains, - &self.stderr_not_contains, - ); - } - - fn match_std( - &self, - actual: &[u8], - expected: &Option, - contains: &[String], - not_contains: &[String], - ) { - let actual = match str::from_utf8(actual) { - Ok(s) => s, - Err(_) => panic!("std wasn't utf8"), - }; - let actual = self.clean(actual); - if let Some(ref expected) = *expected { - diff(&self.clean(expected), &actual); - } - for s in contains { - let s = self.clean(s); - if actual.contains(&s) { - continue; - } - println!( - "\nfailed to find contents within output stream\n\ - expected to find\n {}\n\nwithin:\n\n{}\n\n", - s, actual - ); - panic!("test failed"); - } - for s in not_contains { - let s = self.clean(s); - if !actual.contains(&s) { - continue; - } - println!("expected to not find `{}`", s); - panic!("test failed"); - } - } - - fn clean(&self, s: &str) -> String { - let url = Url::from_file_path(&self.project.root).unwrap(); - let s = s.replace("[CHECKING]", " Checking") - .replace("[FINISHED]", " Finished") - .replace("[COMPILING]", " Compiling") - .replace("[FIXING]", " Fixing") - .replace(&url.to_string(), "CWD") - .replace(&self.project.root.display().to_string(), "CWD") - .replace("\\", "/"); - let lines = s.lines().map(|s| { - let i = match s.find("target(s) in") { - Some(i) => i, - None => return s.to_string(), - }; - if s.trim().starts_with("Finished") { - s[..i].to_string() - } else { - s.to_string() - } - }); - let mut ret = String::new(); - for (i, line) in lines.enumerate() { - if i != 0 { - ret.push_str("\n"); - } - ret.push_str(&line); - } - ret - } -} - -impl<'a> Drop for ExpectCmd<'a> { - fn drop(&mut self) { - if !self.ran { - panic!("forgot to run this command"); - } - } -} - -fn diff(expected: &str, actual: &str) { - 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 { - println!("differences found (+ == actual, - == expected):\n"); - different = true; - } - for diff in diff.lines() { - println!("{} {}", prefix, diff); - } - } - if different { - println!(""); - panic!("found some differences"); - } -} - -mod broken_build; -mod broken_lints; -mod dependencies; -mod edition_upgrade; -mod rust_flags; -mod smoke; -mod subtargets; -mod vcs; -mod warnings; diff --git a/cargo-fix/tests/all/rust_flags.rs b/cargo-fix/tests/all/rust_flags.rs deleted file mode 100644 index 1d31c4c..0000000 --- a/cargo-fix/tests/all/rust_flags.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::project; - -#[test] -fn specify_rustflags() { - let p = project() - .file( - "src/lib.rs", - r#" - #![allow(unused)] - #![feature(rust_2018_preview)] - - mod foo { - pub const FOO: &str = "fooo"; - } - - fn main() { - let x = ::foo::FOO; - } - "#, - ) - .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 --prepare-for 2018") - .env("RUSTFLAGS", "-C target-cpu=native") - .stdout("") - .stderr(stderr) - .run(); -} diff --git a/cargo-fix/tests/all/smoke.rs b/cargo-fix/tests/all/smoke.rs deleted file mode 100644 index d0be805..0000000 --- a/cargo-fix/tests/all/smoke.rs +++ /dev/null @@ -1,182 +0,0 @@ -use super::project; - -#[test] -fn no_changes_necessary() { - let p = project().file("src/lib.rs", "").build(); - - let stderr = "\ -[CHECKING] foo v0.1.0 (CWD) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix") - .stdout("") - .stderr(stderr) - .run(); -} - -#[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.1.0 (CWD) -[FIXING] src/lib.rs (1 fix) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix") - .fix_everything() - .stdout("") - .stderr(stderr) - .run(); -} - -#[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.1.0 (CWD) -[FIXING] src/lib.rs (2 fixes) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix") - .fix_everything() - .stdout("") - .stderr(stderr) - .run(); -} - -#[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.1.0 (CWD) -[FIXING] src/lib.rs (2 fixes) -[FINISHED] dev [unoptimized + debuginfo] -"; - p.expect_cmd("cargo-fix fix") - .fix_everything() - .stdout("") - .stderr(stderr) - .run(); -} - -#[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.expect_cmd("cargo-fix fix").fix_everything().run(); - assert!(p.read("src/lib.rs").contains("\r\n")); -} - -#[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.expect_cmd("cargo-fix fix").fix_everything().run(); -} - -#[test] -fn fix_deny_warnings_but_not_others() { - let p = project() - .file( - "src/lib.rs", - " - #![deny(warnings)] - - pub fn foo() -> u32 { - let mut x = 3; - x - } - - fn bar() {} - ", - ) - .build(); - - p.expect_cmd("cargo-fix fix").fix_everything().run(); - assert!(!p.read("src/lib.rs").contains("let mut x = 3;")); - assert!(p.read("src/lib.rs").contains("fn bar() {}")); -} - -#[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.expect_cmd("cargo-fix fix") - .fix_everything() - .stderr_contains("[FIXING] src/bar.rs (1 fix)") - .stderr_contains("[FIXING] src/lib.rs (1 fix)") - .run(); - assert!(!p.read("src/lib.rs").contains("let mut x = 3;")); - assert!(!p.read("src/bar.rs").contains("let mut x = 3;")); -} diff --git a/cargo-fix/tests/all/subtargets.rs b/cargo-fix/tests/all/subtargets.rs deleted file mode 100644 index db87211..0000000 --- a/cargo-fix/tests/all/subtargets.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::project; - -#[test] -fn fixes_missing_ampersand() { - let p = project() - .file( - "src/main.rs", - r#" - 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", - r#" - fn main() { let mut x = 3; drop(x); } - "#, - ) - .file( - "build.rs", - r#" - fn main() { let mut x = 3; drop(x); } - "#, - ) - .build(); - - p.expect_cmd("cargo fix -- --all-targets") - .fix_everything() - .stdout("") - .stderr_contains("[COMPILING] foo v0.1.0 (CWD)") - .stderr_contains("[FIXING] 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. - .stderr_contains("[FIXING] src/lib.rs") - .stderr_contains("[FIXING] src/main.rs (1 fix)") - .stderr_contains("[FIXING] examples/foo.rs (1 fix)") - .stderr_contains("[FIXING] tests/a.rs (1 fix)") - .stderr_contains("[FINISHED] dev [unoptimized + debuginfo]") - .run(); - p.expect_cmd("cargo build").run(); - p.expect_cmd("cargo test").run(); -} - -#[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.expect_cmd("cargo fix").run(); - p.expect_cmd("cargo build").run(); - p.expect_cmd("cargo fix -- --features bar").run(); - p.expect_cmd("cargo build --features bar").run(); -} diff --git a/cargo-fix/tests/all/vcs.rs b/cargo-fix/tests/all/vcs.rs deleted file mode 100644 index 7f04a49..0000000 --- a/cargo-fix/tests/all/vcs.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::project; - -#[test] -fn warns_if_no_vcs_detected() { - let p = project() - .use_temp_dir() - .file( - "src/lib.rs", - r#" - pub fn foo() { - } - "#, - ) - .build(); - - p.expect_cmd("cargo-fix fix") - .check_vcs(true) - .stderr( - "warning: Could not detect a version control system\n\ - You should consider using a VCS so you can easily see and revert rustfix's changes.\n\ - error: No VCS found, aborting. Overwrite this behavior with `--allow-no-vcs`.\n\ - ", - ) - .status(102) - .run(); - p.expect_cmd("cargo-fix fix").status(0).run(); -} - -#[test] -fn warns_about_dirty_working_directory() { - let p = project() - .file( - "src/lib.rs", - r#" - pub fn foo() { - } - "#, - ) - .build(); - - p.expect_cmd("git init").run(); - p.expect_cmd("cargo-fix fix") - .check_vcs(true) - .stderr( - "warning: Working directory dirty\n\ - Make sure your working directory is clean so you can easily revert rustfix's changes.\n\ - ?? Cargo.toml\n\ - ?? src/\n\ - error: Aborting because of dirty working directory. Overwrite this behavior with `--allow-dirty`.\n\n", - ) - .status(102) - .run(); - p.expect_cmd("cargo-fix fix --allow-dirty").status(0).run(); -} - -#[test] -fn does_not_warn_about_clean_working_directory() { - let p = project() - .file( - "src/lib.rs", - r#" - pub fn foo() { - } - "#, - ) - .build(); - - p.expect_cmd("git init").run(); - p.expect_cmd("git add .").run(); - p.expect_cmd("git config user.email rustfix@rustlang.org") - .run(); - p.expect_cmd("git config user.name RustFix").run(); - p.expect_cmd("git commit -m Initial-commit").run(); - p.expect_cmd("cargo-fix fix") - .check_vcs(true) - .status(0) - .run(); -} diff --git a/cargo-fix/tests/all/warnings.rs b/cargo-fix/tests/all/warnings.rs deleted file mode 100644 index fa53ea8..0000000 --- a/cargo-fix/tests/all/warnings.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::project; - -#[test] -fn shows_warnings() { - let p = project() - .file( - "src/lib.rs", - r#" - use std::default::Default; - - pub fn foo() { - } - "#, - ) - .build(); - - p.expect_cmd("cargo fix") - .stderr_contains("warning: unused import") - .run(); -}