Adds pass command to clif-util. (#487)
* Adds pass command to clif-util.
This commit is contained in:
committed by
Dan Gohman
parent
17bb62c16c
commit
59b83912ba
@@ -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")))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user