diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index a9e6c454ffa..cc687b53204 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -50,6 +50,7 @@ pub mod runtest; pub mod common; pub mod errors; mod raise_fd_limit; +mod uidiff; fn main() { #[cfg(cargobuild)] diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 9e4f331d16e..d97e96da16d 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -18,6 +18,7 @@ use header::TestProps; use header; use procsrv; use test::TestPaths; +use uidiff; use util::logv; use std::env; @@ -2115,8 +2116,8 @@ actual:\n\ let normalized_stderr = self.normalize_output(&proc_res.stderr); let mut errors = 0; - errors += self.compare_output("stdout", normalized_stdout.as_bytes(), &expected_stdout); - errors += self.compare_output("stderr", normalized_stderr.as_bytes(), &expected_stderr); + errors += self.compare_output("stdout", &normalized_stdout, &expected_stdout); + errors += self.compare_output("stderr", &normalized_stderr, &expected_stderr); if errors > 0 { println!("To update references, run this command from build directory:"); @@ -2127,7 +2128,8 @@ actual:\n\ self.config.src_base.display(), self.config.build_base.display(), relative_path_to_file.display()); - self.fatal(&format!("{} errors occurred comparing output.", errors)); + self.fatal_proc_rec(&format!("{} errors occurred comparing output.", errors), + &proc_res); } } @@ -2135,7 +2137,9 @@ actual:\n\ let parent_dir = self.testpaths.file.parent().unwrap(); let parent_dir_str = parent_dir.display().to_string(); output.replace(&parent_dir_str, "$DIR") - .replace("\\", "/") // windows, you know. + .replace("\\", "/") // normalize for paths on windows + .replace("\r\n", "\n") // normalize for linebreaks on windows + .replace("\t", "\\t") // makes tabs visible } fn expected_output_path(&self, kind: &str) -> PathBuf { @@ -2146,13 +2150,13 @@ actual:\n\ self.testpaths.file.with_extension(extension) } - fn load_expected_output(&self, path: &Path) -> Vec { + fn load_expected_output(&self, path: &Path) -> String { if !path.exists() { - return vec![]; + return String::new(); } - let mut result = Vec::new(); - match File::open(path).and_then(|mut f| f.read_to_end(&mut result)) { + let mut result = String::new(); + match File::open(path).and_then(|mut f| f.read_to_string(&mut result)) { Ok(_) => result, Err(e) => { self.fatal(&format!("failed to load expected output from `{}`: {}", path.display(), e)) @@ -2160,17 +2164,20 @@ actual:\n\ } } - fn compare_output(&self, kind: &str, actual: &[u8], expected: &[u8]) -> usize { - if self.config.verbose { - println!("normalized {}:\n{}\n", kind, str::from_utf8(actual).unwrap_or("not utf8")); - println!("expected {}:\n{}\n", kind, str::from_utf8(expected).unwrap_or("not utf8")); - } + fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize { if actual == expected { return 0; } + println!("normalized {}:\n{}\n", kind, actual); + println!("expected {}:\n{}\n", kind, expected); + println!("diff of {}:\n", kind); + for line in uidiff::diff_lines(actual, expected) { + println!("{}", line); + } + let output_file = self.output_base_name().with_extension(kind); - match File::create(&output_file).and_then(|mut f| f.write_all(actual)) { + match File::create(&output_file).and_then(|mut f| f.write_all(actual.as_bytes())) { Ok(()) => { } Err(e) => { self.fatal(&format!("failed to write {} to `{}`: {}", diff --git a/src/tools/compiletest/src/uidiff.rs b/src/tools/compiletest/src/uidiff.rs new file mode 100644 index 00000000000..66573393971 --- /dev/null +++ b/src/tools/compiletest/src/uidiff.rs @@ -0,0 +1,76 @@ +// Copyright 2012-2014 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. + +//! Code for checking whether the output of the compiler matches what is +//! expected. + +pub fn diff_lines(actual: &str, expected: &str) -> Vec { + // mega simplistic diff algorithm that just prints the things added/removed + zip_all(actual.lines(), expected.lines()).enumerate().filter_map(|(i, (a,e))| { + match (a, e) { + (Some(a), Some(e)) => { + if lines_match(e, a) { + None + } else { + Some(format!("{:3} - |{}|\n + |{}|\n", i, e, a)) + } + }, + (Some(a), None) => { + Some(format!("{:3} -\n + |{}|\n", i, a)) + }, + (None, Some(e)) => { + Some(format!("{:3} - |{}|\n +\n", i, e)) + }, + (None, None) => panic!("Cannot get here") + } + }).collect() +} + +fn lines_match(expected: &str, mut actual: &str) -> bool { + for (i, part) in expected.split("[..]").enumerate() { + match actual.find(part) { + Some(j) => { + if i == 0 && j != 0 { + return false + } + actual = &actual[j + part.len()..]; + } + None => { + return false + } + } + } + actual.is_empty() || expected.ends_with("[..]") +} + +struct ZipAll { + first: I1, + second: I2, +} + +impl, I2: Iterator> Iterator for ZipAll { + type Item = (Option, Option); + fn next(&mut self) -> Option<(Option, Option)> { + let first = self.first.next(); + let second = self.second.next(); + + match (first, second) { + (None, None) => None, + (a, b) => Some((a, b)) + } + } +} + +fn zip_all, I2: Iterator>(a: I1, b: I2) -> ZipAll { + ZipAll { + first: a, + second: b, + } +}