X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Ftest%2Fmod.rs;h=5ac0a1d445099a27311b66fd3c19fa80292446b6;hb=a4eab17d7c4cdeee1d0e72ea2a8a5f6a377627f2;hp=a9015cedf6a4f0c83e9b5b57abf7dab633d237a5;hpb=cd8549e0133cd4f29b152142b75d6e17288c4efb;p=rust.git diff --git a/src/test/mod.rs b/src/test/mod.rs index a9015cedf6a..5ac0a1d4450 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -1,15 +1,3 @@ -// Copyright 2015 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. - -extern crate assert_cli; - use std::collections::{HashMap, HashSet}; use std::env; use std::fs; @@ -17,32 +5,96 @@ use std::iter::{Enumerate, Peekable}; use std::mem; use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; use std::str::Chars; +use std::thread; -use config::{Color, Config, EmitMode, FileName, ReportTactic}; -use formatting::{ModifiedChunk, SourceFile}; -use rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, OutputWriter}; -use source_file; -use {FormatReport, Input, Session}; +use crate::config::{Color, Config, EmitMode, FileName, NewlineStyle, ReportTactic, Verbosity}; +use crate::formatting::{ReportedErrors, SourceFile}; +use crate::is_nightly_channel; +use crate::rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, ModifiedChunk, OutputWriter}; +use crate::source_file; +use crate::{FormatReport, FormatReportFormatterBuilder, Input, Session}; const DIFF_CONTEXT_SIZE: usize = 3; const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md"; -// Returns a `Vec` containing `PathBuf`s of files with a rs extension in the +// A list of files on which we want to skip testing. +const SKIP_FILE_WHITE_LIST: &[&str] = &[ + // We want to make sure that the `skip_children` is correctly working, + // so we do not want to test this file directly. + "configs/skip_children/foo/mod.rs", + "issue-3434/no_entry.rs", + // These files and directory are a part of modules defined inside `cfg_if!`. + "cfg_if/mod.rs", + "cfg_if/detect", + // These files and directory are a part of modules defined inside `cfg_attr(..)`. + "cfg_mod/dir", + "cfg_mod/bar.rs", + "cfg_mod/foo.rs", + "cfg_mod/wasm32.rs", +]; + +struct TestSetting { + /// The size of the stack of the thread that run tests. + stack_size: usize, +} + +impl Default for TestSetting { + fn default() -> Self { + TestSetting { + stack_size: 8_388_608, // 8MB + } + } +} + +fn run_test_with(test_setting: &TestSetting, f: F) +where + F: FnOnce(), + F: Send + 'static, +{ + thread::Builder::new() + .stack_size(test_setting.stack_size) + .spawn(f) + .expect("Failed to create a test thread") + .join() + .expect("Failed to join a test thread") +} + +fn is_subpath

