diff options
| author | bors <bors@rust-lang.org> | 2016-05-16 14:41:50 -0700 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2016-05-16 14:41:50 -0700 |
| commit | cd6a400175cc230008a5094a8bbb44a3794f0465 (patch) | |
| tree | bbddcde08c131f5dd1f3ba58eb5c1dfcb5d0b3ab /src/tools | |
| parent | 4fdf2c4f976ce52163841ba5b3117bb2bb06d97e (diff) | |
| parent | 24cfa1efb0385ede414d47a4a59f7673045151dc (diff) | |
| download | rust-cd6a400175cc230008a5094a8bbb44a3794f0465.tar.gz rust-cd6a400175cc230008a5094a8bbb44a3794f0465.zip | |
Auto merge of #33588 - nikomatsakis:compiletest-ui, r=acrichto
add UI testing framework This adds a framework for capturing and tracking the precise output of rustc, which allows us to check all manner of minor details with the output. It's pretty strict right now -- the output must match almost exactly -- and hence maybe a bit too strict. But I figure we can add wildcards or whatever later. There is also a script intended to make updating the references easy, though the script could make things a *bit* easier (in particular, it'd be nice if it would find the build directory for you automatically). One thing I was wondering about is the best way to test colors. Since windows doesn't embed those in the output stream, this test framework can't test colors on windows -- so I figure we can just write tests that are ignored on windows and which pass `--color=always` or whatever to rustc. cc @jonathandturner r? @alexcrichton
Diffstat (limited to 'src/tools')
| -rw-r--r-- | src/tools/compiletest/src/common.rs | 3 | ||||
| -rw-r--r-- | src/tools/compiletest/src/main.rs | 1 | ||||
| -rw-r--r-- | src/tools/compiletest/src/runtest.rs | 96 | ||||
| -rw-r--r-- | src/tools/compiletest/src/uidiff.rs | 76 |
4 files changed, 175 insertions, 1 deletions
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index ae8beb83530..5ec62e06e37 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -28,6 +28,7 @@ pub enum Mode { CodegenUnits, Incremental, RunMake, + Ui, } impl FromStr for Mode { @@ -47,6 +48,7 @@ impl FromStr for Mode { "codegen-units" => Ok(CodegenUnits), "incremental" => Ok(Incremental), "run-make" => Ok(RunMake), + "ui" => Ok(Ui), _ => Err(()), } } @@ -68,6 +70,7 @@ impl fmt::Display for Mode { CodegenUnits => "codegen-units", Incremental => "incremental", RunMake => "run-make", + Ui => "ui", }, f) } } 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 aa1b9d2bafb..a213c6d2d54 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -11,13 +11,14 @@ use common::Config; use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind}; use common::{Codegen, DebugInfoLldb, DebugInfoGdb, Rustdoc, CodegenUnits}; -use common::{Incremental, RunMake}; +use common::{Incremental, RunMake, Ui}; use errors::{self, ErrorKind, Error}; use json; use header::TestProps; use header; use procsrv; use test::TestPaths; +use uidiff; use util::logv; use std::env; @@ -29,6 +30,7 @@ use std::io::prelude::*; use std::net::TcpStream; use std::path::{Path, PathBuf}; use std::process::{Command, Output, ExitStatus}; +use std::str; pub fn run(config: Config, testpaths: &TestPaths) { match &*config.target { @@ -118,6 +120,7 @@ impl<'test> TestCx<'test> { CodegenUnits => self.run_codegen_units_test(), Incremental => self.run_incremental_test(), RunMake => self.run_rmake_test(), + Ui => self.run_ui_test(), } } @@ -1314,6 +1317,7 @@ actual:\n\ Codegen | Rustdoc | RunMake | + Ui | CodegenUnits => { // do not use JSON output } @@ -2096,6 +2100,96 @@ actual:\n\ } fs::remove_dir(path) } + + fn run_ui_test(&self) { + println!("ui: {}", self.testpaths.file.display()); + + let proc_res = self.compile_test(); + + let expected_stderr_path = self.expected_output_path("stderr"); + let expected_stderr = self.load_expected_output(&expected_stderr_path); + + let expected_stdout_path = self.expected_output_path("stdout"); + let expected_stdout = self.load_expected_output(&expected_stdout_path); + + let normalized_stdout = self.normalize_output(&proc_res.stdout); + let normalized_stderr = self.normalize_output(&proc_res.stderr); + + let mut errors = 0; + 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:"); + let relative_path_to_file = + self.testpaths.relative_dir + .join(self.testpaths.file.file_name().unwrap()); + println!("{}/update-references.sh '{}' '{}'", + self.config.src_base.display(), + self.config.build_base.display(), + relative_path_to_file.display()); + self.fatal_proc_rec(&format!("{} errors occurred comparing output.", errors), + &proc_res); + } + } + + fn normalize_output(&self, output: &str) -> String { + let parent_dir = self.testpaths.file.parent().unwrap(); + let parent_dir_str = parent_dir.display().to_string(); + output.replace(&parent_dir_str, "$DIR") + .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 { + let extension = match self.revision { + Some(r) => format!("{}.{}", r, kind), + None => kind.to_string(), + }; + self.testpaths.file.with_extension(extension) + } + + fn load_expected_output(&self, path: &Path) -> String { + if !path.exists() { + return String::new(); + } + + 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)) + } + } + } + + 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.as_bytes())) { + Ok(()) => { } + Err(e) => { + self.fatal(&format!("failed to write {} to `{}`: {}", + kind, output_file.display(), e)) + } + } + + println!("\nThe actual {0} differed from the expected {0}.", kind); + println!("Actual {} saved to {}", kind, output_file.display()); + 1 + } } struct ProcArgs { 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 <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. + +//! Code for checking whether the output of the compiler matches what is +//! expected. + +pub fn diff_lines(actual: &str, expected: &str) -> Vec<String> { + // 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<I1: Iterator, I2: Iterator> { + first: I1, + second: I2, +} + +impl<T, I1: Iterator<Item=T>, I2: Iterator<Item=T>> Iterator for ZipAll<I1, I2> { + type Item = (Option<T>, Option<T>); + fn next(&mut self) -> Option<(Option<T>, Option<T>)> { + let first = self.first.next(); + let second = self.second.next(); + + match (first, second) { + (None, None) => None, + (a, b) => Some((a, b)) + } + } +} + +fn zip_all<T, I1: Iterator<Item=T>, I2: Iterator<Item=T>>(a: I1, b: I2) -> ZipAll<I1, I2> { + ZipAll { + first: a, + second: b, + } +} |
