cargo/tests/testsuite/support/mod.rs

1650 lines
51 KiB
Rust
Raw Normal View History

/*
2019-02-03 04:01:23 +00:00
# Introduction to `support`.
Cargo has a wide variety of integration tests that execute the `cargo` binary
2019-02-03 04:01:23 +00:00
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
2019-02-03 04:01:23 +00:00
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
2019-02-03 04:01:23 +00:00
`/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 `/`.
2019-02-03 04:01:23 +00:00
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
}
2019-02-03 04:01:23 +00:00
/// Adds 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));
}
2019-02-03 04:01:23 +00:00
/// Adds 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
}
2019-02-03 04:01:23 +00:00
/// Creates 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
))
}
2019-02-03 04:01:23 +00:00
/// Changes the contents of an existing file.
pub fn change_file(&self, path: &str, body: &str) {
FileBuilder::new(self.root().join(path), body).mk()
}
2019-02-03 04:01:23 +00:00
/// Creates 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 {
2018-12-06 19:17:36 +00:00
let mut p = crate::support::process(program);
p.cwd(self.root());
2018-08-28 20:38:26 +00:00
execs().with_process_builder(p)
}
2019-02-03 04:01:23 +00:00
/// Creates a `ProcessBuilder` to run cargo.
2018-05-24 04:09:27 +00:00
/// 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
}
/// Safely run a process after `cargo build`.
///
/// Windows has a problem where a process cannot be reliably
/// be replaced, removed, or renamed immediately after executing it.
/// The action may fail (with errors like Access is denied), or
/// it may succeed, but future attempts to use the same filename
/// will fail with "Already Exists".
///
/// If you have a test that needs to do `cargo run` multiple
/// times, you should instead use `cargo build` and use this
/// method to run the executable. Each time you call this,
/// use a new name for `dst`.
2019-02-03 04:01:23 +00:00
/// See rust-lang/cargo#5481.
2019-01-18 16:39:25 +00:00
pub fn rename_run(&self, src: &str, dst: &str) -> Execs {
let src = self.bin(src);
let dst = self.bin(dst);
fs::rename(&src, &dst)
.unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
self.process(dst)
}
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
})
2018-12-08 11:19:47 +00:00
})
.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>>,
expect_json_contains_unordered: 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stdout is equal to the given lines.
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stderr is equal to the given lines.
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies 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
}
2019-02-03 04:01:23 +00:00
/// Removes 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stdout contains the given contiguous lines somewhere in
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stderr contains the given contiguous lines somewhere in
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that either stdout or stderr contains the given contiguous
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stdout contains the given contiguous lines somewhere in
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stdout does not contain the given contiguous lines.
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that stderr does not contain the given contiguous lines.
2018-05-24 04:09:27 +00:00
/// See `lines_match` for supported patterns.
///
/// Care should be taken when using this method because there is a
2019-02-03 04:01:23 +00:00
/// limitless number of possible things that *won't* appear. A typo means
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies that all of the stderr output is equal to the given lines,
2018-05-24 04:09:27 +00:00
/// 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
2019-02-03 04:01:23 +00:00
/// do anything like longest-match. For example, avoid something like:
///
2018-05-24 04:09:27 +00:00
/// [RUNNING] `rustc [..]
/// [RUNNING] `rustc --crate-name foo [..]
2019-02-03 04:01:23 +00:00
///
2018-05-24 04:09:27 +00:00
/// 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
}
2019-02-03 04:01:23 +00:00
/// Verifies the JSON output matches the given JSON.
2018-05-24 04:09:27 +00:00
/// 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(|line| line.parse().expect("line to be a valid JSON value"))
2018-03-14 15:17:44 +00:00
.collect(),
);
self
}
2019-02-03 04:01:23 +00:00
/// Verifies JSON output contains the given objects (in any order) somewhere
/// in its output.
///
/// CAUTION: Be very careful when using this. Make sure every object is
/// unique (not a subset of one another). Also avoid using objects that
/// could possibly match multiple output lines unless you're very sure of
/// what you are doing.
///
/// See `with_json` for more detail.
pub fn with_json_contains_unordered(&mut self, expected: &str) -> &mut Self {
self.expect_json_contains_unordered.extend(
expected
.split("\n\n")
.map(|line| line.parse().expect("line to be a valid JSON value")),
);
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 verify_checks_output(&self, output: &Output) {
if self.expect_exit_code.unwrap_or(0) != 0
&& self.expect_stdout.is_none()
&& self.expect_stdin.is_none()
&& self.expect_stderr.is_none()
&& self.expect_stdout_contains.is_empty()
&& self.expect_stderr_contains.is_empty()
&& self.expect_either_contains.is_empty()
&& self.expect_stdout_contains_n.is_empty()
&& self.expect_stdout_not_contains.is_empty()
&& self.expect_stderr_not_contains.is_empty()
&& self.expect_stderr_unordered.is_empty()
&& self.expect_neither_contains.is_empty()
&& self.expect_json.is_none()
&& self.expect_json_contains_unordered.is_empty()
{
panic!(
"`with_status()` is used, but no output is checked.\n\
The test must check the output to ensure the correct error is triggered.\n\
--- stdout\n{}\n--- stderr\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
}
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)),
true,
2018-08-28 08:07:33 +00:00
)
} 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.verify_checks_output(actual);
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
}
}
if !self.expect_json_contains_unordered.is_empty() {
let stdout = str::from_utf8(&actual.stdout)
.map_err(|_| "stdout was not utf8 encoded".to_owned())?;
let mut lines = stdout
.lines()
.filter(|line| line.starts_with('{'))
.collect::<Vec<_>>();
for obj in &self.expect_json_contains_unordered {
match lines
.iter()
.position(|line| self.match_json(obj, line).is_ok())
{
Some(index) => lines.remove(index),
None => {
return Err(format!(
"Did not find expected JSON:\n\
{}\n\
Remaining available output:\n\
{}\n",
serde_json::to_string_pretty(obj).unwrap(),
lines.join("\n")
));
}
};
}
}
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(),
2018-12-08 11:19:47 +00:00
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.
2018-12-08 11:19:47 +00:00
let replaced =
replaced.replace("[ROOT]", if cfg!(windows) { r#"[..]:\"# } else { "/" });
replaced
2018-12-08 11:19:47 +00:00
}
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,
};
2019-02-03 04:01:23 +00:00
// 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")
2018-12-12 21:20:44 +00:00
));
}
};
}
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,
};
find_json_mismatch(expected, &actual)
}
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"),
2018-12-08 11:19:47 +00:00
})
.collect()
}
}
impl Drop for Execs {
fn drop(&mut self) {
if !self.ran && !std::thread::panicking() {
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,
}
2019-02-03 04:01:23 +00:00
/// Compares a line with an expected pattern.
2018-05-24 04:09:27 +00:00
/// - 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.
/// - `[ROOT]` is `/` or `[..]:\` on Windows.
/// - `[CWD]` is the working directory of the process that was run.
pub fn lines_match(expected: &str, actual: &str) -> bool {
// Let's not deal with / vs \ (windows...)
2018-11-18 09:08:15 +00:00
// First replace backslash-escaped backslashes with forward slashes
// which can occur in, for example, JSON output
let expected = expected.replace("\\\\", "/").replace("\\", "/");
let mut actual: &str = &actual.replace("\\\\", "/").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.
2019-02-03 04:01:23 +00:00
/// 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
2019-02-03 04:01:23 +00:00
/// (e.g., rustc) rather than Cargo itself). Arrays are sorted before comparison.
pub fn find_json_mismatch(expected: &Value, actual: &Value) -> Result<(), String> {
match find_json_mismatch_r(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(()),
}
}
fn find_json_mismatch_r<'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_json_mismatch_r(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_json_mismatch_r(l, r))
2018-03-14 15:17:44 +00:00
.nth(0)
}
(&Null, &Null) => None,
2019-02-03 04:01:23 +00:00
// Magic string literal `"{...}"` acts as wildcard for any sub-JSON.
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
(&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 {
2018-12-06 19:21:24 +00:00
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,
expect_json_contains_unordered: Vec::new(),
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"),
2019-03-16 21:48:30 +00:00
("[EXE]", env::consts::EXE_SUFFIX),
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())
2018-12-08 11:19:47 +00:00
.env_remove("CARGO_HOME")
.env("HOME", paths::home())
.env("CARGO_HOME", paths::home().join(".cargo"))
.env("__CARGO_TEST_ROOT", paths::root())
2019-02-03 04:01:23 +00:00
// Force Cargo to think it's on the stable channel for all tests, this
2018-12-08 11:19:47 +00:00
// 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
2019-02-03 04:01:23 +00:00
/// Returns `true` if the local filesystem has low-resolution mtimes.
2018-11-02 22:18:17 +00:00
pub fn is_coarse_mtime() -> bool {
2019-01-13 14:31:22 +00:00
// If the filetime crate is being used to emulate HFS then
2019-02-03 04:01:23 +00:00
// return `true`, without looking at the actual hardware.
2019-01-13 14:31:22 +00:00
cfg!(emulate_second_only_system) ||
2019-02-03 04:01:23 +00:00
// This should actually be a test that `$CARGO_TARGET_DIR` is on an HFS
2018-11-02 22:18:17 +00:00
// 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()
}
/// Some CI setups are much slower then the equipment used by Cargo itself.
/// Architectures that do not have a modern processor, hardware emulation, ect.
/// This provides a way for those setups to increase the cut off for all the time based test.
pub fn slow_cpu_multiplier(main: u64) -> Duration {
lazy_static::lazy_static! {
static ref SLOW_CPU_MULTIPLIER: u64 =
env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER").ok().and_then(|m| m.parse().ok()).unwrap_or(1);
}
Duration::from_secs(*SLOW_CPU_MULTIPLIER * main)
}