diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 7b90a902c3..de6cf109b5 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -5,6 +5,8 @@ // // ====------------------------------------------------------------------------------------==== // +pub use verifier::verify_function; + pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod ir; diff --git a/src/libcretonne/verifier.rs b/src/libcretonne/verifier.rs index e134b36285..91131ee6ef 100644 --- a/src/libcretonne/verifier.rs +++ b/src/libcretonne/verifier.rs @@ -55,6 +55,10 @@ use ir::{Function, ValueDef, Ebb, Inst}; use ir::instructions::InstructionFormat; +pub fn verify_function(func: &Function) -> Result<(), String> { + Verifier::new(func).run() +} + pub struct Verifier<'a> { func: &'a Function, } diff --git a/src/tools/cat.rs b/src/tools/cat.rs index 0db69b48db..14871b07c2 100644 --- a/src/tools/cat.rs +++ b/src/tools/cat.rs @@ -3,9 +3,12 @@ //! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of //! normalizing formatting and removing comments. +use std::borrow::Cow; +use cretonne::ir::Function; +use cton_reader::{parse_functions, TestCommand}; use CommandResult; use utils::read_to_string; -use cton_reader::parse_functions; +use filetest::subtest::{self, SubTest, Context, Result as STResult}; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { @@ -30,3 +33,34 @@ fn cat_one(filename: String) -> CommandResult { Ok(()) } + +/// Object implementing the `test cat` sub-test. +/// +/// This command is used for testing the parser and function printer. It simply parses a function +/// and prints it out again. +/// +/// The result is verified by filecheck. +struct TestCat; + +pub fn subtest(parsed: &TestCommand) -> STResult> { + assert_eq!(parsed.command, "cat"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestCat)) + } +} + +impl SubTest for TestCat { + fn name(&self) -> Cow { + Cow::from("cat") + } + + fn needs_verifier(&self) -> bool { + false + } + + fn run(&self, func: Cow, context: &Context) -> STResult<()> { + subtest::run_filecheck(&func.to_string(), context) + } +} diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index 078e287dde..c1019446ca 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -7,6 +7,7 @@ use std::path::Path; use CommandResult; use filetest::runner::TestRunner; +pub mod subtest; mod runner; /// Main entry point for `cton-util test`. diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index e7e33b735b..1f7ac7ee88 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -7,11 +7,15 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::error::Error; use std::mem; +use std::borrow::{Borrow, Cow}; use std::panic::catch_unwind; use std::time; use CommandResult; use utils::read_to_string; use cton_reader::parse_test; +use cretonne::ir::Function; +use cretonne::verify_function; +use filetest::subtest::{self, SubTest, Context}; type TestResult = Result; @@ -208,13 +212,56 @@ impl Job { let started = time::Instant::now(); let buffer = try!(read_to_string(&self.path).map_err(|e| e.to_string())); let testfile = try!(parse_test(&buffer).map_err(|e| e.to_string())); - if testfile.commands.is_empty() { - return Err("no test commands found".to_string()); - } if testfile.functions.is_empty() { return Err("no functions found".to_string()); } + // Parse the test commands. + let mut tests = + try!(testfile.commands.iter().map(subtest::new).collect::>>()); + + // Sort the tests so the mutators are at the end, and those that + // don't need the verifier are at the front + tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); + + // Isolate the last test in the hope that this is the only mutating test. + // If so, we can completely avoid cloning functions. + let last_test = match tests.pop() { + None => return Err("no test commands found".to_string()), + Some(t) => t, + }; + + for (func, details) in testfile.functions { + let mut context = subtest::Context { + details: details, + verified: false, + }; + + for test in &tests { + try!(self.run_one_test(test.borrow(), Cow::Borrowed(&func), &mut context)); + } + // Run the last test with an owned function which means it won't need to clone it + // before mutating. + try!(self.run_one_test(last_test.borrow(), Cow::Owned(func), &mut context)); + } + + // TODO: Actually run the tests. Ok(started.elapsed()) } + + fn run_one_test(&self, + test: &SubTest, + func: Cow, + context: &mut Context) + -> subtest::Result<()> { + let name = format!("{}({})", test.name(), func.name); + + // Should we run the verifier before this test? + if !context.verified && test.needs_verifier() { + try!(verify_function(&func)); + context.verified = true; + } + + test.run(func, context).map_err(|e| format!("{}: {}", name, e)) + } } diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs new file mode 100644 index 0000000000..69ca47aa64 --- /dev/null +++ b/src/tools/filetest/subtest.rs @@ -0,0 +1,77 @@ +//! SubTest trait. + +use std::result; +use std::borrow::Cow; +use cretonne::ir::Function; +use cton_reader::{TestCommand, Details}; +use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; + +pub type Result = result::Result; + +/// Create a new subcommand trait object to match `parsed.command`. +pub fn new(parsed: &TestCommand) -> Result> { + use cat; + match parsed.command { + "cat" => cat::subtest(parsed), + _ => Err(format!("unknown test command '{}'", parsed.command)), + } +} + +/// Context for running a a test on a single function. +pub struct Context<'a> { + /// Additional details about the function from the parser. + pub details: Details<'a>, + + /// Was the function verified before running this test? + pub verified: bool, +} + +/// Common interface for implementations of test commands. +/// +/// Each `.cton` test file may contain multiple test commands, each represented by a `SubTest` +/// trait object. +pub trait SubTest { + /// Name identifying this subtest. Typically the same as the test command. + fn name(&self) -> Cow; + + /// Should the verifier be run on the function before running the test? + fn needs_verifier(&self) -> bool { + true + } + + /// Does this test mutate the function when it runs? + /// This is used as a hint to avoid cloning the function needlessly. + fn is_mutating(&self) -> bool { + false + } + + /// Run this test on `func`. + fn run(&self, func: Cow, context: &Context) -> Result<()>; +} + +/// Run filecheck on `text`, using directives extracted from `context`. +pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { + let checker = try!(build_filechecker(&context.details)); + if try!(checker.check(&text, NO_VARIABLES).map_err(|e| format!("filecheck: {}", e))) { + Ok(()) + } else { + // Filecheck mismatch. Emit an explanation as output. + let (_, explain) = try!(checker.explain(&text, NO_VARIABLES) + .map_err(|e| format!("explain: {}", e))); + Err(format!("filecheck failed:\n{}{}", checker, explain)) + } +} + +/// Build a filechecker using the directives in the function's comments. +pub fn build_filechecker(details: &Details) -> Result { + let mut builder = CheckerBuilder::new(); + for comment in &details.comments { + try!(builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))); + } + let checker = builder.finish(); + if checker.is_empty() { + Err("no filecheck directives in function".to_string()) + } else { + Ok(checker) + } +}