cargo/tests/testsuite/support/mod.rs

1533 lines
46 KiB
Rust
Raw Normal View History

/*
# Introduction To `support`
Cargo has a wide variety of integration tests that execute the `cargo` binary
and verify its behavior. The `support` module contains many helpers to make
this process easy.
The general form of a test involves creating a "project", running cargo, and
checking the result. Projects are created with the `ProjectBuilder` where you
specify some files to create. The general form looks like this:
```
let p = project()
.file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
.build();
```
If you do not specify a `Cargo.toml` manifest using `file()`, one is
automatically created with a project name of `foo` using `basic_manifest()`.
To run cargo, call the `cargo` method and make assertions on the execution:
```
p.cargo("run --bin foo")
.with_stderr(
"\
[COMPILING] foo [..]
[FINISHED] [..]
2018-08-02 09:18:48 +00:00
[RUNNING] `target/debug/foo`
",
)
.with_stdout("hi!")
.run();
```
The project creates a mini sandbox under the "cargo integration test"
directory with each test getting a separate directory such as
`/path/to/cargo/target/cit/t123/`. Each project appears as a separate
directory. There is also an empty `home` directory created that will be used
as a home directory instead of your normal home directory.
See `support::lines_match` for an explanation of the string pattern matching.
Browse the `pub` functions in the `support` module for a variety of other
helpful utilities.
## Testing Nightly Features
If you are testing a Cargo feature that only works on "nightly" cargo, then
you need to call `masquerade_as_nightly_cargo` on the process builder like
this:
```
p.cargo("build").masquerade_as_nightly_cargo()
```
If you are testing a feature that only works on *nightly rustc* (such as
benchmarks), then you should exit the test if it is not running with nightly
rust, like this:
```
if !is_nightly() {
return;
}
```
## Platform-specific Notes
2018-08-02 09:18:48 +00:00
When checking output, use `/` for paths even on Windows: the actual output
of `\` on Windows will be replaced with `/`.
Be careful when executing binaries on Windows. You should not rename, delete,
or overwrite a binary immediately after running it. Under some conditions
Windows will fail with errors like "directory not empty" or "failed to remove"
or "access is denied".
## Specifying Dependencies
You should not write any tests that use the network such as contacting
crates.io. Typically, simple path dependencies are the easiest way to add a
dependency. Example:
```
let p = project()
.file("Cargo.toml", r#"
[package]
name = "foo"
version = "1.0.0"
[dependencies]
bar = {path = "bar"}
"#)
.file("src/lib.rs", "extern crate bar;")
.file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("bar/src/lib.rs", "")
.build();
```
If you need to test with registry dependencies, see
`support::registry::Package` for creating packages you can depend on.
If you need to test git dependencies, see `support::git` to create a git
dependency.
*/
2015-02-06 07:27:53 +00:00
use std::env;
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::{Command, Output};
2015-03-26 18:17:44 +00:00
use std::str;
2018-11-02 22:18:17 +00:00
use std::time::{self, Duration};
use std::usize;
use cargo;
use cargo::util::{CargoResult, ProcessBuilder, ProcessError, Rustc};
2018-11-02 22:18:17 +00:00
use filetime;
2018-04-22 00:21:42 +00:00
use serde_json::{self, Value};
use url::Url;
use self::paths::CargoPathExt;
macro_rules! t {
2018-04-22 00:21:42 +00:00
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
pub mod cross_compile;
2018-04-22 00:21:42 +00:00
pub mod git;
pub mod paths;
pub mod publish;
2018-04-22 00:21:42 +00:00
pub mod registry;
2018-09-26 20:48:48 +00:00
#[macro_use]
pub mod resolver;
2014-03-20 22:17:19 +00:00
/*
*
* ===== Builders =====
*
*/
2018-03-14 15:17:44 +00:00
#[derive(PartialEq, Clone)]
struct FileBuilder {
path: PathBuf,
2018-03-14 15:17:44 +00:00
body: String,
}
impl FileBuilder {
pub fn new(path: PathBuf, body: &str) -> FileBuilder {
2018-03-14 15:17:44 +00:00
FileBuilder {
path,
body: body.to_string(),
}
}
fn mk(&self) {
self.dirname().mkdir_p();
2018-03-14 15:17:44 +00:00
let mut file = fs::File::create(&self.path)
.unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e));
t!(file.write_all(self.body.as_bytes()));
}
fn dirname(&self) -> &Path {
self.path.parent().unwrap()
}
}
2018-03-14 15:17:44 +00:00
#[derive(PartialEq, Clone)]
struct SymlinkBuilder {
dst: PathBuf,
src: PathBuf,
}
impl SymlinkBuilder {
pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
SymlinkBuilder { dst, src }
}
#[cfg(unix)]
fn mk(&self) {
self.dirname().mkdir_p();
t!(os::unix::fs::symlink(&self.dst, &self.src));
}
#[cfg(windows)]
fn mk(&self) {
self.dirname().mkdir_p();
t!(os::windows::fs::symlink_file(&self.dst, &self.src));
}
fn dirname(&self) -> &Path {
self.src.parent().unwrap()
}
}
pub struct Project {
root: PathBuf,
}
#[must_use]
2014-06-12 22:51:16 +00:00
pub struct ProjectBuilder {
root: Project,
files: Vec<FileBuilder>,
symlinks: Vec<SymlinkBuilder>,
no_manifest: bool,
}
impl ProjectBuilder {
2018-05-24 04:09:27 +00:00
/// Root of the project, ex: `/path/to/cargo/target/cit/t0/foo`
pub fn root(&self) -> PathBuf {
self.root.root()
}
2018-05-24 04:09:27 +00:00
/// Project's debug dir, ex: `/path/to/cargo/target/cit/t0/foo/target/debug`
pub fn target_debug_dir(&self) -> PathBuf {
self.root.target_debug_dir()
}
pub fn new(root: PathBuf) -> ProjectBuilder {
ProjectBuilder {
root: Project { root },
files: vec![],
symlinks: vec![],
no_manifest: false,
}
}
2018-07-19 12:55:39 +00:00
pub fn at<P: AsRef<Path>>(mut self, path: P) -> Self {
self.root = Project {
root: paths::root().join(path),
};
2018-07-19 12:55:39 +00:00
self
}
2018-05-24 04:09:27 +00:00
/// Add a file to the project.
2018-03-14 15:17:44 +00:00
pub fn file<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
self._file(path.as_ref(), body);
self
}
fn _file(&mut self, path: &Path, body: &str) {
2018-03-14 15:17:44 +00:00
self.files
.push(FileBuilder::new(self.root.root().join(path), body));
}
2018-05-24 04:09:27 +00:00
/// Add a symlink to the project.
2018-03-14 15:17:44 +00:00
pub fn symlink<T: AsRef<Path>>(mut self, dst: T, src: T) -> Self {
self.symlinks.push(SymlinkBuilder::new(
self.root.root().join(dst),
self.root.root().join(src),
2018-03-14 15:17:44 +00:00
));
self
}
pub fn no_manifest(mut self) -> Self {
self.no_manifest = true;
self
}
2018-05-24 04:09:27 +00:00
/// Create the project.
pub fn build(mut self) -> Project {
// First, clean the directory if it already exists
self.rm_root();
// Create the empty directory
self.root.root().mkdir_p();
let manifest_path = self.root.root().join("Cargo.toml");
if !self.no_manifest && self.files.iter().all(|fb| fb.path != manifest_path) {
2018-07-24 22:35:01 +00:00
self._file(Path::new("Cargo.toml"), &basic_manifest("foo", "0.0.1"))
}
2018-11-02 22:18:17 +00:00
let past = time::SystemTime::now() - Duration::new(1, 0);
let ftime = filetime::FileTime::from_system_time(past);
for file in self.files.iter() {
file.mk();
2018-11-02 22:18:17 +00:00
if is_coarse_mtime() {
// Place the entire project 1 second in the past to ensure
// that if cargo is called multiple times, the 2nd call will
// see targets as "fresh". Without this, if cargo finishes in
// under 1 second, the second call will see the mtime of
// source == mtime of output and consider it dirty.
filetime::set_file_times(&file.path, ftime, ftime).unwrap();
}
}
for symlink in self.symlinks.iter() {
symlink.mk();
}
let ProjectBuilder { root, .. } = self;
root
}
fn rm_root(&self) {
self.root.root().rm_rf()
}
}
impl Project {
2018-05-24 04:09:27 +00:00
/// Root of the project, ex: `/path/to/cargo/target/cit/t0/foo`
pub fn root(&self) -> PathBuf {
self.root.clone()
}
2018-05-24 04:09:27 +00:00
/// Project's target dir, ex: `/path/to/cargo/target/cit/t0/foo/target`
pub fn build_dir(&self) -> PathBuf {
self.root().join("target")
}
2018-05-24 04:09:27 +00:00
/// Project's debug dir, ex: `/path/to/cargo/target/cit/t0/foo/target/debug`
2017-01-19 20:43:56 +00:00
pub fn target_debug_dir(&self) -> PathBuf {
self.build_dir().join("debug")
}
2018-05-24 04:09:27 +00:00
/// File url for root, ex: `file:///path/to/cargo/target/cit/t0/foo`
2018-03-14 15:17:44 +00:00
pub fn url(&self) -> Url {
path2url(self.root())
}
2018-05-24 04:09:27 +00:00
/// Path to an example built as a library.
/// `kind` should be one of: "lib", "rlib", "staticlib", "dylib", "proc-macro"
/// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/examples/libex.rlib`
2017-01-19 20:43:56 +00:00
pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
let prefix = Project::get_lib_prefix(kind);
2017-01-19 20:43:56 +00:00
let extension = Project::get_lib_extension(kind);
2017-01-19 20:43:56 +00:00
2018-03-14 15:17:44 +00:00
let lib_file_name = format!("{}{}.{}", prefix, name, extension);
2017-01-19 20:43:56 +00:00
self.target_debug_dir()
.join("examples")
.join(&lib_file_name)
}
2018-05-24 04:09:27 +00:00
/// Path to a debug binary.
/// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/foo`
pub fn bin(&self, b: &str) -> PathBuf {
2018-03-14 15:17:44 +00:00
self.build_dir()
.join("debug")
.join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
2014-07-07 08:50:05 +00:00
}
2018-05-24 04:09:27 +00:00
/// Path to a release binary.
/// ex: `/path/to/cargo/target/cit/t0/foo/target/release/foo`
pub fn release_bin(&self, b: &str) -> PathBuf {
2018-03-14 15:17:44 +00:00
self.build_dir()
.join("release")
.join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
}
2018-05-24 04:09:27 +00:00
/// Path to a debug binary for a specific target triple.
/// ex: `/path/to/cargo/target/cit/t0/foo/target/i686-apple-darwin/debug/foo`
pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
2018-03-14 15:17:44 +00:00
self.build_dir().join(target).join("debug").join(&format!(
"{}{}",
b,
env::consts::EXE_SUFFIX
))
}
2018-05-24 04:09:27 +00:00
/// Change the contents of an existing file.
pub fn change_file(&self, path: &str, body: &str) {
FileBuilder::new(self.root().join(path), body).mk()
}
/// Create a `ProcessBuilder` to run a program in the project
/// and wrap it in an Execs to assert on the execution.
2018-05-24 04:09:27 +00:00
/// Example:
/// p.process(&p.bin("foo"))
/// .with_stdout("bar\n")
/// .run();
2018-08-28 20:38:26 +00:00
pub fn process<T: AsRef<OsStr>>(&self, program: T) -> Execs {
let mut p = ::support::process(program);
p.cwd(self.root());
2018-08-28 20:38:26 +00:00
execs().with_process_builder(p)
}
2018-05-24 04:09:27 +00:00
/// Create a `ProcessBuilder` to run cargo.
/// Arguments can be separated by spaces.
/// Example:
/// p.cargo("build --bin foo").run();
pub fn cargo(&self, cmd: &str) -> Execs {
2018-08-28 20:38:26 +00:00
let mut execs = self.process(&cargo_exe());
if let Some(ref mut p) = execs.process_builder {
split_and_add_args(p, cmd);
}
execs
2014-06-12 22:51:16 +00:00
}
2018-05-24 04:09:27 +00:00
/// Returns the contents of `Cargo.lock`.
2016-08-25 08:34:25 +00:00
pub fn read_lockfile(&self) -> String {
self.read_file("Cargo.lock")
}
/// Returns the contents of a path in the project root
pub fn read_file(&self, path: &str) -> String {
2016-08-25 08:34:25 +00:00
let mut buffer = String::new();
fs::File::open(self.root().join(path))
2018-03-14 15:17:44 +00:00
.unwrap()
.read_to_string(&mut buffer)
.unwrap();
2016-08-25 08:34:25 +00:00
buffer
}
2018-05-24 04:09:27 +00:00
/// Modifies `Cargo.toml` to remove all commented lines.
pub fn uncomment_root_manifest(&self) {
let mut contents = String::new();
fs::File::open(self.root().join("Cargo.toml"))
.unwrap()
.read_to_string(&mut contents)
.unwrap();
fs::File::create(self.root().join("Cargo.toml"))
.unwrap()
.write_all(contents.replace("#", "").as_bytes())
.unwrap();
}
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"
}
}
2018-03-14 15:17:44 +00:00
_ => unreachable!(),
2017-01-19 20:43:56 +00:00
}
}
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"
2018-03-14 15:17:44 +00:00
} else if cfg!(target_os = "macos") {
2017-01-19 20:43:56 +00:00
"dylib"
} else {
"so"
}
}
2018-03-14 15:17:44 +00:00
_ => unreachable!(),
2017-01-19 20:43:56 +00:00
}
}
}
// Generates a project layout
pub fn project() -> ProjectBuilder {
ProjectBuilder::new(paths::root().join("foo"))
}
// Generates a project layout inside our fake home dir
pub fn project_in_home(name: &str) -> ProjectBuilder {
ProjectBuilder::new(paths::home().join(name))
}
// === 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");
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),
2018-03-14 15:17:44 +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 {
2018-03-14 15:17:44 +00:00
env::var_os("CARGO_BIN_PATH")
.map(PathBuf::from)
.or_else(|| {
env::current_exe().ok().map(|mut path| {
2016-11-17 20:21:13 +00:00
path.pop();
2018-03-14 15:17:44 +00:00
if path.ends_with("deps") {
path.pop();
}
path
})
}).unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set. Cannot continue running test"))
2014-03-20 22:17:19 +00:00
}
pub fn cargo_exe() -> PathBuf {
cargo_dir().join(format!("cargo{}", env::consts::EXE_SUFFIX))
}
2014-03-20 22:17:19 +00:00
/*
*
* ===== Matchers =====
*
*/
2014-04-02 23:34:19 +00:00
pub type MatchResult = Result<(), String>;
2018-08-23 15:33:37 +00:00
#[must_use]
2015-01-04 09:02:16 +00:00
#[derive(Clone)]
2015-03-22 23:58:11 +00:00
pub struct Execs {
ran: bool,
process_builder: Option<ProcessBuilder>,
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>,
2016-01-12 17:47:31 +00:00
expect_stdout_contains: Vec<String>,
expect_stderr_contains: Vec<String>,
2018-01-22 20:27:50 +00:00
expect_either_contains: Vec<String>,
expect_stdout_contains_n: Vec<(String, usize)>,
expect_stdout_not_contains: Vec<String>,
expect_stderr_not_contains: Vec<String>,
expect_stderr_unordered: Vec<String>,
2018-01-22 20:27:50 +00:00
expect_neither_contains: Vec<String>,
expect_json: Option<Vec<Value>>,
stream_output: bool,
2014-04-02 23:34:19 +00:00
}
impl Execs {
pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs {
self.process_builder = Some(p);
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stdout is equal to the given lines.
/// See `lines_match` for supported patterns.
pub fn with_stdout<S: ToString>(&mut self, expected: S) -> &mut Self {
self.expect_stdout = Some(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stderr is equal to the given lines.
/// See `lines_match` for supported patterns.
pub fn with_stderr<S: ToString>(&mut self, expected: S) -> &mut Self {
self.expect_stderr = Some(expected.to_string());
2018-08-24 15:55:34 +00:00
self
}
2018-05-24 04:09:27 +00:00
/// Verify the exit code from the process.
///
/// This is not necessary if the expected exit code is `0`.
pub fn with_status(&mut self, expected: i32) -> &mut Self {
self.expect_exit_code = Some(expected);
self
}
/// Remove exit code check for the process.
///
/// By default, the expected exit code is `0`.
pub fn without_status(&mut self) -> &mut Self {
self.expect_exit_code = None;
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stdout contains the given contiguous lines somewhere in
/// its output.
/// See `lines_match` for supported patterns.
pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
self.expect_stdout_contains.push(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stderr contains the given contiguous lines somewhere in
/// its output.
/// See `lines_match` for supported patterns.
pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
2016-01-12 17:47:31 +00:00
self.expect_stderr_contains.push(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify that either stdout or stderr contains the given contiguous
/// lines somewhere in its output.
/// See `lines_match` for supported patterns.
pub fn with_either_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
2018-01-22 20:27:50 +00:00
self.expect_either_contains.push(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stdout contains the given contiguous lines somewhere in
/// its output, and should be repeated `number` times.
/// See `lines_match` for supported patterns.
pub fn with_stdout_contains_n<S: ToString>(&mut self, expected: S, number: usize) -> &mut Self {
2018-03-14 15:17:44 +00:00
self.expect_stdout_contains_n
.push((expected.to_string(), number));
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stdout does not contain the given contiguous lines.
/// See `lines_match` for supported patterns.
/// See note on `with_stderr_does_not_contain`.
pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
self.expect_stdout_not_contains.push(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify that stderr does not contain the given contiguous lines.
/// See `lines_match` for supported patterns.
///
/// Care should be taken when using this method because there is a
/// limitless number of possible things that *won't* appear. A typo means
/// your test will pass without verifying the correct behavior. If
/// possible, write the test first so that it fails, and then implement
/// your fix/feature to make it pass.
pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
self.expect_stderr_not_contains.push(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify that all of the stderr output is equal to the given lines,
/// ignoring the order of the lines.
/// See `lines_match` for supported patterns.
/// This is useful when checking the output of `cargo build -v` since
/// the order of the output is not always deterministic.
/// Recommend use `with_stderr_contains` instead unless you really want to
/// check *every* line of output.
///
/// Be careful when using patterns such as `[..]`, because you may end up
/// with multiple lines that might match, and this is not smart enough to
/// do anything like longest-match. For example, avoid something like:
/// [RUNNING] `rustc [..]
/// [RUNNING] `rustc --crate-name foo [..]
/// This will randomly fail if the other crate name is `bar`, and the
/// order changes.
pub fn with_stderr_unordered<S: ToString>(&mut self, expected: S) -> &mut Self {
self.expect_stderr_unordered.push(expected.to_string());
self
}
2018-05-24 04:09:27 +00:00
/// Verify the JSON output matches the given JSON.
/// Typically used when testing cargo commands that emit JSON.
/// Each separate JSON object should be separated by a blank line.
/// Example:
/// assert_that(
/// p.cargo("metadata"),
/// execs().with_json(r#"
/// {"example": "abc"}
///
/// {"example": "def"}
/// "#)
/// );
/// Objects should match in the order given.
/// The order of arrays is ignored.
/// Strings support patterns described in `lines_match`.
/// Use `{...}` to match any object.
pub fn with_json(&mut self, expected: &str) -> &mut Self {
2018-03-14 15:17:44 +00:00
self.expect_json = Some(
expected
.split("\n\n")
.map(|obj| obj.parse().unwrap())
.collect(),
);
self
}
/// Forward subordinate process stdout/stderr to the terminal.
2018-05-24 04:09:27 +00:00
/// Useful for printf debugging of the tests.
/// CAUTION: CI will fail if you leave this in your test!
#[allow(unused)]
pub fn stream(&mut self) -> &mut Self {
self.stream_output = true;
self
}
pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
if let Some(ref mut p) = self.process_builder {
p.arg(arg);
}
self
}
pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
if let Some(ref mut p) = self.process_builder {
p.cwd(path);
}
self
}
pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
if let Some(ref mut p) = self.process_builder {
p.env(key, val);
}
self
}
pub fn env_remove(&mut self, key: &str) -> &mut Self {
if let Some(ref mut p) = self.process_builder {
p.env_remove(key);
}
self
}
pub fn exec_with_output(&mut self) -> CargoResult<Output> {
self.ran = true;
// TODO avoid unwrap
let p = (&self.process_builder).clone().unwrap();
p.exec_with_output()
}
pub fn build_command(&mut self) -> Command {
self.ran = true;
// TODO avoid unwrap
let p = (&self.process_builder).clone().unwrap();
p.build_command()
}
pub fn masquerade_as_nightly_cargo(&mut self) -> &mut Self {
if let Some(ref mut p) = self.process_builder {
p.masquerade_as_nightly_cargo();
}
self
}
pub fn run(&mut self) {
self.ran = true;
let p = (&self.process_builder).clone().unwrap();
if let Err(e) = self.match_process(&p) {
panic!("\nExpected: {:?}\n but: {}", self, e)
}
}
2018-08-28 08:07:33 +00:00
pub fn run_output(&mut self, output: &Output) {
self.ran = true;
if let Err(e) = self.match_output(output) {
panic!("\nExpected: {:?}\n but: {}", self, e)
}
}
fn match_process(&self, process: &ProcessBuilder) -> MatchResult {
2018-08-28 08:07:33 +00:00
println!("running {}", process);
let res = if self.stream_output {
if env::var("CI").is_ok() {
panic!("`.stream()` is for local debugging")
}
process.exec_with_streaming(
&mut |out| Ok(println!("{}", out)),
&mut |err| Ok(eprintln!("{}", err)),
false,
)
} else {
process.exec_with_output()
};
match res {
Ok(out) => self.match_output(&out),
Err(e) => {
let err = e.downcast_ref::<ProcessError>();
if let Some(&ProcessError {
output: Some(ref out),
..
}) = err
{
2018-08-28 08:07:33 +00:00
return self.match_output(out);
}
let mut s = format!("could not exec process {}: {}", process, e);
for cause in e.iter_causes() {
s.push_str(&format!("\ncaused by: {}", cause));
}
Err(s)
}
}
}
fn match_output(&self, actual: &Output) -> MatchResult {
self.match_status(actual)
.and(self.match_stdout(actual))
.and(self.match_stderr(actual))
}
fn match_status(&self, actual: &Output) -> MatchResult {
match self.expect_exit_code {
None => Ok(()),
Some(code) if actual.status.code() == Some(code) => Ok(()),
2018-03-14 15:17:44 +00:00
Some(_) => Err(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) -> MatchResult {
2018-03-14 15:17:44 +00:00
self.match_std(
self.expect_stdout.as_ref(),
&actual.stdout,
"stdout",
&actual.stderr,
MatchKind::Exact,
)?;
for expect in self.expect_stdout_contains.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(expect),
&actual.stdout,
"stdout",
&actual.stderr,
MatchKind::Partial,
)?;
}
2016-01-12 17:47:31 +00:00
for expect in self.expect_stderr_contains.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(expect),
&actual.stderr,
"stderr",
&actual.stdout,
MatchKind::Partial,
)?;
}
for &(ref expect, number) in self.expect_stdout_contains_n.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(&expect),
&actual.stdout,
"stdout",
&actual.stderr,
MatchKind::PartialN(number),
)?;
}
for expect in self.expect_stdout_not_contains.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(expect),
&actual.stdout,
"stdout",
&actual.stderr,
MatchKind::NotPresent,
)?;
}
for expect in self.expect_stderr_not_contains.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(expect),
&actual.stderr,
"stderr",
&actual.stdout,
MatchKind::NotPresent,
)?;
2016-01-12 17:47:31 +00:00
}
for expect in self.expect_stderr_unordered.iter() {
self.match_std(
Some(expect),
&actual.stderr,
"stderr",
&actual.stdout,
MatchKind::Unordered,
)?;
}
2018-01-22 20:27:50 +00:00
for expect in self.expect_neither_contains.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(expect),
&actual.stdout,
"stdout",
&actual.stdout,
MatchKind::NotPresent,
)?;
self.match_std(
Some(expect),
&actual.stderr,
"stderr",
&actual.stderr,
MatchKind::NotPresent,
)?;
2018-01-22 20:27:50 +00:00
}
for expect in self.expect_either_contains.iter() {
2018-03-14 15:17:44 +00:00
let match_std = self.match_std(
Some(expect),
&actual.stdout,
"stdout",
&actual.stdout,
MatchKind::Partial,
);
let match_err = self.match_std(
Some(expect),
&actual.stderr,
"stderr",
&actual.stderr,
MatchKind::Partial,
);
2018-01-22 20:27:50 +00:00
if let (Err(_), Err(_)) = (match_std, match_err) {
2018-03-14 15:17:44 +00:00
Err(format!(
"expected to find:\n\
{}\n\n\
did not find in either output.",
expect
))?;
2018-01-22 20:27:50 +00:00
}
}
2016-08-11 21:47:49 +00:00
if let Some(ref objects) = self.expect_json {
2017-06-16 19:41:27 +00:00
let stdout = str::from_utf8(&actual.stdout)
.map_err(|_| "stdout was not utf8 encoded".to_owned())?;
2018-04-22 00:21:42 +00:00
let lines = stdout
.lines()
.filter(|line| line.starts_with('{'))
2018-04-22 00:21:42 +00:00
.collect::<Vec<_>>();
2016-08-11 21:47:49 +00:00
if lines.len() != objects.len() {
2018-03-14 15:17:44 +00:00
return Err(format!(
"expected {} json lines, got {}, stdout:\n{}",
objects.len(),
lines.len(),
stdout
));
2016-08-11 21:47:49 +00:00
}
for (obj, line) in objects.iter().zip(lines) {
self.match_json(obj, line)?;
2016-08-11 21:47:49 +00:00
}
}
Ok(())
}
fn match_stderr(&self, actual: &Output) -> MatchResult {
2018-03-14 15:17:44 +00:00
self.match_std(
self.expect_stderr.as_ref(),
&actual.stderr,
"stderr",
&actual.stdout,
MatchKind::Exact,
)
}
fn match_std(
&self,
expected: Option<&String>,
actual: &[u8],
description: &str,
extra: &[u8],
kind: MatchKind,
) -> MatchResult {
let out = match expected {
Some(out) => {
// Do the template replacements on the expected string.
let replaced = match self.process_builder {
None => out.to_string(),
Some(ref p) => match p.get_cwd() {
None => out.to_string(),
Some(cwd) => out
.replace( "[CWD]", &cwd.display().to_string())
,
},
};
// On Windows, we need to use a wildcard for the drive,
// because we don't actually know what it will be.
let replaced = replaced
.replace("[ROOT]",
if cfg!(windows) { r#"[..]:\"# } else { "/" });
replaced
},
None => return Ok(()),
};
let actual = match str::from_utf8(actual) {
2018-03-14 15:17:44 +00:00
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>");
match kind {
MatchKind::Exact => {
let a = actual.lines();
let e = out.lines();
let diffs = self.diff_lines(a, e, false);
if diffs.is_empty() {
Ok(())
} else {
2018-03-14 15:17:44 +00:00
Err(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;
}
}
if diffs.is_empty() {
Ok(())
} else {
2018-03-14 15:17:44 +00:00
Err(format!(
"expected to find:\n\
{}\n\n\
did not find in output:\n\
{}",
out, actual
))
}
}
MatchKind::PartialN(number) => {
let mut a = actual.lines();
let e = out.lines();
let mut matches = 0;
while let Some(..) = {
if self.diff_lines(a.clone(), e.clone(), true).is_empty() {
matches += 1;
}
a.next()
} {}
if matches == number {
Ok(())
} else {
2018-03-14 15:17:44 +00:00
Err(format!(
"expected to find {} occurrences:\n\
{}\n\n\
did not find in output:\n\
{}",
number, out, actual
))
}
}
MatchKind::NotPresent => {
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;
}
}
if diffs.is_empty() {
2018-03-14 15:17:44 +00:00
Err(format!(
"expected not to find:\n\
{}\n\n\
but found in output:\n\
{}",
out, actual
))
} else {
Ok(())
}
}
MatchKind::Unordered => {
let mut a = actual.lines().collect::<Vec<_>>();
let e = out.lines();
for e_line in e {
match a.iter().position(|a_line| lines_match(e_line, a_line)) {
Some(index) => a.remove(index),
None => {
return Err(format!(
"Did not find expected line:\n\
{}\n\
Remaining available output:\n\
{}\n",
e_line,
a.join("\n")
))
}
};
}
if !a.is_empty() {
2018-04-22 00:21:42 +00:00
Err(format!(
"Output included extra lines:\n\
{}\n",
a.join("\n")
))
} else {
Ok(())
}
}
2016-11-09 15:11:16 +00:00
}
}
fn match_json(&self, expected: &Value, line: &str) -> MatchResult {
let actual = match line.parse() {
2018-03-14 15:17:44 +00:00
Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)),
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",
serde_json::to_string_pretty(expected).unwrap(),
serde_json::to_string_pretty(&actual).unwrap(),
serde_json::to_string_pretty(expected_part).unwrap(),
serde_json::to_string_pretty(actual_part).unwrap(),
)),
None => Ok(()),
}
}
2018-03-14 15:17:44 +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
});
2018-03-14 15:17:44 +00:00
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))
}
2018-03-14 15:17:44 +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"),
}).collect()
}
}
impl Drop for Execs {
fn drop(&mut self) {
if !self.ran {
2018-08-28 22:12:13 +00:00
panic!("forgot to run this command");
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum MatchKind {
Exact,
Partial,
PartialN(usize),
NotPresent,
Unordered,
}
2018-05-24 04:09:27 +00:00
/// Compare a line with an expected pattern.
/// - Use `[..]` as a wildcard to match 0 or more characters on the same line
/// (similar to `.*` in a regex).
/// - Use `[EXE]` to optionally add `.exe` on Windows (empty string on other
/// platforms).
/// - There is a wide range of macros (such as `[COMPILING]` or `[WARNING]`)
/// to match cargo's "status" output and allows you to ignore the alignment.
/// See `substitute_macros` for a complete list of macros.
pub fn lines_match(expected: &str, actual: &str) -> bool {
// Let's not deal with / vs \ (windows...)
let expected = expected.replace("\\", "/");
let mut actual: &str = &actual.replace("\\", "/");
let expected = substitute_macros(&expected);
for (i, part) in expected.split("[..]").enumerate() {
match actual.find(part) {
Some(j) => {
if i == 0 && j != 0 {
2018-03-14 15:17:44 +00:00
return false;
}
actual = &actual[j + part.len()..];
}
2018-03-14 15:17:44 +00:00
None => return false,
2014-04-02 23:34:19 +00:00
}
}
actual.is_empty() || expected.ends_with("[..]")
2014-04-02 23:34:19 +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"));
}
// Compares JSON object for approximate equality.
// You can use `[..]` wildcard in strings (useful for OS dependent things such
// as paths). 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). Arrays are sorted before comparison.
2018-03-14 15:17:44 +00:00
fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> {
use serde_json::Value::*;
match (expected, actual) {
(&Number(ref l), &Number(ref r)) if l == r => None,
(&Bool(l), &Bool(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));
}
let mut l = l.iter().collect::<Vec<_>>();
let mut r = r.iter().collect::<Vec<_>>();
2018-03-14 15:17:44 +00:00
l.retain(
|l| match r.iter().position(|r| find_mismatch(l, r).is_none()) {
Some(i) => {
r.remove(i);
false
}
2018-03-14 15:17:44 +00:00
None => true,
},
);
if !l.is_empty() {
assert!(!r.is_empty());
Some((&l[0], &r[0]))
} else {
assert_eq!(r.len(), 0);
None
}
}
(&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));
}
2018-03-14 15:17:44 +00:00
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,
_ => 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,
}
2018-03-14 15:17:44 +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,
2018-03-14 15:17:44 +00:00
(a, b) => Some((a, b)),
2014-07-21 19:23:01 +00:00
}
}
}
2018-03-14 15:17:44 +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
}
}
impl fmt::Debug for Execs {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "execs")
}
2014-04-02 23:34:19 +00:00
}
2014-07-18 14:40:15 +00:00
pub fn execs() -> Execs {
Execs {
ran: false,
process_builder: None,
expect_stdout: None,
expect_stderr: None,
expect_stdin: None,
expect_exit_code: Some(0),
2016-01-12 17:47:31 +00:00
expect_stdout_contains: Vec::new(),
expect_stderr_contains: Vec::new(),
2018-01-22 20:27:50 +00:00
expect_either_contains: Vec::new(),
expect_stdout_contains_n: Vec::new(),
expect_stdout_not_contains: Vec::new(),
expect_stderr_not_contains: Vec::new(),
expect_stderr_unordered: Vec::new(),
2018-01-22 20:27:50 +00:00
expect_neither_contains: Vec::new(),
expect_json: None,
stream_output: false,
}
2014-04-02 23:34:19 +00:00
}
2014-05-08 23:49:58 +00:00
2014-05-28 01:33:06 +00:00
pub trait Tap {
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
}
}
pub fn basic_manifest(name: &str, version: &str) -> String {
format!(
r#"
[package]
name = "{}"
version = "{}"
authors = []
"#,
name, version
)
}
2014-06-26 22:14:31 +00:00
pub fn basic_bin_manifest(name: &str) -> String {
2018-03-14 15:17:44 +00:00
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 = "{}"
2018-03-14 15:17:44 +00:00
"#,
name, name
)
2014-06-26 22:14:31 +00:00
}
pub fn basic_lib_manifest(name: &str) -> String {
2018-03-14 15:17:44 +00:00
format!(
r#"
[package]
name = "{}"
version = "0.5.0"
authors = ["wycats@example.com"]
2014-08-14 06:08:02 +00:00
[lib]
name = "{}"
2018-03-14 15:17:44 +00:00
"#,
name, name
)
}
pub fn path2url<P: AsRef<Path>>(p: P) -> Url {
Url::from_file_path(p).ok().unwrap()
}
2016-05-10 23:52:02 +00:00
fn substitute_macros(input: &str) -> String {
let macros = [
2018-03-14 15:17:44 +00:00
("[RUNNING]", " Running"),
("[COMPILING]", " Compiling"),
2018-04-19 18:19:11 +00:00
("[CHECKING]", " Checking"),
2018-03-14 15:17:44 +00:00
("[CREATED]", " Created"),
("[FINISHED]", " Finished"),
("[ERROR]", "error:"),
("[WARNING]", "warning:"),
2016-05-10 23:52:02 +00:00
("[DOCUMENTING]", " Documenting"),
2018-03-14 15:17:44 +00:00
("[FRESH]", " Fresh"),
("[UPDATING]", " Updating"),
("[ADDING]", " Adding"),
("[REMOVING]", " Removing"),
("[DOCTEST]", " Doc-tests"),
("[PACKAGING]", " Packaging"),
2016-05-10 23:52:02 +00:00
("[DOWNLOADING]", " Downloading"),
2018-09-14 20:33:18 +00:00
("[DOWNLOADED]", " Downloaded"),
2018-03-14 15:17:44 +00:00
("[UPLOADING]", " Uploading"),
("[VERIFYING]", " Verifying"),
("[ARCHIVING]", " Archiving"),
("[INSTALLING]", " Installing"),
("[REPLACING]", " Replacing"),
("[UNPACKING]", " Unpacking"),
("[SUMMARY]", " Summary"),
("[FIXING]", " Fixing"),
2018-03-14 15:17:44 +00:00
("[EXE]", if cfg!(windows) { ".exe" } else { "" }),
2016-05-10 23:52:02 +00:00
];
let mut result = input.to_owned();
for &(pat, subst) in &macros {
2016-05-10 23:52:02 +00:00
result = result.replace(pat, subst)
}
result
2016-05-10 23:52:02 +00:00
}
pub mod install;
thread_local!(
pub static RUSTC: Rustc = Rustc::new(
PathBuf::from("rustc"),
None,
Path::new("should be path to rustup rustc, but we don't care in tests"),
None,
).unwrap()
);
/// The rustc host such as `x86_64-unknown-linux-gnu`.
pub fn rustc_host() -> String {
RUSTC.with(|r| r.host.clone())
}
pub fn is_nightly() -> bool {
RUSTC.with(|r| r.verbose_version.contains("-nightly") || r.verbose_version.contains("-dev"))
}
pub fn process<T: AsRef<OsStr>>(t: T) -> cargo::util::ProcessBuilder {
_process(t.as_ref())
}
fn _process(t: &OsStr) -> cargo::util::ProcessBuilder {
let mut p = cargo::util::process(t);
p.cwd(&paths::root())
.env_remove("CARGO_HOME")
.env("HOME", paths::home())
.env("CARGO_HOME", paths::home().join(".cargo"))
.env("__CARGO_TEST_ROOT", paths::root())
// Force cargo to think it's on the stable channel for all tests, this
// should hopefully not surprise us as we add cargo features over time and
// cargo rides the trains.
.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
// For now disable incremental by default as support hasn't ridden to the
// stable channel yet. Once incremental support hits the stable compiler we
// can switch this to one and then fix the tests.
.env("CARGO_INCREMENTAL", "0")
// This env var can switch the git backend from libgit2 to git2-curl, which
// can tweak error messages and cause some tests to fail, so let's forcibly
// remove it.
.env_remove("CARGO_HTTP_CHECK_REVOKE")
.env_remove("__CARGO_DEFAULT_LIB_METADATA")
.env_remove("RUSTC")
.env_remove("RUSTDOC")
.env_remove("RUSTC_WRAPPER")
.env_remove("RUSTFLAGS")
.env_remove("XDG_CONFIG_HOME") // see #2345
.env("GIT_CONFIG_NOSYSTEM", "1") // keep trying to sandbox ourselves
.env_remove("EMAIL")
.env_remove("MFLAGS")
.env_remove("MAKEFLAGS")
.env_remove("CARGO_MAKEFLAGS")
.env_remove("GIT_AUTHOR_NAME")
.env_remove("GIT_AUTHOR_EMAIL")
.env_remove("GIT_COMMITTER_NAME")
.env_remove("GIT_COMMITTER_EMAIL")
.env_remove("CARGO_TARGET_DIR") // we assume 'target'
.env_remove("MSYSTEM"); // assume cmd.exe everywhere on windows
p
}
pub trait ChannelChanger: Sized {
fn masquerade_as_nightly_cargo(&mut self) -> &mut Self;
}
impl ChannelChanger for cargo::util::ProcessBuilder {
fn masquerade_as_nightly_cargo(&mut self) -> &mut Self {
self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
}
}
fn split_and_add_args(p: &mut ProcessBuilder, s: &str) {
for arg in s.split_whitespace() {
if arg.contains('"') || arg.contains('\'') {
panic!("shell-style argument parsing is not supported")
}
p.arg(arg);
}
}
pub fn cargo_process(s: &str) -> Execs {
let mut p = process(&cargo_exe());
split_and_add_args(&mut p, s);
execs().with_process_builder(p)
}
2018-08-02 14:27:06 +00:00
pub fn git_process(s: &str) -> ProcessBuilder {
let mut p = process("git");
split_and_add_args(&mut p, s);
p
}
pub fn sleep_ms(ms: u64) {
::std::thread::sleep(Duration::from_millis(ms));
}
2018-11-02 22:18:17 +00:00
/// Returns true if the local filesystem has low-resolution mtimes.
pub fn is_coarse_mtime() -> bool {
// This should actually be a test that $CARGO_TARGET_DIR is on an HFS
// filesystem, (or any filesystem with low-resolution mtimes). However,
// that's tricky to detect, so for now just deal with CI.
cfg!(target_os = "macos") && env::var("CI").is_ok()
}