]>
Commit | Line | Data |
---|---|---|
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 | 9 | use std::env; |
80fe0e6d | 10 | use std::ffi::OsStr; |
0f5deb64 | 11 | use std::fmt::Write; |
a6dad622 | 12 | use std::fs; |
d46aec56 | 13 | use std::os; |
a6dad622 | 14 | use std::path::{Path, PathBuf}; |
b5ee3635 | 15 | use std::process::{Command, Output}; |
964e72ff | 16 | use std::str; |
20b5ca3d | 17 | use std::time::{self, Duration}; |
9ed3a6ea | 18 | |
e132bb53 | 19 | use anyhow::{bail, Result}; |
dbfdd495 | 20 | use cargo_util::{is_ci, ProcessBuilder, ProcessError}; |
e132bb53 | 21 | use serde_json; |
43b42d6f | 22 | use url::Url; |
d1ec90b3 | 23 | |
43b42d6f | 24 | use self::paths::CargoPathExt; |
0ccb2fa2 | 25 | |
9115b2c3 | 26 | #[macro_export] |
763ba535 | 27 | macro_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] |
37 | pub 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 |
51 | pub use cargo_test_macro::cargo_test; |
52 | ||
e132bb53 | 53 | pub mod compare; |
ba804bb5 | 54 | pub mod cross_compile; |
16b5402f | 55 | mod diff; |
b9181ef3 | 56 | pub mod git; |
e132bb53 | 57 | pub mod install; |
b9181ef3 | 58 | pub mod paths; |
ba804bb5 | 59 | pub mod publish; |
b9181ef3 | 60 | pub mod registry; |
7b229bbe | 61 | pub mod tools; |
d1ec90b3 | 62 | |
aec45be7 CL |
63 | /* |
64 | * | |
65 | * ===== Builders ===== | |
66 | * | |
67 | */ | |
68 | ||
1e682848 | 69 | #[derive(PartialEq, Clone)] |
d1ec90b3 | 70 | struct FileBuilder { |
a6dad622 | 71 | path: PathBuf, |
1e682848 | 72 | body: String, |
dde290e6 | 73 | executable: bool, |
d1ec90b3 CL |
74 | } |
75 | ||
76 | impl 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 | 111 | struct SymlinkBuilder { |
a6dad622 AC |
112 | dst: PathBuf, |
113 | src: PathBuf, | |
77cfceea | 114 | src_is_dir: bool, |
f4881742 TJ |
115 | } |
116 | ||
117 | impl 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 | 160 | pub struct Project { |
85984a87 | 161 | root: PathBuf, |
d43ee1dd NK |
162 | } |
163 | ||
164 | #[must_use] | |
35e21c76 | 165 | pub 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 | ||
172 | impl 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 | ||
287 | impl 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 |
444 | pub 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 |
449 | pub 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 | 455 | pub 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 | 470 | pub 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 |
485 | pub 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. | |
496 | pub 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 | 504 | pub 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 | ||
523 | impl 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 |
959 | impl 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 |
967 | pub 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 |
988 | pub 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 | 1000 | pub 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 | 1017 | pub 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 |
1034 | pub fn path2url<P: AsRef<Path>>(p: P) -> Url { |
1035 | Url::from_file_path(p).ok().unwrap() | |
e2191677 AC |
1036 | } |
1037 | ||
10224938 EH |
1038 | struct RustcInfo { |
1039 | verbose_version: String, | |
1040 | host: String, | |
1041 | } | |
1042 | ||
1043 | impl 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 | ||
1063 | lazy_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 |
1068 | pub 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). |
1073 | pub fn rustc_host_env() -> String { | |
1074 | rustc_host().to_uppercase().replace('-', "_") | |
1075 | } | |
1076 | ||
43b42d6f | 1077 | pub 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 | 1083 | pub fn process<T: AsRef<OsStr>>(t: T) -> ProcessBuilder { |
43b42d6f DW |
1084 | _process(t.as_ref()) |
1085 | } | |
1086 | ||
88810035 EH |
1087 | fn _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 | ||
1161 | pub trait ChannelChanger: Sized { | |
1162 | fn masquerade_as_nightly_cargo(&mut self) -> &mut Self; | |
1163 | } | |
1164 | ||
88810035 | 1165 | impl 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 | 1171 | fn 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 | 1184 | pub 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 |
1190 | pub 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 | ||
1196 | pub 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 | 1201 | pub 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. |
1214 | pub 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 |
1222 | pub 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)] | |
1233 | pub 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))] | |
1260 | pub fn symlink_supported() -> bool { | |
1261 | true | |
1262 | } | |
00a47302 EH |
1263 | |
1264 | /// The error message for ENOENT. | |
80e55c77 EH |
1265 | pub fn no_such_file_err_msg() -> String { |
1266 | std::io::Error::from_raw_os_error(2).to_string() | |
1267 | } |