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