From ddadf2e7c1f71d0641f1179adc6e67bcd12687bc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Sep 2016 12:20:41 -0700 Subject: [PATCH] Add scaffolding for a 'cton-util test' command. This command accepts files and directories containing test cases to run. Recursively searches for test files in any directory it is passed. Actually running tests is not yet implemented. --- cranelift/src/tools/filetest/mod.rs | 33 ++++++++ cranelift/src/tools/filetest/runner.rs | 112 +++++++++++++++++++++++++ cranelift/src/tools/main.rs | 8 +- 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 cranelift/src/tools/filetest/mod.rs create mode 100644 cranelift/src/tools/filetest/runner.rs diff --git a/cranelift/src/tools/filetest/mod.rs b/cranelift/src/tools/filetest/mod.rs new file mode 100644 index 0000000000..078e287dde --- /dev/null +++ b/cranelift/src/tools/filetest/mod.rs @@ -0,0 +1,33 @@ +//! File tests. +//! +//! This module contains the main driver for `cton-util test` as well as implementations of the +//! available test commands. + +use std::path::Path; +use CommandResult; +use filetest::runner::TestRunner; + +mod runner; + +/// Main entry point for `cton-util test`. +/// +/// Take a list of filenames which can be either `.cton` files or directories. +/// +/// Files are interpreted as test cases and executed immediately. +/// +/// Directories are scanned recursively for test cases ending in `.cton`. These test cases are +/// executed on background threads. +/// +pub fn run(files: Vec) -> CommandResult { + let mut runner = TestRunner::new(); + + for path in files.iter().map(Path::new) { + if path.is_file() { + runner.push_test(path); + } else { + runner.push_dir(path); + } + } + + runner.run() +} diff --git a/cranelift/src/tools/filetest/runner.rs b/cranelift/src/tools/filetest/runner.rs new file mode 100644 index 0000000000..7f39ec6490 --- /dev/null +++ b/cranelift/src/tools/filetest/runner.rs @@ -0,0 +1,112 @@ +//! Test runner. +//! +//! This module implements the `TestRunner` struct which manages executing tests as well as +//! scanning directories for tests. + +use std::ffi::OsStr; +use std::path::PathBuf; +use std::error::Error; +use CommandResult; + +pub struct TestRunner { + // Directories that have not yet been scanned. + dir_stack: Vec, + + // Filenames of tests to run. + test_list: Vec, + + errors: usize, +} + +impl TestRunner { + /// Create a new blank TrstRunner. + pub fn new() -> TestRunner { + TestRunner { + dir_stack: Vec::new(), + test_list: Vec::new(), + errors: 0, + } + } + + /// Add a directory path to be scanned later. + /// + /// If `dir` turns out to be a regular file, it is silently ignored. + /// Otherwise, any problems reading the directory are reported. + pub fn push_dir>(&mut self, dir: P) { + self.dir_stack.push(dir.into()); + } + + /// Add a test to be executed later. + /// + /// Any problems reading `file` as a test case file will be reported as a test failure. + pub fn push_test>(&mut self, file: P) { + self.test_list.push(file.into()); + } + + /// Scan any directories pushed so far. + /// Push any potential test cases found. + pub fn scan_dirs(&mut self) { + // This recursive search tries to minimize statting in a directory hierarchy containing + // mostly test cases. + // + // - Directory entries with a "cton" extension are presumed to be test case files. + // - Directory entries with no extension are presumed to be subdirectories. + // - Anything else is ignored. + // + while let Some(dir) = self.dir_stack.pop() { + match dir.read_dir() { + Err(err) => { + // Fail silently if `dir` was actually a regular file. + // This lets us skip spurious extensionless files without statting everything + // needlessly. + if !dir.is_file() { + self.path_error(dir, err); + } + } + Ok(entries) => { + // Read all directory entries. Avoid statting. + for entry_result in entries { + match entry_result { + Err(err) => { + // Not sure why this would happen. `read_dir` succeeds, but there's + // a problem with an entry. I/O error during a getdirentries + // syscall seems to be the reason. The implementation in + // libstd/sys/unix/fs.rs seems to suggest that breaking now would + // be a good idea, or the iterator could keep returning the same + // error forever. + self.path_error(dir, err); + break; + } + Ok(entry) => { + let path = entry.path(); + // Recognize directories and tests by extension. + // Yes, this means we ignore directories with '.' in their name. + match path.extension().and_then(OsStr::to_str) { + Some("cton") => self.push_test(path), + Some(_) => {} + None => self.push_dir(path), + } + } + } + } + } + } + } + } + + /// Report an error related to a path. + fn path_error(&mut self, path: PathBuf, err: E) { + self.errors += 1; + println!("path-error {}: {}", path.to_string_lossy(), err); + } + + /// Scan pushed directories for tests and run them. + pub fn run(&mut self) -> CommandResult { + self.scan_dirs(); + match self.errors { + 0 => Ok(()), + 1 => Err("1 failure".to_string()), + n => Err(format!("{} failures", n)), + } + } +} diff --git a/cranelift/src/tools/main.rs b/cranelift/src/tools/main.rs index bd0999cd4e..5abfa89462 100644 --- a/cranelift/src/tools/main.rs +++ b/cranelift/src/tools/main.rs @@ -10,7 +10,7 @@ use docopt::Docopt; use std::io::{self, Write}; use std::process; - +mod filetest; mod cat; mod print_cfg; mod rsfilecheck; @@ -19,6 +19,7 @@ const USAGE: &'static str = " Cretonne code generator utility Usage: + cton-util test ... cton-util cat ... cton-util filecheck [-v] cton-util print-cfg ... @@ -33,6 +34,7 @@ Options: #[derive(RustcDecodable, Debug)] struct Args { + cmd_test: bool, cmd_cat: bool, cmd_filecheck: bool, cmd_print_cfg: bool, @@ -55,7 +57,9 @@ fn cton_util() -> CommandResult { .unwrap_or_else(|e| e.exit()); // Find the sub-command to execute. - if args.cmd_cat { + if args.cmd_test { + filetest::run(args.arg_file) + } else if args.cmd_cat { cat::run(args.arg_file) } else if args.cmd_filecheck { rsfilecheck::run(args.arg_file, args.flag_verbose)