Skip to content

compiletest: Run revisions as independent tests. #50400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 17, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/test/ui-fulldeps/update-references.sh
Original file line number Diff line number Diff line change
@@ -31,18 +31,18 @@ MYDIR=$(dirname $0)
BUILD_DIR="$1"
shift

shopt -s nullglob

while [[ "$1" != "" ]]; do
STDERR_NAME="${1/%.rs/.stderr}"
STDOUT_NAME="${1/%.rs/.stdout}"
for EXT in "stderr" "stdout"; do
for OUT_NAME in $BUILD_DIR/${1%.rs}*/*$EXT; do
OUT_DIR=`dirname "$1"`
OUT_BASE=`basename "$OUT_NAME"`
if ! (diff $OUT_NAME $MYDIR/$OUT_DIR/$OUT_BASE >& /dev/null); then
echo updating $MYDIR/$OUT_DIR/$OUT_BASE
cp $OUT_NAME $MYDIR/$OUT_DIR
fi
done
done
shift
if [ -f $BUILD_DIR/$STDOUT_NAME ] && \
! (diff $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME >& /dev/null); then
echo updating $MYDIR/$STDOUT_NAME
cp $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME
fi
if [ -f $BUILD_DIR/$STDERR_NAME ] && \
! (diff $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME >& /dev/null); then
echo updating $MYDIR/$STDERR_NAME
cp $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME
fi
done
2 changes: 1 addition & 1 deletion src/test/ui/update-references.sh
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ shopt -s nullglob

while [[ "$1" != "" ]]; do
for EXT in "stderr" "stdout" "fixed"; do
for OUT_NAME in $BUILD_DIR/${1%.rs}.*$EXT; do
for OUT_NAME in $BUILD_DIR/${1%.rs}*/*$EXT; do
OUT_DIR=`dirname "$1"`
OUT_BASE=`basename "$OUT_NAME"`
if ! (diff $OUT_NAME $MYDIR/$OUT_DIR/$OUT_BASE >& /dev/null); then
62 changes: 51 additions & 11 deletions src/tools/compiletest/src/common.rs
Original file line number Diff line number Diff line change
@@ -10,10 +10,11 @@
pub use self::Mode::*;

use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::path::PathBuf;

use test::ColorConfig;
use util::PathBufExt;

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Mode {
@@ -103,7 +104,7 @@ pub enum CompareMode {
impl CompareMode {
pub(crate) fn to_str(&self) -> &'static str {
match *self {
CompareMode::Nll => "nll"
CompareMode::Nll => "nll",
}
}

@@ -245,24 +246,28 @@ pub struct Config {
pub nodejs: Option<String>,
}

#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct TestPaths {
pub file: PathBuf, // e.g., compile-test/foo/bar/baz.rs
pub base: PathBuf, // e.g., compile-test, auxiliary
pub relative_dir: PathBuf, // e.g., foo/bar
}

/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
pub fn expected_output_path(testpaths: &TestPaths,
revision: Option<&str>,
compare_mode: &Option<CompareMode>,
kind: &str) -> PathBuf {

pub fn expected_output_path(
testpaths: &TestPaths,
revision: Option<&str>,
compare_mode: &Option<CompareMode>,
kind: &str,
) -> PathBuf {
assert!(UI_EXTENSIONS.contains(&kind));
let mut parts = Vec::new();

if let Some(x) = revision { parts.push(x); }
if let Some(ref x) = *compare_mode { parts.push(x.to_str()); }
if let Some(x) = revision {
parts.push(x);
}
if let Some(ref x) = *compare_mode {
parts.push(x.to_str());
}
parts.push(kind);

let extension = parts.join(".");
@@ -273,3 +278,38 @@ 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 = "fixed";

/// Absolute path to the directory where all output for all tests in the given
/// `relative_dir` group should reside. Example:
/// /path/to/build/host-triple/test/ui/relative/
/// This is created early when tests are collected to avoid race conditions.
pub fn output_relative_path(config: &Config, relative_dir: &Path) -> PathBuf {
config.build_base.join(relative_dir)
}

/// Generates a unique name for the test, such as `testname.revision.mode`.
pub fn output_testname_unique(
config: &Config,
testpaths: &TestPaths,
revision: Option<&str>,
) -> PathBuf {
let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str());
PathBuf::from(&testpaths.file.file_stem().unwrap())
.with_extra_extension(revision.unwrap_or(""))
.with_extra_extension(mode)
}

/// Absolute path to the directory where all output for the given
/// test/revision should reside. Example:
/// /path/to/build/host-triple/test/ui/relative/testname.revision.mode/
pub fn output_base_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
output_relative_path(config, &testpaths.relative_dir)
.join(output_testname_unique(config, testpaths, revision))
}

/// Absolute path to the base filename used as output for the given
/// test/revision. Example:
/// /path/to/build/host-triple/test/ui/relative/testname.revision.mode/testname
pub fn output_base_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap())
}
70 changes: 41 additions & 29 deletions src/tools/compiletest/src/errors.rs
Original file line number Diff line number Diff line change
@@ -11,8 +11,8 @@ use self::WhichLine::*;

