use std::collections::HashMap; use std::env; use std::fs; use std::io::{self, BufRead, BufReader, Read, Write}; use std::iter::Peekable; use std::mem; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::Chars; use std::thread; use crate::config::{Color, Config, EmitMode, FileName, NewlineStyle}; use crate::formatting::{ReportedErrors, SourceFile}; use crate::rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, ModifiedChunk, OutputWriter}; use crate::source_file; use crate::{is_nightly_channel, FormatReport, FormatReportFormatterBuilder, Input, Session}; use rustfmt_config_proc_macro::nightly_only_test; mod configuration_snippet; mod mod_resolver; mod parser; const DIFF_CONTEXT_SIZE: usize = 3; // A list of files on which we want to skip testing. const FILE_SKIP_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", "issue-3665/sub_mod.rs", // Testing for issue-3779 "issue-3779/ice.rs", // These files and directory are a part of modules defined inside `cfg_if!`. "cfg_if/mod.rs", "cfg_if/detect", "issue-3253/foo.rs", "issue-3253/bar.rs", "issue-3253/paths", // 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", "skip/foo.rs", ]; fn init_log() { let _ = env_logger::builder().is_test(true).try_init(); } 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 { FILE_SKIP_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 {}", path.to_str().unwrap() )) { 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") && !is_file_skip(&path) { files.push(path); } } } files } fn verify_config_used(path: &Path, config_name: &str) { for entry in fs::read_dir(path).expect(&format!( "couldn't read {} directory", path.to_str().unwrap() )) { 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) .unwrap_or_else(|_| panic!("couldn't read file {}", path.display())), ); assert!( filebuf .lines() .map(Result::unwrap) .take_while(|l| l.starts_with("//")) .any(|l| l.starts_with(&format!("// rustfmt-{}", config_name))), "config option file {} does not contain expected config name", path.display() ); } } } #[test] fn verify_config_test_names() { init_log(); for path in &[ 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"); let path = entry.path(); if path.is_dir() { let config_name = path.file_name().unwrap().to_str().unwrap(); // Make sure that config name is used in the files in the directory. verify_config_used(&path, config_name); } } } } // 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) { let mut writer = OutputWriter::new(Color::Auto); writer.writeln(msg, None); } // 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() { init_log(); 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); assert!( count >= 300, "Expected a minimum of {} system tests to be executed", 300 ) }); } // Do the same for tests/coverage-source directory. // The only difference is the coverage mode. #[test] fn coverage_tests() { init_log(); let files = get_test_files(Path::new("tests/coverage/source"), true); let (_reports, count, fails) = check_files(files, &None); println!("Ran {} tests in coverage mode.", count); assert_eq!(fails, 0, "{} tests failed", fails); } #[test] fn checkstyle_test() { init_log(); let filename = "tests/writemode/source/fn-single-line.rs"; let expected_filename = "tests/writemode/target/checkstyle.xml"; assert_output(Path::new(filename), Path::new(expected_filename)); } #[test] fn json_test() { init_log(); let filename = "tests/writemode/source/json.rs"; let expected_filename = "tests/writemode/target/output.json"; assert_output(Path::new(filename), Path::new(expected_filename)); } #[test] fn modified_test() { init_log(); use std::io::BufRead; // Test "modified" output let filename = "tests/writemode/source/modified.rs"; let mut data = Vec::new(); let mut config = Config::default(); config .set() .emit_mode(crate::config::EmitMode::ModifiedLines); { let mut session = Session::new(config, Some(&mut data)); session.format(Input::File(filename.into())).unwrap(); } let mut lines = data.lines(); let mut chunks = Vec::new(); while let Some(Ok(header)) = lines.next() { // Parse the header line let values: Vec<_> = header .split(' ') .map(|s| s.parse::().unwrap()) .collect(); assert_eq!(values.len(), 3); let line_number_orig = values[0]; let lines_removed = values[1]; let num_added = values[2]; let mut added_lines = Vec::new(); for _ in 0..num_added { added_lines.push(lines.next().unwrap().unwrap()); } chunks.push(ModifiedChunk { line_number_orig, lines_removed, lines: added_lines, }); } assert_eq!( chunks, vec![ ModifiedChunk { line_number_orig: 4, lines_removed: 4, lines: vec!["fn blah() {}".into()], }, ModifiedChunk { line_number_orig: 9, lines_removed: 6, lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()], }, ], ); } // Helper function for comparing the results of rustfmt // to a known output file generated by one of the write modes. fn assert_output(source: &Path, expected_filename: &Path) { let config = read_config(source); let (_, source_file, _) = format_file(source, config.clone()); // Populate output by writing to a vec. let mut out = vec![]; 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_text = String::new(); expected_file .read_to_string(&mut expected_text) .expect("Failed reading target"); let compare = make_diff(&expected_text, &output, DIFF_CONTEXT_SIZE); if !compare.is_empty() { let mut failures = HashMap::new(); failures.insert(source.to_owned(), compare); print_mismatches_default_message(failures); panic!("Text does not match expected output"); } } // Helper function for comparing the results of rustfmt // to a known output generated by one of the write modes. fn assert_stdin_output( source: &Path, expected_filename: &Path, emit_mode: EmitMode, has_diff: bool, ) { let mut config = Config::default(); config.set().newline_style(NewlineStyle::Unix); config.set().emit_mode(emit_mode); let mut source_file = fs::File::open(&source).expect("couldn't open source"); let mut source_text = String::new(); source_file .read_to_string(&mut source_text) .expect("Failed reading target"); let input = Input::Text(source_text); // Populate output by writing to a vec. let mut buf: Vec = vec![]; { let mut session = Session::new(config, Some(&mut buf)); session.format(input).unwrap(); let errors = ReportedErrors { has_diff: has_diff, ..Default::default() }; assert_eq!(session.errors, errors); } 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) .expect("Failed reading target"); let output = String::from_utf8(buf).unwrap(); let compare = make_diff(&expected_text, &output, DIFF_CONTEXT_SIZE); if !compare.is_empty() { let mut failures = HashMap::new(); failures.insert(source.to_owned(), compare); print_mismatches_default_message(failures); panic!("Text does not match expected output"); } } // Idempotence tests. Files in tests/target are checked to be unaltered by // rustfmt. #[nightly_only_test] #[test] fn idempotence_tests() { init_log(); run_test_with(&TestSetting::default(), || { // 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); assert!( count >= 400, "Expected a minimum of {} idempotent tests to be executed", 400 ) }); } // Run rustfmt on itself. This operation must be idempotent. We also check that // no warnings are emitted. // Issue-3443: these tests require nightly #[nightly_only_test] #[test] fn self_tests() { init_log(); 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 { let mut path = PathBuf::from("src"); path.push(dir); path.push("main.rs"); files.push(path); } files.push(PathBuf::from("src/lib.rs")); let (reports, count, fails) = check_files(files, &Some(PathBuf::from("rustfmt.toml"))); let mut warnings = 0; // Display results. println!("Ran {} self tests.", count); assert_eq!(fails, 0, "{} self tests failed", fails); for format_report in reports { println!( "{}", FormatReportFormatterBuilder::new(&format_report).build() ); warnings += format_report.warning_count(); } assert_eq!( warnings, 0, "Rustfmt's code generated {} warnings", warnings ); } #[test] fn format_files_find_new_files_via_cfg_if() { init_log(); run_test_with(&TestSetting::default(), || { // To repro issue-4656, it is necessary that these files are parsed // as a part of the same session (hence this separate test runner). let files = vec![ Path::new("tests/source/issue-4656/lib2.rs"), Path::new("tests/source/issue-4656/lib.rs"), ]; let config = Config::default(); let mut session = Session::::new(config, None); let mut write_result = HashMap::new(); for file in files { assert!(file.exists()); let result = session.format(Input::File(file.into())).unwrap(); assert!(!session.has_formatting_errors()); assert!(!result.has_warnings()); let mut source_file = SourceFile::new(); mem::swap(&mut session.source_file, &mut source_file); for (filename, text) in source_file { if let FileName::Real(ref filename) = filename { write_result.insert(filename.to_owned(), text); } } } assert_eq!( 3, write_result.len(), "Should have uncovered an extra file (format_me_please.rs) via lib.rs" ); assert!(handle_result(write_result, None).is_ok()); }); } #[test] fn stdin_formatting_smoke_test() { init_log(); let input = Input::Text("fn main () {}".to_owned()); let mut config = Config::default(); config.set().emit_mode(EmitMode::Stdout); let mut buf: Vec = vec![]; { let mut session = Session::new(config, Some(&mut buf)); session.format(input).unwrap(); assert!(session.has_no_errors()); } #[cfg(not(windows))] assert_eq!(buf, ":\n\nfn main() {}\n".as_bytes()); #[cfg(windows)] assert_eq!(buf, ":\n\nfn main() {}\r\n".as_bytes()); } #[test] fn stdin_parser_panic_caught() { init_log(); // 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() { init_log(); 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()); } /// Ensures that `EmitMode::Json` works with input from `stdin`. #[test] fn stdin_works_with_json() { init_log(); assert_stdin_output( Path::new("tests/writemode/source/stdin.rs"), Path::new("tests/writemode/target/stdin.json"), EmitMode::Json, true, ); } /// Ensures that `EmitMode::Checkstyle` works with input from `stdin`. #[test] fn stdin_works_with_checkstyle() { init_log(); assert_stdin_output( Path::new("tests/writemode/source/stdin.rs"), Path::new("tests/writemode/target/stdin.xml"), EmitMode::Checkstyle, false, ); } #[test] fn stdin_disable_all_formatting_test() { init_log(); 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 stdin_generated_files_issue_5172() { init_log(); let input = Input::Text("//@generated\nfn main() {}".to_owned()); let mut config = Config::default(); config.set().emit_mode(EmitMode::Stdout); config.set().format_generated_files(false); config.set().newline_style(NewlineStyle::Unix); let mut buf: Vec = vec![]; { let mut session = Session::new(config, Some(&mut buf)); session.format(input).unwrap(); assert!(session.has_no_errors()); } // N.B. this should be changed once `format_generated_files` is supported with stdin assert_eq!( String::from_utf8(buf).unwrap(), ":\n\n//@generated\nfn main() {}\n", ); } #[test] fn stdin_handles_mod_inner_ignore_attr() { // see https://github.com/rust-lang/rustfmt/issues/5368 init_log(); let input = String::from("#![rustfmt::skip]\n\nfn main() { }"); let mut child = Command::new(rustfmt().to_str().unwrap()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .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() { init_log(); let long_identifier = String::from_utf8(vec![b'a'; 239]).unwrap(); let input = Input::Text(format!("fn {}() {{}}", long_identifier)); let mut config = Config::default(); config.set().error_on_line_overflow(true); let mut session = Session::::new(config, None); session.format(input).unwrap(); assert!(session.has_formatting_errors()); } #[test] fn format_lines_errors_are_reported_with_tabs() { init_log(); let long_identifier = String::from_utf8(vec![b'a'; 97]).unwrap(); let input = Input::Text(format!("fn a() {{\n\t{}\n}}", long_identifier)); let mut config = Config::default(); config.set().error_on_line_overflow(true); config.set().hard_tabs(true); let mut session = Session::::new(config, None); session.format(input).unwrap(); assert!(session.has_formatting_errors()); } // 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) { 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!("{}", FormatReportFormatterBuilder::new(report).build()); fails += 1; } Ok(report) => reports.push(report), Err(err) => { if let IdempotentCheckError::Mismatch(msg) = err { print_mismatches_default_message(msg); } fails += 1; } } count += 1; } (reports, count, fails) } fn print_mismatches_default_message(result: HashMap>) { for (file_name, diff) in result { let mismatch_msg_formatter = |line_num| format!("\nMismatch at {}:{}:", file_name.display(), line_num); print_diff(diff, &mismatch_msg_formatter, &Default::default()); } if let Some(mut t) = term::stdout() { t.reset().unwrap_or(()); } } fn print_mismatches String>( result: HashMap>, mismatch_msg_formatter: T, ) { for (_file_name, diff) in result { print_diff(diff, &mismatch_msg_formatter, &Default::default()); } if let Some(mut t) = term::stdout() { t.reset().unwrap_or(()); } } 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 // 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() { get_config(sig_comments.get("config").map(Path::new)) } else { get_config(filename.with_extension("toml").file_name().map(Path::new)) }; for (key, val) in &sig_comments { 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); } } } config } fn format_file>(filepath: P, config: Config) -> (bool, SourceFile, FormatReport) { let filepath = filepath.into(); let input = Input::File(filepath); let mut session = Session::::new(config, None); let result = session.format(input).unwrap(); let parsing_errors = session.has_parsing_errors(); let mut source_file = SourceFile::new(); mem::swap(&mut session.source_file, &mut source_file); (parsing_errors, source_file, result) } enum IdempotentCheckError { Mismatch(HashMap>), Parse, } fn idempotent_check( filename: &PathBuf, opt_config: &Option, ) -> 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") } else { read_config(filename) }; let (parsing_errors, source_file, format_report) = format_file(filename, config); if parsing_errors { return Err(IdempotentCheckError::Parse); } let mut write_result = HashMap::new(); for (filename, text) in source_file { if let FileName::Real(ref filename) = filename { write_result.insert(filename.to_owned(), text); } } let target = sig_comments.get("target").map(|x| &(*x)[..]); handle_result(write_result, target).map(|_| format_report) } // Reads test config file using the supplied (optional) file name. If there's no file name or the // file doesn't exist, just return the default config. Otherwise, the file must be read // successfully. fn get_config(config_file: Option<&Path>) -> Config { let config_file_name = match config_file { None => return Default::default(), Some(file_name) => { let mut full_path = PathBuf::from("tests/config/"); full_path.push(file_name); if !full_path.exists() { return Default::default(); }; full_path } }; 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") } // 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) .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"); // 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"); reader .lines() .map(|line| line.expect("failed getting line")) .filter(|line| line_regex.is_match(line)) .filter_map(|line| { regex.captures_iter(&line).next().map(|capture| { ( capture .get(1) .expect("couldn't unwrap capture") .as_str() .to_owned(), capture .get(2) .expect("couldn't unwrap capture") .as_str() .to_owned(), ) }) }) .collect() } // Compares output to input. // TODO: needs a better name, more explanation. fn handle_result( result: HashMap, target: Option<&str>, ) -> Result<(), IdempotentCheckError> { let mut failures = HashMap::new(); 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 mut f = fs::File::open(&target).expect(&open_error); let mut text = String::new(); let read_error = format!("failed reading target {:?}", target); f.read_to_string(&mut text).expect(&read_error); // Ignore LF and CRLF difference for Windows. if !string_eq_ignore_newline_repr(&fmt_text, &text) { let diff = make_diff(&text, &fmt_text, DIFF_CONTEXT_SIZE); assert!( !diff.is_empty(), "Empty diff? Maybe due to a missing a newline at the end of a file?" ); failures.insert(file_name, diff); } } if failures.is_empty() { Ok(()) } else { Err(IdempotentCheckError::Mismatch(failures)) } } // 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() .position(|c| c.as_os_str() == "source") { let mut target_file_name = PathBuf::new(); for (i, c) in file_name.components().enumerate() { if i == n { target_file_name.push("target"); } else { target_file_name.push(c.as_os_str()); } } if let Some(replace_name) = target { target_file_name.with_file_name(replace_name) } else { target_file_name } } else { // This is either and idempotence check or a self check. file_name.to_owned() } } #[test] fn rustfmt_diff_make_diff_tests() { init_log(); let diff = make_diff("a\nb\nc\nd", "a\ne\nc\nd", 3); assert_eq!( diff, vec![Mismatch { line_number: 1, line_number_orig: 1, lines: vec![ DiffLine::Context("a".into()), DiffLine::Resulting("b".into()), DiffLine::Expected("e".into()), DiffLine::Context("c".into()), DiffLine::Context("d".into()), ], }] ); } #[test] fn rustfmt_diff_no_diff_test() { init_log(); let diff = make_diff("a\nb\nc\nd", "a\nb\nc\nd", 3); assert_eq!(diff, vec![]); } // Compare strings without distinguishing between CRLF and LF fn string_eq_ignore_newline_repr(left: &str, right: &str) -> bool { let left = CharsIgnoreNewlineRepr(left.chars().peekable()); let right = CharsIgnoreNewlineRepr(right.chars().peekable()); left.eq(right) } struct CharsIgnoreNewlineRepr<'a>(Peekable>); impl<'a> Iterator for CharsIgnoreNewlineRepr<'a> { type Item = char; fn next(&mut self) -> Option { self.0.next().map(|c| { if c == '\r' { if *self.0.peek().unwrap_or(&'\0') == '\n' { self.0.next(); '\n' } else { '\r' } } else { c } }) } } #[test] fn string_eq_ignore_newline_repr_test() { init_log(); assert!(string_eq_ignore_newline_repr("", "")); assert!(!string_eq_ignore_newline_repr("", "abc")); assert!(!string_eq_ignore_newline_repr("abc", "")); assert!(string_eq_ignore_newline_repr("a\nb\nc\rd", "a\nb\r\nc\rd")); assert!(string_eq_ignore_newline_repr("a\r\n\r\n\r\nb", "a\n\n\nb")); assert!(!string_eq_ignore_newline_repr("a\r\nbcd", "a\nbcdefghijk")); } struct TempFile { path: PathBuf, } fn make_temp_file(file_name: &'static str) -> TempFile { use std::env::var; use std::fs::File; // Used in the Rust build system. 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 content = "fn main() {}\n"; file.write_all(content.as_bytes()) .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"); } } fn rustfmt() -> PathBuf { let mut me = env::current_exe().expect("failed to get current executable"); // Chop of the test name. me.pop(); // Chop off `deps`. me.pop(); me.push("rustfmt"); assert!( me.is_file() || me.with_extension("exe").is_file(), "{}", if cfg!(release) { "no rustfmt bin, try running `cargo build --release` before testing" } else { "no rustfmt bin, try running `cargo build` before testing" } ); me } #[test] fn verify_check_works() { init_log(); let temp_file = make_temp_file("temp_check.rs"); Command::new(rustfmt().to_str().unwrap()) .arg("--check") .arg(temp_file.path.to_str().unwrap()) .status() .expect("run with check option failed"); } #[test] fn verify_check_works_with_stdin() { init_log(); let mut child = Command::new(rustfmt().to_str().unwrap()) .arg("--check") .stdin(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("run with check option failed"); { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); stdin .write_all("fn main() {}\n".as_bytes()) .expect("Failed to write to rustfmt --check"); } let output = child .wait_with_output() .expect("Failed to wait on rustfmt child"); assert!(output.status.success()); } #[test] fn verify_check_l_works_with_stdin() { init_log(); let mut child = Command::new(rustfmt().to_str().unwrap()) .arg("--check") .arg("-l") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("run with check option failed"); { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); stdin .write_all("fn main()\n{}\n".as_bytes()) .expect("Failed to write to rustfmt --check"); } let output = child .wait_with_output() .expect("Failed to wait on rustfmt child"); assert!(output.status.success()); assert_eq!(std::str::from_utf8(&output.stdout).unwrap(), "\n"); }