2015-02-06 07:27:53 +00:00
|
|
|
use std::env;
|
2015-02-27 01:04:25 +00:00
|
|
|
use std::error::Error;
|
2015-04-02 18:12:21 +00:00
|
|
|
use std::ffi::OsStr;
|
2015-01-13 16:41:04 +00:00
|
|
|
use std::fmt;
|
2015-02-27 01:04:25 +00:00
|
|
|
use std::fs;
|
|
|
|
use std::io::prelude::*;
|
2015-05-13 04:22:14 +00:00
|
|
|
use std::os;
|
2015-02-27 01:04:25 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::process::Output;
|
2015-03-26 18:17:44 +00:00
|
|
|
use std::str;
|
2015-10-01 00:58:08 +00:00
|
|
|
use std::usize;
|
2014-12-21 23:19:44 +00:00
|
|
|
|
2016-01-24 21:16:33 +00:00
|
|
|
use rustc_serialize::json::Json;
|
2014-08-06 16:21:10 +00:00
|
|
|
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;
|
2014-06-19 07:55:17 +00:00
|
|
|
use cargo::util::ProcessError;
|
2014-03-19 01:10:48 +00:00
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
use support::paths::CargoPathExt;
|
2014-06-27 21:06:50 +00:00
|
|
|
|
2016-05-26 00:06:25 +00:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! t {
|
|
|
|
($e:expr) => (match $e {
|
|
|
|
Ok(e) => e,
|
|
|
|
Err(e) => panic!("{} failed with {}", stringify!($e), e),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2014-06-12 22:51:16 +00:00
|
|
|
pub mod paths;
|
2014-09-09 14:23:09 +00:00
|
|
|
pub mod git;
|
2014-10-23 05:05:30 +00:00
|
|
|
pub mod registry;
|
2014-03-19 01:10:48 +00:00
|
|
|
|
2014-03-20 22:17:19 +00:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* ===== Builders =====
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2015-01-04 09:02:16 +00:00
|
|
|
#[derive(PartialEq,Clone)]
|
2014-03-19 01:10:48 +00:00
|
|
|
struct FileBuilder {
|
2015-02-27 01:04:25 +00:00
|
|
|
path: PathBuf,
|
2014-05-27 23:14:34 +00:00
|
|
|
body: String
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl FileBuilder {
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn new(path: PathBuf, body: &str) -> FileBuilder {
|
2014-07-09 13:38:10 +00:00
|
|
|
FileBuilder { path: path, body: body.to_string() }
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
2016-05-26 00:06:25 +00:00
|
|
|
fn mk(&self) {
|
|
|
|
self.dirname().mkdir_p();
|
2014-03-19 01:10:48 +00:00
|
|
|
|
2016-05-26 00:06:25 +00:00
|
|
|
let mut file = fs::File::create(&self.path).unwrap_or_else(|e| {
|
|
|
|
panic!("could not create file {}: {}", self.path.display(), e)
|
|
|
|
});
|
2014-03-19 01:10:48 +00:00
|
|
|
|
2016-05-26 00:06:25 +00:00
|
|
|
t!(file.write_all(self.body.as_bytes()));
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
fn dirname(&self) -> &Path {
|
|
|
|
self.path.parent().unwrap()
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-04 09:02:16 +00:00
|
|
|
#[derive(PartialEq,Clone)]
|
2014-07-07 21:46:03 +00:00
|
|
|
struct SymlinkBuilder {
|
2015-02-27 01:04:25 +00:00
|
|
|
dst: PathBuf,
|
|
|
|
src: PathBuf,
|
2014-07-07 21:46:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SymlinkBuilder {
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
|
2014-07-07 21:46:03 +00:00
|
|
|
SymlinkBuilder { dst: dst, src: src }
|
|
|
|
}
|
|
|
|
|
2015-05-13 04:22:14 +00:00
|
|
|
#[cfg(unix)]
|
2016-05-26 00:06:25 +00:00
|
|
|
fn mk(&self) {
|
|
|
|
self.dirname().mkdir_p();
|
|
|
|
t!(os::unix::fs::symlink(&self.dst, &self.src));
|
2015-05-13 04:22:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
2016-05-26 00:06:25 +00:00
|
|
|
fn mk(&self) {
|
|
|
|
self.dirname().mkdir_p();
|
|
|
|
t!(os::windows::fs::symlink_file(&self.dst, &self.src));
|
2014-07-07 21:46:03 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
fn dirname(&self) -> &Path {
|
|
|
|
self.src.parent().unwrap()
|
2014-07-07 21:46:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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,
|
2015-02-27 01:04:25 +00:00
|
|
|
root: PathBuf,
|
2014-07-07 21:46:03 +00:00
|
|
|
files: Vec<FileBuilder>,
|
|
|
|
symlinks: Vec<SymlinkBuilder>
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ProjectBuilder {
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn new(name: &str, root: PathBuf) -> ProjectBuilder {
|
2014-03-19 01:10:48 +00:00
|
|
|
ProjectBuilder {
|
2014-07-09 13:38:10 +00:00
|
|
|
name: name.to_string(),
|
2014-03-19 01:10:48 +00:00
|
|
|
root: root,
|
2015-11-23 16:56:10 +00:00
|
|
|
files: vec![],
|
|
|
|
symlinks: vec![]
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn root(&self) -> PathBuf {
|
2014-06-25 05:06:11 +00:00
|
|
|
self.root.clone()
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
2014-08-06 16:21:10 +00:00
|
|
|
pub fn url(&self) -> Url { path2url(self.root()) }
|
|
|
|
|
2017-01-19 20:43:56 +00:00
|
|
|
pub fn target_debug_dir(&self) -> PathBuf {
|
|
|
|
self.build_dir().join("debug")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
|
|
|
|
let prefix = ProjectBuilder::get_lib_prefix(kind);
|
|
|
|
|
|
|
|
let extension = ProjectBuilder::get_lib_extension(kind);
|
|
|
|
|
|
|
|
let lib_file_name = format!("{}{}.{}",
|
|
|
|
prefix,
|
|
|
|
name,
|
|
|
|
extension);
|
|
|
|
|
|
|
|
self.target_debug_dir()
|
|
|
|
.join("examples")
|
|
|
|
.join(&lib_file_name)
|
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn bin(&self, b: &str) -> PathBuf {
|
2015-03-03 21:46:02 +00:00
|
|
|
self.build_dir().join("debug").join(&format!("{}{}", b,
|
|
|
|
env::consts::EXE_SUFFIX))
|
2014-07-07 08:50:05 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn release_bin(&self, b: &str) -> PathBuf {
|
|
|
|
self.build_dir().join("release").join(&format!("{}{}", b,
|
|
|
|
env::consts::EXE_SUFFIX))
|
2014-08-25 14:51:13 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
|
2015-03-03 21:46:02 +00:00
|
|
|
self.build_dir().join(target).join("debug")
|
|
|
|
.join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
|
2014-07-11 16:08:51 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn build_dir(&self) -> PathBuf {
|
2014-07-07 08:50:05 +00:00
|
|
|
self.root.join("target")
|
2014-06-25 05:06:11 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 18:12:21 +00:00
|
|
|
pub fn process<T: AsRef<OsStr>>(&self, program: T) -> ProcessBuilder {
|
2015-12-17 17:53:14 +00:00
|
|
|
let mut p = ::process(program);
|
|
|
|
p.cwd(self.root());
|
|
|
|
return p
|
2015-02-27 01:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-08-21 16:24:34 +00:00
|
|
|
pub fn cargo_process(&self, cmd: &str) -> ProcessBuilder {
|
2014-06-12 22:51:16 +00:00
|
|
|
self.build();
|
2015-02-27 01:04:25 +00:00
|
|
|
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 {
|
2015-02-27 01:04:25 +00:00
|
|
|
self.files.push(FileBuilder::new(self.root.join(path), body));
|
2014-03-19 01:10:48 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2015-03-26 18:17:44 +00:00
|
|
|
pub fn symlink<T: AsRef<Path>>(mut self, dst: T,
|
|
|
|
src: T) -> ProjectBuilder {
|
2014-07-07 21:46:03 +00:00
|
|
|
self.symlinks.push(SymlinkBuilder::new(self.root.join(dst),
|
|
|
|
self.root.join(src)));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2014-03-19 01:10:48 +00:00
|
|
|
// TODO: return something different than a ProjectBuilder
|
2014-07-21 21:36:08 +00:00
|
|
|
pub fn build(&self) -> &ProjectBuilder {
|
2014-03-19 01:10:48 +00:00
|
|
|
// First, clean the directory if it already exists
|
2016-05-26 00:06:25 +00:00
|
|
|
self.rm_root();
|
2014-03-19 01:10:48 +00:00
|
|
|
|
|
|
|
// Create the empty directory
|
2016-05-26 00:06:25 +00:00
|
|
|
self.root.mkdir_p();
|
2014-03-19 01:10:48 +00:00
|
|
|
|
|
|
|
for file in self.files.iter() {
|
2016-05-26 00:06:25 +00:00
|
|
|
file.mk();
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
2014-07-07 21:46:03 +00:00
|
|
|
for symlink in self.symlinks.iter() {
|
2016-05-26 00:06:25 +00:00
|
|
|
symlink.mk();
|
2014-07-07 21:46:03 +00:00
|
|
|
}
|
2016-05-26 00:06:25 +00:00
|
|
|
self
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
2016-08-25 08:34:25 +00:00
|
|
|
pub fn read_lockfile(&self) -> String {
|
|
|
|
let mut buffer = String::new();
|
|
|
|
fs::File::open(self.root().join("Cargo.lock")).unwrap()
|
|
|
|
.read_to_string(&mut buffer).unwrap();
|
|
|
|
buffer
|
|
|
|
}
|
|
|
|
|
2017-01-19 20:43:56 +00:00
|
|
|
fn get_lib_prefix(kind: &str) -> &str {
|
|
|
|
match kind {
|
|
|
|
"lib" | "rlib" => "lib",
|
|
|
|
"staticlib" | "dylib" | "proc-macro" => {
|
|
|
|
if cfg!(windows) {
|
|
|
|
""
|
|
|
|
} else {
|
|
|
|
"lib"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_lib_extension(kind: &str) -> &str {
|
|
|
|
match kind {
|
|
|
|
"lib" | "rlib" => "rlib",
|
|
|
|
"staticlib" => {
|
|
|
|
if cfg!(windows) {
|
|
|
|
"lib"
|
|
|
|
} else {
|
|
|
|
"a"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"dylib" | "proc-macro" => {
|
|
|
|
if cfg!(windows) {
|
|
|
|
"dll"
|
|
|
|
} else if cfg!(target_os="macos") {
|
|
|
|
"dylib"
|
|
|
|
} else {
|
|
|
|
"so"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-26 00:06:25 +00:00
|
|
|
fn rm_root(&self) {
|
|
|
|
self.root.rm_rf()
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
2016-09-08 19:13:31 +00:00
|
|
|
// Generates a project layout inside our fake home dir
|
|
|
|
pub fn project_in_home(name: &str) -> ProjectBuilder {
|
|
|
|
ProjectBuilder::new(name, paths::home().join(name))
|
|
|
|
}
|
|
|
|
|
2014-03-19 01:10:48 +00:00
|
|
|
// === Helpers ===
|
|
|
|
|
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");
|
|
|
|
|
2014-07-09 13:38:10 +00:00
|
|
|
buf.to_string()
|
2014-06-12 22:51:16 +00:00
|
|
|
}
|
|
|
|
|
2014-03-19 01:10:48 +00:00
|
|
|
trait ErrMsg<T> {
|
2014-05-27 23:14:34 +00:00
|
|
|
fn with_err_msg(self, val: String) -> Result<T, String>;
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
|
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> {
|
2014-03-19 01:10:48 +00:00
|
|
|
match self {
|
|
|
|
Ok(val) => Ok(val),
|
2014-05-08 23:49:58 +00:00
|
|
|
Err(err) => Err(format!("{}; original={}", val, err))
|
2014-03-19 01:10:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-03-20 22:17:19 +00:00
|
|
|
|
|
|
|
// Path to cargo executables
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn cargo_dir() -> PathBuf {
|
2015-03-26 18:17:44 +00:00
|
|
|
env::var_os("CARGO_BIN_PATH").map(PathBuf::from).or_else(|| {
|
2016-11-17 20:21:13 +00:00
|
|
|
env::current_exe().ok().map(|mut path| {
|
|
|
|
path.pop();
|
|
|
|
if path.ends_with("deps") {
|
|
|
|
path.pop();
|
|
|
|
}
|
|
|
|
path
|
|
|
|
})
|
2015-02-27 01:04:25 +00:00
|
|
|
}).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>,
|
2015-09-12 21:32:35 +00:00
|
|
|
expect_exit_code: Option<i32>,
|
2016-01-12 17:47:31 +00:00
|
|
|
expect_stdout_contains: Vec<String>,
|
|
|
|
expect_stderr_contains: Vec<String>,
|
2016-12-17 20:23:33 +00:00
|
|
|
expect_stdout_not_contains: Vec<String>,
|
|
|
|
expect_stderr_not_contains: Vec<String>,
|
2016-08-11 21:47:49 +00:00
|
|
|
expect_json: Option<Vec<Json>>,
|
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 {
|
2014-07-09 13:38:10 +00:00
|
|
|
self.expect_stdout = Some(expected.to_string());
|
2014-06-19 23:45:19 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2014-07-18 14:40:15 +00:00
|
|
|
pub fn with_stderr<S: ToString>(mut self, expected: S) -> Execs {
|
2014-07-09 13:38:10 +00:00
|
|
|
self.expect_stderr = Some(expected.to_string());
|
2014-06-19 23:45:19 +00:00
|
|
|
self
|
2014-04-02 23:34:19 +00:00
|
|
|
}
|
2014-06-19 23:45:19 +00:00
|
|
|
|
2015-01-13 16:41:04 +00:00
|
|
|
pub fn with_status(mut self, expected: i32) -> Execs {
|
2014-06-19 23:45:19 +00:00
|
|
|
self.expect_exit_code = Some(expected);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2015-09-12 21:32:35 +00:00
|
|
|
pub fn with_stdout_contains<S: ToString>(mut self, expected: S) -> Execs {
|
|
|
|
self.expect_stdout_contains.push(expected.to_string());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-01-12 17:47:31 +00:00
|
|
|
pub fn with_stderr_contains<S: ToString>(mut self, expected: S) -> Execs {
|
|
|
|
self.expect_stderr_contains.push(expected.to_string());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-12-17 20:23:33 +00:00
|
|
|
pub fn with_stdout_does_not_contain<S: ToString>(mut self, expected: S) -> Execs {
|
|
|
|
self.expect_stdout_not_contains.push(expected.to_string());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_stderr_does_not_contain<S: ToString>(mut self, expected: S) -> Execs {
|
|
|
|
self.expect_stderr_not_contains.push(expected.to_string());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-01-24 21:16:33 +00:00
|
|
|
pub fn with_json(mut self, expected: &str) -> Execs {
|
2016-08-11 21:47:49 +00:00
|
|
|
self.expect_json = Some(expected.split("\n\n").map(|obj| {
|
|
|
|
Json::from_str(obj).unwrap()
|
|
|
|
}).collect());
|
2016-01-24 21:16:33 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
fn match_output(&self, actual: &Output) -> ham::MatchResult {
|
2014-06-19 23:45:19 +00:00
|
|
|
self.match_status(actual)
|
|
|
|
.and(self.match_stdout(actual))
|
|
|
|
.and(self.match_stderr(actual))
|
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
fn match_status(&self, actual: &Output) -> ham::MatchResult {
|
2014-06-19 23:45:19 +00:00
|
|
|
match self.expect_exit_code {
|
|
|
|
None => ham::success(),
|
|
|
|
Some(code) => {
|
|
|
|
ham::expect(
|
2015-02-27 01:04:25 +00:00
|
|
|
actual.status.code() == Some(code),
|
2014-06-19 23:45:19 +00:00
|
|
|
format!("exited with {}\n--- stdout\n{}\n--- stderr\n{}",
|
|
|
|
actual.status,
|
2015-02-27 01:04:25 +00:00
|
|
|
String::from_utf8_lossy(&actual.stdout),
|
|
|
|
String::from_utf8_lossy(&actual.stderr)))
|
2014-06-19 23:45:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
fn match_stdout(&self, actual: &Output) -> ham::MatchResult {
|
2016-11-11 13:25:20 +00:00
|
|
|
self.match_std(self.expect_stdout.as_ref(), &actual.stdout,
|
2016-12-17 20:23:33 +00:00
|
|
|
"stdout", &actual.stderr, MatchKind::Exact)?;
|
2015-10-01 00:58:08 +00:00
|
|
|
for expect in self.expect_stdout_contains.iter() {
|
2016-11-11 13:25:20 +00:00
|
|
|
self.match_std(Some(expect), &actual.stdout, "stdout",
|
2016-12-17 20:23:33 +00:00
|
|
|
&actual.stderr, MatchKind::Partial)?;
|
2015-10-01 00:58:08 +00:00
|
|
|
}
|
2016-01-12 17:47:31 +00:00
|
|
|
for expect in self.expect_stderr_contains.iter() {
|
2016-11-11 13:25:20 +00:00
|
|
|
self.match_std(Some(expect), &actual.stderr, "stderr",
|
2016-12-17 20:23:33 +00:00
|
|
|
&actual.stdout, MatchKind::Partial)?;
|
|
|
|
}
|
|
|
|
for expect in self.expect_stdout_not_contains.iter() {
|
|
|
|
self.match_std(Some(expect), &actual.stdout, "stdout",
|
|
|
|
&actual.stderr, MatchKind::NotPresent)?;
|
|
|
|
}
|
|
|
|
for expect in self.expect_stderr_not_contains.iter() {
|
|
|
|
self.match_std(Some(expect), &actual.stderr, "stderr",
|
|
|
|
&actual.stdout, MatchKind::NotPresent)?;
|
2016-01-12 17:47:31 +00:00
|
|
|
}
|
2016-01-24 21:16:33 +00:00
|
|
|
|
2016-08-11 21:47:49 +00:00
|
|
|
if let Some(ref objects) = self.expect_json {
|
|
|
|
let lines = match str::from_utf8(&actual.stdout) {
|
|
|
|
Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
|
|
|
|
Ok(stdout) => stdout.lines().collect::<Vec<_>>(),
|
|
|
|
};
|
|
|
|
if lines.len() != objects.len() {
|
|
|
|
return Err(format!("expected {} json lines, got {}",
|
|
|
|
objects.len(), lines.len()));
|
|
|
|
}
|
|
|
|
for (obj, line) in objects.iter().zip(lines) {
|
2016-11-11 13:25:20 +00:00
|
|
|
self.match_json(obj, line)?;
|
2016-08-11 21:47:49 +00:00
|
|
|
}
|
2016-01-24 21:16:33 +00:00
|
|
|
}
|
2015-10-01 00:58:08 +00:00
|
|
|
Ok(())
|
2014-06-19 23:45:19 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
fn match_stderr(&self, actual: &Output) -> ham::MatchResult {
|
|
|
|
self.match_std(self.expect_stderr.as_ref(), &actual.stderr,
|
2016-12-17 20:23:33 +00:00
|
|
|
"stderr", &actual.stdout, MatchKind::Exact)
|
2014-06-19 23:45:19 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 00:58:08 +00:00
|
|
|
fn match_std(&self, expected: Option<&String>, actual: &[u8],
|
|
|
|
description: &str, extra: &[u8],
|
2016-12-17 20:23:33 +00:00
|
|
|
kind: MatchKind) -> ham::MatchResult {
|
2015-10-01 00:58:08 +00:00
|
|
|
let out = match expected {
|
2016-11-06 12:33:49 +00:00
|
|
|
Some(out) => out,
|
2015-10-01 00:58:08 +00:00
|
|
|
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>");
|
|
|
|
|
2016-12-17 20:23:33 +00:00
|
|
|
match kind {
|
|
|
|
MatchKind::Exact => {
|
|
|
|
let a = actual.lines();
|
|
|
|
let e = out.lines();
|
|
|
|
|
|
|
|
let diffs = self.diff_lines(a, e, false);
|
|
|
|
ham::expect(diffs.is_empty(),
|
|
|
|
format!("differences:\n\
|
|
|
|
{}\n\n\
|
|
|
|
other output:\n\
|
|
|
|
`{}`", diffs.join("\n"),
|
|
|
|
String::from_utf8_lossy(extra)))
|
|
|
|
}
|
|
|
|
MatchKind::Partial => {
|
|
|
|
let mut a = actual.lines();
|
|
|
|
let e = out.lines();
|
|
|
|
|
|
|
|
let mut diffs = self.diff_lines(a.clone(), e.clone(), true);
|
|
|
|
while let Some(..) = a.next() {
|
|
|
|
let a = self.diff_lines(a.clone(), e.clone(), true);
|
|
|
|
if a.len() < diffs.len() {
|
|
|
|
diffs = a;
|
|
|
|
}
|
2015-09-12 21:32:35 +00:00
|
|
|
}
|
2016-12-17 20:23:33 +00:00
|
|
|
ham::expect(diffs.is_empty(),
|
|
|
|
format!("expected to find:\n\
|
|
|
|
{}\n\n\
|
|
|
|
did not find in output:\n\
|
|
|
|
{}", out,
|
|
|
|
actual))
|
|
|
|
}
|
|
|
|
MatchKind::NotPresent => {
|
|
|
|
ham::expect(!actual.contains(out),
|
|
|
|
format!("expected not to find:\n\
|
|
|
|
{}\n\n\
|
|
|
|
but found in output:\n\
|
|
|
|
{}", out,
|
|
|
|
actual))
|
2015-10-01 00:58:08 +00:00
|
|
|
}
|
2016-11-09 15:11:16 +00:00
|
|
|
}
|
2015-09-12 21:32:35 +00:00
|
|
|
}
|
|
|
|
|
2016-08-11 21:47:49 +00:00
|
|
|
fn match_json(&self, expected: &Json, line: &str) -> ham::MatchResult {
|
|
|
|
let actual = match Json::from_str(line) {
|
|
|
|
Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)),
|
2016-01-24 21:16:33 +00:00
|
|
|
Ok(actual) => actual,
|
|
|
|
};
|
|
|
|
|
|
|
|
match find_mismatch(expected, &actual) {
|
|
|
|
Some((expected_part, actual_part)) => Err(format!(
|
|
|
|
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
|
|
|
|
expected.pretty(), actual.pretty(),
|
|
|
|
expected_part.pretty(), actual_part.pretty()
|
|
|
|
)),
|
|
|
|
None => Ok(()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-01 00:58:08 +00:00
|
|
|
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))
|
2014-07-23 19:47:59 +00:00
|
|
|
}
|
2015-10-01 00:58:08 +00:00
|
|
|
},
|
|
|
|
(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")
|
2014-07-23 19:47:59 +00:00
|
|
|
}
|
2015-10-01 00:58:08 +00:00
|
|
|
}).collect()
|
2014-07-23 19:47:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 20:23:33 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
|
enum MatchKind {
|
|
|
|
Exact,
|
|
|
|
Partial,
|
|
|
|
NotPresent,
|
|
|
|
}
|
|
|
|
|
2016-08-22 23:59:31 +00:00
|
|
|
pub fn lines_match(expected: &str, mut actual: &str) -> bool {
|
2016-11-06 12:33:49 +00:00
|
|
|
let expected = substitute_macros(expected);
|
2016-03-03 18:18:02 +00:00
|
|
|
for (i, part) in expected.split("[..]").enumerate() {
|
2015-02-27 01:04:25 +00:00
|
|
|
match actual.find(part) {
|
2016-03-03 18:18:02 +00:00
|
|
|
Some(j) => {
|
|
|
|
if i == 0 && j != 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
actual = &actual[j + part.len()..];
|
|
|
|
}
|
2014-07-23 19:47:59 +00:00
|
|
|
None => {
|
|
|
|
return false
|
2014-06-19 23:45:19 +00:00
|
|
|
}
|
2014-04-02 23:34:19 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-15 14:51:28 +00:00
|
|
|
actual.is_empty() || expected.ends_with("[..]")
|
2014-04-02 23:34:19 +00:00
|
|
|
}
|
|
|
|
|
2016-03-03 18:18:02 +00:00
|
|
|
#[test]
|
|
|
|
fn lines_match_works() {
|
|
|
|
assert!(lines_match("a b", "a b"));
|
|
|
|
assert!(lines_match("a[..]b", "a b"));
|
|
|
|
assert!(lines_match("a[..]", "a b"));
|
|
|
|
assert!(lines_match("[..]", "a b"));
|
|
|
|
assert!(lines_match("[..]b", "a b"));
|
|
|
|
|
|
|
|
assert!(!lines_match("[..]b", "c"));
|
|
|
|
assert!(!lines_match("b", "c"));
|
|
|
|
assert!(!lines_match("b", "cb"));
|
|
|
|
}
|
|
|
|
|
2016-01-24 21:16:33 +00:00
|
|
|
// Compares JSON object for approximate equality.
|
|
|
|
// You can use `[..]` wildcard in strings (useful for OS dependent things such as paths).
|
make build tests not depend on minutiæ of rustc output
This little patch arises from the maelstrom of my purity born of pain.
It's the pain of seeing rust-lang/rust#38103 in its perfect
crystalline beauty waste away on page four of
https://github.com/rust-lang/rust/pulls, waiting, ready, itching to
land, dying with anticipation to bring the light of clearer lint group
error messages to Rust users of all creeds and nations, only for its
promise to be cruelly blocked by the fateful, hateful hand of circular
dependency. For it is written in src/tools/cargotest/main.rs that the
Cargo tests must pass before the PR can receive Appveyor's blessing,
but the Cargo tests could not pass (because they depend on fine
details of the output that the PR is meant to change), and the Cargo
tests could not be changed (because updating the test expectation to
match the proposed new compiler output, would fail with the current
compiler).
The Gordian knot is cut in the bowels of cargotest's very notion of
comparison (of JSON objects) itself, by means of introducing a magic
string literal `"{...}"`, which can server as a wildcard for any JSON
sub-object.
And so it will be for the children, and the children's children, and
unto the 1.17.0 and 1.18.0 releases, that Cargo's build test
expectations will faithfully expect the exact JSON output by Cargo
itself, but the string literal `"{...}"` shall be a token upon the
JSON output by rustc, and when I see `"{...}"`, I will pass over you,
and the failure shall not be upon you.
And this day shall be unto you for a memorial.
2017-02-01 04:51:24 +00:00
|
|
|
// You can use a `"{...}"` string literal as a wildcard for arbitrary nested JSON (useful
|
|
|
|
// for parts of object emitted by other programs (e.g. rustc) rather than Cargo itself).
|
2016-01-24 21:16:33 +00:00
|
|
|
// Arrays are sorted before comparison.
|
|
|
|
fn find_mismatch<'a>(expected: &'a Json, actual: &'a Json) -> Option<(&'a Json, &'a Json)> {
|
|
|
|
use rustc_serialize::json::Json::*;
|
|
|
|
match (expected, actual) {
|
|
|
|
(&I64(l), &I64(r)) if l == r => None,
|
|
|
|
(&F64(l), &F64(r)) if l == r => None,
|
|
|
|
(&U64(l), &U64(r)) if l == r => None,
|
|
|
|
(&Boolean(l), &Boolean(r)) if l == r => None,
|
|
|
|
(&String(ref l), &String(ref r)) if lines_match(l, r) => None,
|
|
|
|
(&Array(ref l), &Array(ref r)) => {
|
|
|
|
if l.len() != r.len() {
|
|
|
|
return Some((expected, actual));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sorted(xs: &Vec<Json>) -> Vec<&Json> {
|
|
|
|
let mut result = xs.iter().collect::<Vec<_>>();
|
make build tests not depend on minutiæ of rustc output
This little patch arises from the maelstrom of my purity born of pain.
It's the pain of seeing rust-lang/rust#38103 in its perfect
crystalline beauty waste away on page four of
https://github.com/rust-lang/rust/pulls, waiting, ready, itching to
land, dying with anticipation to bring the light of clearer lint group
error messages to Rust users of all creeds and nations, only for its
promise to be cruelly blocked by the fateful, hateful hand of circular
dependency. For it is written in src/tools/cargotest/main.rs that the
Cargo tests must pass before the PR can receive Appveyor's blessing,
but the Cargo tests could not pass (because they depend on fine
details of the output that the PR is meant to change), and the Cargo
tests could not be changed (because updating the test expectation to
match the proposed new compiler output, would fail with the current
compiler).
The Gordian knot is cut in the bowels of cargotest's very notion of
comparison (of JSON objects) itself, by means of introducing a magic
string literal `"{...}"`, which can server as a wildcard for any JSON
sub-object.
And so it will be for the children, and the children's children, and
unto the 1.17.0 and 1.18.0 releases, that Cargo's build test
expectations will faithfully expect the exact JSON output by Cargo
itself, but the string literal `"{...}"` shall be a token upon the
JSON output by rustc, and when I see `"{...}"`, I will pass over you,
and the failure shall not be upon you.
And this day shall be unto you for a memorial.
2017-02-01 04:51:24 +00:00
|
|
|
result.sort_by(|x, y| x.partial_cmp(y).expect("JSON spec does not allow NaNs"));
|
2016-01-24 21:16:33 +00:00
|
|
|
result
|
|
|
|
}
|
|
|
|
|
|
|
|
sorted(l).iter().zip(sorted(r))
|
|
|
|
.filter_map(|(l, r)| find_mismatch(l, r))
|
|
|
|
.nth(0)
|
|
|
|
}
|
|
|
|
(&Object(ref l), &Object(ref r)) => {
|
|
|
|
let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
|
|
|
|
if !same_keys {
|
|
|
|
return Some((expected, actual));
|
|
|
|
}
|
|
|
|
|
|
|
|
l.values().zip(r.values())
|
|
|
|
.filter_map(|(l, r)| find_mismatch(l, r))
|
|
|
|
.nth(0)
|
|
|
|
}
|
|
|
|
(&Null, &Null) => None,
|
make build tests not depend on minutiæ of rustc output
This little patch arises from the maelstrom of my purity born of pain.
It's the pain of seeing rust-lang/rust#38103 in its perfect
crystalline beauty waste away on page four of
https://github.com/rust-lang/rust/pulls, waiting, ready, itching to
land, dying with anticipation to bring the light of clearer lint group
error messages to Rust users of all creeds and nations, only for its
promise to be cruelly blocked by the fateful, hateful hand of circular
dependency. For it is written in src/tools/cargotest/main.rs that the
Cargo tests must pass before the PR can receive Appveyor's blessing,
but the Cargo tests could not pass (because they depend on fine
details of the output that the PR is meant to change), and the Cargo
tests could not be changed (because updating the test expectation to
match the proposed new compiler output, would fail with the current
compiler).
The Gordian knot is cut in the bowels of cargotest's very notion of
comparison (of JSON objects) itself, by means of introducing a magic
string literal `"{...}"`, which can server as a wildcard for any JSON
sub-object.
And so it will be for the children, and the children's children, and
unto the 1.17.0 and 1.18.0 releases, that Cargo's build test
expectations will faithfully expect the exact JSON output by Cargo
itself, but the string literal `"{...}"` shall be a token upon the
JSON output by rustc, and when I see `"{...}"`, I will pass over you,
and the failure shall not be upon you.
And this day shall be unto you for a memorial.
2017-02-01 04:51:24 +00:00
|
|
|
// magic string literal "{...}" acts as wildcard for any sub-JSON
|
|
|
|
(&String(ref l), _) if l == "{...}" => None,
|
2016-01-24 21:16:33 +00:00
|
|
|
_ => Some((expected, actual)),
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
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 {
|
2014-10-17 22:05:54 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "execs")
|
2014-06-19 23:45:19 +00:00
|
|
|
}
|
2014-04-02 23:34:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ham::Matcher<ProcessBuilder> for Execs {
|
2015-02-27 01:04:25 +00:00
|
|
|
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 {
|
2016-05-15 00:14:24 +00:00
|
|
|
println!("running {}", process);
|
2014-06-19 23:45:19 +00:00
|
|
|
let res = process.exec_with_output();
|
|
|
|
|
|
|
|
match res {
|
|
|
|
Ok(out) => self.match_output(&out),
|
|
|
|
Err(ProcessError { output: Some(ref out), .. }) => {
|
|
|
|
self.match_output(out)
|
|
|
|
}
|
2014-08-02 07:08:31 +00:00
|
|
|
Err(e) => {
|
|
|
|
let mut s = format!("could not exec process {}: {}", process, e);
|
2014-12-21 23:19:44 +00:00
|
|
|
match e.cause() {
|
2015-03-26 18:17:44 +00:00
|
|
|
Some(cause) => s.push_str(&format!("\ncaused by: {}",
|
|
|
|
cause.description())),
|
2014-08-02 07:08:31 +00:00
|
|
|
None => {}
|
|
|
|
}
|
|
|
|
Err(s)
|
|
|
|
}
|
2014-06-19 23:45:19 +00:00
|
|
|
}
|
2014-04-02 23:34:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix running Cargo concurrently
Cargo has historically had no protections against running it concurrently. This
is pretty unfortunate, however, as it essentially just means that you can only
run one instance of Cargo at a time **globally on a system**.
An "easy solution" to this would be the use of file locks, except they need to
be applied judiciously. It'd be a pretty bad experience to just lock the entire
system globally for Cargo (although it would work), but otherwise Cargo must be
principled how it accesses the filesystem to ensure that locks are properly
held. This commit intends to solve all of these problems.
A new utility module is added to cargo, `util::flock`, which contains two types:
* `FileLock` - a locked version of a `File`. This RAII guard will unlock the
lock on `Drop` and I/O can be performed through this object. The actual
underlying `Path` can be read from this object as well.
* `Filesystem` - an unlocked representation of a `Path`. There is no "safe"
method to access the underlying path without locking a file on the filesystem
first.
Built on the [fs2] library, these locks use the `flock` system call on Unix and
`LockFileEx` on Windows. Although file locking on Unix is [documented as not so
great][unix-bad], but largely only because of NFS, these are just advisory, and
there's no byte-range locking. These issues don't necessarily plague Cargo,
however, so we should try to leverage them. On both Windows and Unix the file
locks are released when the underlying OS handle is closed, which means that
if the process dies the locks are released.
Cargo has a number of global resources which it now needs to lock, and the
strategy is done in a fairly straightforward way:
* Each registry's index contains one lock (a dotfile in the index). Updating the
index requires a read/write lock while reading the index requires a shared
lock. This should allow each process to ensure a registry update happens while
not blocking out others for an unnecessarily long time. Additionally any
number of processes can read the index.
* When downloading crates, each downloaded crate is individually locked. A lock
for the downloaded crate implies a lock on the output directory as well.
Because downloaded crates are immutable, once the downloaded directory exists
the lock is no longer needed as it won't be modified, so it can be released.
This granularity of locking allows multiple Cargo instances to download
dependencies in parallel.
* Git repositories have separate locks for the database and for the project
checkout. The datbase and checkout are locked for read/write access when an
update is performed, and the lock of the checkout is held for the entire
lifetime of the git source. This is done to ensure that any other Cargo
processes must wait while we use the git repository. Unfortunately there's
just not that much parallelism here.
* Binaries managed by `cargo install` are locked by the local metadata file that
Cargo manages. This is relatively straightforward.
* The actual artifact output directory is just globally locked for the entire
build. It's hypothesized that running Cargo concurrently in *one directory* is
less of a feature needed rather than running multiple instances of Cargo
globally (for now at least). It would be possible to have finer grained
locking here, but that can likely be deferred to a future PR.
So with all of this infrastructure in place, Cargo is now ready to grab some
locks and ensure that you can call it concurrently anywhere at any time and
everything always works out as one might expect.
One interesting question, however, is what does Cargo do on contention? On one
hand Cargo could immediately abort, but this would lead to a pretty poor UI as
any Cargo process on the system could kick out any other. Instead this PR takes
a more nuanced approach.
* First, all locks are attempted to be acquired (a "try lock"). If this
succeeds, we're done.
* Next, Cargo prints a message to the console that it's going to block waiting
for a lock. This is done because it's indeterminate how long Cargo will wait
for the lock to become available, and most long-lasting operations in Cargo
have a message printed for them.
* Finally, a blocking acquisition of the lock is issued and we wait for it to
become available.
So all in all this should help Cargo fix any future concurrency bugs with file
locking in a principled fashion while also allowing concurrent Cargo processes
to proceed reasonably across the system.
[fs2]: https://github.com/danburkert/fs2-rs
[unix-bad]: http://0pointer.de/blog/projects/locking.html
Closes #354
2016-03-12 17:58:53 +00:00
|
|
|
impl ham::Matcher<Output> for Execs {
|
|
|
|
fn matches(&self, output: Output) -> ham::MatchResult {
|
|
|
|
self.match_output(&output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-18 14:40:15 +00:00
|
|
|
pub fn execs() -> Execs {
|
|
|
|
Execs {
|
2014-05-09 23:57:13 +00:00
|
|
|
expect_stdout: None,
|
|
|
|
expect_stderr: None,
|
|
|
|
expect_stdin: None,
|
2015-09-12 21:32:35 +00:00
|
|
|
expect_exit_code: None,
|
2016-01-12 17:47:31 +00:00
|
|
|
expect_stdout_contains: Vec::new(),
|
|
|
|
expect_stderr_contains: Vec::new(),
|
2016-12-17 20:23:33 +00:00
|
|
|
expect_stdout_not_contains: Vec::new(),
|
|
|
|
expect_stderr_not_contains: Vec::new(),
|
2016-01-24 21:16:33 +00:00
|
|
|
expect_json: None,
|
2014-05-09 23:57:13 +00:00
|
|
|
}
|
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 {
|
2014-10-17 22:05:54 +00:00
|
|
|
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])
|
2014-06-19 23:45:19 +00:00
|
|
|
-> ham::MatchResult
|
|
|
|
{
|
2014-07-17 01:44:30 +00:00
|
|
|
let actual = String::from_utf8_lossy(actual);
|
2014-07-09 13:38:10 +00:00
|
|
|
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 {
|
2016-10-26 15:25:15 +00:00
|
|
|
fn tap<F: FnOnce(&mut Self)>(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-25 05:06:11 +00:00
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2014-07-21 00:02:09 +00:00
|
|
|
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]
|
2014-07-21 00:02:09 +00:00
|
|
|
|
|
|
|
name = "{}"
|
|
|
|
"#, name, name)
|
|
|
|
}
|
|
|
|
|
2015-02-27 01:04:25 +00:00
|
|
|
pub fn path2url(p: PathBuf) -> Url {
|
|
|
|
Url::from_file_path(&*p).ok().unwrap()
|
2014-08-06 16:21:10 +00:00
|
|
|
}
|
|
|
|
|
2016-05-10 23:52:02 +00:00
|
|
|
fn substitute_macros(input: &str) -> String {
|
|
|
|
let macros = [
|
|
|
|
("[RUNNING]", " Running"),
|
|
|
|
("[COMPILING]", " Compiling"),
|
2016-07-29 19:52:33 +00:00
|
|
|
("[CREATED]", " Created"),
|
2016-07-25 23:30:03 +00:00
|
|
|
("[FINISHED]", " Finished"),
|
2016-05-10 23:52:02 +00:00
|
|
|
("[ERROR]", "error:"),
|
2016-05-12 01:34:15 +00:00
|
|
|
("[WARNING]", "warning:"),
|
2016-05-10 23:52:02 +00:00
|
|
|
("[DOCUMENTING]", " Documenting"),
|
|
|
|
("[FRESH]", " Fresh"),
|
|
|
|
("[UPDATING]", " Updating"),
|
|
|
|
("[ADDING]", " Adding"),
|
|
|
|
("[REMOVING]", " Removing"),
|
|
|
|
("[DOCTEST]", " Doc-tests"),
|
|
|
|
("[PACKAGING]", " Packaging"),
|
|
|
|
("[DOWNLOADING]", " Downloading"),
|
|
|
|
("[UPLOADING]", " Uploading"),
|
|
|
|
("[VERIFYING]", " Verifying"),
|
|
|
|
("[ARCHIVING]", " Archiving"),
|
|
|
|
("[INSTALLING]", " Installing"),
|
2016-02-05 23:14:17 +00:00
|
|
|
("[REPLACING]", " Replacing"),
|
|
|
|
("[UNPACKING]", " Unpacking"),
|
2016-11-04 05:21:43 +00:00
|
|
|
("[EXE]", if cfg!(windows) {".exe"} else {""}),
|
2016-11-06 02:14:16 +00:00
|
|
|
("[/]", if cfg!(windows) {"\\"} else {"/"}),
|
2016-05-10 23:52:02 +00:00
|
|
|
];
|
|
|
|
let mut result = input.to_owned();
|
|
|
|
for &(pat, subst) in macros.iter() {
|
|
|
|
result = result.replace(pat, subst)
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|