cargo/tests/support/mod.rs

564 lines
16 KiB
Rust
Raw Normal View History

2015-02-06 07:27:53 +00:00
use std::env;
use std::error::Error;
use std::ffi::OsStr;
2015-01-13 16:41:04 +00:00
use std::fmt;
use std::fs;
use std::io::prelude::*;
use std::os;
use std::path::{Path, PathBuf};
use std::process::Output;
2015-03-26 18:17:44 +00:00
use std::str;
use std::usize;
use url::Url;
2014-08-19 04:54:12 +00:00
use hamcrest as ham;
2015-10-28 09:20:00 +00:00
use cargo::util::ProcessBuilder;
use cargo::util::ProcessError;
2015-10-28 09:20:00 +00:00
use cargo::util::process;
use support::paths::CargoPathExt;
2014-06-12 22:51:16 +00:00
pub mod paths;
2014-09-09 14:23:09 +00:00
pub mod git;
pub mod registry;
2014-03-20 22:17:19 +00:00
/*
*
* ===== Builders =====
*
*/
2015-01-04 09:02:16 +00:00
#[derive(PartialEq,Clone)]
struct FileBuilder {
path: PathBuf,
2014-05-27 23:14:34 +00:00
body: String
}
impl FileBuilder {
pub fn new(path: PathBuf, 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_all(self.body.as_bytes())
.with_err_msg(format!("Could not write to file; path={}",
self.path.display()))
}
fn dirname(&self) -> &Path {
self.path.parent().unwrap()
}
}
2015-01-04 09:02:16 +00:00
#[derive(PartialEq,Clone)]
struct SymlinkBuilder {
dst: PathBuf,
src: PathBuf,
}
impl SymlinkBuilder {
pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
SymlinkBuilder { dst: dst, src: src }
}
#[cfg(unix)]
fn mk(&self) -> Result<(), String> {
try!(mkdir_recursive(&self.dirname()));
os::unix::fs::symlink(&self.dst, &self.src)
.with_err_msg(format!("Could not create symlink; dst={} src={}",
self.dst.display(), self.src.display()))
}
#[cfg(windows)]
fn mk(&self) -> Result<(), String> {
try!(mkdir_recursive(&self.dirname()));
os::windows::fs::symlink_file(&self.dst, &self.src)
.with_err_msg(format!("Could not create symlink; dst={} src={}",
self.dst.display(), self.src.display()))
}
fn dirname(&self) -> &Path {
self.src.parent().unwrap()
}
}
2015-01-04 09:02:16 +00:00
#[derive(PartialEq,Clone)]
2014-06-12 22:51:16 +00:00
pub struct ProjectBuilder {
2014-05-27 23:14:34 +00:00
name: String,
root: PathBuf,
files: Vec<FileBuilder>,
symlinks: Vec<SymlinkBuilder>
}
impl ProjectBuilder {
pub fn new(name: &str, root: PathBuf) -> ProjectBuilder {
ProjectBuilder {
name: name.to_string(),
root: root,
files: vec![],
symlinks: vec![]
}
}
pub fn root(&self) -> PathBuf {
self.root.clone()
}
pub fn url(&self) -> Url { path2url(self.root()) }
pub fn bin(&self, b: &str) -> PathBuf {
self.build_dir().join("debug").join(&format!("{}{}", b,
env::consts::EXE_SUFFIX))
2014-07-07 08:50:05 +00:00
}
pub fn release_bin(&self, b: &str) -> PathBuf {
self.build_dir().join("release").join(&format!("{}{}", b,
env::consts::EXE_SUFFIX))
}
pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
self.build_dir().join(target).join("debug")
.join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
}
pub fn build_dir(&self) -> PathBuf {
2014-07-07 08:50:05 +00:00
self.root.join("target")
}
pub fn process<T: AsRef<OsStr>>(&self, program: T) -> ProcessBuilder {
let mut p = ::process(program);
p.cwd(self.root());
return p
}
pub fn cargo(&self, cmd: &str) -> ProcessBuilder {
let mut p = self.process(&cargo_dir().join("cargo"));
p.arg(cmd);
return p;
2014-06-12 22:51:16 +00:00
}
pub fn cargo_process(&self, cmd: &str) -> ProcessBuilder {
2014-06-12 22:51:16 +00:00
self.build();
self.cargo(cmd)
2014-03-20 22:17:19 +00:00
}
2015-03-26 18:17:44 +00:00
pub fn file<B: AsRef<Path>>(mut self, path: B,
body: &str) -> ProjectBuilder {
self.files.push(FileBuilder::new(self.root.join(path), body));
self
}
2015-03-26 18:17:44 +00:00
pub fn symlink<T: AsRef<Path>>(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
pub fn build(&self) -> &ProjectBuilder {
match self.build_with_result() {
2014-10-30 01:59:06 +00:00
Err(e) => panic!(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.c_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> {
fs::create_dir_all(path)
.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()))
}
2015-03-26 18:17:44 +00:00
pub fn main_file(println: &str, deps: &[&str]) -> String {
2014-06-12 22:51:16 +00:00
let mut buf = String::new();
for dep in deps.iter() {
2015-03-26 18:17:44 +00:00
buf.push_str(&format!("extern crate {};\n", dep));
2014-06-12 22:51:16 +00:00
}
buf.push_str("fn main() { println!(");
2015-03-26 18:17:44 +00:00
buf.push_str(&println);
2014-06-12 22:51:16 +00:00
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>;
}
2015-01-23 18:42:29 +00:00
impl<T, E: fmt::Display> 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
pub fn cargo_dir() -> PathBuf {
2015-03-26 18:17:44 +00:00
env::var_os("CARGO_BIN_PATH").map(PathBuf::from).or_else(|| {
env::current_exe().ok().as_ref().and_then(|s| s.parent())
.map(|s| s.to_path_buf())
}).unwrap_or_else(|| {
panic!("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
2015-01-04 09:02:16 +00:00
#[derive(Clone)]
2015-03-22 23:58:11 +00:00
pub 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<i32>,
expect_stdout_contains: Vec<String>
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
}
2015-01-13 16:41:04 +00:00
pub fn with_status(mut self, expected: i32) -> Execs {
self.expect_exit_code = Some(expected);
self
}
pub fn with_stdout_contains<S: ToString>(mut self, expected: S) -> Execs {
self.expect_stdout_contains.push(expected.to_string());
self
}
fn match_output(&self, actual: &Output) -> ham::MatchResult {
self.match_status(actual)
.and(self.match_stdout(actual))
.and(self.match_stderr(actual))
}
fn match_status(&self, actual: &Output) -> ham::MatchResult {
match self.expect_exit_code {
None => ham::success(),
Some(code) => {
ham::expect(
actual.status.code() == Some(code),
format!("exited with {}\n--- stdout\n{}\n--- stderr\n{}",
actual.status,
String::from_utf8_lossy(&actual.stdout),
String::from_utf8_lossy(&actual.stderr)))
}
}
}
fn match_stdout(&self, actual: &Output) -> ham::MatchResult {
try!(self.match_std(self.expect_stdout.as_ref(), &actual.stdout,
"stdout", &actual.stderr, false));
for expect in self.expect_stdout_contains.iter() {
try!(self.match_std(Some(expect), &actual.stdout, "stdout",
&actual.stderr, true));
}
Ok(())
}
fn match_stderr(&self, actual: &Output) -> ham::MatchResult {
self.match_std(self.expect_stderr.as_ref(), &actual.stderr,
"stderr", &actual.stdout, false)
}
#[allow(deprecated)] // connect => join in 1.3
fn match_std(&self, expected: Option<&String>, actual: &[u8],
description: &str, extra: &[u8],
partial: bool) -> ham::MatchResult {
let out = match expected {
Some(out) => out,
None => return ham::success(),
};
let actual = match str::from_utf8(actual) {
Err(..) => return Err(format!("{} was not utf8 encoded",
description)),
Ok(actual) => actual,
};
// Let's not deal with \r\n vs \n on windows...
let actual = actual.replace("\r", "");
let actual = actual.replace("\t", "<tab>");
let mut a = actual.lines();
let e = out.lines();
let diffs = if partial {
let mut min = self.diff_lines(a.clone(), e.clone(), partial);
while let Some(..) = a.next() {
let a = self.diff_lines(a.clone(), e.clone(), partial);
if a.len() < min.len() {
min = a;
}
}
min
} else {
self.diff_lines(a, e, partial)
};
ham::expect(diffs.len() == 0,
format!("differences:\n\
{}\n\n\
other output:\n\
`{}`", diffs.connect("\n"),
String::from_utf8_lossy(extra)))
}
fn diff_lines<'a>(&self, actual: str::Lines<'a>, expected: str::Lines<'a>,
partial: bool) -> Vec<String> {
let actual = actual.take(if partial {
expected.clone().count()
} else {
usize::MAX
});
zip_all(actual, expected).enumerate().filter_map(|(i, (a,e))| {
match (a, e) {
(Some(a), Some(e)) => {
if lines_match(&e, &a) {
None
} else {
Some(format!("{:3} - |{}|\n + |{}|\n", i, e, a))
}
},
(Some(a), None) => {
Some(format!("{:3} -\n + |{}|\n", i, a))
},
(None, Some(e)) => {
Some(format!("{:3} - |{}|\n +\n", i, e))
},
(None, None) => panic!("Cannot get here")
}
}).collect()
}
}
fn lines_match(expected: &str, mut actual: &str) -> bool {
for part in expected.split("[..]") {
match actual.find(part) {
2015-01-25 06:03:42 +00:00
Some(i) => actual = &actual[i + part.len()..],
None => {
return false
}
2014-04-02 23:34:19 +00:00
}
}
actual.len() == 0 || expected.ends_with("[..]")
2014-04-02 23:34:19 +00:00
}
2015-02-22 23:06:12 +00:00
struct ZipAll<I1: Iterator, I2: Iterator> {
2014-07-21 19:23:01 +00:00
first: I1,
second: I2,
}
2015-02-22 23:06:12 +00:00
impl<T, I1: Iterator<Item=T>, I2: Iterator<Item=T>> Iterator for ZipAll<I1, I2> {
2015-01-04 09:02:16 +00:00
type Item = (Option<T>, Option<T>);
2014-07-21 19:23:01 +00:00
fn next(&mut self) -> Option<(Option<T>, Option<T>)> {
let first = self.first.next();
let second = self.second.next();
match (first, second) {
(None, None) => None,
(a, b) => Some((a, b))
}
}
}
2015-02-22 23:06:12 +00:00
fn zip_all<T, I1: Iterator<Item=T>, I2: Iterator<Item=T>>(a: I1, b: I2) -> ZipAll<I1, I2> {
2014-07-21 19:23:01 +00:00
ZipAll {
first: a,
2015-02-22 23:06:12 +00:00
second: b,
2014-07-21 19:23:01 +00:00
}
}
2015-01-23 18:42:29 +00:00
impl fmt::Display for Execs {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "execs")
}
2014-04-02 23:34:19 +00:00
}
impl ham::Matcher<ProcessBuilder> for Execs {
fn matches(&self, mut process: ProcessBuilder) -> ham::MatchResult {
self.matches(&mut process)
}
}
impl<'a> ham::Matcher<&'a mut ProcessBuilder> for Execs {
fn matches(&self, process: &'a mut 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) => {
let mut s = format!("could not exec process {}: {}", process, e);
match e.cause() {
2015-03-26 18:17:44 +00:00
Some(cause) => s.push_str(&format!("\ncaused by: {}",
cause.description())),
None => {}
}
Err(s)
}
}
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,
expect_stdout_contains: vec![]
}
2014-04-02 23:34:19 +00:00
}
2014-05-08 23:49:58 +00:00
2015-01-04 09:02:16 +00:00
#[derive(Clone)]
2015-03-22 23:58:11 +00:00
pub struct ShellWrites {
2014-05-28 01:33:06 +00:00
expected: String
}
2015-01-23 18:42:29 +00:00
impl fmt::Display for ShellWrites {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "`{}` written to the shell", self.expected)
2014-05-28 01:33:06 +00:00
}
}
2014-06-22 01:53:07 +00:00
impl<'a> ham::Matcher<&'a [u8]> for ShellWrites {
fn matches(&self, actual: &[u8])
-> ham::MatchResult
{
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)
}
}
2015-01-23 18:42:29 +00:00
pub fn shell_writes<T: fmt::Display>(string: T) -> ShellWrites {
2014-07-18 14:40:15 +00:00
ShellWrites { expected: string.to_string() }
2014-05-28 01:33:06 +00:00
}
pub trait Tap {
2015-01-13 16:41:04 +00:00
fn tap<F: FnOnce(&mut Self)>(mut self, callback: F) -> Self;
2014-05-28 01:33:06 +00:00
}
impl<T> Tap for T {
2015-01-13 16:41:04 +00:00
fn tap<F: FnOnce(&mut Self)>(mut self, callback: F) -> T {
2014-05-28 01:33:06 +00:00
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 fn basic_lib_manifest(name: &str) -> String {
format!(r#"
[package]
name = "{}"
version = "0.5.0"
authors = ["wycats@example.com"]
2014-08-14 06:08:02 +00:00
[lib]
name = "{}"
"#, name, name)
}
pub fn path2url(p: PathBuf) -> Url {
Url::from_file_path(&*p).ok().unwrap()
}
pub static RUNNING: &'static str = " Running";
pub static COMPILING: &'static str = " Compiling";
pub static DOCUMENTING: &'static str = " Documenting";
pub static FRESH: &'static str = " Fresh";
pub static UPDATING: &'static str = " Updating";
pub static ADDING: &'static str = " Adding";
pub static REMOVING: &'static str = " Removing";
pub static DOCTEST: &'static str = " Doc-tests";
pub static PACKAGING: &'static str = " Packaging";
pub static DOWNLOADING: &'static str = " Downloading";
2014-09-09 14:23:09 +00:00
pub static UPLOADING: &'static str = " Uploading";
pub static VERIFYING: &'static str = " Verifying";
pub static ARCHIVING: &'static str = " Archiving";
pub static INSTALLING: &'static str = " Installing";