]> git.proxmox.com Git - cargo.git/blob - tests/testsuite/cargo_command.rs
Auto merge of #8122 - kornelski:future-edition, r=Eh2406
[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::{self, File};
5 use std::path::{Path, PathBuf};
6 use std::str;
7
8 use cargo_test_support::cargo_process;
9 use cargo_test_support::paths::{self, CargoPathExt};
10 use cargo_test_support::registry::Package;
11 use cargo_test_support::{basic_bin_manifest, basic_manifest, cargo_exe, project, Project};
12
13 #[cfg_attr(windows, allow(dead_code))]
14 enum FakeKind<'a> {
15 Executable,
16 Symlink { target: &'a Path },
17 }
18
19 /// Adds an empty file with executable flags (and platform-dependent suffix).
20 //
21 // TODO: move this to `Project` if other cases using this emerge.
22 fn fake_file(proj: Project, dir: &Path, name: &str, kind: &FakeKind<'_>) -> Project {
23 let path = proj
24 .root()
25 .join(dir)
26 .join(&format!("{}{}", name, env::consts::EXE_SUFFIX));
27 path.parent().unwrap().mkdir_p();
28 match *kind {
29 FakeKind::Executable => {
30 File::create(&path).unwrap();
31 make_executable(&path);
32 }
33 FakeKind::Symlink { target } => {
34 make_symlink(&path, target);
35 }
36 }
37 return proj;
38
39 #[cfg(unix)]
40 fn make_executable(p: &Path) {
41 use std::os::unix::prelude::*;
42
43 let mut perms = fs::metadata(p).unwrap().permissions();
44 let mode = perms.mode();
45 perms.set_mode(mode | 0o111);
46 fs::set_permissions(p, perms).unwrap();
47 }
48 #[cfg(windows)]
49 fn make_executable(_: &Path) {}
50 #[cfg(unix)]
51 fn make_symlink(p: &Path, t: &Path) {
52 ::std::os::unix::fs::symlink(t, p).expect("Failed to create symlink");
53 }
54 #[cfg(windows)]
55 fn make_symlink(_: &Path, _: &Path) {
56 panic!("Not supported")
57 }
58 }
59
60 fn path() -> Vec<PathBuf> {
61 env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
62 }
63
64 #[cargo_test]
65 fn list_commands_with_descriptions() {
66 let p = project().build();
67 p.cargo("--list")
68 .with_stdout_contains(
69 " build Compile a local package and all of its dependencies",
70 )
71 // Assert that `read-manifest` prints the right one-line description followed by another
72 // command, indented.
73 .with_stdout_contains(
74 " read-manifest Print a JSON representation of a Cargo.toml manifest.",
75 )
76 .run();
77 }
78
79 #[cargo_test]
80 fn list_command_looks_at_path() {
81 let proj = project().build();
82 let proj = fake_file(
83 proj,
84 Path::new("path-test"),
85 "cargo-1",
86 &FakeKind::Executable,
87 );
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 // Windows and symlinks don't currently mix well.
105 #[cfg(unix)]
106 #[cargo_test]
107 fn list_command_resolves_symlinks() {
108 let proj = project().build();
109 let proj = fake_file(
110 proj,
111 Path::new("path-test"),
112 "cargo-2",
113 &FakeKind::Symlink {
114 target: &cargo_exe(),
115 },
116 );
117
118 let mut path = path();
119 path.push(proj.root().join("path-test"));
120 let path = env::join_paths(path.iter()).unwrap();
121 let output = cargo_process("-v --list")
122 .env("PATH", &path)
123 .exec_with_output()
124 .unwrap();
125 let output = str::from_utf8(&output.stdout).unwrap();
126 assert!(
127 output.contains("\n 2 "),
128 "missing 2: {}",
129 output
130 );
131 }
132
133 #[cargo_test]
134 fn find_closest_biuld_to_build() {
135 cargo_process("biuld")
136 .with_status(101)
137 .with_stderr_contains(
138 "\
139 error: no such subcommand: `biuld`
140
141 <tab>Did you mean `build`?
142 ",
143 )
144 .run();
145
146 // But, if we actually have `biuld`, it must work!
147 // https://github.com/rust-lang/cargo/issues/5201
148 Package::new("cargo-biuld", "1.0.0")
149 .file(
150 "src/main.rs",
151 r#"
152 fn main() {
153 println!("Similar, but not identical to, build");
154 }
155 "#,
156 )
157 .publish();
158
159 cargo_process("install cargo-biuld").run();
160 cargo_process("biuld")
161 .with_stdout("Similar, but not identical to, build\n")
162 .run();
163 cargo_process("--list")
164 .with_stdout_contains(
165 " build Compile a local package and all of its dependencies\n",
166 )
167 .with_stdout_contains(" biuld\n")
168 .run();
169 }
170
171 #[cargo_test]
172 fn find_closest_alias() {
173 let root = paths::root();
174 let my_home = root.join("my_home");
175 fs::create_dir(&my_home).unwrap();
176 fs::write(
177 &my_home.join("config"),
178 r#"
179 [alias]
180 myalias = "build"
181 "#,
182 )
183 .unwrap();
184
185 cargo_process("myalais")
186 .env("CARGO_HOME", &my_home)
187 .with_status(101)
188 .with_stderr_contains(
189 "\
190 error: no such subcommand: `myalais`
191
192 <tab>Did you mean `myalias`?
193 ",
194 )
195 .run();
196
197 // But, if no alias is defined, it must not suggest one!
198 cargo_process("myalais")
199 .with_status(101)
200 .with_stderr_contains(
201 "\
202 error: no such subcommand: `myalais`
203 ",
204 )
205 .with_stderr_does_not_contain(
206 "\
207 <tab>Did you mean `myalias`?
208 ",
209 )
210 .run();
211 }
212
213 // If a subcommand is more than an edit distance of 3 away, we don't make a suggestion.
214 #[cargo_test]
215 fn find_closest_dont_correct_nonsense() {
216 cargo_process("there-is-no-way-that-there-is-a-command-close-to-this")
217 .cwd(&paths::root())
218 .with_status(101)
219 .with_stderr(
220 "[ERROR] no such subcommand: \
221 `there-is-no-way-that-there-is-a-command-close-to-this`
222 ",
223 )
224 .run();
225 }
226
227 #[cargo_test]
228 fn displays_subcommand_on_error() {
229 cargo_process("invalid-command")
230 .with_status(101)
231 .with_stderr("[ERROR] no such subcommand: `invalid-command`\n")
232 .run();
233 }
234
235 #[cargo_test]
236 fn override_cargo_home() {
237 let root = paths::root();
238 let my_home = root.join("my_home");
239 fs::create_dir(&my_home).unwrap();
240 fs::write(
241 &my_home.join("config"),
242 r#"
243 [cargo-new]
244 name = "foo"
245 email = "bar"
246 git = false
247 "#,
248 )
249 .unwrap();
250
251 cargo_process("new foo")
252 .env("USER", "foo")
253 .env("CARGO_HOME", &my_home)
254 .run();
255
256 let toml = paths::root().join("foo/Cargo.toml");
257 let contents = fs::read_to_string(&toml).unwrap();
258 assert!(contents.contains(r#"authors = ["foo <bar>"]"#));
259 }
260
261 #[cargo_test]
262 fn cargo_subcommand_env() {
263 let src = format!(
264 r#"
265 use std::env;
266
267 fn main() {{
268 println!("{{}}", env::var("{}").unwrap());
269 }}
270 "#,
271 cargo::CARGO_ENV
272 );
273
274 let p = project()
275 .at("cargo-envtest")
276 .file("Cargo.toml", &basic_bin_manifest("cargo-envtest"))
277 .file("src/main.rs", &src)
278 .build();
279
280 let target_dir = p.target_debug_dir();
281
282 p.cargo("build").run();
283 assert!(p.bin("cargo-envtest").is_file());
284
285 let cargo = cargo_exe().canonicalize().unwrap();
286 let mut path = path();
287 path.push(target_dir);
288 let path = env::join_paths(path.iter()).unwrap();
289
290 cargo_process("envtest")
291 .env("PATH", &path)
292 .with_stdout(cargo.to_str().unwrap())
293 .run();
294 }
295
296 #[cargo_test]
297 fn cargo_subcommand_args() {
298 let p = project()
299 .at("cargo-foo")
300 .file("Cargo.toml", &basic_manifest("cargo-foo", "0.0.1"))
301 .file(
302 "src/main.rs",
303 r#"
304 fn main() {
305 let args: Vec<_> = ::std::env::args().collect();
306 println!("{:?}", args);
307 }
308 "#,
309 )
310 .build();
311
312 p.cargo("build").run();
313 let cargo_foo_bin = p.bin("cargo-foo");
314 assert!(cargo_foo_bin.is_file());
315
316 let mut path = path();
317 path.push(p.target_debug_dir());
318 let path = env::join_paths(path.iter()).unwrap();
319
320 cargo_process("foo bar -v --help")
321 .env("PATH", &path)
322 .with_stdout(
323 r#"["[CWD]/cargo-foo/target/debug/cargo-foo[EXE]", "foo", "bar", "-v", "--help"]"#,
324 )
325 .run();
326 }
327
328 #[cargo_test]
329 fn cargo_help() {
330 cargo_process("").run();
331 cargo_process("help").run();
332 cargo_process("-h").run();
333 cargo_process("help build").run();
334 cargo_process("build -h").run();
335 cargo_process("help help").run();
336 }
337
338 #[cargo_test]
339 fn cargo_help_external_subcommand() {
340 Package::new("cargo-fake-help", "1.0.0")
341 .file(
342 "src/main.rs",
343 r#"
344 fn main() {
345 if ::std::env::args().nth(2) == Some(String::from("--help")) {
346 println!("fancy help output");
347 }
348 }"#,
349 )
350 .publish();
351 cargo_process("install cargo-fake-help").run();
352 cargo_process("help fake-help")
353 .with_stdout("fancy help output\n")
354 .run();
355 }
356
357 #[cargo_test]
358 fn explain() {
359 cargo_process("--explain E0001")
360 .with_stdout_contains(
361 "This error suggests that the expression arm corresponding to the noted pattern",
362 )
363 .run();
364 }
365
366 // Test that the output of `cargo -Z help` shows a different help screen with
367 // all the `-Z` flags.
368 #[cargo_test]
369 fn z_flags_help() {
370 cargo_process("-Z help")
371 .with_stdout_contains(" -Z unstable-options -- Allow the usage of unstable options")
372 .run();
373 }