]> git.proxmox.com Git - cargo.git/blame_incremental - 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
1//! Tests for custom cargo commands and other global command features.
2
3use std::env;
4use std::fs;
5use std::io::Read;
6use std::path::{Path, PathBuf};
7use std::process::Stdio;
8use std::str;
9
10use cargo_test_support::basic_manifest;
11use cargo_test_support::paths::CargoPathExt;
12use cargo_test_support::registry::Package;
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};
17use cargo_util::paths::join_paths;
18
19fn path() -> Vec<PathBuf> {
20 env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
21}
22
23#[cargo_test]
24fn list_commands_with_descriptions() {
25 let p = project().build();
26 p.cargo("--list")
27 .with_stdout_contains(
28 " build Compile a local package and all of its dependencies",
29 )
30 // Assert that `read-manifest` prints the right one-line description followed by another
31 // command, indented.
32 .with_stdout_contains(
33 " read-manifest Print a JSON representation of a Cargo.toml manifest.",
34 )
35 .run();
36}
37
38#[cargo_test]
39fn list_builtin_aliases_with_descriptions() {
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
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")
63 .with_stdout_contains(" myaliasstr alias: foo --bar")
64 .with_stdout_contains(" myaliasvec alias: foo --bar")
65 .run();
66}
67
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
86#[cargo_test]
87fn list_command_looks_at_path() {
88 let proj = project()
89 .executable(Path::new("path-test").join("cargo-1"), "")
90 .build();
91
92 let mut path = path();
93 path.push(proj.root().join("path-test"));
94 let path = env::join_paths(path.iter()).unwrap();
95 let output = cargo_process("-v --list")
96 .env("PATH", &path)
97 .exec_with_output()
98 .unwrap();
99 let output = str::from_utf8(&output.stdout).unwrap();
100 assert!(
101 output.contains("\n 1 "),
102 "missing 1: {}",
103 output
104 );
105}
106
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
132#[cargo_test]
133fn list_command_resolves_symlinks() {
134 let proj = project()
135 .symlink(cargo_exe(), Path::new("path-test").join("cargo-2"))
136 .build();
137
138 let mut path = path();
139 path.push(proj.root().join("path-test"));
140 let path = env::join_paths(path.iter()).unwrap();
141 let output = cargo_process("-v --list")
142 .env("PATH", &path)
143 .exec_with_output()
144 .unwrap();
145 let output = str::from_utf8(&output.stdout).unwrap();
146 assert!(
147 output.contains("\n 2 "),
148 "missing 2: {}",
149 output
150 );
151}
152
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]
168fn find_closest_capital_b_to_b() {
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
181#[cargo_test]
182fn find_closest_biuld_to_build() {
183 cargo_process("biuld")
184 .with_status(101)
185 .with_stderr_contains(
186 "\
187error: no such subcommand: `biuld`
188
189<tab>Did you mean `build`?
190",
191 )
192 .run();
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#"
200 fn main() {
201 println!("Similar, but not identical to, build");
202 }
203 "#,
204 )
205 .publish();
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",
214 )
215 .with_stdout_contains(" biuld\n")
216 .run();
217}
218
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();
224 fs::write(
225 &my_home.join("config"),
226 r#"
227 [alias]
228 myalias = "build"
229 "#,
230 )
231 .unwrap();
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
261// If a subcommand is more than an edit distance of 3 away, we don't make a suggestion.
262#[cargo_test]
263fn find_closest_dont_correct_nonsense() {
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(
268 "\
269[ERROR] no such subcommand: `there-is-no-way-that-there-is-a-command-close-to-this`
270
271<tab>View all installed commands with `cargo --list`",
272 )
273 .run();
274}
275
276#[cargo_test]
277fn displays_subcommand_on_error() {
278 cargo_process("invalid-command")
279 .with_status(101)
280 .with_stderr(
281 "\
282[ERROR] no such subcommand: `invalid-command`
283
284<tab>View all installed commands with `cargo --list`",
285 )
286 .run();
287}
288
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
312#[cargo_test]
313fn cargo_subcommand_env() {
314 let src = format!(
315 r#"
316 use std::env;
317
318 fn main() {{
319 println!("{{}}", env::var("{}").unwrap());
320 }}
321 "#,
322 cargo::CARGO_ENV
323 );
324
325 let p = project()
326 .at("cargo-envtest")
327 .file("Cargo.toml", &basic_bin_manifest("cargo-envtest"))
328 .file("src/main.rs", &src)
329 .build();
330
331 let target_dir = p.target_debug_dir();
332
333 p.cargo("build").run();
334 assert!(p.bin("cargo-envtest").is_file());
335
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
341 cargo_process("envtest")
342 .env("PATH", &path)
343 .with_stdout(cargo.to_str().unwrap())
344 .run();
345}
346
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]
424#[cargo_test]
425fn cargo_subcommand_args() {
426 let p = echo_subcommand();
427 let cargo_foo_bin = p.bin("cargo-echo");
428 assert!(cargo_foo_bin.is_file());
429
430 let mut path = path();
431 path.push(p.target_debug_dir());
432 let path = env::join_paths(path.iter()).unwrap();
433
434 cargo_process("echo bar -v --help")
435 .env("PATH", &path)
436 .with_stdout("echo bar -v --help")
437 .run();
438}
439
440#[cargo_test]
441fn explain() {
442 cargo_process("--explain E0001")
443 .with_stdout_contains(
444 "This error suggests that the expression arm corresponding to the noted pattern",
445 )
446 .run();
447}
448
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());
467 assert!(s.is_empty(), "{}", s);
468}
469
470#[cargo_test]
471fn subcommand_leading_plus_output_contains() {
472 cargo_process("+nightly")
473 .with_status(101)
474 .with_stderr(
475 "\
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`",
495 )
496 .run();
497}