(path: &Path, subpath: &P) -> bool +where + P: AsRef, +{ + (0..path.components().count()) + .map(|i| { + path.components() + .skip(i) + .take(subpath.as_ref().components().count()) + }) + .any(|c| c.zip(subpath.as_ref().components()).all(|(a, b)| a == b)) +} + +fn is_file_skip(path: &Path) -> bool { + SKIP_FILE_WHITE_LIST + .iter() + .any(|file_path| is_subpath(path, file_path)) +} + +// Returns a `Vec` containing `PathBuf`s of files with an `rs` extension in the // given path. The `recursive` argument controls if files from subdirectories // are also returned. fn get_test_files(path: &Path, recursive: bool) -> Vec { let mut files = vec![]; if path.is_dir() { for entry in fs::read_dir(path).expect(&format!( - "Couldn't read directory {}", + "couldn't read directory {}", path.to_str().unwrap() )) { - let entry = entry.expect("Couldn't get DirEntry"); + let entry = entry.expect("couldn't get `DirEntry`"); let path = entry.path(); if path.is_dir() && recursive { files.append(&mut get_test_files(&path, recursive)); - } else if path.extension().map_or(false, |f| f == "rs") { + } else if path.extension().map_or(false, |f| f == "rs") && !is_file_skip(&path) { files.push(path); } } @@ -52,20 +104,21 @@ fn get_test_files(path: &Path, recursive: bool) -> Vec { fn verify_config_used(path: &Path, config_name: &str) { for entry in fs::read_dir(path).expect(&format!( - "Couldn't read {} directory", + "couldn't read {} directory", path.to_str().unwrap() )) { - let entry = entry.expect("Couldn't get directory entry"); + let entry = entry.expect("couldn't get directory entry"); let path = entry.path(); if path.extension().map_or(false, |f| f == "rs") { // check if "// rustfmt-:" appears in the file. let filebuf = BufReader::new( - fs::File::open(&path).expect(&format!("Couldn't read file {}", path.display())), + fs::File::open(&path) + .unwrap_or_else(|_| panic!("couldn't read file {}", path.display())), ); assert!( filebuf .lines() - .map(|l| l.unwrap()) + .map(Result::unwrap) .take_while(|l| l.starts_with("//")) .any(|l| l.starts_with(&format!("// rustfmt-{}", config_name))), format!( @@ -83,8 +136,8 @@ fn verify_config_test_names() { Path::new("tests/source/configs"), Path::new("tests/target/configs"), ] { - for entry in fs::read_dir(path).expect("Couldn't read configs directory") { - let entry = entry.expect("Couldn't get directory entry"); + for entry in fs::read_dir(path).expect("couldn't read configs directory") { + let entry = entry.expect("couldn't get directory entry"); let path = entry.path(); if path.is_dir() { let config_name = path.file_name().unwrap().to_str().unwrap(); @@ -96,8 +149,8 @@ fn verify_config_test_names() { } } -// This writes to the terminal using the same approach (via term::stdout or -// println!) that is used by `rustfmt::rustfmt_diff::print_diff`. Writing +// This writes to the terminal using the same approach (via `term::stdout` or +// `println!`) that is used by `rustfmt::rustfmt_diff::print_diff`. Writing // using only one or the other will cause the output order to differ when // `print_diff` selects the approach not used. fn write_message(msg: &str) { @@ -105,27 +158,29 @@ fn write_message(msg: &str) { writer.writeln(msg, None); } -// Integration tests. The files in the tests/source are formatted and compared -// to their equivalent in tests/target. The target file and config can be +// Integration tests. The files in `tests/source` are formatted and compared +// to their equivalent in `tests/target`. The target file and config can be // overridden by annotations in the source file. The input and output must match // exactly. #[test] fn system_tests() { - // Get all files in the tests/source directory. - let files = get_test_files(Path::new("tests/source"), true); - let (_reports, count, fails) = check_files(files, None); - - // Display results. - println!("Ran {} system tests.", count); - assert_eq!(fails, 0, "{} system tests failed", fails); + run_test_with(&TestSetting::default(), || { + // Get all files in the tests/source directory. + let files = get_test_files(Path::new("tests/source"), true); + let (_reports, count, fails) = check_files(files, &None); + + // Display results. + println!("Ran {} system tests.", count); + assert_eq!(fails, 0, "{} system tests failed", fails); + }); } -// Do the same for tests/coverage-source directory -// the only difference is the coverage mode +// Do the same for tests/coverage-source directory. +// The only difference is the coverage mode. #[test] fn coverage_tests() { let files = get_test_files(Path::new("tests/coverage/source"), true); - let (_reports, count, fails) = check_files(files, None); + let (_reports, count, fails) = check_files(files, &None); println!("Ran {} tests in coverage mode.", count); assert_eq!(fails, 0, "{} tests failed", fails); @@ -146,7 +201,9 @@ fn modified_test() { let filename = "tests/writemode/source/modified.rs"; let mut data = Vec::new(); let mut config = Config::default(); - config.set().emit_mode(::config::EmitMode::ModifiedLines); + config + .set() + .emit_mode(crate::config::EmitMode::ModifiedLines); { let mut session = Session::new(config, Some(&mut data)); @@ -204,7 +261,7 @@ fn assert_output(source: &Path, expected_filename: &Path) { let _ = source_file::write_all_files(&source_file, &mut out, &config); let output = String::from_utf8(out).unwrap(); - let mut expected_file = fs::File::open(&expected_filename).expect("Couldn't open target"); + let mut expected_file = fs::File::open(&expected_filename).expect("couldn't open target"); let mut expected_text = String::new(); expected_file .read_to_string(&mut expected_text) @@ -215,7 +272,7 @@ fn assert_output(source: &Path, expected_filename: &Path) { let mut failures = HashMap::new(); failures.insert(source.to_owned(), compare); print_mismatches_default_message(failures); - assert!(false, "Text does not match expected output"); + panic!("Text does not match expected output"); } } @@ -223,23 +280,29 @@ fn assert_output(source: &Path, expected_filename: &Path) { // rustfmt. #[test] fn idempotence_tests() { - match option_env!("CFG_RELEASE_CHANNEL") { - None | Some("nightly") => {} - _ => return, // these tests require nightly - } - // Get all files in the tests/target directory. - let files = get_test_files(Path::new("tests/target"), true); - let (_reports, count, fails) = check_files(files, None); - - // Display results. - println!("Ran {} idempotent tests.", count); - assert_eq!(fails, 0, "{} idempotent tests failed", fails); + run_test_with(&TestSetting::default(), || { + // these tests require nightly + if !is_nightly_channel!() { + return; + } + // Get all files in the tests/target directory. + let files = get_test_files(Path::new("tests/target"), true); + let (_reports, count, fails) = check_files(files, &None); + + // Display results. + println!("Ran {} idempotent tests.", count); + assert_eq!(fails, 0, "{} idempotent tests failed", fails); + }); } // Run rustfmt on itself. This operation must be idempotent. We also check that // no warnings are emitted. #[test] fn self_tests() { + // Issue-3443: these tests require nightly + if !is_nightly_channel!() { + return; + } let mut files = get_test_files(Path::new("tests"), false); let bin_directories = vec!["cargo-fmt", "git-rustfmt", "bin", "format-diff"]; for dir in bin_directories { @@ -250,7 +313,7 @@ fn self_tests() { } files.push(PathBuf::from("src/lib.rs")); - let (reports, count, fails) = check_files(files, Some(PathBuf::from("rustfmt.toml"))); + let (reports, count, fails) = check_files(files, &Some(PathBuf::from("rustfmt.toml"))); let mut warnings = 0; // Display results. @@ -258,7 +321,10 @@ fn self_tests() { assert_eq!(fails, 0, "{} self tests failed", fails); for format_report in reports { - println!("{}", format_report); + println!( + "{}", + FormatReportFormatterBuilder::new(&format_report).build() + ); warnings += format_report.warning_count(); } @@ -280,35 +346,76 @@ fn stdin_formatting_smoke_test() { session.format(input).unwrap(); assert!(session.has_no_errors()); } - //eprintln!("{:?}", ); + #[cfg(not(windows))] - assert_eq!(buf, "fn main() {}\n".as_bytes()); + assert_eq!(buf, "stdin:\n\nfn main() {}\n".as_bytes()); #[cfg(windows)] - assert_eq!(buf, "fn main() {}\r\n".as_bytes()); -} - -// FIXME(#1990) restore this test -// #[test] -// fn stdin_disable_all_formatting_test() { -// let input = String::from("fn main() { println!(\"This should not be formatted.\"); }"); -// let mut child = Command::new("./target/debug/rustfmt") -// .stdin(Stdio::piped()) -// .stdout(Stdio::piped()) -// .arg("--config-path=./tests/config/disable_all_formatting.toml") -// .spawn() -// .expect("failed to execute child"); - -// { -// let stdin = child.stdin.as_mut().expect("failed to get stdin"); -// stdin -// .write_all(input.as_bytes()) -// .expect("failed to write stdin"); -// } -// let output = child.wait_with_output().expect("failed to wait on child"); -// assert!(output.status.success()); -// assert!(output.stderr.is_empty()); -// assert_eq!(input, String::from_utf8(output.stdout).unwrap()); -// } + assert_eq!(buf, "stdin:\n\nfn main() {}\r\n".as_bytes()); +} + +#[test] +fn stdin_parser_panic_caught() { + // See issue #3239. + for text in ["{", "}"].iter().cloned().map(String::from) { + let mut buf = vec![]; + let mut session = Session::new(Default::default(), Some(&mut buf)); + let _ = session.format(Input::Text(text)); + + assert!(session.has_parsing_errors()); + } +} + +/// Ensures that `EmitMode::ModifiedLines` works with input from `stdin`. Useful +/// when embedding Rustfmt (e.g. inside RLS). +#[test] +fn stdin_works_with_modified_lines() { + let input = "\nfn\n some( )\n{\n}\nfn main () {}\n"; + let output = "1 6 2\nfn some() {}\nfn main() {}\n"; + + let input = Input::Text(input.to_owned()); + let mut config = Config::default(); + config.set().newline_style(NewlineStyle::Unix); + config.set().emit_mode(EmitMode::ModifiedLines); + let mut buf: Vec = vec![]; + { + let mut session = Session::new(config, Some(&mut buf)); + session.format(input).unwrap(); + let errors = ReportedErrors { + has_diff: true, + ..Default::default() + }; + assert_eq!(session.errors, errors); + } + assert_eq!(buf, output.as_bytes()); +} + +#[test] +fn stdin_disable_all_formatting_test() { + match option_env!("CFG_RELEASE_CHANNEL") { + None | Some("nightly") => {} + // These tests require nightly. + _ => return, + } + let input = String::from("fn main() { println!(\"This should not be formatted.\"); }"); + let mut child = Command::new(rustfmt().to_str().unwrap()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .arg("--config-path=./tests/config/disable_all_formatting.toml") + .spawn() + .expect("failed to execute child"); + + { + let stdin = child.stdin.as_mut().expect("failed to get stdin"); + stdin + .write_all(input.as_bytes()) + .expect("failed to write stdin"); + } + + let output = child.wait_with_output().expect("failed to wait on child"); + assert!(output.status.success()); + assert!(output.stderr.is_empty()); + assert_eq!(input, String::from_utf8(output.stdout).unwrap()); +} #[test] fn format_lines_errors_are_reported() { @@ -335,17 +442,27 @@ fn format_lines_errors_are_reported_with_tabs() { // For each file, run rustfmt and collect the output. // Returns the number of files checked and the number of failures. -fn check_files(files: Vec, opt_config: Option) -> (Vec, u32, u32) { +fn check_files(files: Vec, opt_config: &Option) -> (Vec, u32, u32) { let mut count = 0; let mut fails = 0; let mut reports = vec![]; for file_name in files { + let sig_comments = read_significant_comments(&file_name); + if sig_comments.contains_key("unstable") && !is_nightly_channel!() { + debug!( + "Skipping '{}' because it requires unstable \ + features which are only available on nightly...", + file_name.display() + ); + continue; + } + debug!("Testing '{}'...", file_name.display()); match idempotent_check(&file_name, &opt_config) { Ok(ref report) if report.has_warnings() => { - print!("{}", report); + print!("{}", FormatReportFormatterBuilder::new(&report).build()); fails += 1; } Ok(report) => reports.push(report), @@ -390,7 +507,7 @@ fn print_mismatches String>( fn read_config(filename: &Path) -> Config { let sig_comments = read_significant_comments(filename); - // Look for a config file... If there is a 'config' property in the significant comments, use + // Look for a config file. If there is a 'config' property in the significant comments, use // that. Otherwise, if there are no significant comments at all, look for a config file with // the same name as the test file. let mut config = if !sig_comments.is_empty() { @@ -400,7 +517,7 @@ fn read_config(filename: &Path) -> Config { }; for (key, val) in &sig_comments { - if key != "target" && key != "config" { + if key != "target" && key != "config" && key != "unstable" { config.override_value(key, val); if config.is_default(key) { warn!("Default value {} used explicitly for {}", val, key); @@ -436,7 +553,7 @@ fn idempotent_check( ) -> Result { let sig_comments = read_significant_comments(filename); let config = if let Some(ref config_file_path) = opt_config { - Config::from_toml_path(config_file_path).expect("rustfmt.toml not found") + Config::from_toml_path(config_file_path).expect("`rustfmt.toml` not found") } else { read_config(filename) }; @@ -473,43 +590,42 @@ fn get_config(config_file: Option<&Path>) -> Config { } }; - let mut def_config_file = fs::File::open(config_file_name).expect("Couldn't open config"); + let mut def_config_file = fs::File::open(config_file_name).expect("couldn't open config"); let mut def_config = String::new(); def_config_file .read_to_string(&mut def_config) .expect("Couldn't read config"); - Config::from_toml(&def_config, Path::new("tests/config/")).expect("Invalid toml") + Config::from_toml(&def_config, Path::new("tests/config/")).expect("invalid TOML") } -// Reads significant comments of the form: // rustfmt-key: value -// into a hash map. +// Reads significant comments of the form: `// rustfmt-key: value` into a hash map. fn read_significant_comments(file_name: &Path) -> HashMap { - let file = - fs::File::open(file_name).expect(&format!("Couldn't read file {}", file_name.display())); + let file = fs::File::open(file_name) + .unwrap_or_else(|_| panic!("couldn't read file {}", file_name.display())); let reader = BufReader::new(file); let pattern = r"^\s*//\s*rustfmt-([^:]+):\s*(\S+)"; - let regex = regex::Regex::new(pattern).expect("Failed creating pattern 1"); + let regex = regex::Regex::new(pattern).expect("failed creating pattern 1"); // Matches lines containing significant comments or whitespace. let line_regex = regex::Regex::new(r"(^\s*$)|(^\s*//\s*rustfmt-[^:]+:\s*\S+)") - .expect("Failed creating pattern 2"); + .expect("failed creating pattern 2"); reader .lines() - .map(|line| line.expect("Failed getting line")) + .map(|line| line.expect("failed getting line")) .take_while(|line| line_regex.is_match(line)) .filter_map(|line| { regex.captures_iter(&line).next().map(|capture| { ( capture .get(1) - .expect("Couldn't unwrap capture") + .expect("couldn't unwrap capture") .as_str() .to_owned(), capture .get(2) - .expect("Couldn't unwrap capture") + .expect("couldn't unwrap capture") .as_str() .to_owned(), ) @@ -518,7 +634,7 @@ fn read_significant_comments(file_name: &Path) -> HashMap { .collect() } -// Compare output to input. +// Compares output to input. // TODO: needs a better name, more explanation. fn handle_result( result: HashMap, @@ -529,11 +645,11 @@ fn handle_result( for (file_name, fmt_text) in result { // If file is in tests/source, compare to file with same name in tests/target. let target = get_target(&file_name, target); - let open_error = format!("Couldn't open target {:?}", &target); + let open_error = format!("couldn't open target {:?}", target); let mut f = fs::File::open(&target).expect(&open_error); let mut text = String::new(); - let read_error = format!("Failed reading target {:?}", &target); + let read_error = format!("failed reading target {:?}", target); f.read_to_string(&mut text).expect(&read_error); // Ignore LF and CRLF difference for Windows. @@ -554,7 +670,7 @@ fn handle_result( } } -// Map source file paths to their target paths. +// Maps source file paths to their target paths. fn get_target(file_name: &Path, target: Option<&str>) -> PathBuf { if let Some(n) = file_name .components() @@ -574,7 +690,7 @@ fn get_target(file_name: &Path, target: Option<&str>) -> PathBuf { target_file_name } } else { - // This is either and idempotence check or a self check + // This is either and idempotence check or a self check. file_name.to_owned() } } @@ -657,10 +773,10 @@ fn get_section>( ) -> Option { lazy_static! { static ref CONFIG_NAME_REGEX: regex::Regex = - regex::Regex::new(r"^## `([^`]+)`").expect("Failed creating configuration pattern"); + regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern"); static ref CONFIG_VALUE_REGEX: regex::Regex = regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#) - .expect("Failed creating configuration value pattern"); + .expect("failed creating configuration value pattern"); } loop { @@ -726,7 +842,8 @@ fn set_code_block(&mut self, code_block: String, code_block_start: u32) { fn get_block_config(&self) -> Config { let mut config = Config::default(); - if self.config_value.is_some() && self.config_value.is_some() { + config.set().verbose(Verbosity::Quiet); + if self.config_name.is_some() && self.config_value.is_some() { config.override_value( self.config_name.as_ref().unwrap(), self.config_value.as_ref().unwrap(), @@ -744,7 +861,7 @@ fn code_block_valid(&self) -> bool { .code_block .as_ref() .unwrap() - .split('\n') + .lines() .nth(0) .unwrap_or("") == "#![rustfmt::skip]"; @@ -768,7 +885,7 @@ fn code_block_valid(&self) -> bool { true } - fn has_parsing_errors(&self, session: &Session) -> bool { + fn has_parsing_errors(&self, session: &Session<'_, T>) -> bool { if session.has_parsing_errors() { write_message(&format!( "\u{261d}\u{1f3fd} Cannot format {}:{}", @@ -882,10 +999,10 @@ fn configuration_snippet_tests() { fn get_code_blocks() -> Vec { let mut file_iter = BufReader::new( fs::File::open(Path::new(CONFIGURATIONS_FILE_NAME)) - .expect(&format!("Couldn't read file {}", CONFIGURATIONS_FILE_NAME)), + .unwrap_or_else(|_| panic!("couldn't read file {}", CONFIGURATIONS_FILE_NAME)), ) .lines() - .map(|l| l.unwrap()) + .map(Result::unwrap) .enumerate(); let mut code_blocks: Vec = Vec::new(); let mut hash_set = Config::hash_set(); @@ -908,7 +1025,7 @@ fn get_code_blocks() -> Vec { let blocks = get_code_blocks(); let failures = blocks .iter() - .map(|b| b.formatted_is_idempotent()) + .map(ConfigCodeBlock::formatted_is_idempotent) .fold(0, |acc, r| acc + (!r as u32)); // Display results. @@ -928,28 +1045,30 @@ fn make_temp_file(file_name: &'static str) -> TempFile { let target_dir = var("RUSTFMT_TEST_DIR").unwrap_or_else(|_| ".".to_owned()); let path = Path::new(&target_dir).join(file_name); - let mut file = File::create(&path).expect("Couldn't create temp file"); + let mut file = File::create(&path).expect("couldn't create temp file"); let content = "fn main() {}\n"; file.write_all(content.as_bytes()) - .expect("Couldn't write temp file"); + .expect("couldn't write temp file"); TempFile { path } } impl Drop for TempFile { fn drop(&mut self) { use std::fs::remove_file; - remove_file(&self.path).expect("Couldn't delete temp file"); + remove_file(&self.path).expect("couldn't delete temp file"); } } fn rustfmt() -> PathBuf { let mut me = env::current_exe().expect("failed to get current executable"); - me.pop(); // chop of the test name - me.pop(); // chop off `deps` + // Chop of the test name. + me.pop(); + // Chop off `deps`. + me.pop(); - // if we run `cargo test --release` we might only have a release build + // If we run `cargo test --release`, we might only have a release build. if cfg!(release) { - // ../release/ + // `../release/` me.pop(); me.push("release"); } @@ -968,11 +1087,10 @@ fn rustfmt() -> PathBuf { #[test] fn verify_check_works() { let temp_file = make_temp_file("temp_check.rs"); - assert_cli::Assert::command(&[ - rustfmt().to_str().unwrap(), - "--check", - temp_file.path.to_str().unwrap(), - ]) - .succeeds() - .unwrap(); + + Command::new(rustfmt().to_str().unwrap()) + .arg("--check") + .arg(temp_file.path.to_str().unwrap()) + .status() + .expect("run with check option failed"); }