use std::fmt;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;
use std::str::FromStr;

@@ -35,8 +35,7 @@ impl FromStr for ErrorKind {
"ERROR" => Ok(ErrorKind::Error),
"NOTE" => Ok(ErrorKind::Note),
"SUGGESTION" => Ok(ErrorKind::Suggestion),
"WARN" |
"WARNING" => Ok(ErrorKind::Warning),
"WARN" | "WARNING" => Ok(ErrorKind::Warning),
_ => Err(()),
}
}
@@ -101,61 +100,74 @@ pub fn load_errors(testfile: &Path, cfg: Option<&str>) -> Vec<Error> {
rdr.lines()
.enumerate()
.filter_map(|(line_num, line)| {
parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), &tag)
.map(|(which, error)| {
parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), &tag).map(
|(which, error)| {
match which {
FollowPrevious(_) => {}
_ => last_nonfollow_error = Some(error.line_num),
}
error
})
},
)
})
.collect()
}

fn parse_expected(last_nonfollow_error: Option<usize>,
line_num: usize,
line: &str,
tag: &str)
-> Option<(WhichLine, Error)> {
fn parse_expected(
last_nonfollow_error: Option<usize>,
line_num: usize,
line: &str,
tag: &str,
) -> Option<(WhichLine, Error)> {
let start = match line.find(tag) {
Some(i) => i,
None => return None,
};
let (follow, adjusts) = if line[start + tag.len()..].chars().next().unwrap() == '|' {
(true, 0)
} else {
(false, line[start + tag.len()..].chars().take_while(|c| *c == '^').count())
(
false,
line[start + tag.len()..]
.chars()
.take_while(|c| *c == '^')
.count(),
)
};
let kind_start = start + tag.len() + adjusts + (follow as usize);
let (kind, msg);
match line[kind_start..]
.split_whitespace()
.next()
.expect("Encountered unexpected empty comment")
.parse::<ErrorKind>() {
.parse::<ErrorKind>()
{
Ok(k) => {
// If we find `//~ ERROR foo` or something like that:
kind = Some(k);
let letters = line[kind_start..].chars();
msg = letters.skip_while(|c| c.is_whitespace())
msg = letters
.skip_while(|c| c.is_whitespace())
.skip_while(|c| !c.is_whitespace())
.collect::<String>();
}
Err(_) => {
// Otherwise we found `//~ foo`:
kind = None;
let letters = line[kind_start..].chars();
msg = letters.skip_while(|c| c.is_whitespace())
msg = letters
.skip_while(|c| c.is_whitespace())
.collect::<String>();
}
}
let msg = msg.trim().to_owned();

let (which, line_num) = if follow {
assert_eq!(adjusts, 0, "use either //~| or //~^, not both.");
let line_num = last_nonfollow_error.expect("encountered //~| without \
preceding //~^ line.");
let line_num = last_nonfollow_error.expect(
"encountered //~| without \
preceding //~^ line.",
);
(FollowPrevious(line_num), line_num)
} else {
let which = if adjusts > 0 {
@@ -167,16 +179,16 @@ fn parse_expected(last_nonfollow_error: Option<usize>,
(which, line_num)
};

debug!("line={} tag={:?} which={:?} kind={:?} msg={:?}",
line_num,
tag,
which,
kind,
msg);
Some((which,
Error {
line_num,
kind,
msg,
}))
debug!(
"line={} tag={:?} which={:?} kind={:?} msg={:?}",
line_num, tag, which, kind, msg
);
Some((
which,
Error {
line_num,
kind,
msg,
},
))
}
71 changes: 29 additions & 42 deletions src/tools/compiletest/src/header.rs
Original file line number Diff line number Diff line change
@@ -10,12 +10,12 @@

use std::env;
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::{Path, PathBuf};

use common::Config;
use common;
use common::Config;
use util;

use extract_gdb_version;
@@ -38,19 +38,14 @@ impl EarlyProps {
revisions: vec![],
};

iter_header(testfile,
None,
&mut |ln| {
iter_header(testfile, None, &mut |ln| {
// we should check if any only-<platform> exists and if it exists
// and does not matches the current platform, skip the test
props.ignore =
props.ignore ||
config.parse_cfg_name_directive(ln, "ignore") ||
(config.has_cfg_prefix(ln, "only") &&
!config.parse_cfg_name_directive(ln, "only")) ||
ignore_gdb(config, ln) ||
ignore_lldb(config, ln) ||
ignore_llvm(config, ln);
props.ignore = props.ignore || config.parse_cfg_name_directive(ln, "ignore")
|| (config.has_cfg_prefix(ln, "only")
&& !config.parse_cfg_name_directive(ln, "only"))
|| ignore_gdb(config, ln) || ignore_lldb(config, ln)
|| ignore_llvm(config, ln);

if let Some(s) = config.parse_aux_build(ln) {
props.aux.push(s);
@@ -149,7 +144,7 @@ impl EarlyProps {

fn ignore_llvm(config: &Config, line: &str) -> bool {
if config.system_llvm && line.starts_with("no-system-llvm") {
return true;
return true;
}
if let Some(ref actual_version) = config.llvm_version {
if line.starts_with("min-llvm-version") {
@@ -272,11 +267,7 @@ impl TestProps {
}
}

pub fn from_aux_file(&self,
testfile: &Path,
cfg: Option<&str>,
config: &Config)
-> Self {
pub fn from_aux_file(&self, testfile: &Path, cfg: Option<&str>, config: &Config) -> Self {
let mut props = TestProps::new();

// copy over select properties to the aux build:
@@ -296,20 +287,15 @@ impl TestProps {
/// tied to a particular revision `foo` (indicated by writing
/// `//[foo]`), then the property is ignored unless `cfg` is
/// `Some("foo")`.
fn load_from(&mut self,
testfile: &Path,
cfg: Option<&str>,
config: &Config) {
iter_header(testfile,
cfg,
&mut |ln| {
fn load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config) {
iter_header(testfile, cfg, &mut |ln| {
if let Some(ep) = config.parse_error_pattern(ln) {
self.error_patterns.push(ep);
}

if let Some(flags) = config.parse_compile_flags(ln) {
self.compile_flags.extend(flags.split_whitespace()
.map(|s| s.to_owned()));
self.compile_flags
.extend(flags.split_whitespace().map(|s| s.to_owned()));
}

if let Some(r) = config.parse_revisions(ln) {
@@ -382,8 +368,7 @@ impl TestProps {

if !self.compile_pass {
// run-pass implies must_compile_sucessfully
self.compile_pass =
config.parse_compile_pass(ln) || self.run_pass;
self.compile_pass = config.parse_compile_pass(ln) || self.run_pass;
}

if !self.skip_trans {
@@ -453,7 +438,7 @@ fn iter_header(testfile: &Path, cfg: Option<&str>, it: &mut FnMut(&str)) {
None => false,
};
if matches {
it(ln[(close_brace + 1) ..].trim_left());
it(ln[(close_brace + 1)..].trim_left());
}
} else {
panic!("malformed condition directive: expected `{}foo]`, found `{}`",
@@ -554,9 +539,7 @@ impl Config {
fn parse_env(&self, line: &str, name: &str) -> Option<(String, String)> {
self.parse_name_value_directive(line, name).map(|nv| {
// nv is either FOO or FOO=BAR
let mut strs: Vec<String> = nv.splitn(2, '=')
.map(str::to_owned)
.collect();
let mut strs: Vec<String> = nv.splitn(2, '=').map(str::to_owned).collect();

match strs.len() {
1 => (strs.pop().unwrap(), "".to_owned()),
@@ -599,7 +582,10 @@ impl Config {
/// or `normalize-stderr-32bit`. Returns `true` if the line matches it.
fn parse_cfg_name_directive(&self, line: &str, prefix: &str) -> bool {
if line.starts_with(prefix) && line.as_bytes().get(prefix.len()) == Some(&b'-') {
let name = line[prefix.len()+1 ..].split(&[':', ' '][..]).next().unwrap();
let name = line[prefix.len() + 1..]
.split(&[':', ' '][..])
.next()
.unwrap();

name == "test" ||
util::matches_os(&self.target, name) || // target
@@ -612,8 +598,7 @@ impl Config {
common::DebugInfoLldb => name == "lldb",
common::Pretty => name == "pretty",
_ => false,
} ||
(self.target != self.host && name == "cross-compile")
} || (self.target != self.host && name == "cross-compile")
} else {
false
}
@@ -631,14 +616,14 @@ impl Config {
// the line says "ignore-x86_64".
line.starts_with(directive) && match line.as_bytes().get(directive.len()) {
None | Some(&b' ') | Some(&b':') => true,
_ => false
_ => false,
}
}

pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String> {
let colon = directive.len();
if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
let value = line[(colon + 1) ..].to_owned();
let value = line[(colon + 1)..].to_owned();
debug!("{}: {}", directive, value);
Some(expand_variables(value, self))
} else {
@@ -665,8 +650,10 @@ impl Config {
}

pub fn lldb_version_to_int(version_string: &str) -> isize {
let error_string = format!("Encountered LLDB version string with unexpected format: {}",
version_string);
let error_string = format!(
"Encountered LLDB version string with unexpected format: {}",
version_string
);
version_string.parse().expect(&error_string)
}

@@ -713,6 +700,6 @@ fn parse_normalization_string(line: &mut &str) -> Option<String> {
None => return None,
};
let result = line[begin..end].to_owned();
*line = &line[end+1..];
*line = &line[end + 1..];
Some(result)
}
71 changes: 41 additions & 30 deletions src/tools/compiletest/src/json.rs
Original file line number Diff line number Diff line change
@@ -9,10 +9,10 @@
// except according to those terms.

use errors::{Error, ErrorKind};
use runtest::ProcRes;
use serde_json;
use std::str::FromStr;
use std::path::Path;
use runtest::ProcRes;
use std::str::FromStr;

// These structs are a subset of the ones found in
// `syntax::json`.
@@ -58,26 +58,30 @@ struct DiagnosticCode {
}

pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String {
output.lines()
.filter_map(|line| if line.starts_with('{') {
match serde_json::from_str::<Diagnostic>(line) {
Ok(diagnostic) => diagnostic.rendered,
Err(error) => {
proc_res.fatal(Some(&format!("failed to decode compiler output as json: \
`{}`\noutput: {}\nline: {}",
error,
line,
output)));
output
.lines()
.filter_map(|line| {
if line.starts_with('{') {
match serde_json::from_str::<Diagnostic>(line) {
Ok(diagnostic) => diagnostic.rendered,
Err(error) => {
proc_res.fatal(Some(&format!(
"failed to decode compiler output as json: \
`{}`\noutput: {}\nline: {}",
error, line, output
)));
}
}
} else {
None
}
} else {
None
})
.collect()
}

pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
output.lines()
output
.lines()
.flat_map(|line| parse_line(file_name, line, output, proc_res))
.collect()
}
@@ -93,23 +97,26 @@ fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) ->
expected_errors
}
Err(error) => {
proc_res.fatal(Some(&format!("failed to decode compiler output as json: \
`{}`\noutput: {}\nline: {}",
error,
line,
output)));
proc_res.fatal(Some(&format!(
"failed to decode compiler output as json: \
`{}`\noutput: {}\nline: {}",
error, line, output
)));
}
}
} else {
vec![]
}
}

fn push_expected_errors(expected_errors: &mut Vec<Error>,
diagnostic: &Diagnostic,
default_spans: &[&DiagnosticSpan],
file_name: &str) {
let spans_in_this_file: Vec<_> = diagnostic.spans
fn push_expected_errors(
expected_errors: &mut Vec<Error>,
diagnostic: &Diagnostic,
default_spans: &[&DiagnosticSpan],
file_name: &str,
) {
let spans_in_this_file: Vec<_> = diagnostic
.spans
.iter()
.filter(|span| Path::new(&span.file_name) == Path::new(&file_name))
.collect();
@@ -204,8 +211,10 @@ fn push_expected_errors(expected_errors: &mut Vec<Error>,
}

// Add notes for any labels that appear in the message.
for span in spans_in_this_file.iter()
.filter(|span| span.label.is_some()) {
for span in spans_in_this_file
.iter()
.filter(|span| span.label.is_some())
{
expected_errors.push(Error {
line_num: span.line_start,
kind: Some(ErrorKind::Note),
@@ -219,9 +228,11 @@ fn push_expected_errors(expected_errors: &mut Vec<Error>,
}
}

fn push_backtrace(expected_errors: &mut Vec<Error>,
expansion: &DiagnosticSpanMacroExpansion,
file_name: &str) {
fn push_backtrace(
expected_errors: &mut Vec<Error>,
expansion: &DiagnosticSpanMacroExpansion,
file_name: &str,
) {
if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
expected_errors.push(Error {
line_num: expansion.span.line_start,
181 changes: 101 additions & 80 deletions src/tools/compiletest/src/main.rs
Original file line number Diff line number Diff line change
@@ -31,31 +31,31 @@ extern crate serde_json;
extern crate test;
extern crate rustfix;

use common::CompareMode;
use common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
use common::{Config, TestPaths};
use common::{DebugInfoGdb, DebugInfoLldb, Mode, Pretty};
use filetime::FileTime;
use getopts::Options;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::process::Command;
use filetime::FileTime;
use getopts::Options;
use common::{Config, TestPaths};
use common::{DebugInfoGdb, DebugInfoLldb, Mode, Pretty};
use common::{expected_output_path, UI_EXTENSIONS};
use common::CompareMode;
use test::ColorConfig;
use util::logv;

use self::header::EarlyProps;

pub mod util;
mod json;
pub mod header;
pub mod runtest;
pub mod common;
pub mod errors;
pub mod header;
mod json;
mod raise_fd_limit;
mod read2;
pub mod runtest;
pub mod util;

fn main() {
env_logger::init();
@@ -236,7 +236,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
"",
"compare-mode",
"mode describing what file the actual ui output will be compared to",
"COMPARE MODE"
"COMPARE MODE",
)
.optflag("h", "help", "show this message");

@@ -501,7 +501,11 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
filter: config.filter.clone(),
filter_exact: config.filter_exact,
run_ignored: config.run_ignored,
format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
format: if config.quiet {
test::OutputFormat::Terse
} else {
test::OutputFormat::Pretty
},
logfile: config.logfile.clone(),
run_tests: true,
bench_benchmarks: true,
@@ -548,10 +552,9 @@ fn collect_tests_from_dir(
if name == *"Makefile" && config.mode == Mode::RunMake {
let paths = TestPaths {
file: dir.to_path_buf(),
base: base.to_path_buf(),
relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
};
tests.push(make_test(config, &paths));
tests.extend(make_test(config, &paths));
return Ok(());
}
}
@@ -562,7 +565,7 @@ fn collect_tests_from_dir(
// sequential loop because otherwise, if we do it in the
// tests themselves, they race for the privilege of
// creating the directories and sometimes fail randomly.
let build_dir = config.build_base.join(&relative_dir_path);
let build_dir = output_relative_path(config, relative_dir_path);
fs::create_dir_all(&build_dir).unwrap();

// Add each `.rs` file as a test, and recurse further on any
@@ -576,21 +579,12 @@ fn collect_tests_from_dir(
debug!("found test file: {:?}", file_path.display());
let paths = TestPaths {
file: file_path,
base: base.to_path_buf(),
relative_dir: relative_dir_path.to_path_buf(),
};
tests.push(make_test(config, &paths))
tests.extend(make_test(config, &paths))
} else if file_path.is_dir() {
let relative_file_path = relative_dir_path.join(file.file_name());
if &file_name == "auxiliary" {
// `aux` directories contain other crates used for
// cross-crate tests. Don't search them for tests, but
// do create a directory in the build dir for them,
// since we will dump intermediate output in there
// sometimes.
let build_dir = config.build_base.join(&relative_file_path);
fs::create_dir_all(&build_dir).unwrap();
} else {
if &file_name != "auxiliary" {
debug!("found directory: {:?}", file_path.display());
collect_tests_from_dir(config, base, &file_path, &relative_file_path, tests)?;
}
@@ -613,7 +607,7 @@ pub fn is_test(file_name: &OsString) -> bool {
!invalid_prefixes.iter().any(|p| file_name.starts_with(p))
}

pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn {
pub fn make_test(config: &Config, testpaths: &TestPaths) -> Vec<test::TestDescAndFn> {
let early_props = if config.mode == Mode::RunMake {
// Allow `ignore` directives to be in the Makefile.
EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
@@ -633,47 +627,68 @@ pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn
},
};

// Debugging emscripten code doesn't make sense today
let ignore = early_props.ignore
|| !up_to_date(config, testpaths, &early_props)
|| (config.mode == DebugInfoGdb || config.mode == DebugInfoLldb)
&& config.target.contains("emscripten");

test::TestDescAndFn {
desc: test::TestDesc {
name: make_test_name(config, testpaths),
ignore,
should_panic,
allow_fail: false,
},
testfn: make_test_closure(config, testpaths),
}
// Incremental tests are special, they inherently cannot be run in parallel.
// `runtest::run` will be responsible for iterating over revisions.
let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
vec![None]
} else {
early_props.revisions.iter().map(|r| Some(r)).collect()
};
revisions
.into_iter()
.map(|revision| {
// Debugging emscripten code doesn't make sense today
let ignore = early_props.ignore
|| !up_to_date(
config,
testpaths,
&early_props,
revision.map(|s| s.as_str()),
)
|| (config.mode == DebugInfoGdb || config.mode == DebugInfoLldb)
&& config.target.contains("emscripten");
test::TestDescAndFn {
desc: test::TestDesc {
name: make_test_name(config, testpaths, revision),
ignore,
should_panic,
allow_fail: false,
},
testfn: make_test_closure(config, testpaths, revision),
}
})
.collect()
}

fn stamp(config: &Config, testpaths: &TestPaths) -> PathBuf {
let mode_suffix = match config.compare_mode {
Some(ref mode) => format!("-{}", mode.to_str()),
None => format!(""),
};
let stamp_name = format!(
"{}-{}{}.stamp",
testpaths.file.file_name().unwrap().to_str().unwrap(),
config.stage_id,
mode_suffix
);
config
.build_base
.canonicalize()
.unwrap_or_else(|_| config.build_base.clone())
.join(&testpaths.relative_dir)
.join(stamp_name)
fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
output_base_dir(config, testpaths, revision).join("stamp")
}

fn up_to_date(config: &Config, testpaths: &TestPaths, props: &EarlyProps) -> bool {
fn up_to_date(
config: &Config,
testpaths: &TestPaths,
props: &EarlyProps,
revision: Option<&str>,
) -> bool {
let stamp_name = stamp(config, testpaths, revision);
// Check hash.
let mut f = match fs::File::open(&stamp_name) {
Ok(f) => f,
Err(_) => return true,
};
let mut contents = String::new();
f.read_to_string(&mut contents)
.expect("Can't read stamp contents");
let expected_hash = runtest::compute_stamp_hash(config);
if contents != expected_hash {
return true;
}

// Check timestamps.
let rust_src_dir = config
.find_rust_src_root()
.expect("Could not find Rust source root");
let stamp = mtime(&stamp(config, testpaths));
let stamp = mtime(&stamp_name);
let mut inputs = vec![mtime(&testpaths.file), mtime(&config.rustc_path)];
for aux in props.aux.iter() {
inputs.push(mtime(&testpaths
@@ -694,8 +709,7 @@ fn up_to_date(config: &Config, testpaths: &TestPaths, props: &EarlyProps) -> boo
for pretty_printer_file in &pretty_printer_files {
inputs.push(mtime(&rust_src_dir.join(pretty_printer_file)));
}
let mut entries = config.run_lib_path.read_dir().unwrap()
.collect::<Vec<_>>();
let mut entries = config.run_lib_path.read_dir().unwrap().collect::<Vec<_>>();
while let Some(entry) = entries.pop() {
let entry = entry.unwrap();
let path = entry.path();
@@ -712,18 +726,8 @@ fn up_to_date(config: &Config, testpaths: &TestPaths, props: &EarlyProps) -> boo

// UI test files.
for extension in UI_EXTENSIONS {
for revision in &props.revisions {
let path = &expected_output_path(testpaths,
Some(revision),
&config.compare_mode,
extension);
inputs.push(mtime(path));
}

if props.revisions.is_empty() {
let path = &expected_output_path(testpaths, None, &config.compare_mode, extension);
inputs.push(mtime(path));
}
let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
inputs.push(mtime(path));
}

inputs.iter().any(|input| *input > stamp)
@@ -735,7 +739,11 @@ fn mtime(path: &Path) -> FileTime {
.unwrap_or_else(|_| FileTime::zero())
}

pub fn make_test_name(config: &Config, testpaths: &TestPaths) -> test::TestName {
fn make_test_name(
config: &Config,
testpaths: &TestPaths,
revision: Option<&String>,
) -> test::TestName {
// Convert a complete path to something like
//
// run-pass/foo/bar/baz.rs
@@ -746,13 +754,26 @@ pub fn make_test_name(config: &Config, testpaths: &TestPaths) -> test::TestName
Some(ref mode) => format!(" ({})", mode.to_str()),
None => format!(""),
};
test::DynTestName(format!("[{}{}] {}", config.mode, mode_suffix, path.display()))
test::DynTestName(format!(
"[{}{}] {}{}",
config.mode,
mode_suffix,
path.display(),
revision.map_or("".to_string(), |rev| format!("#{}", rev))
))
}

pub fn make_test_closure(config: &Config, testpaths: &TestPaths) -> test::TestFn {
fn make_test_closure(
config: &Config,
testpaths: &TestPaths,
revision: Option<&String>,
) -> test::TestFn {
let config = config.clone();
let testpaths = testpaths.clone();
test::DynTestFn(Box::new(move || runtest::run(config, &testpaths)))
let revision = revision.cloned();
test::DynTestFn(Box::new(move || {
runtest::run(config, &testpaths, revision.as_ref().map(|s| s.as_str()))
}))
}

/// Returns (Path to GDB, GDB Version, GDB has Rust Support)
15 changes: 9 additions & 6 deletions src/tools/compiletest/src/raise_fd_limit.rs
Original file line number Diff line number Diff line change
@@ -34,12 +34,15 @@ pub unsafe fn raise_fd_limit() {
let mut mib: [libc::c_int; 2] = [CTL_KERN, KERN_MAXFILESPERPROC];
let mut maxfiles: libc::c_int = 0;
let mut size: libc::size_t = size_of_val(&maxfiles) as libc::size_t;
if libc::sysctl(&mut mib[0],
2,
&mut maxfiles as *mut _ as *mut _,
&mut size,
null_mut(),
0) != 0 {
if libc::sysctl(
&mut mib[0],
2,
&mut maxfiles as *mut _ as *mut _,
&mut size,
null_mut(),
0,
) != 0
{
let err = io::Error::last_os_error();
panic!("raise_fd_limit: error calling sysctl: {}", err);
}
60 changes: 33 additions & 27 deletions src/tools/compiletest/src/read2.rs
Original file line number Diff line number Diff line change
@@ -16,11 +16,13 @@ pub use self::imp::read2;
#[cfg(not(any(unix, windows)))]
mod imp {
use std::io::{self, Read};
use std::process::{ChildStdout, ChildStderr};
use std::process::{ChildStderr, ChildStdout};

pub fn read2(out_pipe: ChildStdout,
err_pipe: ChildStderr,
data: &mut FnMut(bool, &mut Vec<u8>, bool)) -> io::Result<()> {
pub fn read2(
out_pipe: ChildStdout,
err_pipe: ChildStderr,
data: &mut FnMut(bool, &mut Vec<u8>, bool),
) -> io::Result<()> {
let mut buffer = Vec::new();
out_pipe.read_to_end(&mut buffer)?;
data(true, &mut buffer, true);
@@ -33,16 +35,18 @@ mod imp {

#[cfg(unix)]
mod imp {
use std::io::prelude::*;
use libc;
use std::io;
use std::io::prelude::*;
use std::mem;
use std::os::unix::prelude::*;
use std::process::{ChildStdout, ChildStderr};
use libc;
use std::process::{ChildStderr, ChildStdout};

pub fn read2(mut out_pipe: ChildStdout,
mut err_pipe: ChildStderr,
data: &mut FnMut(bool, &mut Vec<u8>, bool)) -> io::Result<()> {
pub fn read2(
mut out_pipe: ChildStdout,
mut err_pipe: ChildStderr,
data: &mut FnMut(bool, &mut Vec<u8>, bool),
) -> io::Result<()> {
unsafe {
libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
@@ -67,25 +71,23 @@ mod imp {
if r == -1 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue
continue;
}
return Err(err)
return Err(err);
}

// Read as much as we can from each pipe, ignoring EWOULDBLOCK or
// EAGAIN. If we hit EOF, then this will happen because the underlying
// reader will return Ok(0), in which case we'll see `Ok` ourselves. In
// this case we flip the other fd back into blocking mode and read
// whatever's leftover on that file descriptor.
let handle = |res: io::Result<_>| {
match res {
Ok(_) => Ok(true),
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
Ok(false)
} else {
Err(e)
}
let handle = |res: io::Result<_>| match res {
Ok(_) => Ok(true),
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
Ok(false)
} else {
Err(e)
}
}
};
@@ -113,7 +115,7 @@ mod imp {

use std::io;
use std::os::windows::prelude::*;
use std::process::{ChildStdout, ChildStderr};
use std::process::{ChildStderr, ChildStdout};
use std::slice;

use self::miow::iocp::{CompletionPort, CompletionStatus};
@@ -128,9 +130,11 @@ mod imp {
done: bool,
}

pub fn read2(out_pipe: ChildStdout,
err_pipe: ChildStderr,
data: &mut FnMut(bool, &mut Vec<u8>, bool)) -> io::Result<()> {
pub fn read2(
out_pipe: ChildStdout,
err_pipe: ChildStderr,
data: &mut FnMut(bool, &mut Vec<u8>, bool),
) -> io::Result<()> {
let mut out = Vec::new();
let mut err = Vec::new();

@@ -206,7 +210,9 @@ mod imp {
if v.capacity() == v.len() {
v.reserve(1);
}
slice::from_raw_parts_mut(v.as_mut_ptr().offset(v.len() as isize),
v.capacity() - v.len())
slice::from_raw_parts_mut(
v.as_mut_ptr().offset(v.len() as isize),
v.capacity() - v.len(),
)
}
}
283 changes: 132 additions & 151 deletions src/tools/compiletest/src/runtest.rs

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion src/tools/compiletest/src/util.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::ffi::OsStr;
use std::env;
use std::path::PathBuf;
use common::Config;

/// Conversion table from triple OS name to Rust SYSNAME
@@ -73,7 +75,7 @@ pub fn matches_os(triple: &str, name: &str) -> bool {
// For the wasm32 bare target we ignore anything also ignored on emscripten
// and then we also recognize `wasm32-bare` as the os for the target
if triple == "wasm32-unknown-unknown" {
return name == "emscripten" || name == "wasm32-bare"
return name == "emscripten" || name == "wasm32-bare";
}
let triple: Vec<_> = triple.split('-').collect();
for &(triple_os, os) in OS_TABLE {
@@ -128,3 +130,23 @@ pub fn logv(config: &Config, s: String) {
println!("{}", s);
}
}

pub trait PathBufExt {
/// Append an extension to the path, even if it already has one.
fn with_extra_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf;
}

impl PathBufExt for PathBuf {
fn with_extra_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf {
if extension.as_ref().len() == 0 {
self.clone()
} else {
let mut fname = self.file_name().unwrap().to_os_string();
if !extension.as_ref().to_str().unwrap().starts_with(".") {
fname.push(".");
}
fname.push(extension);
self.with_file_name(fname)
}
}
}