Run the filetests as part of "cargo test".

Refactor the filetests harness so that it can be run as part of
`cargo test`. And begin reorganizing the test harness code in preparation
for moving it out of the src directory.
 - Test subcommand files are now named `test_*.rs`.
 - cton-util subcommand files now just export their `run` and nothing else.
 - src/filetest/mod.rs now also just exports `run` and nothing else.
 - Tests are now run in release mode (with debug assertions enabled).
This commit is contained in:
Dan Gohman
2017-08-29 10:43:41 -07:00
parent b2acd457d5
commit 00af7a28f3
20 changed files with 242 additions and 175 deletions

View File

@@ -23,7 +23,7 @@ docopt = "0.8.0"
serde = "1.0.8" serde = "1.0.8"
serde_derive = "1.0.8" serde_derive = "1.0.8"
num_cpus = "1.5.1" num_cpus = "1.5.1"
tempdir="0.3.5" tempdir = "0.3.5"
term = "0.5" term = "0.5"
[workspace] [workspace]

View File

@@ -324,6 +324,22 @@ Test the simple GVN pass.
The simple GVN pass is run on each function, and then results are run The simple GVN pass is run on each function, and then results are run
through filecheck. through filecheck.
`test licm`
-----------------
Test the LICM pass.
The LICM pass is run on each function, and then results are run
through filecheck.
`test preopt`
-----------------
Test the preopt pass.
The preopt pass is run on each function, and then results are run
through filecheck.
`test compile` `test compile`
-------------- --------------

View File

@@ -3,12 +3,9 @@
//! 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 cton_reader::parse_functions;
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 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() {
@@ -37,34 +34,3 @@ 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.display(context.isa).to_string(), context)
}
}

View File

@@ -7,27 +7,27 @@ use std::path::Path;
use std::time; use std::time;
use cton_reader::TestCommand; use cton_reader::TestCommand;
use CommandResult; use CommandResult;
use cat;
use print_cfg;
use filetest::runner::TestRunner; use filetest::runner::TestRunner;
pub mod subtest;
mod binemit;
mod compile;
mod concurrent; mod concurrent;
mod domtree;
mod legalizer;
mod licm;
mod preopt;
mod regalloc;
mod runner; mod runner;
mod runone; mod runone;
mod simple_gvn; mod subtest;
mod verifier;
mod test_binemit;
mod test_cat;
mod test_compile;
mod test_domtree;
mod test_legalizer;
mod test_licm;
mod test_preopt;
mod test_print_cfg;
mod test_regalloc;
mod test_simple_gvn;
mod test_verifier;
/// The result of running the test in a file. /// The result of running the test in a file.
pub type TestResult = Result<time::Duration, String>; type TestResult = Result<time::Duration, String>;
/// Main entry point for `cton-util test`. /// Main entry point for `cton-util test`.
/// ///
@@ -59,17 +59,17 @@ pub fn run(verbose: bool, files: Vec<String>) -> CommandResult {
/// a `.cton` test file. /// a `.cton` test file.
fn new_subtest(parsed: &TestCommand) -> subtest::Result<Box<subtest::SubTest>> { fn new_subtest(parsed: &TestCommand) -> subtest::Result<Box<subtest::SubTest>> {
match parsed.command { match parsed.command {
"binemit" => binemit::subtest(parsed), "binemit" => test_binemit::subtest(parsed),
"cat" => cat::subtest(parsed), "cat" => test_cat::subtest(parsed),
"compile" => compile::subtest(parsed), "compile" => test_compile::subtest(parsed),
"domtree" => domtree::subtest(parsed), "domtree" => test_domtree::subtest(parsed),
"legalizer" => legalizer::subtest(parsed), "legalizer" => test_legalizer::subtest(parsed),
"licm" => licm::subtest(parsed), "licm" => test_licm::subtest(parsed),
"preopt" => preopt::subtest(parsed), "preopt" => test_preopt::subtest(parsed),
"print-cfg" => print_cfg::subtest(parsed), "print-cfg" => test_print_cfg::subtest(parsed),
"regalloc" => regalloc::subtest(parsed), "regalloc" => test_regalloc::subtest(parsed),
"simple-gvn" => simple_gvn::subtest(parsed), "simple-gvn" => test_simple_gvn::subtest(parsed),
"verifier" => verifier::subtest(parsed), "verifier" => test_verifier::subtest(parsed),
_ => Err(format!("unknown test command '{}'", parsed.command)), _ => Err(format!("unknown test command '{}'", parsed.command)),
} }
} }

View File

@@ -0,0 +1,37 @@
//! The `cat` subtest.
use std::borrow::Cow;
use cretonne::ir::Function;
use cton_reader::TestCommand;
use filetest::subtest::{self, SubTest, Context, Result as STResult};
/// 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.display(context.isa).to_string(), context)
}
}

