Add a SubTest trait and filecheck utilities.
This trait serves as a shared interface for the different kinds of test commands the 'cton-util test' understands. Many tests produce output that is run through filecheck for validation. Provide a simple run_filecheck() function to help with this. Implement the 'test cat' sub-test which is probably the simplest possible.
This commit is contained in:
@@ -5,6 +5,8 @@
|
|||||||
//
|
//
|
||||||
// ====------------------------------------------------------------------------------------==== //
|
// ====------------------------------------------------------------------------------------==== //
|
||||||
|
|
||||||
|
pub use verifier::verify_function;
|
||||||
|
|
||||||
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
pub mod ir;
|
pub mod ir;
|
||||||
|
|||||||
@@ -55,6 +55,10 @@
|
|||||||
use ir::{Function, ValueDef, Ebb, Inst};
|
use ir::{Function, ValueDef, Ebb, Inst};
|
||||||
use ir::instructions::InstructionFormat;
|
use ir::instructions::InstructionFormat;
|
||||||
|
|
||||||
|
pub fn verify_function(func: &Function) -> Result<(), String> {
|
||||||
|
Verifier::new(func).run()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Verifier<'a> {
|
pub struct Verifier<'a> {
|
||||||
func: &'a Function,
|
func: &'a Function,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
//! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of
|
//! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of
|
||||||
//! normalizing formatting and removing comments.
|
//! normalizing formatting and removing comments.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use cretonne::ir::Function;
|
||||||
|
use cton_reader::{parse_functions, TestCommand};
|
||||||
use CommandResult;
|
use CommandResult;
|
||||||
use utils::read_to_string;
|
use utils::read_to_string;
|
||||||
use cton_reader::parse_functions;
|
use filetest::subtest::{self, SubTest, Context, Result as STResult};
|
||||||
|
|
||||||
pub fn run(files: Vec<String>) -> CommandResult {
|
pub fn run(files: Vec<String>) -> CommandResult {
|
||||||
for (i, f) in files.into_iter().enumerate() {
|
for (i, f) in files.into_iter().enumerate() {
|
||||||
@@ -30,3 +33,34 @@ fn cat_one(filename: String) -> CommandResult {
|
|||||||
|
|
||||||
Ok(())
|
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<Box<SubTest>> {
|
||||||
|
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<str> {
|
||||||
|
Cow::from("cat")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_verifier(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, func: Cow<Function>, context: &Context) -> STResult<()> {
|
||||||
|
subtest::run_filecheck(&func.to_string(), context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::path::Path;
|
|||||||
use CommandResult;
|
use CommandResult;
|
||||||
use filetest::runner::TestRunner;
|
use filetest::runner::TestRunner;
|
||||||
|
|
||||||
|
pub mod subtest;
|
||||||
mod runner;
|
mod runner;
|
||||||
|
|
||||||
/// Main entry point for `cton-util test`.
|
/// Main entry point for `cton-util test`.
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ use std::ffi::OsStr;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::panic::catch_unwind;
|
use std::panic::catch_unwind;
|
||||||
use std::time;
|
use std::time;
|
||||||
use CommandResult;
|
use CommandResult;
|
||||||
use utils::read_to_string;
|
use utils::read_to_string;
|
||||||
use cton_reader::parse_test;
|
use cton_reader::parse_test;
|
||||||
|
use cretonne::ir::Function;
|
||||||
|
use cretonne::verify_function;
|
||||||
|
use filetest::subtest::{self, SubTest, Context};
|
||||||
|
|
||||||
type TestResult = Result<time::Duration, String>;
|
type TestResult = Result<time::Duration, String>;
|
||||||
|
|
||||||
@@ -208,13 +212,56 @@ impl Job {
|
|||||||
let started = time::Instant::now();
|
let started = time::Instant::now();
|
||||||
let buffer = try!(read_to_string(&self.path).map_err(|e| e.to_string()));
|
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()));
|
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() {
|
if testfile.functions.is_empty() {
|
||||||
return Err("no functions found".to_string());
|
return Err("no functions found".to_string());
|
||||||
}
|
}
|
||||||
|
// Parse the test commands.
|
||||||
|
let mut tests =
|
||||||
|
try!(testfile.commands.iter().map(subtest::new).collect::<subtest::Result<Vec<_>>>());
|
||||||
|
|
||||||
|
// 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.
|
// TODO: Actually run the tests.
|
||||||
Ok(started.elapsed())
|
Ok(started.elapsed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_one_test(&self,
|
||||||
|
test: &SubTest,
|
||||||
|
func: Cow<Function>,
|
||||||
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/tools/filetest/subtest.rs
Normal file
77
src/tools/filetest/subtest.rs
Normal file
@@ -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<T> = result::Result<T, String>;
|
||||||
|
|
||||||
|
/// Create a new subcommand trait object to match `parsed.command`.
|
||||||
|
pub fn new(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||||
|
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<str>;
|
||||||
|
|
||||||
|
/// 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<Function>, 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<Checker> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user