cargo/crates/cargo-test-support/src/lib.rs

1799 lines
57 KiB
Rust
Raw Normal View History

2020-09-18 20:17:58 +00:00
//! # Cargo test support.
//!
//! See https://rust-lang.github.io/cargo/contrib/ for a guide on writing tests.
2021-04-13 16:02:07 +00:00
#![allow(clippy::all)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::redundant_clone)]
2015-02-06 07:27:53 +00:00
use std::env;
use std::ffi::OsStr;
use std::fmt::Write;
use std::fs;
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};
2021-06-11 19:37:27 +00:00
use anyhow::{bail, format_err, Result};
2021-03-20 19:04:25 +00:00
use cargo_util::{is_ci, ProcessBuilder, ProcessError};
2018-04-22 00:21:42 +00:00
use serde_json::{self, Value};
use url::Url;
use self::paths::CargoPathExt;
#[macro_export]
macro_rules! t {
2018-04-22 00:21:42 +00:00
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => $crate::panic_error(&format!("failed running {}", stringify!($e)), e),
2018-04-22 00:21:42 +00:00
}
};
}
#[track_caller]
pub fn panic_error(what: &str, err: impl Into<anyhow::Error>) -> ! {
let err = err.into();
pe(what, err);
#[track_caller]
fn pe(what: &str, err: anyhow::Error) -> ! {
let mut result = format!("{}\nerror: {}", what, err);
for cause in err.chain().skip(1) {
drop(writeln!(result, "\nCaused by:"));
drop(write!(result, "{}", cause));
}
panic!("\n{}", result);
}
}
pub use cargo_test_macro::cargo_test;
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;
pub mod tools;
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();
fs::write(&self.path, &self.body)
2018-03-14 15:17:44 +00:00
.unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e));
}
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,
src_is_dir: bool,
}
impl SymlinkBuilder {
pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
SymlinkBuilder {
dst,
src,
src_is_dir: false,
}
}
pub fn new_dir(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
SymlinkBuilder {
dst,
src,
src_is_dir: true,
}
}
#[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();
if self.src_is_dir {
t!(os::windows::fs::symlink_dir(&self.dst, &self.src));
} else {
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));
}
/// Adds a symlink to a file 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
}
/// Create a symlink to a directory
pub fn symlink_dir<T: AsRef<Path>>(mut self, dst: T, src: T) -> Self {
self.symlinks.push(SymlinkBuilder::new_dir(
self.root.root().join(dst),
self.root.root().join(src),
));
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 {
self.target_debug_dir()
.join("examples")
.join(paths::get_lib_filename(name, kind))
2017-01-19 20:43:56 +00:00
}
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-03-26 20:21:05 +00:00
/// Returns an iterator of paths matching the glob pattern, which is
/// relative to the project root.
pub fn glob<P: AsRef<Path>>(&self, pattern: P) -> glob::Paths {
let pattern = self.root().join(pattern);
glob::glob(pattern.to_str().expect("failed to convert pattern to str"))
.expect("failed to glob")
}
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 {
let mut p = 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 {
let full = self.root().join(path);
fs::read_to_string(&full)
.unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e))
2016-08-25 08:34:25 +00:00
}
2018-05-24 04:09:27 +00:00
/// Modifies `Cargo.toml` to remove all commented lines.
pub fn uncomment_root_manifest(&self) {
let contents = self.read_file("Cargo.toml").replace("#", "");
fs::write(self.root().join("Cargo.toml"), contents).unwrap();
}
pub fn symlink(&self, src: impl AsRef<Path>, dst: impl AsRef<Path>) {
let src = self.root().join(src.as_ref());
let dst = self.root().join(dst.as_ref());
#[cfg(unix)]
{
if let Err(e) = os::unix::fs::symlink(&src, &dst) {
panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
}
}
#[cfg(windows)]
{
if src.is_dir() {
if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
}
} else {
if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
}
}
}
}
}
// 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!(");
2019-03-27 00:53:53 +00:00
buf.push_str(println);
2014-06-12 22:51:16 +00:00
buf.push_str("); }\n");
2019-03-27 01:51:13 +00:00
buf
2014-06-12 22:51:16 +00:00
}
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
/// This is the raw output from the process.
///
/// This is similar to `std::process::Output`, however the `status` is
/// translated to the raw `code`. This is necessary because `ProcessError`
/// does not have access to the raw `ExitStatus` because `ProcessError` needs
/// to be serializable (for the Rustc cache), and `ExitStatus` does not
/// provide a constructor.
pub struct RawOutput {
pub code: Option<i32>,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
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_stderr_with_without: Vec<(Vec<String>, Vec<String>)>,
2019-07-18 15:35:25 +00:00
expect_json: Option<Vec<String>>,
expect_json_contains_unordered: Vec<String>,
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
}
/// Verify that a particular line appears in stderr with and without the
/// given substrings. Exactly one line must match.
///
/// The substrings are matched as `contains`. Example:
///
/// ```no_run
/// execs.with_stderr_line_without(
/// &[
/// "[RUNNING] `rustc --crate-name build_script_build",
/// "-C opt-level=3",
/// ],
/// &["-C debuginfo", "-C incremental"],
/// )
/// ```
///
/// This will check that a build line includes `-C opt-level=3` but does
/// not contain `-C debuginfo` or `-C incremental`.
///
/// Be careful writing the `without` fragments, see note in
/// `with_stderr_does_not_contain`.
pub fn with_stderr_line_without<S: ToString>(
&mut self,
with: &[S],
without: &[S],
) -> &mut Self {
let with = with.iter().map(|s| s.to_string()).collect();
let without = without.iter().map(|s| s.to_string()).collect();
self.expect_stderr_with_without.push((with, without));
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")
2019-07-18 15:35:25 +00:00
.map(|line| line.to_string())
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 {
2019-07-18 15:35:25 +00:00
self.expect_json_contains_unordered
.extend(expected.split("\n\n").map(|line| line.to_string()));
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 {
if let Some(cwd) = p.get_cwd() {
let new_path = cwd.join(path.as_ref());
p.cwd(new_path);
} else {
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
}
2021-06-11 19:37:27 +00:00
pub fn exec_with_output(&mut self) -> Result<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 enable_mac_dsym(&mut self) -> &mut Self {
if cfg!(target_os = "macos") {
self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
.env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
.env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
.env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
}
self
}
#[track_caller]
pub fn run(&mut self) {
self.ran = true;
let p = (&self.process_builder).clone().unwrap();
if let Err(e) = self.match_process(&p) {
panic_error(&format!("test failed running {}", p), e);
}
}
2018-08-28 08:07:33 +00:00
/// Runs the process, checks the expected output, and returns the first
/// JSON object on stdout.
#[track_caller]
pub fn run_json(&mut self) -> serde_json::Value {
self.ran = true;
let p = (&self.process_builder).clone().unwrap();
match self.match_process(&p) {
Err(e) => panic_error(&format!("test failed running {}", p), e),
Ok(output) => serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
panic!(
"\nfailed to parse JSON: {}\n\
output was:\n{}\n",
e,
String::from_utf8_lossy(&output.stdout)
);
}),
}
}
#[track_caller]
pub fn run_output(&mut self, output: &Output) {
self.ran = true;
if let Err(e) = self.match_output(output) {
panic_error("process did not return the expected result", e)
}
}
fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) {
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_stderr_with_without.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(stdout),
String::from_utf8_lossy(stderr),
);
}
}
fn match_process(&self, process: &ProcessBuilder) -> Result<RawOutput> {
2018-08-28 08:07:33 +00:00
println!("running {}", process);
let res = if self.stream_output {
2019-07-25 04:48:15 +00:00
if is_ci() {
2018-08-28 08:07:33 +00:00
panic!("`.stream()` is for local debugging")
}
process.exec_with_streaming(
2019-05-20 19:36:21 +00:00
&mut |out| {
println!("{}", out);
Ok(())
},
&mut |err| {
eprintln!("{}", err);
Ok(())
},
true,
2018-08-28 08:07:33 +00:00
)
} else {
process.exec_with_output()
};
match res {
Ok(out) => {
self.match_output(&out)?;
return Ok(RawOutput {
stdout: out.stdout,
stderr: out.stderr,
code: out.status.code(),
});
}
2018-08-28 08:07:33 +00:00
Err(e) => {
if let Some(ProcessError {
stdout: Some(stdout),
stderr: Some(stderr),
code,
..
}) = e.downcast_ref::<ProcessError>()
{
self.match_status(*code, stdout, stderr)
.and(self.match_stdout(stdout, stderr))
.and(self.match_stderr(stdout, stderr))?;
return Ok(RawOutput {
stdout: stdout.to_vec(),
stderr: stderr.to_vec(),
code: *code,
});
2018-08-28 08:07:33 +00:00
}
2021-06-11 19:37:27 +00:00
bail!("could not exec process {}: {:?}", process, e)
2018-08-28 08:07:33 +00:00
}
}
}
2021-06-11 19:37:27 +00:00
fn match_output(&self, actual: &Output) -> Result<()> {
self.match_status(actual.status.code(), &actual.stdout, &actual.stderr)
.and(self.match_stdout(&actual.stdout, &actual.stderr))
.and(self.match_stderr(&actual.stdout, &actual.stderr))
}
2021-06-11 19:37:27 +00:00
fn match_status(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
self.verify_checks_output(stdout, stderr);
match self.expect_exit_code {
None => Ok(()),
Some(expected) if code == Some(expected) => Ok(()),
Some(expected) => bail!(
"process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
code.unwrap_or(-1),
expected,
2021-04-13 16:02:07 +00:00
String::from_utf8_lossy(stdout),
String::from_utf8_lossy(stderr)
2021-06-11 19:37:27 +00:00
),
}
}
2021-06-11 19:37:27 +00:00
fn match_stdout(&self, stdout: &[u8], stderr: &[u8]) -> Result<()> {
2018-03-14 15:17:44 +00:00
self.match_std(
self.expect_stdout.as_ref(),
stdout,
2018-03-14 15:17:44 +00:00
"stdout",
stderr,
2018-03-14 15:17:44 +00:00
MatchKind::Exact,
)?;
for expect in self.expect_stdout_contains.iter() {
self.match_std(Some(expect), stdout, "stdout", stderr, MatchKind::Partial)?;
}
2016-01-12 17:47:31 +00:00
for expect in self.expect_stderr_contains.iter() {
self.match_std(Some(expect), stderr, "stderr", 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(
2019-03-27 00:53:53 +00:00
Some(expect),
stdout,
2018-03-14 15:17:44 +00:00
"stdout",
stderr,
2018-03-14 15:17:44 +00:00
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),
stdout,
2018-03-14 15:17:44 +00:00
"stdout",
stderr,
2018-03-14 15:17:44 +00:00
MatchKind::NotPresent,
)?;
}
for expect in self.expect_stderr_not_contains.iter() {
2018-03-14 15:17:44 +00:00
self.match_std(
Some(expect),
stderr,
2018-03-14 15:17:44 +00:00
"stderr",
stdout,
2018-03-14 15:17:44 +00:00
MatchKind::NotPresent,
)?;
2016-01-12 17:47:31 +00:00
}
for expect in self.expect_stderr_unordered.iter() {
self.match_std(Some(expect), stderr, "stderr", 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),
stdout,
2018-03-14 15:17:44 +00:00
"stdout",
stdout,
2018-03-14 15:17:44 +00:00
MatchKind::NotPresent,
)?;
self.match_std(
Some(expect),
stderr,
2018-03-14 15:17:44 +00:00
"stderr",
stderr,
2018-03-14 15:17:44 +00:00
MatchKind::NotPresent,
)?;
2018-01-22 20:27:50 +00:00
}
for expect in self.expect_either_contains.iter() {
let match_std =
self.match_std(Some(expect), stdout, "stdout", stdout, MatchKind::Partial);
let match_err =
self.match_std(Some(expect), stderr, "stderr", stderr, MatchKind::Partial);
2018-01-22 20:27:50 +00:00
if let (Err(_), Err(_)) = (match_std, match_err) {
2021-06-11 19:37:27 +00:00
bail!(
2018-03-14 15:17:44 +00:00
"expected to find:\n\
{}\n\n\
did not find in either output.",
expect
2021-06-11 19:37:27 +00:00
);
2018-01-22 20:27:50 +00:00
}
}
for (with, without) in self.expect_stderr_with_without.iter() {
self.match_with_without(stderr, with, without)?;
}
2016-08-11 21:47:49 +00:00
if let Some(ref objects) = self.expect_json {
let stdout =
2021-06-11 19:37:27 +00:00
str::from_utf8(stdout).map_err(|_| format_err!("stdout was not utf8 encoded"))?;
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() {
2021-06-11 19:37:27 +00:00
bail!(
2018-03-14 15:17:44 +00:00
"expected {} json lines, got {}, stdout:\n{}",
objects.len(),
lines.len(),
stdout
2021-06-11 19:37:27 +00:00
);
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 =
2021-06-11 19:37:27 +00:00
str::from_utf8(stdout).map_err(|_| format_err!("stdout was not utf8 encoded"))?;
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 => {
2021-06-11 19:37:27 +00:00
bail!(
"Did not find expected JSON:\n\
{}\n\
Remaining available output:\n\
{}\n",
serde_json::to_string_pretty(obj).unwrap(),
lines.join("\n")
2021-06-11 19:37:27 +00:00
);
}
};
}
}
Ok(())
}
2021-06-11 19:37:27 +00:00
fn match_stderr(&self, stdout: &[u8], stderr: &[u8]) -> Result<()> {
2018-03-14 15:17:44 +00:00
self.match_std(
self.expect_stderr.as_ref(),
stderr,
2018-03-14 15:17:44 +00:00
"stderr",
stdout,
2018-03-14 15:17:44 +00:00
MatchKind::Exact,
)
}
2021-06-11 19:37:27 +00:00
fn normalize_actual(&self, description: &str, actual: &[u8]) -> Result<String> {
let actual = match str::from_utf8(actual) {
2021-06-11 19:37:27 +00:00
Err(..) => bail!("{} was not utf8 encoded", description),
Ok(actual) => actual,
};
2019-07-18 15:35:25 +00:00
Ok(self.normalize_matcher(actual))
}
fn normalize_matcher(&self, matcher: &str) -> String {
2019-11-30 21:55:52 +00:00
normalize_matcher(
matcher,
self.process_builder.as_ref().and_then(|p| p.get_cwd()),
)
}
2018-03-14 15:17:44 +00:00
fn match_std(
&self,
expected: Option<&String>,
actual: &[u8],
description: &str,
extra: &[u8],
kind: MatchKind,
2021-06-11 19:37:27 +00:00
) -> Result<()> {
let out = match expected {
2019-07-18 15:35:25 +00:00
Some(out) => self.normalize_matcher(out),
None => return Ok(()),
};
let actual = self.normalize_actual(description, actual)?;
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 {
2021-06-11 19:37:27 +00:00
bail!(
"{} did not match:\n\
2018-03-14 15:17:44 +00:00
{}\n\n\
other output:\n\
`{}`",
description,
2018-03-14 15:17:44 +00:00
diffs.join("\n"),
String::from_utf8_lossy(extra)
2021-06-11 19:37:27 +00:00
)
}
}
MatchKind::Partial => {
let mut a = actual.lines();
let e = out.lines();
let mut diffs = self.diff_lines(a.clone(), e.clone(), true);
2019-05-20 19:36:21 +00:00
while a.next().is_some() {
let a = self.diff_lines(a.clone(), e.clone(), true);
if a.len() < diffs.len() {
diffs = a;
}
}
if diffs.is_empty() {
Ok(())
} else {
2021-06-11 19:37:27 +00:00
bail!(
2018-03-14 15:17:44 +00:00
"expected to find:\n\
{}\n\n\
did not find in output:\n\
{}",
2021-06-11 19:37:27 +00:00
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 {
2021-06-11 19:37:27 +00:00
bail!(
2018-03-14 15:17:44 +00:00
"expected to find {} occurrences:\n\
{}\n\n\
did not find in output:\n\
{}",
2021-06-11 19:37:27 +00:00
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);
2019-05-20 19:36:21 +00:00
while a.next().is_some() {
let a = self.diff_lines(a.clone(), e.clone(), true);
if a.len() < diffs.len() {
diffs = a;
}
}
if diffs.is_empty() {
2021-06-11 19:37:27 +00:00
bail!(
2018-03-14 15:17:44 +00:00
"expected not to find:\n\
{}\n\n\
but found in output:\n\
{}",
2021-06-11 19:37:27 +00:00
out,
actual
)
} else {
Ok(())
}
}
MatchKind::Unordered => lines_match_unordered(&out, &actual),
2016-11-09 15:11:16 +00:00
}
}
2021-06-11 19:37:27 +00:00
fn match_with_without(&self, actual: &[u8], with: &[String], without: &[String]) -> Result<()> {
let actual = self.normalize_actual("stderr", actual)?;
let contains = |s, line| {
2019-07-18 15:35:25 +00:00
let mut s = self.normalize_matcher(s);
s.insert_str(0, "[..]");
s.push_str("[..]");
lines_match(&s, line)
};
let matches: Vec<&str> = actual
.lines()
.filter(|line| with.iter().all(|with| contains(with, line)))
.filter(|line| !without.iter().any(|without| contains(without, line)))
.collect();
match matches.len() {
2021-06-11 19:37:27 +00:00
0 => bail!(
"Could not find expected line in output.\n\
With contents: {:?}\n\
Without contents: {:?}\n\
Actual stderr:\n\
{}\n",
2021-06-11 19:37:27 +00:00
with,
without,
actual
),
1 => Ok(()),
2021-06-11 19:37:27 +00:00
_ => bail!(
"Found multiple matching lines, but only expected one.\n\
With contents: {:?}\n\
Without contents: {:?}\n\
Matching lines:\n\
{}\n",
with,
without,
matches.join("\n")
2021-06-11 19:37:27 +00:00
),
}
}
2021-06-11 19:37:27 +00:00
fn match_json(&self, expected: &str, line: &str) -> Result<()> {
let actual = match line.parse() {
2021-06-11 19:37:27 +00:00
Err(e) => bail!("invalid json, {}:\n`{}`", e, line),
2018-03-14 15:17:44 +00:00
Ok(actual) => actual,
};
2019-07-18 15:35:25 +00:00
let expected = match expected.parse() {
2021-06-11 19:37:27 +00:00
Err(e) => bail!("invalid json, {}:\n`{}`", e, line),
2019-07-18 15:35:25 +00:00
Ok(expected) => expected,
};
2021-03-25 19:56:24 +00:00
let cwd = self.process_builder.as_ref().and_then(|p| p.get_cwd());
find_json_mismatch(&expected, &actual, cwd)
}
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)) => {
2019-03-27 00:53:53 +00:00
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). It is non-greedy.
2018-05-24 04:09:27 +00:00
/// - 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.
2019-07-18 15:35:25 +00:00
/// - `[ROOT]` the path to the test directory's root
/// - `[CWD]` is the working directory of the process that was run.
2019-07-18 15:35:25 +00:00
pub fn lines_match(expected: &str, mut actual: &str) -> bool {
2019-08-14 09:36:35 +00:00
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
}
2021-06-11 19:37:27 +00:00
pub fn lines_match_unordered(expected: &str, actual: &str) -> Result<()> {
let mut a = actual.lines().collect::<Vec<_>>();
// match more-constrained lines first, although in theory we'll
// need some sort of recursive match here. This handles the case
// that you expect "a\n[..]b" and two lines are printed out,
// "ab\n"a", where technically we do match unordered but a naive
// search fails to find this. This simple sort at least gets the
// test suite to pass for now, but we may need to get more fancy
// if tests start failing again.
a.sort_by_key(|s| s.len());
let mut failures = Vec::new();
for e_line in expected.lines() {
match a.iter().position(|a_line| lines_match(e_line, a_line)) {
Some(index) => {
a.remove(index);
}
None => failures.push(e_line),
}
}
if !failures.is_empty() {
2021-06-11 19:37:27 +00:00
bail!(
"Did not find expected line(s):\n{}\n\
Remaining available output:\n{}\n",
failures.join("\n"),
a.join("\n")
2021-06-11 19:37:27 +00:00
);
}
if !a.is_empty() {
2021-06-11 19:37:27 +00:00
bail!(
"Output included extra lines:\n\
{}\n",
a.join("\n")
2021-06-11 19:37:27 +00:00
)
} else {
Ok(())
}
}
2019-11-30 21:55:52 +00:00
/// Variant of `lines_match` that applies normalization to the strings.
pub fn normalized_lines_match(expected: &str, actual: &str, cwd: Option<&Path>) -> bool {
let expected = normalize_matcher(expected, cwd);
let actual = normalize_matcher(actual, cwd);
lines_match(&expected, &actual)
}
fn normalize_matcher(matcher: &str, cwd: Option<&Path>) -> String {
// Let's not deal with / vs \ (windows...)
let matcher = matcher.replace("\\\\", "/").replace("\\", "/");
// Weirdness for paths on Windows extends beyond `/` vs `\` apparently.
// Namely paths like `c:\` and `C:\` are equivalent and that can cause
// issues. The return value of `env::current_dir()` may return a
// lowercase drive name, but we round-trip a lot of values through `Url`
// which will auto-uppercase the drive name. To just ignore this
// distinction we try to canonicalize as much as possible, taking all
// forms of a path and canonicalizing them to one.
let replace_path = |s: &str, path: &Path, with: &str| {
let path_through_url = Url::from_file_path(path).unwrap().to_file_path().unwrap();
let path1 = path.display().to_string().replace("\\", "/");
let path2 = path_through_url.display().to_string().replace("\\", "/");
s.replace(&path1, with)
.replace(&path2, with)
.replace(with, &path1)
};
// Do the template replacements on the expected string.
let matcher = match cwd {
None => matcher,
Some(p) => replace_path(&matcher, p, "[CWD]"),
};
// Similar to cwd above, perform similar treatment to the root path
// which in theory all of our paths should otherwise get rooted at.
let root = paths::root();
let matcher = replace_path(&matcher, &root, "[ROOT]");
// Let's not deal with \r\n vs \n on windows...
let matcher = matcher.replace("\r", "");
// It's easier to read tabs in outputs if they don't show up as literal
// hidden characters
matcher.replace("\t", "<tab>")
}
#[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
/// (e.g., rustc) rather than Cargo itself).
2021-06-11 19:37:27 +00:00
pub fn find_json_mismatch(expected: &Value, actual: &Value, cwd: Option<&Path>) -> Result<()> {
2021-03-25 19:56:24 +00:00
match find_json_mismatch_r(expected, actual, cwd) {
2021-06-11 19:37:27 +00:00
Some((expected_part, actual_part)) => bail!(
"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(),
2021-06-11 19:37:27 +00:00
),
None => Ok(()),
}
}
fn find_json_mismatch_r<'a>(
expected: &'a Value,
actual: &'a Value,
2021-03-25 19:56:24 +00:00
cwd: Option<&Path>,
) -> 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,
2021-03-25 19:56:24 +00:00
(&String(ref l), _) if l == "{...}" => None,
(&String(ref l), &String(ref r)) => {
let normalized = normalize_matcher(r, cwd);
if lines_match(l, &normalized) {
None
} else {
Some((expected, actual))
}
}
(&Array(ref l), &Array(ref r)) => {
if l.len() != r.len() {
return Some((expected, actual));
}
l.iter()
.zip(r.iter())
2021-03-25 19:56:24 +00:00
.filter_map(|(l, r)| find_json_mismatch_r(l, r, cwd))
.next()
}
(&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())
2021-03-25 19:56:24 +00:00
.filter_map(|(l, r)| find_json_mismatch_r(l, r, cwd))
2020-01-17 11:19:12 +00:00
.next()
}
(&Null, &Null) => None,
2019-02-03 04:01:23 +00:00
// Magic string literal `"{...}"` acts as wildcard for any sub-JSON.
_ => 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
}
}
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_stderr_with_without: 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
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"),
2019-09-20 21:11:49 +00:00
("[COMPLETED]", " Completed"),
2018-03-14 15:17:44 +00:00
("[CREATED]", " Created"),
("[FINISHED]", " Finished"),
("[ERROR]", "error:"),
("[WARNING]", "warning:"),
2020-02-18 02:46:48 +00:00
("[NOTE]", "note:"),
2020-10-14 02:01:12 +00:00
("[HELP]", "help:"),
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"),
("[FIXED]", " Fixed"),
("[FIXING]", " Fixing"),
("[EXE]", env::consts::EXE_SUFFIX),
2019-03-31 00:33:35 +00:00
("[IGNORED]", " Ignored"),
("[INSTALLED]", " Installed"),
("[REPLACED]", " Replaced"),
("[BUILDING]", " Building"),
2020-12-01 19:16:16 +00:00
("[LOGIN]", " Login"),
("[LOGOUT]", " Logout"),
("[YANK]", " Yank"),
("[OWNER]", " Owner"),
("[MIGRATING]", " Migrating"),
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;
struct RustcInfo {
verbose_version: String,
host: String,
}
impl RustcInfo {
fn new() -> RustcInfo {
let output = ProcessBuilder::new("rustc")
.arg("-vV")
.exec_with_output()
.expect("rustc should exec");
let verbose_version = String::from_utf8(output.stdout).expect("utf8 output");
let host = verbose_version
.lines()
.filter_map(|line| line.strip_prefix("host: "))
.next()
.expect("verbose version has host: field")
.to_string();
RustcInfo {
verbose_version,
host,
}
}
}
lazy_static::lazy_static! {
static ref RUSTC_INFO: RustcInfo = RustcInfo::new();
}
/// The rustc host such as `x86_64-unknown-linux-gnu`.
pub fn rustc_host() -> &'static str {
&RUSTC_INFO.host
}
pub fn is_nightly() -> bool {
let vv = &RUSTC_INFO.verbose_version;
env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
&& (vv.contains("-nightly") || vv.contains("-dev"))
}
2021-03-20 18:28:38 +00:00
pub fn process<T: AsRef<OsStr>>(t: T) -> ProcessBuilder {
_process(t.as_ref())
}
2021-03-20 18:28:38 +00:00
fn _process(t: &OsStr) -> ProcessBuilder {
let mut p = ProcessBuilder::new(t);
// In general just clear out all cargo-specific configuration already in the
// environment. Our tests all assume a "default configuration" unless
// specified otherwise.
for (k, _v) in env::vars() {
if k.starts_with("CARGO_") {
p.env_remove(&k);
}
}
if env::var_os("RUSTUP_TOOLCHAIN").is_some() {
// Override the PATH to avoid executing the rustup wrapper thousands
// of times. This makes the testsuite run substantially faster.
let path = env::var_os("PATH").unwrap_or_default();
let paths = env::split_paths(&path);
let mut outer_cargo = PathBuf::from(env::var_os("CARGO").unwrap());
outer_cargo.pop();
let new_path = env::join_paths(std::iter::once(outer_cargo).chain(paths)).unwrap();
p.env("PATH", new_path);
}
p.cwd(&paths::root())
2018-12-08 11:19:47 +00:00
.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")
.env_remove("__CARGO_DEFAULT_LIB_METADATA")
.env_remove("RUSTC")
.env_remove("RUSTDOC")
.env_remove("RUSTC_WRAPPER")
.env_remove("RUSTFLAGS")
2020-04-27 21:31:24 +00:00
.env_remove("RUSTDOCFLAGS")
2018-12-08 11:19:47 +00:00
.env_remove("XDG_CONFIG_HOME") // see #2345
.env("GIT_CONFIG_NOSYSTEM", "1") // keep trying to sandbox ourselves
.env_remove("EMAIL")
.env_remove("USER") // not set on some rust-lang docker images
2018-12-08 11:19:47 +00:00
.env_remove("MFLAGS")
.env_remove("MAKEFLAGS")
.env_remove("GIT_AUTHOR_NAME")
.env_remove("GIT_AUTHOR_EMAIL")
.env_remove("GIT_COMMITTER_NAME")
.env_remove("GIT_COMMITTER_EMAIL")
.env_remove("MSYSTEM"); // assume cmd.exe everywhere on windows
if cfg!(target_os = "macos") {
// Work-around a bug in macOS 10.15, see `link_or_copy` for details.
p.env("__CARGO_COPY_DONT_LINK_DO_NOT_USE_THIS", "1");
}
p
}
pub trait ChannelChanger: Sized {
fn masquerade_as_nightly_cargo(&mut self) -> &mut Self;
}
2021-03-20 18:28:38 +00:00
impl ChannelChanger for 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 mut arg in s.split_whitespace() {
if (arg.starts_with('"') && arg.ends_with('"'))
|| (arg.starts_with('\'') && arg.ends_with('\''))
{
arg = &arg[1..(arg.len() - 1).max(1)];
} else if 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.
2019-07-25 04:48:15 +00:00
cfg!(target_os = "macos") && is_ci()
2018-11-02 22:18:17 +00:00
}
/// Some CI setups are much slower then the equipment used by Cargo itself.
2019-05-03 12:22:11 +00:00
/// Architectures that do not have a modern processor, hardware emulation, etc.
/// 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)
}
pub fn command_is_available(cmd: &str) -> bool {
if let Err(e) = process(cmd).arg("-V").exec_with_output() {
eprintln!("{} not available, skipping tests", cmd);
eprintln!("{:?}", e);
false
} else {
true
}
}
#[cfg(windows)]
pub fn symlink_supported() -> bool {
2019-07-25 04:48:15 +00:00
if is_ci() {
// We want to be absolutely sure this runs on CI.
return true;
}
let src = paths::root().join("symlink_src");
fs::write(&src, "").unwrap();
let dst = paths::root().join("symlink_dst");
let result = match os::windows::fs::symlink_file(&src, &dst) {
Ok(_) => {
fs::remove_file(&dst).unwrap();
true
}
Err(e) => {
eprintln!(
"symlinks not supported: {:?}\n\
Windows 10 users should enable developer mode.",
e
);
false
}
};
fs::remove_file(&src).unwrap();
return result;
}
#[cfg(not(windows))]
pub fn symlink_supported() -> bool {
true
}
2019-12-01 20:47:13 +00:00
/// The error message for ENOENT.
pub fn no_such_file_err_msg() -> String {
std::io::Error::from_raw_os_error(2).to_string()
}