]>
Commit | Line | Data |
---|---|---|
83571aee EH |
1 | //! Tests for custom cargo commands and other global command features. |
2 | ||
ee5e24ff | 3 | use std::env; |
dde290e6 | 4 | use std::fs; |
7274307a | 5 | use std::io::Read; |
a6dad622 | 6 | use std::path::{Path, PathBuf}; |
7274307a | 7 | use std::process::Stdio; |
8cce8996 | 8 | use std::str; |
8cce8996 | 9 | |
c712f088 DC |
10 | use cargo_test_support::basic_manifest; |
11 | use cargo_test_support::paths::CargoPathExt; | |
9115b2c3 | 12 | use cargo_test_support::registry::Package; |
5a56cf2e NK |
13 | use cargo_test_support::tools::echo_subcommand; |
14 | use cargo_test_support::{ | |
15 | basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home, | |
16 | }; | |
c712f088 | 17 | use cargo_util::paths::join_paths; |
a3f6a404 | 18 | |
a6dad622 | 19 | fn path() -> Vec<PathBuf> { |
23591fe5 | 20 | env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect() |
db3823a8 | 21 | } |
ee5e24ff | 22 | |
0e0d9688 | 23 | #[cargo_test] |
af2c3555 DW |
24 | fn list_commands_with_descriptions() { |
25 | let p = project().build(); | |
a173fc0a | 26 | p.cargo("--list") |
fecb7246 AC |
27 | .with_stdout_contains( |
28 | " build Compile a local package and all of its dependencies", | |
29 | ) | |
f7c91ba6 AR |
30 | // Assert that `read-manifest` prints the right one-line description followed by another |
31 | // command, indented. | |
fecb7246 AC |
32 | .with_stdout_contains( |
33 | " read-manifest Print a JSON representation of a Cargo.toml manifest.", | |
34 | ) | |
a173fc0a | 35 | .run(); |
af2c3555 DW |
36 | } |
37 | ||
7b16c7c1 | 38 | #[cargo_test] |
1c5e68b4 | 39 | fn list_builtin_aliases_with_descriptions() { |
7b16c7c1 C |
40 | let p = project().build(); |
41 | p.cargo("--list") | |
42 | .with_stdout_contains(" b alias: build") | |
43 | .with_stdout_contains(" c alias: check") | |
44 | .with_stdout_contains(" r alias: run") | |
45 | .with_stdout_contains(" t alias: test") | |
46 | .run(); | |
47 | } | |
48 | ||
1c5e68b4 DM |
49 | #[cargo_test] |
50 | fn list_custom_aliases_with_descriptions() { | |
51 | let p = project_in_home("proj") | |
52 | .file( | |
53 | &paths::home().join(".cargo").join("config"), | |
54 | r#" | |
55 | [alias] | |
56 | myaliasstr = "foo --bar" | |
57 | myaliasvec = ["foo", "--bar"] | |
58 | "#, | |
59 | ) | |
60 | .build(); | |
61 | ||
62 | p.cargo("--list") | |
910569aa EH |
63 | .with_stdout_contains(" myaliasstr alias: foo --bar") |
64 | .with_stdout_contains(" myaliasvec alias: foo --bar") | |
1c5e68b4 DM |
65 | .run(); |
66 | } | |
67 | ||
0ab79d7a NK |
68 | #[cargo_test] |
69 | fn list_dedupe() { | |
70 | let p = project() | |
71 | .executable(Path::new("path-test-1").join("cargo-dupe"), "") | |
72 | .executable(Path::new("path-test-2").join("cargo-dupe"), "") | |
73 | .build(); | |
74 | ||
75 | let mut path = path(); | |
76 | path.push(p.root().join("path-test-1")); | |
77 | path.push(p.root().join("path-test-2")); | |
78 | let path = env::join_paths(path.iter()).unwrap(); | |
79 | ||
80 | p.cargo("--list") | |
81 | .env("PATH", &path) | |
82 | .with_stdout_contains_n(" dupe", 1) | |
83 | .run(); | |
84 | } | |
85 | ||
0e0d9688 | 86 | #[cargo_test] |
6950bbb0 | 87 | fn list_command_looks_at_path() { |
dde290e6 NK |
88 | let proj = project() |
89 | .executable(Path::new("path-test").join("cargo-1"), "") | |
90 | .build(); | |
a3f6a404 | 91 | |
5d0cb3f2 | 92 | let mut path = path(); |
db3823a8 | 93 | path.push(proj.root().join("path-test")); |
ee5e24ff | 94 | let path = env::join_paths(path.iter()).unwrap(); |
fecb7246 AC |
95 | let output = cargo_process("-v --list") |
96 | .env("PATH", &path) | |
97 | .exec_with_output() | |
98 | .unwrap(); | |
63b34b64 DW |
99 | let output = str::from_utf8(&output.stdout).unwrap(); |
100 | assert!( | |
101 | output.contains("\n 1 "), | |
102 | "missing 1: {}", | |
103 | output | |
104 | ); | |
6950bbb0 | 105 | } |
12f5de8e | 106 | |
1edd8630 NK |
107 | #[cargo_test] |
108 | fn list_command_handles_known_external_commands() { | |
109 | let p = project() | |
110 | .executable(Path::new("path-test").join("cargo-fmt"), "") | |
111 | .build(); | |
112 | ||
113 | let fmt_desc = " fmt Formats all bin and lib files of the current crate using rustfmt."; | |
114 | ||
115 | // Without path - fmt isn't there | |
116 | p.cargo("--list") | |
117 | .env("PATH", "") | |
118 | .with_stdout_does_not_contain(fmt_desc) | |
119 | .run(); | |
120 | ||
121 | // With path - fmt is there with known description | |
122 | let mut path = path(); | |
123 | path.push(p.root().join("path-test")); | |
124 | let path = env::join_paths(path.iter()).unwrap(); | |
125 | ||
126 | p.cargo("--list") | |
127 | .env("PATH", &path) | |
128 | .with_stdout_contains(fmt_desc) | |
129 | .run(); | |
130 | } | |
131 | ||
0e0d9688 | 132 | #[cargo_test] |
6950bbb0 | 133 | fn list_command_resolves_symlinks() { |
dde290e6 NK |
134 | let proj = project() |
135 | .symlink(cargo_exe(), Path::new("path-test").join("cargo-2")) | |
136 | .build(); | |
974d5834 JB |
137 | |
138 | let mut path = path(); | |
139 | path.push(proj.root().join("path-test")); | |
140 | let path = env::join_paths(path.iter()).unwrap(); | |
fecb7246 AC |
141 | let output = cargo_process("-v --list") |
142 | .env("PATH", &path) | |
143 | .exec_with_output() | |
144 | .unwrap(); | |
63b34b64 DW |
145 | let output = str::from_utf8(&output.stdout).unwrap(); |
146 | assert!( | |
147 | output.contains("\n 2 "), | |
148 | "missing 2: {}", | |
149 | output | |
150 | ); | |
6950bbb0 | 151 | } |
974d5834 | 152 | |
42528799 ML |
153 | #[cargo_test] |
154 | fn find_closest_capital_c_to_c() { | |
155 | cargo_process("C") | |
156 | .with_status(101) | |
157 | .with_stderr_contains( | |
158 | "\ | |
159 | error: no such subcommand: `C` | |
160 | ||
161 | <tab>Did you mean `c`? | |
162 | ", | |
163 | ) | |
164 | .run(); | |
165 | } | |
166 | ||
167 | #[cargo_test] | |
6f13c466 | 168 | fn find_closest_capital_b_to_b() { |
42528799 ML |
169 | cargo_process("B") |
170 | .with_status(101) | |
171 | .with_stderr_contains( | |
172 | "\ | |
173 | error: no such subcommand: `B` | |
174 | ||
175 | <tab>Did you mean `b`? | |
176 | ", | |
177 | ) | |
178 | .run(); | |
179 | } | |
180 | ||
0e0d9688 | 181 | #[cargo_test] |
6950bbb0 | 182 | fn find_closest_biuld_to_build() { |
85984a87 DW |
183 | cargo_process("biuld") |
184 | .with_status(101) | |
185 | .with_stderr_contains( | |
1e682848 | 186 | "\ |
a1735c7a AK |
187 | error: no such subcommand: `biuld` |
188 | ||
189 | <tab>Did you mean `build`? | |
1e682848 | 190 | ", |
fecb7246 AC |
191 | ) |
192 | .run(); | |
a1735c7a AK |
193 | |
194 | // But, if we actually have `biuld`, it must work! | |
195 | // https://github.com/rust-lang/cargo/issues/5201 | |
196 | Package::new("cargo-biuld", "1.0.0") | |
197 | .file( | |
198 | "src/main.rs", | |
199 | r#" | |
6f8c7d5a EH |
200 | fn main() { |
201 | println!("Similar, but not identical to, build"); | |
202 | } | |
203 | "#, | |
fecb7246 AC |
204 | ) |
205 | .publish(); | |
85984a87 DW |
206 | |
207 | cargo_process("install cargo-biuld").run(); | |
208 | cargo_process("biuld") | |
209 | .with_stdout("Similar, but not identical to, build\n") | |
210 | .run(); | |
211 | cargo_process("--list") | |
212 | .with_stdout_contains( | |
213 | " build Compile a local package and all of its dependencies\n", | |
fecb7246 AC |
214 | ) |
215 | .with_stdout_contains(" biuld\n") | |
85984a87 | 216 | .run(); |
6950bbb0 | 217 | } |
12f5de8e | 218 | |
ff3e880c ZL |
219 | #[cargo_test] |
220 | fn find_closest_alias() { | |
221 | let root = paths::root(); | |
222 | let my_home = root.join("my_home"); | |
223 | fs::create_dir(&my_home).unwrap(); | |
4ae79d2f EH |
224 | fs::write( |
225 | &my_home.join("config"), | |
226 | r#" | |
227 | [alias] | |
228 | myalias = "build" | |
229 | "#, | |
230 | ) | |
231 | .unwrap(); | |
ff3e880c ZL |
232 | |
233 | cargo_process("myalais") | |
234 | .env("CARGO_HOME", &my_home) | |
235 | .with_status(101) | |
236 | .with_stderr_contains( | |
237 | "\ | |
238 | error: no such subcommand: `myalais` | |
239 | ||
240 | <tab>Did you mean `myalias`? | |
241 | ", | |
242 | ) | |
243 | .run(); | |
244 | ||
245 | // But, if no alias is defined, it must not suggest one! | |
246 | cargo_process("myalais") | |
247 | .with_status(101) | |
248 | .with_stderr_contains( | |
249 | "\ | |
250 | error: no such subcommand: `myalais` | |
251 | ", | |
252 | ) | |
253 | .with_stderr_does_not_contain( | |
254 | "\ | |
255 | <tab>Did you mean `myalias`? | |
256 | ", | |
257 | ) | |
258 | .run(); | |
259 | } | |
260 | ||
f7c91ba6 | 261 | // If a subcommand is more than an edit distance of 3 away, we don't make a suggestion. |
0e0d9688 | 262 | #[cargo_test] |
6950bbb0 | 263 | fn find_closest_dont_correct_nonsense() { |
85984a87 DW |
264 | cargo_process("there-is-no-way-that-there-is-a-command-close-to-this") |
265 | .cwd(&paths::root()) | |
266 | .with_status(101) | |
267 | .with_stderr( | |
5f3ded12 | 268 | "\ |
269 | [ERROR] no such subcommand: `there-is-no-way-that-there-is-a-command-close-to-this` | |
270 | ||
42df8740 | 271 | <tab>View all installed commands with `cargo --list`", |
fecb7246 AC |
272 | ) |
273 | .run(); | |
79858995 KA |
274 | } |
275 | ||
0e0d9688 | 276 | #[cargo_test] |
79858995 | 277 | fn displays_subcommand_on_error() { |
85984a87 DW |
278 | cargo_process("invalid-command") |
279 | .with_status(101) | |
b2f44de8 | 280 | .with_stderr( |
5f3ded12 | 281 | "\ |
282 | [ERROR] no such subcommand: `invalid-command` | |
283 | ||
42df8740 | 284 | <tab>View all installed commands with `cargo --list`", |
b2f44de8 | 285 | ) |
85984a87 | 286 | .run(); |
6950bbb0 | 287 | } |
2badab8c | 288 | |
b0998864 J |
289 | #[cargo_test] |
290 | fn override_cargo_home() { | |
291 | let root = paths::root(); | |
292 | let my_home = root.join("my_home"); | |
293 | fs::create_dir(&my_home).unwrap(); | |
294 | fs::write( | |
295 | &my_home.join("config"), | |
296 | r#" | |
297 | [cargo-new] | |
298 | vcs = "none" | |
299 | "#, | |
300 | ) | |
301 | .unwrap(); | |
302 | ||
303 | cargo_process("new foo").env("CARGO_HOME", &my_home).run(); | |
304 | ||
305 | assert!(!paths::root().join("foo/.git").is_dir()); | |
306 | ||
307 | cargo_process("new foo2").run(); | |
308 | ||
309 | assert!(paths::root().join("foo2/.git").is_dir()); | |
310 | } | |
311 | ||
0e0d9688 | 312 | #[cargo_test] |
015a08a0 | 313 | fn cargo_subcommand_env() { |
1e682848 AC |
314 | let src = format!( |
315 | r#" | |
015a08a0 VK |
316 | use std::env; |
317 | ||
318 | fn main() {{ | |
319 | println!("{{}}", env::var("{}").unwrap()); | |
320 | }} | |
1e682848 AC |
321 | "#, |
322 | cargo::CARGO_ENV | |
323 | ); | |
015a08a0 | 324 | |
85984a87 DW |
325 | let p = project() |
326 | .at("cargo-envtest") | |
015a08a0 | 327 | .file("Cargo.toml", &basic_bin_manifest("cargo-envtest")) |
d43ee1dd NK |
328 | .file("src/main.rs", &src) |
329 | .build(); | |
015a08a0 VK |
330 | |
331 | let target_dir = p.target_debug_dir(); | |
332 | ||
85984a87 | 333 | p.cargo("build").run(); |
570fe892 | 334 | assert!(p.bin("cargo-envtest").is_file()); |
015a08a0 | 335 | |
015a08a0 VK |
336 | let cargo = cargo_exe().canonicalize().unwrap(); |
337 | let mut path = path(); | |
338 | path.push(target_dir); | |
339 | let path = env::join_paths(path.iter()).unwrap(); | |
340 | ||
85984a87 DW |
341 | cargo_process("envtest") |
342 | .env("PATH", &path) | |
343 | .with_stdout(cargo.to_str().unwrap()) | |
344 | .run(); | |
015a08a0 VK |
345 | } |
346 | ||
c712f088 DC |
347 | #[cargo_test] |
348 | fn cargo_cmd_bins_vs_explicit_path() { | |
349 | // Set up `cargo-foo` binary in two places: inside `$HOME/.cargo/bin` and outside of it | |
350 | // | |
351 | // Return paths to both places | |
352 | fn set_up_cargo_foo() -> (PathBuf, PathBuf) { | |
353 | let p = project() | |
354 | .at("cargo-foo") | |
355 | .file("Cargo.toml", &basic_manifest("cargo-foo", "1.0.0")) | |
356 | .file( | |
357 | "src/bin/cargo-foo.rs", | |
358 | r#"fn main() { println!("INSIDE"); }"#, | |
359 | ) | |
360 | .file( | |
361 | "src/bin/cargo-foo2.rs", | |
362 | r#"fn main() { println!("OUTSIDE"); }"#, | |
363 | ) | |
364 | .build(); | |
365 | p.cargo("build").run(); | |
366 | let cargo_bin_dir = paths::home().join(".cargo/bin"); | |
367 | cargo_bin_dir.mkdir_p(); | |
368 | let root_bin_dir = paths::root().join("bin"); | |
369 | root_bin_dir.mkdir_p(); | |
370 | let exe_name = format!("cargo-foo{}", env::consts::EXE_SUFFIX); | |
371 | fs::rename(p.bin("cargo-foo"), cargo_bin_dir.join(&exe_name)).unwrap(); | |
372 | fs::rename(p.bin("cargo-foo2"), root_bin_dir.join(&exe_name)).unwrap(); | |
373 | ||
374 | (root_bin_dir, cargo_bin_dir) | |
375 | } | |
376 | ||
377 | let (outside_dir, inside_dir) = set_up_cargo_foo(); | |
378 | ||
379 | // If `$CARGO_HOME/bin` is not in a path, prefer it over anything in `$PATH`. | |
380 | // | |
381 | // This is the historical behavior we don't want to break. | |
382 | cargo_process("foo").with_stdout_contains("INSIDE").run(); | |
383 | ||
384 | // When `$CARGO_HOME/bin` is in the `$PATH` | |
385 | // use only `$PATH` so the user-defined ordering is respected. | |
386 | { | |
387 | cargo_process("foo") | |
388 | .env( | |
389 | "PATH", | |
390 | join_paths(&[&inside_dir, &outside_dir], "PATH").unwrap(), | |
391 | ) | |
392 | .with_stdout_contains("INSIDE") | |
393 | .run(); | |
394 | ||
395 | cargo_process("foo") | |
396 | // Note: trailing slash | |
397 | .env( | |
398 | "PATH", | |
399 | join_paths(&[inside_dir.join(""), outside_dir.join("")], "PATH").unwrap(), | |
400 | ) | |
401 | .with_stdout_contains("INSIDE") | |
402 | .run(); | |
403 | ||
404 | cargo_process("foo") | |
405 | .env( | |
406 | "PATH", | |
407 | join_paths(&[&outside_dir, &inside_dir], "PATH").unwrap(), | |
408 | ) | |
409 | .with_stdout_contains("OUTSIDE") | |
410 | .run(); | |
411 | ||
412 | cargo_process("foo") | |
413 | // Note: trailing slash | |
414 | .env( | |
415 | "PATH", | |
416 | join_paths(&[outside_dir.join(""), inside_dir.join("")], "PATH").unwrap(), | |
417 | ) | |
418 | .with_stdout_contains("OUTSIDE") | |
419 | .run(); | |
420 | } | |
421 | } | |
422 | ||
423 | #[test] | |
0e0d9688 | 424 | #[cargo_test] |
deb1c1e1 | 425 | fn cargo_subcommand_args() { |
5a56cf2e NK |
426 | let p = echo_subcommand(); |
427 | let cargo_foo_bin = p.bin("cargo-echo"); | |
570fe892 | 428 | assert!(cargo_foo_bin.is_file()); |
deb1c1e1 AK |
429 | |
430 | let mut path = path(); | |
431 | path.push(p.target_debug_dir()); | |
432 | let path = env::join_paths(path.iter()).unwrap(); | |
433 | ||
5a56cf2e | 434 | cargo_process("echo bar -v --help") |
85984a87 | 435 | .env("PATH", &path) |
5a56cf2e | 436 | .with_stdout("echo bar -v --help") |
49f73b9c | 437 | .run(); |
deb1c1e1 AK |
438 | } |
439 | ||
0e0d9688 | 440 | #[cargo_test] |
6950bbb0 | 441 | fn explain() { |
85984a87 DW |
442 | cargo_process("--explain E0001") |
443 | .with_stdout_contains( | |
b0c181d9 | 444 | "This error suggests that the expression arm corresponding to the noted pattern", |
fecb7246 AC |
445 | ) |
446 | .run(); | |
6950bbb0 | 447 | } |
a4104914 | 448 | |
7274307a EH |
449 | #[cargo_test] |
450 | fn closed_output_ok() { | |
451 | // Checks that closed output doesn't cause an error. | |
452 | let mut p = cargo_process("--list").build_command(); | |
453 | p.stdout(Stdio::piped()).stderr(Stdio::piped()); | |
454 | let mut child = p.spawn().unwrap(); | |
455 | // Close stdout | |
456 | drop(child.stdout.take()); | |
457 | // Read stderr | |
458 | let mut s = String::new(); | |
459 | child | |
460 | .stderr | |
461 | .as_mut() | |
462 | .unwrap() | |
463 | .read_to_string(&mut s) | |
464 | .unwrap(); | |
465 | let status = child.wait().unwrap(); | |
466 | assert!(status.success()); | |
f5a3d559 | 467 | assert!(s.is_empty(), "{}", s); |
7274307a | 468 | } |
8f5f2ed2 | 469 | |
470 | #[cargo_test] | |
471 | fn subcommand_leading_plus_output_contains() { | |
472 | cargo_process("+nightly") | |
473 | .with_status(101) | |
222d90b7 | 474 | .with_stderr( |
8f5f2ed2 | 475 | "\ |
222d90b7 | 476 | error: no such subcommand: `+nightly` |
477 | ||
478 | <tab>Cargo does not handle `+toolchain` directives. | |
479 | <tab>Did you mean to invoke `cargo` through `rustup` instead?", | |
480 | ) | |
481 | .run(); | |
482 | } | |
483 | ||
484 | #[cargo_test] | |
485 | fn full_did_you_mean() { | |
486 | cargo_process("bluid") | |
487 | .with_status(101) | |
488 | .with_stderr( | |
489 | "\ | |
490 | error: no such subcommand: `bluid` | |
491 | ||
492 | <tab>Did you mean `build`? | |
493 | ||
494 | <tab>View all installed commands with `cargo --list`", | |
8f5f2ed2 | 495 | ) |
496 | .run(); | |
497 | } |