Adds pass command to clif-util. (#487)

* Adds pass command to clif-util.
This commit is contained in:
Caroline Cullen
2018-09-04 16:31:24 -07:00
committed by Dan Gohman
parent 17bb62c16c
commit 59b83912ba
8 changed files with 207 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
#![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)]
#![warn(unused_import_braces)]
#![deny(trivial_numeric_casts)]
#![warn(unused_import_braces, unstable_features, unused_extern_crates)]
#![cfg_attr(
feature = "cargo-clippy",
warn(
@@ -15,13 +15,13 @@ extern crate cfg_if;
extern crate capstone;
extern crate clap;
extern crate cranelift_codegen;
extern crate cranelift_entity;
extern crate cranelift_filetests;
extern crate cranelift_reader;
extern crate pretty_env_logger;
cfg_if! {
if #[cfg(feature = "wasm")] {
extern crate cranelift_entity;
extern crate cranelift_wasm;
extern crate term;
extern crate wabt;
@@ -53,6 +53,21 @@ fn add_input_file_arg<'a>() -> clap::Arg<'a, 'a> {
.help("Specify file(s) to be used for test")
}
fn add_single_input_file_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("single-file")
.required(true)
.value_name("single-file")
.help("Specify a file to be used")
}
fn add_pass_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("pass")
.required(true)
.multiple(true)
.value_name("pass")
.help("Specify pass(s) to be run on test file")
}
fn add_verbose_flag<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("verbose").short("v").help("Be more verbose")
}
@@ -162,7 +177,16 @@ fn main() {
"Just checks the correctness of Cranelift IR translated from WebAssembly",
)),
)
.subcommand(add_wasm_or_compile("wasm"));
.subcommand(add_wasm_or_compile("wasm"))
.subcommand(
SubCommand::with_name("pass")
.about("Run specified pass(s) on an input file.")
.arg(add_single_input_file_arg())
.arg(add_target_flag())
.arg(add_pass_arg())
.arg(add_debug_flag())
.arg(add_time_flag()),
);
let res_util = match app_cmds.get_matches().subcommand() {
("cat", Some(rest_cmd)) => {
@@ -172,10 +196,26 @@ fn main() {
("test", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug"));
cranelift_filetests::run(
rest_cmd.is_present("time-passes"),
rest_cmd.is_present("verbose"),
&get_vec(rest_cmd.values_of("file")),
).map(|_time| ())
}
("pass", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug"));
let mut target_val: &str = "";
if let Some(clap_target) = rest_cmd.value_of("target") {
target_val = clap_target;
}
// Can be unwrapped because 'single-file' is required
cranelift_filetests::run_passes(
rest_cmd.is_present("verbose"),
&get_vec(rest_cmd.values_of("pass")),
target_val,
&rest_cmd.value_of("single-file").unwrap().to_string(),
).map(|_time| ())
}
("print-cfg", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug"));
print_cfg::run(&get_vec(rest_cmd.values_of("file")))

View File

@@ -80,7 +80,7 @@ fn handle_module(
fisa: FlagsOrIsa,
) -> Result<(), String> {
let buffer = read_to_string(&path).map_err(|e| format!("{}: {}", name, e))?;
let test_file = parse_test(&buffer).map_err(|e| format!("{}: {}", name, e))?;
let test_file = parse_test(&buffer, None, None).map_err(|e| format!("{}: {}", name, e))?;
// If we have an isa from the command-line, use that. Otherwise if the
// file contains a unique isa, use that.

View File

@@ -131,7 +131,8 @@ fn worker_thread(
// The receiver should always be present for this as long as we have jobs.
replies.send(Reply::Starting { jobid, thread_num }).unwrap();
let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| {
let result = catch_unwind(|| runone::run(path.as_path(), None, None))
.unwrap_or_else(|e| {
// The test panicked, leaving us a `Box<Any>`.
// Panics are usually strings.
if let Some(msg) = e.downcast_ref::<String>() {

View File

@@ -79,6 +79,24 @@ pub fn run(verbose: bool, files: &[String]) -> TestResult {
runner.run()
}
/// Used for 'pass' subcommand.
/// Commands are interpreted as test and executed.
///
/// Directories are scanned recursively for test cases ending in `.clif`.
///
pub fn run_passes(verbose: bool, passes: &[String], target: &str, file: &String) -> TestResult {
let mut runner = TestRunner::new(verbose);
let path = Path::new(file);
if path.is_file() {
runner.push_test(path);
} else {
runner.push_dir(path);
}
runner.run_passes(passes, target)
}
/// Create a new subcommand trait object to match `parsed.command`.
///
/// This function knows how to create all of the possible `test <foo>` commands that can appear in

View File

@@ -30,6 +30,12 @@ enum State {
Done(TestResult),
}
#[derive(PartialEq, Eq, Debug)]
pub enum IsPass {
Pass,
NotPass,
}
impl QueueEntry {
pub fn path(&self) -> &Path {
self.path.as_path()
@@ -118,7 +124,7 @@ impl TestRunner {
/// Scan any directories pushed so far.
/// Push any potential test cases found.
pub fn scan_dirs(&mut self) {
pub fn scan_dirs(&mut self, pass_status: IsPass) {
// This recursive search tries to minimize statting in a directory hierarchy containing
// mostly test cases.
//
@@ -164,10 +170,14 @@ impl TestRunner {
}
}
}
if pass_status == IsPass::Pass {
continue;
} else {
// Get the new jobs running before moving on to the next directory.
self.schedule_jobs();
}
}
}
/// Report an error related to a path.
fn path_error<E: Error>(&mut self, path: &PathBuf, err: &E) {
@@ -203,7 +213,7 @@ impl TestRunner {
} else {
// Run test synchronously.
self.tests[jobid].state = State::Running;
let result = runone::run(self.tests[jobid].path());
let result = runone::run(self.tests[jobid].path(), None, None);
self.finish_job(jobid, result);
}
self.new_tests = jobid + 1;
@@ -215,6 +225,20 @@ impl TestRunner {
}
}
/// Schedule any new job to run for the pass command.
fn schedule_pass_job(&mut self, passes: &[String], target: &str) {
self.tests[0].state = State::Running;
let result: Result<time::Duration, String>;
let specified_target = match target {
"" => None,
targ => Some(targ),
};
result = runone::run(self.tests[0].path(), Some(passes), specified_target);
self.finish_job(0, result);
}
/// Report the end of a job.
fn finish_job(&mut self, jobid: usize, result: TestResult) {
assert_eq!(self.tests[jobid].state, State::Running);
@@ -331,10 +355,26 @@ impl TestRunner {
/// Scan pushed directories for tests and run them.
pub fn run(&mut self) -> TestResult {
let started = time::Instant::now();
self.scan_dirs();
self.scan_dirs(IsPass::NotPass);
self.schedule_jobs();
self.drain_threads();
self.report_slow_tests();
self.drain_threads();
println!("{} tests", self.tests.len());
match self.errors {
0 => Ok(started.elapsed()),
1 => Err("1 failure".to_string()),
n => Err(format!("{} failures", n)),
}
}
/// Scan pushed directories for tests and run specified passes from commandline on them.
pub fn run_passes(&mut self, passes: &[String], target: &str) -> TestResult {
let started = time::Instant::now();
self.scan_dirs(IsPass::Pass);
self.schedule_pass_job(passes, target);
self.report_slow_tests();
println!("{} tests", self.tests.len());
match self.errors {
0 => Ok(started.elapsed()),

View File

@@ -27,12 +27,12 @@ fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
/// Load `path` and run the test in it.
///
/// If running this test causes a panic, it will propagate as normal.
pub fn run(path: &Path) -> TestResult {
pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> TestResult {
let _tt = timing::process_file();
info!("---\nFile: {}", path.to_string_lossy());
let started = time::Instant::now();
let buffer = read_to_string(path).map_err(|e| e.to_string())?;
let testfile = parse_test(&buffer).map_err(|e| e.to_string())?;
let testfile = parse_test(&buffer, passes, target).map_err(|e| e.to_string())?;
if testfile.functions.is_empty() {
return Err("no functions found".to_string());
}

View File

@@ -32,20 +32,39 @@ use testfile::{Comment, Details, TestFile};
/// Any test commands or target declarations are ignored.
pub fn parse_functions(text: &str) -> ParseResult<Vec<Function>> {
let _tt = timing::parse_text();
parse_test(text).map(|file| file.functions.into_iter().map(|(func, _)| func).collect())
parse_test(text, None, None)
.map(|file| file.functions.into_iter().map(|(func, _)| func).collect())
}
/// Parse the entire `text` as a test case file.
///
/// The returned `TestFile` contains direct references to substrings of `text`.
pub fn parse_test(text: &str) -> ParseResult<TestFile> {
pub fn parse_test<'a>(
text: &'a str,
passes: Option<&'a [String]>,
target: Option<&str>,
) -> ParseResult<TestFile<'a>> {
let _tt = timing::parse_text();
let mut parser = Parser::new(text);
// Gather the preamble comments.
parser.start_gathering_comments();
let commands = parser.parse_test_commands();
let isa_spec = parser.parse_target_specs()?;
let isa_spec: isaspec::IsaSpec;
let commands: Vec<TestCommand<'a>>;
// Check for specified passes and target, if present throw out test commands/targets specified in file.
match passes {
Some(pass_vec) => {
parser.parse_test_commands();
commands = parser.parse_cmdline_passes(pass_vec);
parser.parse_target_specs()?;
isa_spec = parser.parse_cmdline_target(target)?;
}
None => {
commands = parser.parse_test_commands();
isa_spec = parser.parse_target_specs()?;
}
};
parser.token();
parser.claim_gathered_comments(AnyEntity::Function);
@@ -705,6 +724,15 @@ impl<'a> Parser<'a> {
}
}
/// Parse a list of test command passes specified in command line.
pub fn parse_cmdline_passes(&mut self, passes: &'a [String]) -> Vec<TestCommand<'a>> {
let mut list = Vec::new();
for pass in passes {
list.push(TestCommand::new(pass));
}
list
}
/// Parse a list of test commands.
pub fn parse_test_commands(&mut self) -> Vec<TestCommand<'a>> {
let mut list = Vec::new();
@@ -714,12 +742,58 @@ impl<'a> Parser<'a> {
list
}
/// Parse a target spec.
///
/// Accept the target from the command line for pass command.
///
pub fn parse_cmdline_target(
&mut self,
target_pass: Option<&str>,
) -> ParseResult<isaspec::IsaSpec> {
// Were there any `target` commands specified?
let mut specified_target = false;
let mut targets = Vec::new();
let flag_builder = settings::builder();
match target_pass {
Some(targ) => {
let loc = self.loc;
let triple = match Triple::from_str(targ) {
Ok(triple) => triple,
Err(err) => return err!(loc, err),
};
let mut isa_builder = match isa::lookup(triple) {
Err(isa::LookupError::SupportDisabled) => {
return err!(loc, "support disabled target '{}'", targ)
}
Err(isa::LookupError::Unsupported) => {
return err!(loc, "unsupported target '{}'", targ)
}
Ok(b) => b,
};
specified_target = true;
// Construct a trait object with the aggregate settings.
targets.push(isa_builder.finish(settings::Flags::new(flag_builder.clone())));
}
None => (),
};
if !specified_target {
// No `target` commands.
Ok(isaspec::IsaSpec::None(settings::Flags::new(flag_builder)))
} else {
Ok(isaspec::IsaSpec::Some(targets))
}
}
/// Parse a list of target specs.
///
/// Accept a mix of `target` and `set` command lines. The `set` commands are cumulative.
///
pub fn parse_target_specs(&mut self) -> ParseResult<isaspec::IsaSpec> {
// Was there any `target` commands?
// Were there any `target` commands?
let mut seen_target = false;
// Location of last `set` command since the last `target`.
let mut last_set_loc = None;
@@ -2703,6 +2777,8 @@ mod tests {
set enable_float=false
; still preamble
function %comment() system_v {}",
None,
None,
).unwrap();
assert_eq!(tf.commands.len(), 2);
assert_eq!(tf.commands[0].command, "cfg");

View File

@@ -222,6 +222,8 @@ mod tests {
ebb0(v4: i32, v7: i32):
v10 = iadd v4, v7
}",
None,
None,
).unwrap();
let map = &tf.functions[0].1.map;