]> git.proxmox.com Git - cargo.git/blob - tests/testsuite/cargo_command.rs
Auto merge of #11044 - Eh2406:file_hash, r=weihanglo
[cargo.git] / tests / testsuite / cargo_command.rs
1 //! Tests for custom cargo commands and other global command features.
2
3 use std::env;
4 use std::fs;
5 use std::io::Read;
6 use std::path::{Path, PathBuf};
7 use std::process::Stdio;
8 use std::str;
9
10 use cargo_test_support::registry::Package;
11 use cargo_test_support::tools::echo_subcommand;
12 use cargo_test_support::{
13 basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home,
14 };
15
16 fn path() -> Vec<PathBuf> {
17 env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
18 }
19
20 #[cargo_test]
21 fn list_commands_with_descriptions() {
22 let p = project().build();
23 p.cargo("--list")
24 .with_stdout_contains(
25 " build Compile a local package and all of its dependencies",
26 )
27 // Assert that `read-manifest` prints the right one-line description followed by another
28 // command, indented.
29 .with_stdout_contains(
30 " read-manifest Print a JSON representation of a Cargo.toml manifest.",
31 )
32 .run();
33 }
34
35 #[cargo_test]
36 fn list_builtin_aliases_with_descriptions() {
37 let p = project().build();
38 p.cargo("--list")
39 .with_stdout_contains(" b alias: build")
40 .with_stdout_contains(" c alias: check")
41 .with_stdout_contains(" r alias: run")
42 .with_stdout_contains(" t alias: test")
43 .run();
44 }
45
46 #[cargo_test]
47 fn list_custom_aliases_with_descriptions() {
48 let p = project_in_home("proj")
49 .file(
50 &paths::home().join(".cargo").join("config"),
51 r#"
52 [alias]
53 myaliasstr = "foo --bar"
54 myaliasvec = ["foo", "--bar"]
55 "#,
56 )
57 .build();
58
59 p.cargo("--list")
60 .with_stdout_contains(" myaliasstr alias: foo --bar")
61 .with_stdout_contains(" myaliasvec alias: foo --bar")
62 .run();
63 }
64
65 #[cargo_test]
66 fn list_dedupe() {
67 let p = project()
68 .executable(Path::new("path-test-1").join("cargo-dupe"), "")
69 .executable(Path::new("path-test-2").join("cargo-dupe"), "")
70 .build();
71
72 let mut path = path();
73 path.push(p.root().join("path-test-1"));
74 path.push(p.root().join("path-test-2"));
75 let path = env::join_paths(path.iter()).unwrap();
76
77 p.cargo("--list")
78 .env("PATH", &path)
79 .with_stdout_contains_n(" dupe", 1)
80 .run();
81 }
82
83 #[cargo_test]
84 fn list_command_looks_at_path() {
85 let proj = project()
86 .executable(Path::new("path-test").join("cargo-1"), "")
87 .build();
88
89 let mut path = path();
90 path.push(proj.root().join("path-test"));
91 let path = env::join_paths(path.iter()).unwrap();
92 let output = cargo_process("-v --list")
93 .env("PATH", &path)
94 .exec_with_output()
95 .unwrap();
96 let output = str::from_utf8(&output.stdout).unwrap();
97 assert!(
98 output.contains("\n 1 "),
99 "missing 1: {}",
100 output
101 );
102 }
103
104 #[cargo_test]
105 fn list_command_handles_known_external_commands() {
106 let p = project()
107 .executable(Path::new("path-test").join("cargo-fmt"), "")
108 .build();
109
110 let fmt_desc = " fmt Formats all bin and lib files of the current crate using rustfmt.";
111
112 // Without path - fmt isn't there
113 p.cargo("--list")
114 .env("PATH", "")
115 .with_stdout_does_not_contain(fmt_desc)
116 .run();
117
118 // With path - fmt is there with known description
119 let mut path = path();
120 path.push(p.root().join("path-test"));
121 let path = env::join_paths(path.iter()).unwrap();
122
123 p.cargo("--list")
124 .env("PATH", &path)
125 .with_stdout_contains(fmt_desc)
126 .run();
127 }
128
129 #[cargo_test]
130 fn list_command_resolves_symlinks() {
131 let proj = project()
132 .symlink(cargo_exe(), Path::new("path-test").join("cargo-2"))
133 .build();
134
135 let mut path = path();
136 path.push(proj.root().join("path-test"));
137 let path = env::join_paths(path.iter()).unwrap();
138 let output = cargo_process("-v --list")
139 .env("PATH", &path)
140 .exec_with_output()
141 .unwrap();
142 let output = str::from_utf8(&output.stdout).unwrap();
143 assert!(
144 output.contains("\n 2 "),
145 "missing 2: {}",
146 output
147 );
148 }
149
150 #[cargo_test]
151 fn find_closest_capital_c_to_c() {
152 cargo_process("C")
153 .with_status(101)
154 .with_stderr_contains(
155 "\
156 error: no such subcommand: `C`
157
158 <tab>Did you mean `c`?
159 ",
160 )
161 .run();
162 }
163
164 #[cargo_test]
165 fn find_closest_capital_b_to_b() {
166 cargo_process("B")
167 .with_status(101)
168 .with_stderr_contains(
169 "\
170 error: no such subcommand: `B`
171
172 <tab>Did you mean `b`?
173 ",
174 )
175 .run();
176 }
177
178 #[cargo_test]
179 fn find_closest_biuld_to_build() {
180 cargo_process("biuld")
181 .with_status(101)
182 .with_stderr_contains(
183 "\
184 error: no such subcommand: `biuld`
185
186 <tab>Did you mean `build`?
187 ",
188 )
189 .run();
190
191 // But, if we actually have `biuld`, it must work!
192 // https://github.com/rust-lang/cargo/issues/5201
193 Package::new("cargo-biuld", "1.0.0")
194 .file(
195 "src/main.rs",
196 r#"
197 fn main() {
198 println!("Similar, but not identical to, build");
199 }
200 "#,
201 )
202 .publish();
203
204 cargo_process("install cargo-biuld").run();
205 cargo_process("biuld")
206 .with_stdout("Similar, but not identical to, build\n")
207 .run();
208 cargo_process("--list")
209 .with_stdout_contains(
210 " build Compile a local package and all of its dependencies\n",
211 )
212 .with_stdout_contains(" biuld\n")
213 .run();
214 }
215
216 #[cargo_test]
217 fn find_closest_alias() {
218 let root = paths::root();
219 let my_home = root.join("my_home");
220 fs::create_dir(&my_home).unwrap();
221 fs::write(
222 &my_home.join("config"),
223 r#"
224 [alias]
225 myalias = "build"
226 "#,
227 )
228 .unwrap();
229
230 cargo_process("myalais")
231 .env("CARGO_HOME", &my_home)
232 .with_status(101)
233 .with_stderr_contains(
234 "\
235 error: no such subcommand: `myalais`
236
237 <tab>Did you mean `myalias`?
238 ",
239 )
240 .run();
241
242 // But, if no alias is defined, it must not suggest one!
243 cargo_process("myalais")
244 .with_status(101)
245 .with_stderr_contains(
246 "\
247 error: no such subcommand: `myalais`
248 ",
249 )
250 .with_stderr_does_not_contain(
251 "\
252 <tab>Did you mean `myalias`?
253 ",
254 )
255 .run();
256 }
257
258 // If a subcommand is more than an edit distance of 3 away, we don't make a suggestion.
259 #[cargo_test]
260 fn find_closest_dont_correct_nonsense() {
261 cargo_process("there-is-no-way-that-there-is-a-command-close-to-this")
262 .cwd(&paths::root())
263 .with_status(101)
264 .with_stderr(
265 "\
266 [ERROR] no such subcommand: `there-is-no-way-that-there-is-a-command-close-to-this`
267
268 <tab>View all installed commands with `cargo --list`",
269 )
270 .run();
271 }
272
273 #[cargo_test]
274 fn displays_subcommand_on_error() {
275 cargo_process("invalid-command")
276 .with_status(101)
277 .with_stderr(
278 "\
279 [ERROR] no such subcommand: `invalid-command`
280
281 <tab>View all installed commands with `cargo --list`",
282 )
283 .run();
284 }
285
286 #[cargo_test]
287 fn override_cargo_home() {
288 let root = paths::root();
289 let my_home = root.join("my_home");
290 fs::create_dir(&my_home).unwrap();
291 fs::write(
292 &my_home.join("config"),
293 r#"
294 [cargo-new]
295 vcs = "none"
296 "#,
297 )
298 .unwrap();
299
300 cargo_process("new foo").env("CARGO_HOME", &my_home).run();
301
302 assert!(!paths::root().join("foo/.git").is_dir());
303
304 cargo_process("new foo2").run();
305
306 assert!(paths::root().join("foo2/.git").is_dir());
307 }
308
309 #[cargo_test]
310 fn cargo_subcommand_env() {
311 let src = format!(
312 r#"
313 use std::env;
314
315 fn main() {{
316 println!("{{}}", env::var("{}").unwrap());
317 }}
318 "#,
319 cargo::CARGO_ENV
320 );
321
322 let p = project()
323 .at("cargo-envtest")
324 .file("Cargo.toml", &basic_bin_manifest("cargo-envtest"))
325 .file("src/main.rs", &src)
326 .build();
327
328 let target_dir = p.target_debug_dir();
329
330 p.cargo("build").run();
331 assert!(p.bin("cargo-envtest").is_file());
332
333 let cargo = cargo_exe().canonicalize().unwrap();
334 let mut path = path();
335 path.push(target_dir);
336 let path = env::join_paths(path.iter()).unwrap();
337
338 cargo_process("envtest")
339 .env("PATH", &path)
340 .with_stdout(cargo.to_str().unwrap())
341 .run();
342 }
343
344 #[cargo_test]
345 fn cargo_subcommand_args() {
346 let p = echo_subcommand();
347 let cargo_foo_bin = p.bin("cargo-echo");
348 assert!(cargo_foo_bin.is_file());
349
350 let mut path = path();
351 path.push(p.target_debug_dir());
352 let path = env::join_paths(path.iter()).unwrap();
353
354 cargo_process("echo bar -v --help")
355 .env("PATH", &path)
356 .with_stdout("echo bar -v --help")
357 .run();
358 }
359
360 #[cargo_test]
361 fn explain() {
362 cargo_process("--explain E0001")
363 .with_stdout_contains(
364 "This error suggests that the expression arm corresponding to the noted pattern",
365 )
366 .run();
367 }
368
369 #[cargo_test]
370 fn closed_output_ok() {
371 // Checks that closed output doesn't cause an error.
372 let mut p = cargo_process("--list").build_command();
373 p.stdout(Stdio::piped()).stderr(Stdio::piped());
374 let mut child = p.spawn().unwrap();
375 // Close stdout
376 drop(child.stdout.take());
377 // Read stderr
378 let mut s = String::new();
379 child
380 .stderr
381 .as_mut()
382 .unwrap()
383 .read_to_string(&mut s)
384 .unwrap();
385 let status = child.wait().unwrap();
386 assert!(status.success());
387 assert!(s.is_empty(), "{}", s);
388 }
389
390 #[cargo_test]
391 fn subcommand_leading_plus_output_contains() {
392 cargo_process("+nightly")
393 .with_status(101)
394 .with_stderr(
395 "\
396 error: no such subcommand: `+nightly`
397
398 <tab>Cargo does not handle `+toolchain` directives.
399 <tab>Did you mean to invoke `cargo` through `rustup` instead?",
400 )
401 .run();
402 }
403
404 #[cargo_test]
405 fn full_did_you_mean() {
406 cargo_process("bluid")
407 .with_status(101)
408 .with_stderr(
409 "\
410 error: no such subcommand: `bluid`
411
412 <tab>Did you mean `build`?
413
414 <tab>View all installed commands with `cargo --list`",
415 )
416 .run();
417 }