]> git.proxmox.com Git - cargo.git/blame - tests/testsuite/cargo_command.rs
Do not add home bin path to PATH if it's already there
[cargo.git] / tests / testsuite / cargo_command.rs
CommitLineData
83571aee
EH
1//! Tests for custom cargo commands and other global command features.
2
ee5e24ff 3use std::env;
dde290e6 4use std::fs;
7274307a 5use std::io::Read;
a6dad622 6use std::path::{Path, PathBuf};
7274307a 7use std::process::Stdio;
8cce8996 8use std::str;
8cce8996 9
c712f088
DC
10use cargo_test_support::basic_manifest;
11use cargo_test_support::paths::CargoPathExt;
9115b2c3 12use cargo_test_support::registry::Package;
5a56cf2e
NK
13use cargo_test_support::tools::echo_subcommand;
14use cargo_test_support::{
15 basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home,
16};
c712f088 17use cargo_util::paths::join_paths;
a3f6a404 18
a6dad622 19fn 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
24fn 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 39fn 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]
50fn 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]
69fn 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 87fn 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]
108fn 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 133fn 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]
154fn find_closest_capital_c_to_c() {
155 cargo_process("C")
156 .with_status(101)
157 .with_stderr_contains(
158 "\
159error: no such subcommand: `C`
160
161<tab>Did you mean `c`?
162",
163 )
164 .run();
165}
166
167#[cargo_test]
6f13c466 168fn find_closest_capital_b_to_b() {
42528799
ML
169 cargo_process("B")
170 .with_status(101)
171 .with_stderr_contains(
172 "\
173error: no such subcommand: `B`
174
175<tab>Did you mean `b`?
176",
177 )
178 .run();
179}
180
0e0d9688 181#[cargo_test]
6950bbb0 182fn find_closest_biuld_to_build() {
85984a87
DW
183 cargo_process("biuld")
184 .with_status(101)
185 .with_stderr_contains(
1e682848 186 "\
a1735c7a
AK
187error: 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]
220fn 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 "\
238error: 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 "\
250error: 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 263fn 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 277fn 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]
290fn 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 313fn 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]
348fn 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 425fn 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 441fn 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]
450fn 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]
471fn subcommand_leading_plus_output_contains() {
472 cargo_process("+nightly")
473 .with_status(101)
222d90b7 474 .with_stderr(
8f5f2ed2 475 "\
222d90b7 476error: 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]
485fn full_did_you_mean() {
486 cargo_process("bluid")
487 .with_status(101)
488 .with_stderr(
489 "\
490error: 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}