// use std::io::fs::{mkdir_recursive,rmdir_recursive}; use std::io; use std::io::fs; use std::io::process::{ProcessOutput,ProcessExit}; use std::os; use std::path::{Path,BytesContainer}; use std::str; use std::vec::Vec; use std::fmt::Show; use ham = hamcrest; use cargo::util::{process,ProcessBuilder}; static CARGO_INTEGRATION_TEST_DIR : &'static str = "cargo-integration-tests"; /* * * ===== Builders ===== * */ #[deriving(Eq,Clone)] struct FileBuilder { path: Path, body: ~str } impl FileBuilder { pub fn new(path: Path, body: &str) -> FileBuilder { FileBuilder { path: path, body: body.to_owned() } } fn mk(&self) -> Result<(), ~str> { try!(mkdir_recursive(&self.dirname())); let mut file = try!( fs::File::create(&self.path) .with_err_msg(format!("Could not create file; path={}", self.path.display()))); file.write_str(self.body.as_slice()) .with_err_msg(format!("Could not write to file; path={}", self.path.display())) } fn dirname(&self) -> Path { Path::new(self.path.dirname()) } } #[deriving(Eq,Clone)] struct ProjectBuilder { name: ~str, root: Path, files: Vec } impl ProjectBuilder { pub fn new(name: &str, root: Path) -> ProjectBuilder { ProjectBuilder { name: name.to_owned(), root: root, files: vec!() } } pub fn root(&self) -> Path { self.root.clone() } pub fn cargo_process(&self, program: &str) -> ProcessBuilder { self.build(); process(program) .cwd(self.root()) .extra_path(cargo_dir()) } pub fn file(mut self, path: B, body: &str) -> ProjectBuilder { self.files.push(FileBuilder::new(self.root.join(path), body)); self } // TODO: return something different than a ProjectBuilder pub fn build<'a>(&'a self) -> &'a ProjectBuilder { match self.build_with_result() { Err(e) => fail!(e), _ => return self } } pub fn build_with_result(&self) -> Result<(), ~str> { // First, clean the directory if it already exists try!(self.rm_root()); // Create the empty directory try!(mkdir_recursive(&self.root)); for file in self.files.iter() { try!(file.mk()); } Ok(()) } fn rm_root(&self) -> Result<(), ~str> { if self.root.exists() { rmdir_recursive(&self.root) } else { Ok(()) } } } // Generates a project layout pub fn project(name: &str) -> ProjectBuilder { ProjectBuilder::new(name, os::tmpdir().join(CARGO_INTEGRATION_TEST_DIR)) } // === Helpers === pub fn mkdir_recursive(path: &Path) -> Result<(), ~str> { fs::mkdir_recursive(path, io::UserDir) .with_err_msg(format!("could not create directory; path={}", path.display())) } pub fn rmdir_recursive(path: &Path) -> Result<(), ~str> { fs::rmdir_recursive(path) .with_err_msg(format!("could not rm directory; path={}", path.display())) } trait ErrMsg { fn with_err_msg(self, val: ~str) -> Result; } impl ErrMsg for Result { fn with_err_msg(self, val: ~str) -> Result { match self { Ok(val) => Ok(val), Err(err) => Err(format!("{}; original={}", val, err)) } } } // Path to cargo executables pub fn cargo_dir() -> Path { os::getenv("CARGO_BIN_PATH") .map(|s| Path::new(s)) .unwrap_or_else(|| fail!("CARGO_BIN_PATH wasn't set. Cannot continue running test")) } /* * * ===== Matchers ===== * */ #[deriving(Clone,Eq)] struct Execs { expect_stdout: Option<~str>, expect_stdin: Option<~str>, expect_stderr: Option<~str>, expect_exit_code: Option } impl Execs { pub fn with_stdout(mut ~self, expected: &str) -> Box { self.expect_stdout = Some(expected.to_owned()); self } pub fn with_stderr(mut ~self, expected: &str) -> Box { self.expect_stderr = Some(expected.to_owned()); self } pub fn with_status(mut ~self, expected: int) -> Box { self.expect_exit_code = Some(expected); self } fn match_output(&self, actual: &ProcessOutput) -> ham::MatchResult { self.match_status(actual.status) .and(self.match_stdout(&actual.output)) .and(self.match_stderr(&actual.error)) } fn match_status(&self, actual: ProcessExit) -> ham::MatchResult { match self.expect_exit_code { None => ham::success(), Some(code) => { ham::expect( actual.matches_exit_status(code), format!("exited with {}", actual)) } } } fn match_stdout(&self, actual: &Vec) -> ham::MatchResult { self.match_std(&self.expect_stdout, actual, "stdout") } fn match_stderr(&self, actual: &Vec) -> ham::MatchResult { self.match_std(&self.expect_stderr, actual, "stderr") } fn match_std(&self, expected: &Option<~str>, actual: &Vec, description: &str) -> ham::MatchResult { match expected.as_ref().map(|s| s.as_slice()) { None => ham::success(), Some(out) => { match str::from_utf8(actual.as_slice()) { None => Err(format!("{} was not utf8 encoded", description)), Some(actual) => { ham::expect(actual == out, format!("{} was `{}`", description, actual)) } } } } } } impl ham::SelfDescribing for Execs { fn describe(&self) -> ~str { "execs".to_owned() } } impl ham::Matcher for Execs { fn matches(&self, process: ProcessBuilder) -> ham::MatchResult { let res = process.exec_with_output(); match res { Ok(out) => self.match_output(&out), Err(_) => Err(format!("could not exec process {}", process)) } } } pub fn execs() -> Box { box Execs { expect_stdout: None, expect_stderr: None, expect_stdin: None, expect_exit_code: None } } pub trait ResultTest { fn assert(self) -> T; } impl ResultTest for Result { fn assert(self) -> T { match self { Ok(val) => val, Err(err) => fail!("Result was error: {}", err) } } }