]> git.proxmox.com Git - cargo.git/blame - crates/cargo-test-support/src/lib.rs
Refactor echo cargo subcommand test helper into cargo-test-support
[cargo.git] / crates / cargo-test-support / src / lib.rs
CommitLineData
4d40ef4e
EH
1//! # Cargo test support.
2//!
e132bb53 3//! See <https://rust-lang.github.io/cargo/contrib/> for a guide on writing tests.
43b42d6f 4
10eb56f2
EH
5#![allow(clippy::all)]
6#![warn(clippy::needless_borrow)]
7#![warn(clippy::redundant_clone)]
b590183d 8
ee5e24ff 9use std::env;
80fe0e6d 10use std::ffi::OsStr;
0f5deb64 11use std::fmt::Write;
a6dad622 12use std::fs;
d46aec56 13use std::os;
a6dad622 14use std::path::{Path, PathBuf};
b5ee3635 15use std::process::{Command, Output};
964e72ff 16use std::str;
20b5ca3d 17use std::time::{self, Duration};
9ed3a6ea 18
e132bb53 19use anyhow::{bail, Result};
dbfdd495 20use cargo_util::{is_ci, ProcessBuilder, ProcessError};
e132bb53 21use serde_json;
43b42d6f 22use url::Url;
d1ec90b3 23
43b42d6f 24use self::paths::CargoPathExt;
0ccb2fa2 25
9115b2c3 26#[macro_export]
763ba535 27macro_rules! t {
b9181ef3
EH
28 ($e:expr) => {
29 match $e {
30 Ok(e) => e,
0f5deb64 31 Err(e) => $crate::panic_error(&format!("failed running {}", stringify!($e)), e),
b9181ef3
EH
32 }
33 };
763ba535
AC
34}
35
0f5deb64
EH
36#[track_caller]
37pub fn panic_error(what: &str, err: impl Into<anyhow::Error>) -> ! {
38 let err = err.into();
39 pe(what, err);
40 #[track_caller]
41 fn pe(what: &str, err: anyhow::Error) -> ! {
42 let mut result = format!("{}\nerror: {}", what, err);
43 for cause in err.chain().skip(1) {
44 drop(writeln!(result, "\nCaused by:"));
45 drop(write!(result, "{}", cause));
46 }
47 panic!("\n{}", result);
48 }
49}
50
0dd79670
AC
51pub use cargo_test_macro::cargo_test;
52
e132bb53 53pub mod compare;
ba804bb5 54pub mod cross_compile;
16b5402f 55mod diff;
b9181ef3 56pub mod git;
e132bb53 57pub mod install;
b9181ef3 58pub mod paths;
ba804bb5 59pub mod publish;
b9181ef3 60pub mod registry;
7b229bbe 61pub mod tools;
d1ec90b3 62
aec45be7
CL
63/*
64 *
65 * ===== Builders =====
66 *
67 */
68
1e682848 69#[derive(PartialEq, Clone)]
d1ec90b3 70struct FileBuilder {
a6dad622 71 path: PathBuf,
1e682848 72 body: String,
dde290e6 73 executable: bool,
d1ec90b3
CL
74}
75
76impl FileBuilder {
dde290e6 77 pub fn new(path: PathBuf, body: &str, executable: bool) -> FileBuilder {
1e682848
AC
78 FileBuilder {
79 path,
80 body: body.to_string(),
dde290e6 81 executable: executable,
1e682848 82 }
d1ec90b3
CL
83 }
84
dde290e6
NK
85 fn mk(&mut self) {
86 if self.executable {
87 self.path.set_extension(env::consts::EXE_EXTENSION);
88 }
89
763ba535 90 self.dirname().mkdir_p();
4ae79d2f 91 fs::write(&self.path, &self.body)
1e682848 92 .unwrap_or_else(|e| panic!("could not create file {}: {}", self.path.display(), e));
dde290e6
NK
93
94 #[cfg(unix)]
95 if self.executable {
96 use std::os::unix::fs::PermissionsExt;
97
98 let mut perms = fs::metadata(&self.path).unwrap().permissions();
99 let mode = perms.mode();
100 perms.set_mode(mode | 0o111);
101 fs::set_permissions(&self.path, perms).unwrap();
102 }
d1ec90b3
CL
103 }
104
a6dad622
AC
105 fn dirname(&self) -> &Path {
106 self.path.parent().unwrap()
d1ec90b3
CL
107 }
108}
109
1e682848 110#[derive(PartialEq, Clone)]
f4881742 111struct SymlinkBuilder {
a6dad622
AC
112 dst: PathBuf,
113 src: PathBuf,
77cfceea 114 src_is_dir: bool,
f4881742
TJ
115}
116
117impl SymlinkBuilder {
a6dad622 118 pub fn new(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
673bb69c
TW
119 SymlinkBuilder {
120 dst,
121 src,
122 src_is_dir: false,
123 }
77cfceea
TW
124 }
125
126 pub fn new_dir(dst: PathBuf, src: PathBuf) -> SymlinkBuilder {
673bb69c
TW
127 SymlinkBuilder {
128 dst,
129 src,
130 src_is_dir: true,
131 }
f4881742
TJ
132 }
133
d46aec56 134 #[cfg(unix)]
763ba535
AC
135 fn mk(&self) {
136 self.dirname().mkdir_p();
137 t!(os::unix::fs::symlink(&self.dst, &self.src));
d46aec56
AC
138 }
139
140 #[cfg(windows)]
dde290e6 141 fn mk(&mut self) {
763ba535 142 self.dirname().mkdir_p();
77cfceea 143 if self.src_is_dir {
32130f8e 144 t!(os::windows::fs::symlink_dir(&self.dst, &self.src));
77cfceea 145 } else {
dde290e6
NK
146 if let Some(ext) = self.dst.extension() {
147 if ext == env::consts::EXE_EXTENSION {
148 self.src.set_extension(ext);
149 }
150 }
77cfceea
TW
151 t!(os::windows::fs::symlink_file(&self.dst, &self.src));
152 }
f4881742
TJ
153 }
154
a6dad622
AC
155 fn dirname(&self) -> &Path {
156 self.src.parent().unwrap()
f4881742
TJ
157 }
158}
159
7fc0dffe 160pub struct Project {
85984a87 161 root: PathBuf,
d43ee1dd
NK
162}
163
164#[must_use]
35e21c76 165pub struct ProjectBuilder {
d43ee1dd 166 root: Project,
f4881742 167 files: Vec<FileBuilder>,
5e8eef97 168 symlinks: Vec<SymlinkBuilder>,
d2c815be 169 no_manifest: bool,
d1ec90b3
CL
170}
171
172impl ProjectBuilder {
139c7503 173 /// Root of the project, ex: `/path/to/cargo/target/cit/t0/foo`
d43ee1dd
NK
174 pub fn root(&self) -> PathBuf {
175 self.root.root()
176 }
177
139c7503 178 /// Project's debug dir, ex: `/path/to/cargo/target/cit/t0/foo/target/debug`
d43ee1dd
NK
179 pub fn target_debug_dir(&self) -> PathBuf {
180 self.root.target_debug_dir()
181 }
182
b02ba377 183 pub fn new(root: PathBuf) -> ProjectBuilder {
d1ec90b3 184 ProjectBuilder {
7fc0dffe 185 root: Project { root },
ab1cb51f 186 files: vec![],
5e8eef97 187 symlinks: vec![],
d2c815be 188 no_manifest: false,
d1ec90b3
CL
189 }
190 }
191
d7d513e9 192 pub fn at<P: AsRef<Path>>(mut self, path: P) -> Self {
85984a87
DW
193 self.root = Project {
194 root: paths::root().join(path),
195 };
d7d513e9
DW
196 self
197 }
198
f7c91ba6 199 /// Adds a file to the project.
1e682848 200 pub fn file<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
dde290e6
NK
201 self._file(path.as_ref(), body, false);
202 self
203 }
204
205 /// Adds an executable file to the project.
206 pub fn executable<B: AsRef<Path>>(mut self, path: B, body: &str) -> Self {
207 self._file(path.as_ref(), body, true);
d43ee1dd
NK
208 self
209 }
210
dde290e6
NK
211 fn _file(&mut self, path: &Path, body: &str, executable: bool) {
212 self.files.push(FileBuilder::new(
213 self.root.root().join(path),
214 body,
215 executable,
216 ));
d43ee1dd
NK
217 }
218
32130f8e 219 /// Adds a symlink to a file to the project.
1e682848
AC
220 pub fn symlink<T: AsRef<Path>>(mut self, dst: T, src: T) -> Self {
221 self.symlinks.push(SymlinkBuilder::new(
b02ba377
AC
222 self.root.root().join(dst),
223 self.root.root().join(src),
1e682848 224 ));
d43ee1dd
NK
225 self
226 }
227
32130f8e 228 /// Create a symlink to a directory
77cfceea
TW
229 pub fn symlink_dir<T: AsRef<Path>>(mut self, dst: T, src: T) -> Self {
230 self.symlinks.push(SymlinkBuilder::new_dir(
673bb69c
TW
231 self.root.root().join(dst),
232 self.root.root().join(src),
233 ));
77cfceea
TW
234 self
235 }
236
d2c815be
DW
237 pub fn no_manifest(mut self) -> Self {
238 self.no_manifest = true;
239 self
240 }
241
f7c91ba6 242 /// Creates the project.
2e6e5f88 243 pub fn build(mut self) -> Project {
d43ee1dd
NK
244 // First, clean the directory if it already exists
245 self.rm_root();
246
247 // Create the empty directory
b02ba377 248 self.root.root().mkdir_p();
d43ee1dd 249
2e6e5f88 250 let manifest_path = self.root.root().join("Cargo.toml");
d2c815be 251 if !self.no_manifest && self.files.iter().all(|fb| fb.path != manifest_path) {
dde290e6
NK
252 self._file(
253 Path::new("Cargo.toml"),
254 &basic_manifest("foo", "0.0.1"),
255 false,
256 )
2e6e5f88
DW
257 }
258
20b5ca3d
EH
259 let past = time::SystemTime::now() - Duration::new(1, 0);
260 let ftime = filetime::FileTime::from_system_time(past);
261
dde290e6 262 for file in self.files.iter_mut() {
d43ee1dd 263 file.mk();
20b5ca3d
EH
264 if is_coarse_mtime() {
265 // Place the entire project 1 second in the past to ensure
266 // that if cargo is called multiple times, the 2nd call will
267 // see targets as "fresh". Without this, if cargo finishes in
268 // under 1 second, the second call will see the mtime of
269 // source == mtime of output and consider it dirty.
270 filetime::set_file_times(&file.path, ftime, ftime).unwrap();
271 }
d43ee1dd
NK
272 }
273
dde290e6 274 for symlink in self.symlinks.iter_mut() {
d43ee1dd
NK
275 symlink.mk();
276 }
277
d0624dfc 278 let ProjectBuilder { root, .. } = self;
d43ee1dd
NK
279 root
280 }
281
282 fn rm_root(&self) {
b02ba377 283 self.root.root().rm_rf()
d43ee1dd
NK
284 }
285}
286
287impl Project {
139c7503 288 /// Root of the project, ex: `/path/to/cargo/target/cit/t0/foo`
a6dad622 289 pub fn root(&self) -> PathBuf {
7fc0dffe 290 self.root.clone()
d1ec90b3
CL
291 }
292
139c7503 293 /// Project's target dir, ex: `/path/to/cargo/target/cit/t0/foo/target`
d43ee1dd 294 pub fn build_dir(&self) -> PathBuf {
b02ba377 295 self.root().join("target")
d43ee1dd 296 }
e2191677 297
139c7503 298 /// Project's debug dir, ex: `/path/to/cargo/target/cit/t0/foo/target/debug`
b66b0b74
KA
299 pub fn target_debug_dir(&self) -> PathBuf {
300 self.build_dir().join("debug")
301 }
302
139c7503 303 /// File url for root, ex: `file:///path/to/cargo/target/cit/t0/foo`
1e682848
AC
304 pub fn url(&self) -> Url {
305 path2url(self.root())
306 }
d43ee1dd 307
139c7503
EH
308 /// Path to an example built as a library.
309 /// `kind` should be one of: "lib", "rlib", "staticlib", "dylib", "proc-macro"
310 /// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/examples/libex.rlib`
b66b0b74 311 pub fn example_lib(&self, name: &str, kind: &str) -> PathBuf {
b66b0b74
KA
312 self.target_debug_dir()
313 .join("examples")
aa99e9f2 314 .join(paths::get_lib_filename(name, kind))
b66b0b74
KA
315 }
316
139c7503
EH
317 /// Path to a debug binary.
318 /// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/foo`
a6dad622 319 pub fn bin(&self, b: &str) -> PathBuf {
1e682848
AC
320 self.build_dir()
321 .join("debug")
322 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
0025dbde
JD
323 }
324
139c7503
EH
325 /// Path to a release binary.
326 /// ex: `/path/to/cargo/target/cit/t0/foo/target/release/foo`
a6dad622 327 pub fn release_bin(&self, b: &str) -> PathBuf {
1e682848
AC
328 self.build_dir()
329 .join("release")
330 .join(&format!("{}{}", b, env::consts::EXE_SUFFIX))
37eba453
CR
331 }
332
139c7503
EH
333 /// Path to a debug binary for a specific target triple.
334 /// ex: `/path/to/cargo/target/cit/t0/foo/target/i686-apple-darwin/debug/foo`
a6dad622 335 pub fn target_bin(&self, target: &str, b: &str) -> PathBuf {
1e682848
AC
336 self.build_dir().join(target).join("debug").join(&format!(
337 "{}{}",
338 b,
339 env::consts::EXE_SUFFIX
340 ))
685f2b4e
AC
341 }
342
27a95d0e
EH
343 /// Returns an iterator of paths matching the glob pattern, which is
344 /// relative to the project root.
345 pub fn glob<P: AsRef<Path>>(&self, pattern: P) -> glob::Paths {
346 let pattern = self.root().join(pattern);
347 glob::glob(pattern.to_str().expect("failed to convert pattern to str"))
348 .expect("failed to glob")
349 }
350
f7c91ba6 351 /// Changes the contents of an existing file.
d43ee1dd 352 pub fn change_file(&self, path: &str, body: &str) {
dde290e6 353 FileBuilder::new(self.root().join(path), body, false).mk()
80a81334
AC
354 }
355
f7c91ba6 356 /// Creates a `ProcessBuilder` to run a program in the project
3b25ab3c 357 /// and wrap it in an Execs to assert on the execution.
139c7503 358 /// Example:
3b25ab3c
DW
359 /// p.process(&p.bin("foo"))
360 /// .with_stdout("bar\n")
361 /// .run();
2554afe7 362 pub fn process<T: AsRef<OsStr>>(&self, program: T) -> Execs {
9115b2c3 363 let mut p = process(program);
26a5eeff 364 p.cwd(self.root());
2554afe7 365 execs().with_process_builder(p)
a6dad622
AC
366 }
367
f7c91ba6 368 /// Creates a `ProcessBuilder` to run cargo.
139c7503
EH
369 /// Arguments can be separated by spaces.
370 /// Example:
85984a87 371 /// p.cargo("build --bin foo").run();
b5ee3635 372 pub fn cargo(&self, cmd: &str) -> Execs {
2554afe7
DW
373 let mut execs = self.process(&cargo_exe());
374 if let Some(ref mut p) = execs.process_builder {
375 split_and_add_args(p, cmd);
376 }
377 execs
35e21c76
CLYK
378 }
379
394ec96f
EH
380 /// Safely run a process after `cargo build`.
381 ///
382 /// Windows has a problem where a process cannot be reliably
383 /// be replaced, removed, or renamed immediately after executing it.
384 /// The action may fail (with errors like Access is denied), or
385 /// it may succeed, but future attempts to use the same filename
386 /// will fail with "Already Exists".
387 ///
388 /// If you have a test that needs to do `cargo run` multiple
389 /// times, you should instead use `cargo build` and use this
390 /// method to run the executable. Each time you call this,
391 /// use a new name for `dst`.
f7c91ba6 392 /// See rust-lang/cargo#5481.
2a1adb3d 393 pub fn rename_run(&self, src: &str, dst: &str) -> Execs {
394ec96f
EH
394 let src = self.bin(src);
395 let dst = self.bin(dst);
396 fs::rename(&src, &dst)
397 .unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
398 self.process(dst)
399 }
400
139c7503 401 /// Returns the contents of `Cargo.lock`.
191358fe 402 pub fn read_lockfile(&self) -> String {
b02ba377
AC
403 self.read_file("Cargo.lock")
404 }
405
406 /// Returns the contents of a path in the project root
407 pub fn read_file(&self, path: &str) -> String {
4ae79d2f
EH
408 let full = self.root().join(path);
409 fs::read_to_string(&full)
410 .unwrap_or_else(|e| panic!("could not read file {}: {}", full.display(), e))
191358fe
AK
411 }
412
139c7503 413 /// Modifies `Cargo.toml` to remove all commented lines.
51d23560 414 pub fn uncomment_root_manifest(&self) {
4ae79d2f
EH
415 let contents = self.read_file("Cargo.toml").replace("#", "");
416 fs::write(self.root().join("Cargo.toml"), contents).unwrap();
51d23560 417 }
4f6553ab
EH
418
419 pub fn symlink(&self, src: impl AsRef<Path>, dst: impl AsRef<Path>) {
420 let src = self.root().join(src.as_ref());
421 let dst = self.root().join(dst.as_ref());
422 #[cfg(unix)]
423 {
424 if let Err(e) = os::unix::fs::symlink(&src, &dst) {
425 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
426 }
427 }
428 #[cfg(windows)]
429 {
430 if src.is_dir() {
431 if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
432 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
433 }
434 } else {
435 if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
436 panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
437 }
438 }
439 }
440 }
d1ec90b3
CL
441}
442
443// Generates a project layout
7fe2fbc8
DW
444pub fn project() -> ProjectBuilder {
445 ProjectBuilder::new(paths::root().join("foo"))
d1ec90b3
CL
446}
447
fbd43d92
JH
448// Generates a project layout inside our fake home dir
449pub fn project_in_home(name: &str) -> ProjectBuilder {
b02ba377 450 ProjectBuilder::new(paths::home().join(name))
fbd43d92
JH
451}
452
d1ec90b3
CL
453// === Helpers ===
454
964e72ff 455pub fn main_file(println: &str, deps: &[&str]) -> String {
35e21c76
CLYK
456 let mut buf = String::new();
457
458 for dep in deps.iter() {
964e72ff 459 buf.push_str(&format!("extern crate {};\n", dep));
35e21c76
CLYK
460 }
461
462 buf.push_str("fn main() { println!(");
ef0b4776 463 buf.push_str(println);
35e21c76
CLYK
464 buf.push_str("); }\n");
465
a91a12b4 466 buf
35e21c76
CLYK
467}
468
aec45be7 469// Path to cargo executables
a6dad622 470pub fn cargo_dir() -> PathBuf {
1e682848
AC
471 env::var_os("CARGO_BIN_PATH")
472 .map(PathBuf::from)
473 .or_else(|| {
474 env::current_exe().ok().map(|mut path| {
0a78e431 475 path.pop();
1e682848
AC
476 if path.ends_with("deps") {
477 path.pop();
478 }
479 path
480 })
fecb7246
AC
481 })
482 .unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set. Cannot continue running test"))
aec45be7
CL
483}
484
015a08a0
VK
485pub fn cargo_exe() -> PathBuf {
486 cargo_dir().join(format!("cargo{}", env::consts::EXE_SUFFIX))
487}
488
393077f9
EH
489/// This is the raw output from the process.
490///
491/// This is similar to `std::process::Output`, however the `status` is
492/// translated to the raw `code`. This is necessary because `ProcessError`
493/// does not have access to the raw `ExitStatus` because `ProcessError` needs
494/// to be serializable (for the Rustc cache), and `ExitStatus` does not
495/// provide a constructor.
496pub struct RawOutput {
497 pub code: Option<i32>,
498 pub stdout: Vec<u8>,
499 pub stderr: Vec<u8>,
500}
501
dd0400ec 502#[must_use]
ba280047 503#[derive(Clone)]
1ced1dc8 504pub struct Execs {
b5ee3635
DW
505 ran: bool,
506 process_builder: Option<ProcessBuilder>,
7e6433fe
CL
507 expect_stdout: Option<String>,
508 expect_stdin: Option<String>,
509 expect_stderr: Option<String>,
9d301d6e 510 expect_exit_code: Option<i32>,
c25a7536
AC
511 expect_stdout_contains: Vec<String>,
512 expect_stderr_contains: Vec<String>,
41e49048 513 expect_stdout_contains_n: Vec<(String, usize)>,
7e2c81d8
NC
514 expect_stdout_not_contains: Vec<String>,
515 expect_stderr_not_contains: Vec<String>,
10a6da62 516 expect_stderr_unordered: Vec<String>,
3cf913ef 517 expect_stderr_with_without: Vec<(Vec<String>, Vec<String>)>,
e132bb53
EH
518 expect_json: Option<String>,
519 expect_json_contains_unordered: Option<String>,
005bd4b4 520 stream_output: bool,
79fe6415
CL
521}
522
523impl Execs {
b5ee3635
DW
524 pub fn with_process_builder(mut self, p: ProcessBuilder) -> Execs {
525 self.process_builder = Some(p);
526 self
527 }
528
f7c91ba6 529 /// Verifies that stdout is equal to the given lines.
e132bb53 530 /// See [`compare`] for supported patterns.
76e840bb 531 pub fn with_stdout<S: ToString>(&mut self, expected: S) -> &mut Self {
139e81ea 532 self.expect_stdout = Some(expected.to_string());
64ff29ff
AC
533 self
534 }
535
f7c91ba6 536 /// Verifies that stderr is equal to the given lines.
e132bb53 537 /// See [`compare`] for supported patterns.
76e840bb 538 pub fn with_stderr<S: ToString>(&mut self, expected: S) -> &mut Self {
61a3c68b 539 self.expect_stderr = Some(expected.to_string());
e9bfe8fa 540 self
61a3c68b
AC
541 }
542
f7c91ba6 543 /// Verifies the exit code from the process.
d970d05f
EH
544 ///
545 /// This is not necessary if the expected exit code is `0`.
76e840bb 546 pub fn with_status(&mut self, expected: i32) -> &mut Self {
64ff29ff
AC
547 self.expect_exit_code = Some(expected);
548 self
549 }
550
f7c91ba6 551 /// Removes exit code check for the process.
7b925540
GG
552 ///
553 /// By default, the expected exit code is `0`.
554 pub fn without_status(&mut self) -> &mut Self {
555 self.expect_exit_code = None;
556 self
557 }
558
f7c91ba6 559 /// Verifies that stdout contains the given contiguous lines somewhere in
139c7503 560 /// its output.
e132bb53
EH
561 ///
562 /// See [`compare`] for supported patterns.
76e840bb 563 pub fn with_stdout_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
9d301d6e
FH
564 self.expect_stdout_contains.push(expected.to_string());
565 self
566 }
567
f7c91ba6 568 /// Verifies that stderr contains the given contiguous lines somewhere in
139c7503 569 /// its output.
e132bb53
EH
570 ///
571 /// See [`compare`] for supported patterns.
76e840bb 572 pub fn with_stderr_contains<S: ToString>(&mut self, expected: S) -> &mut Self {
c25a7536
AC
573 self.expect_stderr_contains.push(expected.to_string());
574 self
575 }
576
f7c91ba6 577 /// Verifies that stdout contains the given contiguous lines somewhere in
139c7503 578 /// its output, and should be repeated `number` times.
e132bb53
EH
579 ///
580 /// See [`compare`] for supported patterns.
76e840bb 581 pub fn with_stdout_contains_n<S: ToString>(&mut self, expected: S, number: usize) -> &mut Self {
1e682848
AC
582 self.expect_stdout_contains_n
583 .push((expected.to_string(), number));
41e49048
ML
584 self
585 }
586
f7c91ba6 587 /// Verifies that stdout does not contain the given contiguous lines.
e132bb53
EH
588 ///
589 /// See [`compare`] for supported patterns.
590 ///
591 /// See note on [`Self::with_stderr_does_not_contain`].
76e840bb 592 pub fn with_stdout_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
7e2c81d8
NC
593 self.expect_stdout_not_contains.push(expected.to_string());
594 self
595 }
596
f7c91ba6 597 /// Verifies that stderr does not contain the given contiguous lines.
e132bb53
EH
598 ///
599 /// See [`compare`] for supported patterns.
139c7503
EH
600 ///
601 /// Care should be taken when using this method because there is a
f7c91ba6 602 /// limitless number of possible things that *won't* appear. A typo means
139c7503
EH
603 /// your test will pass without verifying the correct behavior. If
604 /// possible, write the test first so that it fails, and then implement
605 /// your fix/feature to make it pass.
76e840bb 606 pub fn with_stderr_does_not_contain<S: ToString>(&mut self, expected: S) -> &mut Self {
7e2c81d8
NC
607 self.expect_stderr_not_contains.push(expected.to_string());
608 self
609 }
610
f7c91ba6 611 /// Verifies that all of the stderr output is equal to the given lines,
139c7503 612 /// ignoring the order of the lines.
e132bb53
EH
613 ///
614 /// See [`compare`] for supported patterns.
615 ///
139c7503
EH
616 /// This is useful when checking the output of `cargo build -v` since
617 /// the order of the output is not always deterministic.
618 /// Recommend use `with_stderr_contains` instead unless you really want to
619 /// check *every* line of output.
620 ///
621 /// Be careful when using patterns such as `[..]`, because you may end up
622 /// with multiple lines that might match, and this is not smart enough to
f7c91ba6
AR
623 /// do anything like longest-match. For example, avoid something like:
624 ///
e132bb53
EH
625 /// ```text
626 /// [RUNNING] `rustc [..]
627 /// [RUNNING] `rustc --crate-name foo [..]
628 /// ```
f7c91ba6 629 ///
139c7503
EH
630 /// This will randomly fail if the other crate name is `bar`, and the
631 /// order changes.
76e840bb 632 pub fn with_stderr_unordered<S: ToString>(&mut self, expected: S) -> &mut Self {
10a6da62
EH
633 self.expect_stderr_unordered.push(expected.to_string());
634 self
635 }
636
3cf913ef
EH
637 /// Verify that a particular line appears in stderr with and without the
638 /// given substrings. Exactly one line must match.
639 ///
640 /// The substrings are matched as `contains`. Example:
641 ///
642 /// ```no_run
643 /// execs.with_stderr_line_without(
644 /// &[
645 /// "[RUNNING] `rustc --crate-name build_script_build",
646 /// "-C opt-level=3",
647 /// ],
648 /// &["-C debuginfo", "-C incremental"],
649 /// )
650 /// ```
651 ///
652 /// This will check that a build line includes `-C opt-level=3` but does
653 /// not contain `-C debuginfo` or `-C incremental`.
654 ///
655 /// Be careful writing the `without` fragments, see note in
656 /// `with_stderr_does_not_contain`.
657 pub fn with_stderr_line_without<S: ToString>(
658 &mut self,
659 with: &[S],
660 without: &[S],
661 ) -> &mut Self {
662 let with = with.iter().map(|s| s.to_string()).collect();
663 let without = without.iter().map(|s| s.to_string()).collect();
664 self.expect_stderr_with_without.push((with, without));
665 self
666 }
667
f7c91ba6 668 /// Verifies the JSON output matches the given JSON.
e132bb53
EH
669 ///
670 /// This is typically used when testing cargo commands that emit JSON.
139c7503
EH
671 /// Each separate JSON object should be separated by a blank line.
672 /// Example:
139c7503 673 ///
e132bb53
EH
674 /// ```rust,ignore
675 /// assert_that(
676 /// p.cargo("metadata"),
677 /// execs().with_json(r#"
678 /// {"example": "abc"}
679 ///
680 /// {"example": "def"}
681 /// "#)
682 /// );
683 /// ```
684 ///
685 /// - Objects should match in the order given.
686 /// - The order of arrays is ignored.
687 /// - Strings support patterns described in [`compare`].
688 /// - Use `"{...}"` to match any object.
76e840bb 689 pub fn with_json(&mut self, expected: &str) -> &mut Self {
e132bb53 690 self.expect_json = Some(expected.to_string());
b24bf7e6
AK
691 self
692 }
693
f7c91ba6 694 /// Verifies JSON output contains the given objects (in any order) somewhere
9a7fadf6
EH
695 /// in its output.
696 ///
697 /// CAUTION: Be very careful when using this. Make sure every object is
698 /// unique (not a subset of one another). Also avoid using objects that
699 /// could possibly match multiple output lines unless you're very sure of
700 /// what you are doing.
701 ///
702 /// See `with_json` for more detail.
703 pub fn with_json_contains_unordered(&mut self, expected: &str) -> &mut Self {
e132bb53
EH
704 match &mut self.expect_json_contains_unordered {
705 None => self.expect_json_contains_unordered = Some(expected.to_string()),
706 Some(e) => {
707 e.push_str("\n\n");
708 e.push_str(expected);
709 }
710 }
9a7fadf6
EH
711 self
712 }
713
005bd4b4 714 /// Forward subordinate process stdout/stderr to the terminal.
139c7503
EH
715 /// Useful for printf debugging of the tests.
716 /// CAUTION: CI will fail if you leave this in your test!
005bd4b4 717 #[allow(unused)]
76e840bb 718 pub fn stream(&mut self) -> &mut Self {
005bd4b4
AK
719 self.stream_output = true;
720 self
721 }
722
b5ee3635
DW
723 pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
724 if let Some(ref mut p) = self.process_builder {
725 p.arg(arg);
726 }
727 self
728 }
729
730 pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut Self {
731 if let Some(ref mut p) = self.process_builder {
e7124ba2 732 if let Some(cwd) = p.get_cwd() {
f9f339a2
EH
733 let new_path = cwd.join(path.as_ref());
734 p.cwd(new_path);
e7124ba2
EH
735 } else {
736 p.cwd(path);
e7124ba2 737 }
b5ee3635
DW
738 }
739 self
740 }
741
e132bb53
EH
742 fn get_cwd(&self) -> Option<&Path> {
743 self.process_builder.as_ref().and_then(|p| p.get_cwd())
744 }
745
b5ee3635
DW
746 pub fn env<T: AsRef<OsStr>>(&mut self, key: &str, val: T) -> &mut Self {
747 if let Some(ref mut p) = self.process_builder {
748 p.env(key, val);
749 }
750 self
751 }
752
753 pub fn env_remove(&mut self, key: &str) -> &mut Self {
754 if let Some(ref mut p) = self.process_builder {
755 p.env_remove(key);
756 }
757 self
758 }
759
5d1b0f9c 760 pub fn exec_with_output(&mut self) -> Result<Output> {
f16b95e4 761 self.ran = true;
b5ee3635
DW
762 // TODO avoid unwrap
763 let p = (&self.process_builder).clone().unwrap();
764 p.exec_with_output()
765 }
766
b611750a
DW
767 pub fn build_command(&mut self) -> Command {
768 self.ran = true;
b5ee3635
DW
769 // TODO avoid unwrap
770 let p = (&self.process_builder).clone().unwrap();
771 p.build_command()
772 }
773
774 pub fn masquerade_as_nightly_cargo(&mut self) -> &mut Self {
775 if let Some(ref mut p) = self.process_builder {
776 p.masquerade_as_nightly_cargo();
777 }
778 self
779 }
780
ed4568e1
AC
781 pub fn enable_mac_dsym(&mut self) -> &mut Self {
782 if cfg!(target_os = "macos") {
783 self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
784 .env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
785 .env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
786 .env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
787 }
788 self
789 }
790
3f7f0942 791 #[track_caller]
b5ee3635
DW
792 pub fn run(&mut self) {
793 self.ran = true;
794 let p = (&self.process_builder).clone().unwrap();
85984a87 795 if let Err(e) = self.match_process(&p) {
0f5deb64 796 panic_error(&format!("test failed running {}", p), e);
85984a87 797 }
b5ee3635 798 }
e8841eec 799
393077f9
EH
800 /// Runs the process, checks the expected output, and returns the first
801 /// JSON object on stdout.
802 #[track_caller]
803 pub fn run_json(&mut self) -> serde_json::Value {
804 self.ran = true;
805 let p = (&self.process_builder).clone().unwrap();
806 match self.match_process(&p) {
0f5deb64 807 Err(e) => panic_error(&format!("test failed running {}", p), e),
393077f9
EH
808 Ok(output) => serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {
809 panic!(
810 "\nfailed to parse JSON: {}\n\
811 output was:\n{}\n",
812 e,
813 String::from_utf8_lossy(&output.stdout)
814 );
815 }),
816 }
817 }
818
3f7f0942 819 #[track_caller]
b63e0e63
DW
820 pub fn run_output(&mut self, output: &Output) {
821 self.ran = true;
e132bb53 822 if let Err(e) = self.match_output(output.status.code(), &output.stdout, &output.stderr) {
0f5deb64 823 panic_error("process did not return the expected result", e)
b63e0e63
DW
824 }
825 }
826
0f304ca4 827 fn verify_checks_output(&self, stdout: &[u8], stderr: &[u8]) {
f58d107e
EH
828 if self.expect_exit_code.unwrap_or(0) != 0
829 && self.expect_stdout.is_none()
830 && self.expect_stdin.is_none()
831 && self.expect_stderr.is_none()
832 && self.expect_stdout_contains.is_empty()
833 && self.expect_stderr_contains.is_empty()
f58d107e
EH
834 && self.expect_stdout_contains_n.is_empty()
835 && self.expect_stdout_not_contains.is_empty()
836 && self.expect_stderr_not_contains.is_empty()
837 && self.expect_stderr_unordered.is_empty()
3cf913ef 838 && self.expect_stderr_with_without.is_empty()
f58d107e 839 && self.expect_json.is_none()
e132bb53 840 && self.expect_json_contains_unordered.is_none()
f58d107e
EH
841 {
842 panic!(
843 "`with_status()` is used, but no output is checked.\n\
844 The test must check the output to ensure the correct error is triggered.\n\
845 --- stdout\n{}\n--- stderr\n{}",
0f304ca4
EH
846 String::from_utf8_lossy(stdout),
847 String::from_utf8_lossy(stderr),
f58d107e
EH
848 );
849 }
850 }
851
393077f9 852 fn match_process(&self, process: &ProcessBuilder) -> Result<RawOutput> {
e8841eec
DW
853 println!("running {}", process);
854 let res = if self.stream_output {
51a8206c 855 if is_ci() {
e8841eec
DW
856 panic!("`.stream()` is for local debugging")
857 }
858 process.exec_with_streaming(
ec21e12d
EH
859 &mut |out| {
860 println!("{}", out);
861 Ok(())
862 },
863 &mut |err| {
864 eprintln!("{}", err);
865 Ok(())
866 },
17b6df9c 867 true,
e8841eec
DW
868 )
869 } else {
870 process.exec_with_output()
871 };
872
873 match res {
393077f9 874 Ok(out) => {
e132bb53 875 self.match_output(out.status.code(), &out.stdout, &out.stderr)?;
393077f9
EH
876 return Ok(RawOutput {
877 stdout: out.stdout,
878 stderr: out.stderr,
879 code: out.status.code(),
880 });
881 }
e8841eec 882 Err(e) => {
cc5e9df6
AC
883 if let Some(ProcessError {
884 stdout: Some(stdout),
885 stderr: Some(stderr),
886 code,
85984a87 887 ..
cc5e9df6 888 }) = e.downcast_ref::<ProcessError>()
85984a87 889 {
e132bb53 890 self.match_output(*code, stdout, stderr)?;
393077f9
EH
891 return Ok(RawOutput {
892 stdout: stdout.to_vec(),
893 stderr: stderr.to_vec(),
894 code: *code,
895 });
e8841eec 896 }
5d1b0f9c 897 bail!("could not exec process {}: {:?}", process, e)
e8841eec
DW
898 }
899 }
900 }
901
e132bb53 902 fn match_output(&self, code: Option<i32>, stdout: &[u8], stderr: &[u8]) -> Result<()> {
0f304ca4 903 self.verify_checks_output(stdout, stderr);
e132bb53
EH
904 let stdout = str::from_utf8(stdout).expect("stdout is not utf8");
905 let stderr = str::from_utf8(stderr).expect("stderr is not utf8");
906 let cwd = self.get_cwd();
907
64ff29ff 908 match self.expect_exit_code {
e132bb53
EH
909 None => {}
910 Some(expected) if code == Some(expected) => {}
0f5deb64
EH
911 Some(expected) => bail!(
912 "process exited with code {} (expected {})\n--- stdout\n{}\n--- stderr\n{}",
913 code.unwrap_or(-1),
914 expected,
e132bb53
EH
915 stdout,
916 stderr
5d1b0f9c 917 ),
64ff29ff 918 }
64ff29ff 919
e132bb53
EH
920 if let Some(expect_stdout) = &self.expect_stdout {
921 compare::match_exact(expect_stdout, stdout, "stdout", stderr, cwd)?;
922 }
923 if let Some(expect_stderr) = &self.expect_stderr {
924 compare::match_exact(expect_stderr, stderr, "stderr", stdout, cwd)?;
925 }
8995f303 926 for expect in self.expect_stdout_contains.iter() {
e132bb53 927 compare::match_contains(expect, stdout, cwd)?;
8995f303 928 }
c25a7536 929 for expect in self.expect_stderr_contains.iter() {
e132bb53 930 compare::match_contains(expect, stderr, cwd)?;
7e2c81d8 931 }
41e49048 932 for &(ref expect, number) in self.expect_stdout_contains_n.iter() {
e132bb53 933 compare::match_contains_n(expect, number, stdout, cwd)?;
41e49048 934 }
7e2c81d8 935 for expect in self.expect_stdout_not_contains.iter() {
e132bb53 936 compare::match_does_not_contain(expect, stdout, cwd)?;
7e2c81d8
NC
937 }
938 for expect in self.expect_stderr_not_contains.iter() {
e132bb53 939 compare::match_does_not_contain(expect, stderr, cwd)?;
c25a7536 940 }
10a6da62 941 for expect in self.expect_stderr_unordered.iter() {
e132bb53 942 compare::match_unordered(expect, stderr, cwd)?;
10a6da62 943 }
3cf913ef 944 for (with, without) in self.expect_stderr_with_without.iter() {
e132bb53 945 compare::match_with_without(stderr, with, without, cwd)?;
3cf913ef
EH
946 }
947
e132bb53
EH
948 if let Some(ref expect_json) = self.expect_json {
949 compare::match_json(expect_json, stdout, cwd)?;
b24bf7e6 950 }
9a7fadf6 951
e132bb53
EH
952 if let Some(ref expected) = self.expect_json_contains_unordered {
953 compare::match_json_contains_unordered(expected, stdout, cwd)?;
9a7fadf6 954 }
8995f303 955 Ok(())
64ff29ff 956 }
93c2d0d4
AC
957}
958
b5ee3635
DW
959impl Drop for Execs {
960 fn drop(&mut self) {
fa94e8aa 961 if !self.ran && !std::thread::panicking() {
e509118f 962 panic!("forgot to run this command");
b5ee3635
DW
963 }
964 }
965}
966
aaaf9dbf
AC
967pub fn execs() -> Execs {
968 Execs {
b5ee3635
DW
969 ran: false,
970 process_builder: None,
b8621c50
YK
971 expect_stdout: None,
972 expect_stderr: None,
973 expect_stdin: None,
16aeb0cd 974 expect_exit_code: Some(0),
c25a7536
AC
975 expect_stdout_contains: Vec::new(),
976 expect_stderr_contains: Vec::new(),
41e49048 977 expect_stdout_contains_n: Vec::new(),
7e2c81d8
NC
978 expect_stdout_not_contains: Vec::new(),
979 expect_stderr_not_contains: Vec::new(),
10a6da62 980 expect_stderr_unordered: Vec::new(),
3cf913ef 981 expect_stderr_with_without: Vec::new(),
b24bf7e6 982 expect_json: None,
e132bb53 983 expect_json_contains_unordered: None,
005bd4b4 984 stream_output: false,
b8621c50 985 }
79fe6415 986}
48dc0814 987
5659b78b
DW
988pub fn basic_manifest(name: &str, version: &str) -> String {
989 format!(
990 r#"
991 [package]
992 name = "{}"
993 version = "{}"
994 authors = []
995 "#,
996 name, version
997 )
998}
999
13eb1232 1000pub fn basic_bin_manifest(name: &str) -> String {
1e682848
AC
1001 format!(
1002 r#"
a2aa2bf9 1003 [package]
13eb1232
TCS
1004
1005 name = "{}"
1006 version = "0.5.0"
1007 authors = ["wycats@example.com"]
1008
1009 [[bin]]
1010
1011 name = "{}"
1e682848
AC
1012 "#,
1013 name, name
1014 )
13eb1232
TCS
1015}
1016
895f5411 1017pub fn basic_lib_manifest(name: &str) -> String {
1e682848
AC
1018 format!(
1019 r#"
895f5411
BK
1020 [package]
1021
1022 name = "{}"
1023 version = "0.5.0"
1024 authors = ["wycats@example.com"]
1025
22dfc520 1026 [lib]
895f5411
BK
1027
1028 name = "{}"
1e682848
AC
1029 "#,
1030 name, name
1031 )
895f5411
BK
1032}
1033
8798bf0d
MK
1034pub fn path2url<P: AsRef<Path>>(p: P) -> Url {
1035 Url::from_file_path(p).ok().unwrap()
e2191677
AC
1036}
1037
10224938
EH
1038struct RustcInfo {
1039 verbose_version: String,
1040 host: String,
1041}
1042
1043impl RustcInfo {
1044 fn new() -> RustcInfo {
1045 let output = ProcessBuilder::new("rustc")
1046 .arg("-vV")
1047 .exec_with_output()
1048 .expect("rustc should exec");
1049 let verbose_version = String::from_utf8(output.stdout).expect("utf8 output");
1050 let host = verbose_version
1051 .lines()
1052 .filter_map(|line| line.strip_prefix("host: "))
1053 .next()
1054 .expect("verbose version has host: field")
1055 .to_string();
1056 RustcInfo {
1057 verbose_version,
1058 host,
1059 }
1060 }
1061}
1062
1063lazy_static::lazy_static! {
1064 static ref RUSTC_INFO: RustcInfo = RustcInfo::new();
1065}
43b42d6f
DW
1066
1067/// The rustc host such as `x86_64-unknown-linux-gnu`.
10224938
EH
1068pub fn rustc_host() -> &'static str {
1069 &RUSTC_INFO.host
43b42d6f
DW
1070}
1071
28c3bef7
EH
1072/// The host triple suitable for use in a cargo environment variable (uppercased).
1073pub fn rustc_host_env() -> String {
1074 rustc_host().to_uppercase().replace('-', "_")
1075}
1076
43b42d6f 1077pub fn is_nightly() -> bool {
10224938 1078 let vv = &RUSTC_INFO.verbose_version;
6f8cd0c5 1079 env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
10224938 1080 && (vv.contains("-nightly") || vv.contains("-dev"))
43b42d6f
DW
1081}
1082
88810035 1083pub fn process<T: AsRef<OsStr>>(t: T) -> ProcessBuilder {
43b42d6f
DW
1084 _process(t.as_ref())
1085}
1086
88810035
EH
1087fn _process(t: &OsStr) -> ProcessBuilder {
1088 let mut p = ProcessBuilder::new(t);
ef5b89ce
AC
1089
1090 // In general just clear out all cargo-specific configuration already in the
1091 // environment. Our tests all assume a "default configuration" unless
1092 // specified otherwise.
1093 for (k, _v) in env::vars() {
1094 if k.starts_with("CARGO_") {
1095 p.env_remove(&k);
1096 }
1097 }
c73765f9
EH
1098 if env::var_os("RUSTUP_TOOLCHAIN").is_some() {
1099 // Override the PATH to avoid executing the rustup wrapper thousands
1100 // of times. This makes the testsuite run substantially faster.
b67454c6
EH
1101 lazy_static::lazy_static! {
1102 static ref RUSTC_DIR: PathBuf = {
1103 match ProcessBuilder::new("rustup")
1104 .args(&["which", "rustc"])
1105 .exec_with_output()
1106 {
1107 Ok(output) => {
1108 let s = str::from_utf8(&output.stdout).expect("utf8").trim();
1109 let mut p = PathBuf::from(s);
1110 p.pop();
1111 p
1112 }
1113 Err(e) => {
1114 panic!("RUSTUP_TOOLCHAIN was set, but could not run rustup: {}", e);
1115 }
1116 }
1117 };
1118 }
c73765f9
EH
1119 let path = env::var_os("PATH").unwrap_or_default();
1120 let paths = env::split_paths(&path);
b67454c6 1121 let new_path = env::join_paths(std::iter::once(RUSTC_DIR.clone()).chain(paths)).unwrap();
c73765f9
EH
1122 p.env("PATH", new_path);
1123 }
ef5b89ce 1124
43b42d6f 1125 p.cwd(&paths::root())
fecb7246
AC
1126 .env("HOME", paths::home())
1127 .env("CARGO_HOME", paths::home().join(".cargo"))
1128 .env("__CARGO_TEST_ROOT", paths::root())
f7c91ba6 1129 // Force Cargo to think it's on the stable channel for all tests, this
fecb7246
AC
1130 // should hopefully not surprise us as we add cargo features over time and
1131 // cargo rides the trains.
1132 .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "stable")
1133 // For now disable incremental by default as support hasn't ridden to the
1134 // stable channel yet. Once incremental support hits the stable compiler we
1135 // can switch this to one and then fix the tests.
1136 .env("CARGO_INCREMENTAL", "0")
fecb7246
AC
1137 .env_remove("__CARGO_DEFAULT_LIB_METADATA")
1138 .env_remove("RUSTC")
1139 .env_remove("RUSTDOC")
1140 .env_remove("RUSTC_WRAPPER")
1141 .env_remove("RUSTFLAGS")
cf57ce15 1142 .env_remove("RUSTDOCFLAGS")
fecb7246
AC
1143 .env_remove("XDG_CONFIG_HOME") // see #2345
1144 .env("GIT_CONFIG_NOSYSTEM", "1") // keep trying to sandbox ourselves
1145 .env_remove("EMAIL")
03932d61 1146 .env_remove("USER") // not set on some rust-lang docker images
fecb7246
AC
1147 .env_remove("MFLAGS")
1148 .env_remove("MAKEFLAGS")
fecb7246
AC
1149 .env_remove("GIT_AUTHOR_NAME")
1150 .env_remove("GIT_AUTHOR_EMAIL")
1151 .env_remove("GIT_COMMITTER_NAME")
1152 .env_remove("GIT_COMMITTER_EMAIL")
fecb7246 1153 .env_remove("MSYSTEM"); // assume cmd.exe everywhere on windows
457c4bc4
EH
1154 if cfg!(target_os = "macos") {
1155 // Work-around a bug in macOS 10.15, see `link_or_copy` for details.
1156 p.env("__CARGO_COPY_DONT_LINK_DO_NOT_USE_THIS", "1");
1157 }
8798bf0d 1158 p
43b42d6f
DW
1159}
1160
1161pub trait ChannelChanger: Sized {
1162 fn masquerade_as_nightly_cargo(&mut self) -> &mut Self;
1163}
1164
88810035 1165impl ChannelChanger for ProcessBuilder {
43b42d6f
DW
1166 fn masquerade_as_nightly_cargo(&mut self) -> &mut Self {
1167 self.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
1168 }
1169}
1170
8940d306 1171fn split_and_add_args(p: &mut ProcessBuilder, s: &str) {
be31989a
WL
1172 for mut arg in s.split_whitespace() {
1173 if (arg.starts_with('"') && arg.ends_with('"'))
1174 || (arg.starts_with('\'') && arg.ends_with('\''))
1175 {
1176 arg = &arg[1..(arg.len() - 1).max(1)];
1177 } else if arg.contains(&['"', '\''][..]) {
8940d306
DW
1178 panic!("shell-style argument parsing is not supported")
1179 }
1180 p.arg(arg);
1181 }
1182}
1183
85984a87 1184pub fn cargo_process(s: &str) -> Execs {
8940d306
DW
1185 let mut p = process(&cargo_exe());
1186 split_and_add_args(&mut p, s);
85984a87 1187 execs().with_process_builder(p)
43b42d6f
DW
1188}
1189
dda661dd
DW
1190pub fn git_process(s: &str) -> ProcessBuilder {
1191 let mut p = process("git");
1192 split_and_add_args(&mut p, s);
1193 p
43b42d6f
DW
1194}
1195
1196pub fn sleep_ms(ms: u64) {
1197 ::std::thread::sleep(Duration::from_millis(ms));
1198}
20b5ca3d 1199
f7c91ba6 1200/// Returns `true` if the local filesystem has low-resolution mtimes.
20b5ca3d 1201pub fn is_coarse_mtime() -> bool {
97363ca7 1202 // If the filetime crate is being used to emulate HFS then
f7c91ba6 1203 // return `true`, without looking at the actual hardware.
97363ca7 1204 cfg!(emulate_second_only_system) ||
f7c91ba6 1205 // This should actually be a test that `$CARGO_TARGET_DIR` is on an HFS
20b5ca3d
EH
1206 // filesystem, (or any filesystem with low-resolution mtimes). However,
1207 // that's tricky to detect, so for now just deal with CI.
51a8206c 1208 cfg!(target_os = "macos") && is_ci()
20b5ca3d 1209}
4be99b2e
E
1210
1211/// Some CI setups are much slower then the equipment used by Cargo itself.
b4cd6095 1212/// Architectures that do not have a modern processor, hardware emulation, etc.
4be99b2e
E
1213/// This provides a way for those setups to increase the cut off for all the time based test.
1214pub fn slow_cpu_multiplier(main: u64) -> Duration {
1215 lazy_static::lazy_static! {
1216 static ref SLOW_CPU_MULTIPLIER: u64 =
1217 env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER").ok().and_then(|m| m.parse().ok()).unwrap_or(1);
1218 }
1219 Duration::from_secs(*SLOW_CPU_MULTIPLIER * main)
1220}
6fb65f1e 1221
bc4c65c5
K
1222pub fn command_is_available(cmd: &str) -> bool {
1223 if let Err(e) = process(cmd).arg("-V").exec_with_output() {
1224 eprintln!("{} not available, skipping tests", cmd);
6fb65f1e
JL
1225 eprintln!("{:?}", e);
1226 false
1227 } else {
1228 true
1229 }
1230}
4f6553ab
EH
1231
1232#[cfg(windows)]
1233pub fn symlink_supported() -> bool {
51a8206c
EH
1234 if is_ci() {
1235 // We want to be absolutely sure this runs on CI.
1236 return true;
1237 }
4f6553ab
EH
1238 let src = paths::root().join("symlink_src");
1239 fs::write(&src, "").unwrap();
1240 let dst = paths::root().join("symlink_dst");
1241 let result = match os::windows::fs::symlink_file(&src, &dst) {
1242 Ok(_) => {
1243 fs::remove_file(&dst).unwrap();
1244 true
1245 }
1246 Err(e) => {
1247 eprintln!(
1248 "symlinks not supported: {:?}\n\
1249 Windows 10 users should enable developer mode.",
1250 e
1251 );
1252 false
1253 }
1254 };
1255 fs::remove_file(&src).unwrap();
1256 return result;
1257}
1258
1259#[cfg(not(windows))]
1260pub fn symlink_supported() -> bool {
1261 true
1262}
00a47302
EH
1263
1264/// The error message for ENOENT.
80e55c77
EH
1265pub fn no_such_file_err_msg() -> String {
1266 std::io::Error::from_raw_os_error(2).to_string()
1267}