cargo/tests/support/mod.rs

420 lines
11 KiB
Rust
Raw Normal View History

2014-05-08 20:10:08 +00:00
use std::io;
use std::io::fs;
use std::io::process::{ProcessOutput};
2014-03-20 22:17:19 +00:00
use std::os;
2014-05-08 21:12:38 +00:00
use std::path::{Path,BytesContainer};
2014-04-02 23:34:19 +00:00
use std::str;
2014-04-09 20:28:10 +00:00
use std::vec::Vec;
2014-05-08 23:49:58 +00:00
use std::fmt::Show;
2014-04-02 23:34:19 +00:00
use ham = hamcrest;
2014-06-19 08:21:24 +00:00
use cargo::util::{process,ProcessBuilder};
use cargo::util::ProcessError;
use support::paths::PathExt;
2014-06-12 22:51:16 +00:00
pub mod paths;
2014-03-20 22:17:19 +00:00
/*
*
* ===== Builders =====
*
*/
2014-06-09 20:08:09 +00:00
#[deriving(PartialEq,Clone)]
struct FileBuilder {
path: Path,
2014-05-27 23:14:34 +00:00
body: String
}
impl FileBuilder {
pub fn new(path: Path, body: &str) -> FileBuilder {
FileBuilder { path: path, body: body.to_string() }
}
2014-05-27 23:14:34 +00:00
fn mk(&self) -> Result<(), String> {
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(PartialEq,Clone)]
struct SymlinkBuilder {
dst: Path,
src: Path
}
impl SymlinkBuilder {
pub fn new(dst: Path, src: Path) -> SymlinkBuilder {
SymlinkBuilder { dst: dst, src: src }
}
fn mk(&self) -> Result<(), String> {
try!(mkdir_recursive(&self.dirname()));
fs::symlink(&self.dst, &self.src)
.with_err_msg(format!("Could not create symlink; dst={} src={}",
self.dst.display(), self.src.display()))
}
fn dirname(&self) -> Path {
Path::new(self.src.dirname())
}
}
2014-06-09 20:08:09 +00:00
#[deriving(PartialEq,Clone)]
2014-06-12 22:51:16 +00:00
pub struct ProjectBuilder {
2014-05-27 23:14:34 +00:00
name: String,
root: Path,
files: Vec<FileBuilder>,
symlinks: Vec<SymlinkBuilder>
}
impl ProjectBuilder {
pub fn new(name: &str, root: Path) -> ProjectBuilder {
ProjectBuilder {
name: name.to_string(),
root: root,
files: vec!(),
symlinks: vec!()
}
}
pub fn root(&self) -> Path {
self.root.clone()
}
pub fn bin(&self, b: &str) -> Path {
2014-07-07 08:50:05 +00:00
self.build_dir().join(format!("{}{}", b, os::consts::EXE_SUFFIX))
}
pub fn target_bin(&self, target: &str, b: &str) -> Path {
self.build_dir().join(target).join(format!("{}{}", b,
os::consts::EXE_SUFFIX))
}
2014-07-07 08:50:05 +00:00
pub fn build_dir(&self) -> Path {
self.root.join("target")
}
pub fn process<T: ToCStr>(&self, program: T) -> ProcessBuilder {
2014-05-09 00:50:28 +00:00
process(program)
.cwd(self.root())
.env("HOME", Some(paths::home().display().to_string().as_slice()))
.extra_path(cargo_dir())
2014-06-12 22:51:16 +00:00
}
pub fn cargo_process(&self, program: &str) -> ProcessBuilder {
self.build();
self.process(cargo_dir().join(program))
2014-03-20 22:17:19 +00:00
}
pub fn file<B: BytesContainer, S: Str>(mut self, path: B,
body: S) -> ProjectBuilder {
2014-06-12 22:51:16 +00:00
self.files.push(FileBuilder::new(self.root.join(path), body.as_slice()));
self
}
pub fn symlink<T: BytesContainer>(mut self, dst: T,
src: T) -> ProjectBuilder {
self.symlinks.push(SymlinkBuilder::new(self.root.join(dst),
self.root.join(src)));
self
}
// TODO: return something different than a ProjectBuilder
2014-05-09 00:50:28 +00:00
pub fn build<'a>(&'a self) -> &'a ProjectBuilder {
match self.build_with_result() {
Err(e) => fail!(e),
_ => return self
}
}
2014-05-27 23:14:34 +00:00
pub fn build_with_result(&self) -> Result<(), String> {
// 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());
}
for symlink in self.symlinks.iter() {
try!(symlink.mk());
}
Ok(())
}
2014-05-27 23:14:34 +00:00
fn rm_root(&self) -> Result<(), String> {
if self.root.exists() {
rmdir_recursive(&self.root)
} else {
Ok(())
}
}
}
// Generates a project layout
pub fn project(name: &str) -> ProjectBuilder {
2014-06-12 22:51:16 +00:00
ProjectBuilder::new(name, paths::root().join(name))
}
// === Helpers ===
2014-05-27 23:14:34 +00:00
pub fn mkdir_recursive(path: &Path) -> Result<(), String> {
2014-05-08 20:10:08 +00:00
fs::mkdir_recursive(path, io::UserDir)
.with_err_msg(format!("could not create directory; path={}",
path.display()))
}
2014-05-27 23:14:34 +00:00
pub fn rmdir_recursive(path: &Path) -> Result<(), String> {
path.rm_rf()
.with_err_msg(format!("could not rm directory; path={}",
path.display()))
}
2014-06-12 22:51:16 +00:00
pub fn main_file<T: Str>(println: T, deps: &[&str]) -> String {
let mut buf = String::new();
for dep in deps.iter() {
buf.push_str(format!("extern crate {};\n", dep).as_slice());
}
buf.push_str("fn main() { println!(");
buf.push_str(println.as_slice());
buf.push_str("); }\n");
buf.to_string()
2014-06-12 22:51:16 +00:00
}
trait ErrMsg<T> {
2014-05-27 23:14:34 +00:00
fn with_err_msg(self, val: String) -> Result<T, String>;
}
2014-05-08 23:49:58 +00:00
impl<T, E: Show> ErrMsg<T> for Result<T, E> {
2014-05-27 23:14:34 +00:00
fn with_err_msg(self, val: String) -> Result<T, String> {
match self {
Ok(val) => Ok(val),
2014-05-08 23:49:58 +00:00
Err(err) => Err(format!("{}; original={}", val, err))
}
}
}
2014-03-20 22:17:19 +00:00
// Path to cargo executables
2014-04-02 23:34:19 +00:00
pub fn cargo_dir() -> Path {
os::getenv("CARGO_BIN_PATH").map(Path::new)
.or_else(|| os::self_exe_path().map(|p| p.dir_path()))
.unwrap_or_else(|| {
fail!("CARGO_BIN_PATH wasn't set. Cannot continue running test")
})
2014-03-20 22:17:19 +00:00
}
2014-06-09 20:08:09 +00:00
/// Returns an absolute path in the filesystem that `path` points to. The
/// returned path does not contain any symlinks in its hierarchy.
2014-03-20 22:17:19 +00:00
/*
*
* ===== Matchers =====
*
*/
2014-04-02 23:34:19 +00:00
2014-06-09 20:08:09 +00:00
#[deriving(Clone)]
2014-04-02 23:34:19 +00:00
struct Execs {
2014-05-27 23:14:34 +00:00
expect_stdout: Option<String>,
expect_stdin: Option<String>,
expect_stderr: Option<String>,
expect_exit_code: Option<int>
2014-04-02 23:34:19 +00:00
}
impl Execs {
2014-07-18 14:40:15 +00:00
pub fn with_stdout<S: ToString>(mut self, expected: S) -> Execs {
self.expect_stdout = Some(expected.to_string());
self
}
2014-07-18 14:40:15 +00:00
pub fn with_stderr<S: ToString>(mut self, expected: S) -> Execs {
self.expect_stderr = Some(expected.to_string());
self
2014-04-02 23:34:19 +00:00
}
2014-07-18 14:40:15 +00:00
pub fn with_status(mut self, expected: int) -> Execs {
self.expect_exit_code = Some(expected);
self
}
fn match_output(&self, actual: &ProcessOutput) -> ham::MatchResult {
self.match_status(actual)
.and(self.match_stdout(actual))
.and(self.match_stderr(actual))
}
fn match_status(&self, actual: &ProcessOutput) -> ham::MatchResult {
match self.expect_exit_code {
None => ham::success(),
Some(code) => {
ham::expect(
actual.status.matches_exit_status(code),
format!("exited with {}\n--- stdout\n{}\n--- stderr\n{}",
actual.status,
str::from_utf8(actual.output.as_slice()),
str::from_utf8(actual.error.as_slice())))
}
}
}
fn match_stdout(&self, actual: &ProcessOutput) -> ham::MatchResult {
self.match_std(self.expect_stdout.as_ref(), actual.output.as_slice(),
"stdout", actual.error.as_slice())
}
fn match_stderr(&self, actual: &ProcessOutput) -> ham::MatchResult {
self.match_std(self.expect_stderr.as_ref(), actual.error.as_slice(),
"stderr", actual.output.as_slice())
}
fn match_std(&self, expected: Option<&String>, actual: &[u8],
description: &str, extra: &[u8]) -> ham::MatchResult {
match expected.as_ref().map(|s| s.as_slice()) {
None => ham::success(),
Some(out) => {
match str::from_utf8(actual) {
None => Err(format!("{} was not utf8 encoded", description)),
Some(actual) => {
// Let's not deal with \r\n vs \n on windows...
let actual = actual.replace("\r", "");
ham::expect(actual.as_slice() == out,
format!("{} was:\n\
`{}`\n\n\
expected:\n\
`{}`\n\n\
other output:\n\
`{}`", description, actual, out,
String::from_utf8_lossy(extra)))
}
}
}
2014-04-02 23:34:19 +00:00
}
}
}
impl ham::SelfDescribing for Execs {
fn describe(&self) -> String {
"execs".to_string()
}
2014-04-02 23:34:19 +00:00
}
impl ham::Matcher<ProcessBuilder> for Execs {
fn matches(&self, process: ProcessBuilder) -> ham::MatchResult {
let res = process.exec_with_output();
match res {
Ok(out) => self.match_output(&out),
Err(ProcessError { output: Some(ref out), .. }) => {
self.match_output(out)
}
Err(e) => Err(format!("could not exec process {}: {}", process, e))
}
2014-04-02 23:34:19 +00:00
}
}
2014-07-18 14:40:15 +00:00
pub fn execs() -> Execs {
Execs {
expect_stdout: None,
expect_stderr: None,
expect_stdin: None,
expect_exit_code: None
}
2014-04-02 23:34:19 +00:00
}
2014-05-08 23:49:58 +00:00
2014-06-09 20:08:09 +00:00
#[deriving(Clone)]
2014-05-28 01:33:06 +00:00
struct ShellWrites {
expected: String
}
impl ham::SelfDescribing for ShellWrites {
fn describe(&self) -> String {
format!("`{}` written to the shell", self.expected)
}
}
2014-06-22 01:53:07 +00:00
impl<'a> ham::Matcher<&'a [u8]> for ShellWrites {
fn matches(&self, actual: &[u8])
-> ham::MatchResult
{
2014-06-22 01:53:07 +00:00
println!("{}", actual);
let actual = String::from_utf8_lossy(actual);
let actual = actual.to_string();
2014-05-28 01:33:06 +00:00
ham::expect(actual == self.expected, actual)
}
}
2014-07-18 14:40:15 +00:00
pub fn shell_writes<T: Show>(string: T) -> ShellWrites {
ShellWrites { expected: string.to_string() }
2014-05-28 01:33:06 +00:00
}
2014-05-08 23:49:58 +00:00
pub trait ResultTest<T,E> {
fn assert(self) -> T;
}
impl<T,E: Show> ResultTest<T,E> for Result<T,E> {
fn assert(self) -> T {
match self {
Ok(val) => val,
Err(err) => fail!("Result was error: {}", err)
}
}
}
2014-05-28 01:33:06 +00:00
impl<T> ResultTest<T,()> for Option<T> {
fn assert(self) -> T {
match self {
Some(val) => val,
None => fail!("Option was None")
}
}
}
pub trait Tap {
fn tap(mut self, callback: |&mut Self|) -> Self;
}
impl<T> Tap for T {
fn tap(mut self, callback: |&mut T|) -> T {
callback(&mut self);
self
}
}
2014-06-26 22:14:31 +00:00
pub fn basic_bin_manifest(name: &str) -> String {
format!(r#"
2014-06-27 05:53:05 +00:00
[package]
2014-06-26 22:14:31 +00:00
name = "{}"
version = "0.5.0"
authors = ["wycats@example.com"]
[[bin]]
name = "{}"
"#, name, name)
}
pub static RUNNING: &'static str = " Running";
2014-06-26 22:14:31 +00:00
pub static COMPILING: &'static str = " Compiling";
pub static FRESH: &'static str = " Fresh";
pub static UPDATING: &'static str = " Updating";