View File

@@ -0,0 +1,37 @@
//! The `print-cfg` sub-command.
//!
//! Read a series of Cretonne IL files and print their control flow graphs
//! in graphviz format.
use std::borrow::Cow;
use cretonne::ir::Function;
use cretonne::cfg_printer::CFGPrinter;
use cton_reader::TestCommand;
use filetest::subtest::{self, SubTest, Context, Result as STResult};
/// Object implementing the `test print-cfg` sub-test.
struct TestPrintCfg;
pub fn subtest(parsed: &TestCommand) -> STResult<Box<SubTest>> {
assert_eq!(parsed.command, "print-cfg");
if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed))
} else {
Ok(Box::new(TestPrintCfg))
}
}
impl SubTest for TestPrintCfg {
fn name(&self) -> Cow<str> {
Cow::from("print-cfg")
}
fn needs_verifier(&self) -> bool {
false
}
fn run(&self, func: Cow<Function>, context: &Context) -> STResult<()> {
subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context)
}
}

View File

@@ -3,15 +3,9 @@
//! Read a series of Cretonne IL files and print their control flow graphs //! Read a series of Cretonne IL files and print their control flow graphs
//! in graphviz format. //! in graphviz format.
use std::borrow::Cow;
use std::fmt::{Result, Write, Display, Formatter};
use CommandResult; use CommandResult;
use cretonne::flowgraph::ControlFlowGraph; use cretonne::cfg_printer::CFGPrinter;
use cretonne::ir::Function; use cton_reader::parse_functions;
use cretonne::ir::instructions::BranchInfo;
use cton_reader::{parse_functions, TestCommand};
use filetest::subtest::{self, SubTest, Context, Result as STResult};
use utils::read_to_string; use utils::read_to_string;
pub fn run(files: Vec<String>) -> CommandResult { pub fn run(files: Vec<String>) -> CommandResult {
@@ -24,72 +18,6 @@ pub fn run(files: Vec<String>) -> CommandResult {
Ok(()) Ok(())
} }
struct CFGPrinter<'a> {
func: &'a Function,
cfg: ControlFlowGraph,
}
impl<'a> CFGPrinter<'a> {
pub fn new(func: &'a Function) -> CFGPrinter<'a> {
CFGPrinter {
func,
cfg: ControlFlowGraph::with_function(func),
}
}
/// Write the CFG for this function to `w`.
pub fn write(&self, w: &mut Write) -> Result {
self.header(w)?;
self.ebb_nodes(w)?;
self.cfg_connections(w)?;
writeln!(w, "}}")
}
fn header(&self, w: &mut Write) -> Result {
writeln!(w, "digraph \"{}\" {{", self.func.name)?;
if let Some(entry) = self.func.layout.entry_block() {
writeln!(w, " {{rank=min; {}}}", entry)?;
}
Ok(())
}
fn ebb_nodes(&self, w: &mut Write) -> Result {
for ebb in &self.func.layout {
write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)?;
// Add all outgoing branch instructions to the label.
for inst in self.func.layout.ebb_insts(ebb) {
let idata = &self.func.dfg[inst];
match idata.analyze_branch(&self.func.dfg.value_lists) {
BranchInfo::SingleDest(dest, _) => {
write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)?
}
BranchInfo::Table(table) => {
write!(w, " | <{}>{} {}", inst, idata.opcode(), table)?
}
BranchInfo::NotABranch => {}
}
}
writeln!(w, "}}\"]")?
}
Ok(())
}
fn cfg_connections(&self, w: &mut Write) -> Result {
for ebb in &self.func.layout {
for (parent, inst) in self.cfg.pred_iter(ebb) {
writeln!(w, " {}:{} -> {}", parent, inst, ebb)?;
}
}
Ok(())
}
}
impl<'a> Display for CFGPrinter<'a> {
fn fmt(&self, f: &mut Formatter) -> Result {
self.write(f)
}
}
fn print_cfg(filename: String) -> CommandResult { fn print_cfg(filename: String) -> CommandResult {
let buffer = read_to_string(&filename).map_err( let buffer = read_to_string(&filename).map_err(
|e| format!("{}: {}", filename, e), |e| format!("{}: {}", filename, e),
@@ -107,29 +35,3 @@ fn print_cfg(filename: String) -> CommandResult {
Ok(()) Ok(())
} }
/// Object implementing the `test print-cfg` sub-test.
struct TestPrintCfg;
pub fn subtest(parsed: &TestCommand) -> STResult<Box<SubTest>> {
assert_eq!(parsed.command, "print-cfg");
if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed))
} else {
Ok(Box::new(TestPrintCfg))
}
}
impl SubTest for TestPrintCfg {
fn name(&self) -> Cow<str> {
Cow::from("print-cfg")
}
fn needs_verifier(&self) -> bool {
false
}
fn run(&self, func: Cow<Function>, context: &Context) -> STResult<()> {
subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context)
}
}

