From 924795501739887461e07df1ba2ebf463ace1e49 Mon Sep 17 00:00:00 2001 From: Philipp Hansch Date: Sun, 9 Dec 2018 14:22:19 +0100 Subject: [PATCH 1/2] Add rustfix support --- Cargo.toml | 1 + src/common.rs | 4 +++ src/header.rs | 21 ++++++++++++ src/json.rs | 23 +++++++++++++ src/lib.rs | 1 + src/runtest.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 136 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6238a67..83ea576 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ tempfile = { version = "3.0", optional = true } serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +rustfix = "0.4.1" tester = { version = "0.5", optional = true } [target."cfg(unix)".dependencies] diff --git a/src/common.rs b/src/common.rs index 7ab290a..b812886 100644 --- a/src/common.rs +++ b/src/common.rs @@ -234,6 +234,10 @@ pub struct TestPaths { pub relative_dir: PathBuf, // e.g., foo/bar } +pub const UI_STDERR: &str = "stderr"; +pub const UI_STDOUT: &str = "stdout"; +pub const UI_FIXED: &str = "fixed"; + impl Config { /// Add rustc flags to link with the crate's dependencies in addition to the crate itself pub fn link_deps(&mut self) { diff --git a/src/header.rs b/src/header.rs index d57d0c1..9616f53 100644 --- a/src/header.rs +++ b/src/header.rs @@ -222,6 +222,8 @@ pub struct TestProps { // customized normalization rules pub normalize_stdout: Vec<(String, String)>, pub normalize_stderr: Vec<(String, String)>, + pub run_rustfix: bool, + pub rustfix_only_machine_applicable: bool, } impl TestProps { @@ -250,6 +252,8 @@ impl TestProps { run_pass: false, normalize_stdout: vec![], normalize_stderr: vec![], + run_rustfix: false, + rustfix_only_machine_applicable: false, } } @@ -371,6 +375,15 @@ impl TestProps { if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stderr") { self.normalize_stderr.push(rule); } + + if !self.run_rustfix { + self.run_rustfix = config.parse_run_rustfix(ln); + } + + if !self.rustfix_only_machine_applicable { + self.rustfix_only_machine_applicable = + config.parse_rustfix_only_machine_applicable(ln); + } }); for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] { @@ -588,6 +601,14 @@ impl Config { None } + + fn parse_run_rustfix(&self, line: &str) -> bool { + self.parse_name_directive(line, "run-rustfix") + } + + fn parse_rustfix_only_machine_applicable(&self, line: &str) -> bool { + self.parse_name_directive(line, "rustfix-only-machine-applicable") + } } pub fn lldb_version_to_int(version_string: &str) -> isize { diff --git a/src/json.rs b/src/json.rs index 3d7fd57..3e5e98f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -24,6 +24,7 @@ struct Diagnostic { level: String, spans: Vec, children: Vec, + rendered: Option, } #[derive(Deserialize, Clone)] @@ -71,6 +72,28 @@ struct DiagnosticCode { explanation: Option, } +pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String { + output + .lines() + .filter_map(|line| { + if line.starts_with('{') { + match serde_json::from_str::(line) { + Ok(diagnostic) => diagnostic.rendered, + Err(error) => { + proc_res.fatal(Some(&format!( + "failed to decode compiler output as json: \ + `{}`\nline: {}\noutput: {}", + error, line, output + ))); + } + } + } else { + None + } + }) + .collect() +} + pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec { output.lines() .flat_map(|line| parse_line(file_name, line, output, proc_res)) diff --git a/src/lib.rs b/src/lib.rs index 66ec6ca..7522304 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ extern crate diff; extern crate serde_json; #[macro_use] extern crate serde_derive; +extern crate rustfix; use std::env; use std::ffi::OsString; diff --git a/src/runtest.rs b/src/runtest.rs index 26abe5c..f612574 100644 --- a/src/runtest.rs +++ b/src/runtest.rs @@ -9,6 +9,7 @@ // except according to those terms. use common::{Config, TestPaths}; +use common::{UI_FIXED, UI_STDERR, UI_STDOUT}; use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind}; use common::{Codegen, DebugInfoLldb, DebugInfoGdb, Rustdoc, CodegenUnits}; use common::{Incremental, RunMake, Ui, MirOpt}; @@ -17,6 +18,7 @@ use errors::{self, ErrorKind, Error}; use filetime::FileTime; use json; use regex::Regex; +use rustfix::{apply_suggestions, get_suggestions_from_json, Filter}; use header::TestProps; use util::logv; @@ -1430,7 +1432,16 @@ actual:\n\ rustc.arg(dir_opt); } - RunPass | + RunPass | Ui => { + if !self + .props + .compile_flags + .iter() + .any(|s| s.starts_with("--error-format")) + { + rustc.args(&["--error-format", "json"]); + } + } RunFail | RunPassValgrind | Pretty | @@ -1439,7 +1450,6 @@ actual:\n\ Codegen | Rustdoc | RunMake | - Ui | CodegenUnits => { // do not use JSON output } @@ -2222,22 +2232,68 @@ actual:\n\ } fn run_ui_test(&self) { + // if the user specified a format in the ui test + // print the output to the stderr file, otherwise extract + // the rendered error messages from json and print them + let explicit = self + .props + .compile_flags + .iter() + .any(|s| s.contains("--error-format")); let proc_res = self.compile_test(); - let expected_stderr_path = self.expected_output_path("stderr"); + let expected_stderr_path = self.expected_output_path(UI_STDERR); let expected_stderr = self.load_expected_output(&expected_stderr_path); - let expected_stdout_path = self.expected_output_path("stdout"); + let expected_stdout_path = self.expected_output_path(UI_STDOUT); let expected_stdout = self.load_expected_output(&expected_stdout_path); + let expected_fixed_path = self.expected_output_path(UI_FIXED); + let expected_fixed = self.load_expected_output(&expected_fixed_path); + let normalized_stdout = self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout); + + let stderr = if explicit { + proc_res.stderr.clone() + } else { + json::extract_rendered(&proc_res.stderr, &proc_res) + }; + let normalized_stderr = - self.normalize_output(&proc_res.stderr, &self.props.normalize_stderr); + self.normalize_output(&stderr, &self.props.normalize_stderr); let mut errors = 0; - errors += self.compare_output("stdout", &normalized_stdout, &expected_stdout); - errors += self.compare_output("stderr", &normalized_stderr, &expected_stderr); + errors += self.compare_output(UI_STDOUT, &normalized_stdout, &expected_stdout); + errors += self.compare_output(UI_STDERR, &normalized_stderr, &expected_stderr); + + + if self.props.run_rustfix { + // Apply suggestions from lints to the code itself + let unfixed_code = self + .load_expected_output_from_path(&self.testpaths.file) + .expect("Could not load output from path"); + let suggestions = get_suggestions_from_json( + &proc_res.stderr, + &HashSet::new(), + if self.props.rustfix_only_machine_applicable { + Filter::MachineApplicableOnly + } else { + Filter::Everything + }, + ).expect("Could not retrieve suggestions from JSON"); + let fixed_code = apply_suggestions(&unfixed_code, &suggestions).expect(&format!( + "failed to apply suggestions for {:?} with rustfix", + self.testpaths.file + )); + + errors += self.compare_output(UI_FIXED, &fixed_code, &expected_fixed); + } else if !expected_fixed.is_empty() { + panic!( + "the `// run-rustfix` directive wasn't found but a `*.fixed` \ + file was found" + ); + } if errors > 0 { println!("To update references, run this command from build directory:"); @@ -2259,6 +2315,23 @@ actual:\n\ self.fatal_proc_rec("test run failed!", &proc_res); } } + + if self.props.run_rustfix { + // And finally, compile the fixed code and make sure it both + // succeeds and has no diagnostics. + let mut rustc = self.make_compile_args( + &self.testpaths.file.with_extension(UI_FIXED), + TargetLocation::ThisFile(self.make_exe_name()), + ); + rustc.arg("-L").arg(&self.aux_output_dir_name()); + let res = self.compose_and_run_compiler(rustc, None); + if !res.status.success() { + self.fatal_proc_rec("failed to compile fixed code", &res); + } + if !res.stderr.is_empty() && !self.props.rustfix_only_machine_applicable { + self.fatal_proc_rec("fixed code is still producing diagnostics", &res); + } + } } fn run_mir_opt_test(&self) { @@ -2487,6 +2560,12 @@ actual:\n\ } } + fn load_expected_output_from_path(&self, path: &Path) -> Result { + fs::read_to_string(path).map_err(|err| { + format!("failed to load expected output from `{}`: {}", path.display(), err) + }) + } + fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize { if actual == expected { return 0; From 9779f6c973c38461b7919a5c4b4ccaf3baf681fe Mon Sep 17 00:00:00 2001 From: Philipp Hansch Date: Wed, 19 Dec 2018 19:10:26 +0100 Subject: [PATCH 2/2] Add a rustfix test to the test project --- test-project/tests/tests.rs | 1 + test-project/tests/ui/dyn-keyword.fixed | 19 +++++++++++++++++++ test-project/tests/ui/dyn-keyword.rs | 19 +++++++++++++++++++ test-project/tests/ui/dyn-keyword.stderr | 16 ++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 test-project/tests/ui/dyn-keyword.fixed create mode 100644 test-project/tests/ui/dyn-keyword.rs create mode 100644 test-project/tests/ui/dyn-keyword.stderr diff --git a/test-project/tests/tests.rs b/test-project/tests/tests.rs index 983e193..39bef89 100644 --- a/test-project/tests/tests.rs +++ b/test-project/tests/tests.rs @@ -19,6 +19,7 @@ fn run_mode(mode: &'static str) { fn compile_test() { run_mode("compile-fail"); run_mode("run-pass"); + run_mode("ui"); #[cfg(not(feature = "stable"))] run_mode("pretty"); diff --git a/test-project/tests/ui/dyn-keyword.fixed b/test-project/tests/ui/dyn-keyword.fixed new file mode 100644 index 0000000..beae11f --- /dev/null +++ b/test-project/tests/ui/dyn-keyword.fixed @@ -0,0 +1,19 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// run-rustfix + +#![allow(unused_variables)] +#![deny(keyword_idents)] + +fn main() { + let r#dyn = (); //~ ERROR dyn + //~^ WARN hard error in the 2018 edition +} diff --git a/test-project/tests/ui/dyn-keyword.rs b/test-project/tests/ui/dyn-keyword.rs new file mode 100644 index 0000000..ea5b588 --- /dev/null +++ b/test-project/tests/ui/dyn-keyword.rs @@ -0,0 +1,19 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// run-rustfix + +#![allow(unused_variables)] +#![deny(keyword_idents)] + +fn main() { + let dyn = (); //~ ERROR dyn + //~^ WARN hard error in the 2018 edition +} diff --git a/test-project/tests/ui/dyn-keyword.stderr b/test-project/tests/ui/dyn-keyword.stderr new file mode 100644 index 0000000..52f22da --- /dev/null +++ b/test-project/tests/ui/dyn-keyword.stderr @@ -0,0 +1,16 @@ +error: `dyn` is a keyword in the 2018 edition + --> $DIR/dyn-keyword.rs:17:9 + | +17 | let dyn = (); //~ ERROR dyn + | ^^^ help: you can use a raw identifier to stay compatible: `r#dyn` + | +note: lint level defined here + --> $DIR/dyn-keyword.rs:14:9 + | +14 | #![deny(keyword_idents)] + | ^^^^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in the 2018 edition! + = note: for more information, see issue #49716 + +error: aborting due to previous error +