1
Fork 0

First step towards rustfix compiletest mode

This is the first small step towards testing auto-fixable compiler
suggestions using compiletest. Currently, it only checks if next to a
UI test there also happens to a `*.rs.fixed` file, and then uses rustfix
(added as external crate) on the original file, and asserts that it
produces the fixed version.

To show that this works, I've included one such test. I picked this test
case at random (and because it was simple) -- It is not relevant to the
2018 edition. Indeed, in the near future, we want to be able to restrict
rustfix to edition-lints, so this test cast might go away soon.

In case you still think this is somewhat feature-complete, here's a
quick list of things currently missing that I want to add before telling
people they can use this:

- [ ] Make this an actual compiletest mode, with `test [fix] …` output
  and everything
- [ ] Assert that fixed files still compile
- [ ] Assert that fixed files produce no (or a known set of) diagnostics
  output
- [ ] Update `update-references.sh` to support rustfix
- [ ] Use a published version of rustfix (i.e.: publish a new version
  rustfix that exposes a useful API for this)
This commit is contained in:
Pascal Hertleif 2018-04-20 00:04:08 +02:00 committed by Alex Crichton
parent 91db9dcf37
commit fd6aa149bc
6 changed files with 110 additions and 2 deletions

View file

@ -0,0 +1,20 @@
// Copyright 2017 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Point at the captured immutable outer variable
fn foo(mut f: Box<FnMut()>) {
f();
}
fn main() {
let mut y = true;
foo(Box::new(move || y = false) as Box<_>); //~ ERROR cannot assign to captured outer variable
}

View file

@ -13,6 +13,7 @@ regex = "0.2"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
rustfix = { git = "https://github.com/rust-lang-nursery/rustfix" }
[target.'cfg(unix)'.dependencies]
libc = "0.2"

View file

@ -0,0 +1,70 @@
use rustfix::{get_suggestions_from_json, Replacement};
use std::collections::HashSet;
use std::error::Error;
pub fn run_rustfix(code: &str, json: &str) -> String {
let suggestions = get_suggestions_from_json(&json, &HashSet::new())
.expect("could not load suggestions");
let mut fixed = code.to_string();
for sug in suggestions.into_iter().rev() {
for sol in sug.solutions {
for r in sol.replacements {
fixed = apply_suggestion(&mut fixed, &r)
.expect("could not apply suggestion");
}
}
}
fixed
}
fn apply_suggestion(
file_content: &mut String,
suggestion: &Replacement,
) -> Result<String, Box<Error>> {
use std::cmp::max;
let mut new_content = String::new();
// Add the lines before the section we want to replace
new_content.push_str(&file_content
.lines()
.take(max(suggestion.snippet.line_range.start.line - 1, 0) as usize)
.collect::<Vec<_>>()
.join("\n"));
new_content.push_str("\n");
// Parts of line before replacement
new_content.push_str(&file_content
.lines()
.nth(suggestion.snippet.line_range.start.line - 1)
.unwrap_or("")
.chars()
.take(suggestion.snippet.line_range.start.column - 1)
.collect::<String>());
// Insert new content! Finally!
new_content.push_str(&suggestion.replacement);
// Parts of line after replacement
new_content.push_str(&file_content
.lines()
.nth(suggestion.snippet.line_range.end.line - 1)
.unwrap_or("")
.chars()
.skip(suggestion.snippet.line_range.end.column - 1)
.collect::<String>());
// Add the lines after the section we want to replace
new_content.push_str("\n");
new_content.push_str(&file_content
.lines()
.skip(suggestion.snippet.line_range.end.line as usize)
.collect::<Vec<_>>()
.join("\n"));
new_content.push_str("\n");
Ok(new_content)
}

View file

@ -269,6 +269,7 @@ pub fn expected_output_path(testpaths: &TestPaths,
testpaths.file.with_extension(extension)
}
pub const UI_EXTENSIONS: &[&str] = &[UI_STDERR, UI_STDOUT];
pub const UI_EXTENSIONS: &[&str] = &[UI_STDERR, UI_STDOUT, UI_FIXED];
pub const UI_STDERR: &str = "stderr";
pub const UI_STDOUT: &str = "stdout";
pub const UI_FIXED: &str = "rs.fixed";

View file

@ -26,6 +26,7 @@ extern crate regex;
extern crate serde_derive;
extern crate serde_json;
extern crate test;
extern crate rustfix;
use std::env;
use std::ffi::OsString;
@ -52,6 +53,7 @@ pub mod common;
pub mod errors;
mod raise_fd_limit;
mod read2;
mod autofix;
fn main() {
env_logger::init();

View file

@ -12,7 +12,7 @@ use common::{Config, TestPaths};
use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind};
use common::{Codegen, CodegenUnits, DebugInfoGdb, DebugInfoLldb, Rustdoc};
use common::{Incremental, MirOpt, RunMake, Ui};
use common::{expected_output_path, UI_STDERR, UI_STDOUT};
use common::{expected_output_path, UI_STDERR, UI_STDOUT, UI_FIXED};
use common::CompareMode;
use diff;
use errors::{self, Error, ErrorKind};
@ -35,6 +35,7 @@ use std::path::{Path, PathBuf};
use std::process::{Child, Command, ExitStatus, Output, Stdio};
use std::str;
use autofix::run_rustfix;
use extract_gdb_version;
/// The name of the environment variable that holds dynamic library locations.
@ -2603,6 +2604,19 @@ impl<'test> TestCx<'test> {
self.check_error_patterns(&proc_res.stderr, &proc_res);
}
}
let fixture_path = expected_output_path(&self.testpaths, None, &None, UI_FIXED);
if fixture_path.exists() {
let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file)
.unwrap();
let expected_fixed = self.load_expected_output_from_path(&fixture_path).unwrap();
let fixed_code = run_rustfix(&unfixed_code, &proc_res.stderr);
let errors = self.compare_output("rs.fixed", &fixed_code, &expected_fixed);
if errors > 0 {
panic!("rustfix produced different fixed file!");
// TODO: Add info for update-references.sh call
}
}
}
fn run_mir_opt_test(&self) {