View File

@@ -43,21 +43,19 @@ if [ -n "$needcheck" ]; then
fi fi
cd "$topdir" cd "$topdir"
banner "Rust unit tests"
cargo test --all
# Build cton-util for parser testing. # Make sure the code builds in debug mode.
cd "$topdir" banner "Rust debug build"
banner "Rust documentation" cargo build
echo "open $topdir/target/doc/cretonne/index.html"
# Make sure the code builds in release mode, and run the unit tests. We run
# these in release mode for speed, but note that the top-level Cargo.toml file
# does enable debug assertions in release builds.
banner "Rust release build and unit tests"
cargo test --all --release
# Make sure the documentation builds.
banner "Rust documentation: $topdir/target/doc/cretonne/index.html"
cargo doc cargo doc
banner "Rust release build"
cargo build --release
export CTONUTIL="$topdir/target/release/cton-util"
cd "$topdir"
banner "File tests"
"$CTONUTIL" test filetests docs
banner "OK" banner "OK"

View File

@@ -0,0 +1,34 @@
//! Run `cton-util test` on all available testcases.
use std::process::{Command, Output};
use std::env;
use std::path::PathBuf;
use std::io::{self, Write};
/// Returns the target directory, where we can find build artifacts
/// and such for the current configuration.
fn get_target_dir() -> PathBuf {
let mut path = env::current_exe().unwrap();
path.pop(); // chop off exe name
path.pop(); // chop off deps name
path
}
#[test]
fn cton_util_test() {
let mut cmd = Command::new(&get_target_dir().join("cton-util"));
cmd.arg("test");
// We have testcases in the following directories:
cmd.arg("filetests");
cmd.arg("docs");
let Output {
status,
stdout,
stderr,
} = cmd.output().unwrap();
io::stdout().write(&stdout).unwrap();
io::stderr().write(&stderr).unwrap();
assert!(status.success(), "failed with exit status {}", status);
}

View File

@@ -0,0 +1,76 @@
//! The `CFGPrinter` utility.
use std::fmt::{Result, Write, Display, Formatter};
use flowgraph::ControlFlowGraph;
use ir::Function;
use ir::instructions::BranchInfo;
/// A utility for pretty-printing the CFG of a `Function`.
pub struct CFGPrinter<'a> {
func: &'a Function,
cfg: ControlFlowGraph,
}
/// A utility for pretty-printing the CFG of a `Function`.
impl<'a> CFGPrinter<'a> {
/// Create a new CFGPrinter.
pub fn new(func: &'a Function) -> CFGPrinter<'a> {
CFGPrinter {
func,
cfg: ControlFlowGraph::with_function(func),
}
}
/// Write the CFG for this function to `w`.
pub fn write(&self, w: &mut Write) -> Result {
self.header(w)?;
self.ebb_nodes(w)?;
self.cfg_connections(w)?;
writeln!(w, "}}")
}
fn header(&self, w: &mut Write) -> Result {
writeln!(w, "digraph \"{}\" {{", self.func.name)?;
if let Some(entry) = self.func.layout.entry_block() {
writeln!(w, " {{rank=min; {}}}", entry)?;
}
Ok(())
}
fn ebb_nodes(&self, w: &mut Write) -> Result {
for ebb in &self.func.layout {
write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)?;
// Add all outgoing branch instructions to the label.
for inst in self.func.layout.ebb_insts(ebb) {
let idata = &self.func.dfg[inst];
match idata.analyze_branch(&self.func.dfg.value_lists) {
BranchInfo::SingleDest(dest, _) => {
write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)?
}
BranchInfo::Table(table) => {
write!(w, " | <{}>{} {}", inst, idata.opcode(), table)?
}
BranchInfo::NotABranch => {}
}
}
writeln!(w, "}}\"]")?
}
Ok(())
}
fn cfg_connections(&self, w: &mut Write) -> Result {
for ebb in &self.func.layout {
for (parent, inst) in self.cfg.pred_iter(ebb) {
writeln!(w, " {}:{} -> {}", parent, inst, ebb)?;
}
}
Ok(())
}
}
impl<'a> Display for CFGPrinter<'a> {
fn fmt(&self, f: &mut Formatter) -> Result {
self.write(f)
}
}

View File

@@ -19,6 +19,7 @@ pub mod entity;
pub mod bforest; pub mod bforest;
pub mod binemit; pub mod binemit;
pub mod cfg_printer;
pub mod cursor; pub mod cursor;
pub mod dominator_tree; pub mod dominator_tree;
pub mod flowgraph; pub mod flowgraph;