]> git.proxmox.com Git - cargo.git/blob - tests/testsuite/cargo_command.rs
Minor testsuite organization.
[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::prelude::*;
6 use std::path::{Path, PathBuf};
7 use std::str;
8
9 use cargo;
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_command_looks_at_path() {
83 let proj = project().build();
84 let proj = fake_file(
85 proj,
86 Path::new("path-test"),
87 "cargo-1",
88 &FakeKind::Executable,
89 );
90
91 let mut path = path();
92 path.push(proj.root().join("path-test"));
93 let path = env::join_paths(path.iter()).unwrap();
94 let output = cargo_process("-v --list")
95 .env("PATH", &path)
96 .exec_with_output()
97 .unwrap();
98 let output = str::from_utf8(&output.stdout).unwrap();
99 assert!(
100 output.contains("\n 1 "),
101 "missing 1: {}",
102 output
103 );
104 }
105
106 // Windows and symlinks don't currently mix well.
107 #[cfg(unix)]
108 #[cargo_test]
109 fn list_command_resolves_symlinks() {
110 let proj = project().build();
111 let proj = fake_file(
112 proj,
113 Path::new("path-test"),
114 "cargo-2",
115 &FakeKind::Symlink {
116 target: &cargo_exe(),
117 },
118 );
119
120 let mut path = path();
121 path.push(proj.root().join("path-test"));
122 let path = env::join_paths(path.iter()).unwrap();
123 let output = cargo_process("-v --list")
124 .env("PATH", &path)
125 .exec_with_output()
126 .unwrap();
127 let output = str::from_utf8(&output.stdout).unwrap();
128 assert!(
129 output.contains("\n 2 "),
130 "missing 2: {}",
131 output
132 );
133 }
134
135 #[cargo_test]
136 fn find_closest_biuld_to_build() {
137 cargo_process("biuld")
138 .with_status(101)
139 .with_stderr_contains(
140 "\
141 error: no such subcommand: `biuld`
142
143 <tab>Did you mean `build`?
144 ",
145 )
146 .run();
147
148 // But, if we actually have `biuld`, it must work!
149 // https://github.com/rust-lang/cargo/issues/5201
150 Package::new("cargo-biuld", "1.0.0")
151 .file(
152 "src/main.rs",
153 r#"
154 fn main() {
155 println!("Similar, but not identical to, build");
156 }
157 "#,
158 )
159 .publish();
160
161 cargo_process("install cargo-biuld").run();
162 cargo_process("biuld")
163 .with_stdout("Similar, but not identical to, build\n")
164 .run();
165 cargo_process("--list")
166 .with_stdout_contains(
167 " build Compile a local package and all of its dependencies\n",
168 )
169 .with_stdout_contains(" biuld\n")
170 .run();
171 }
172
173 #[cargo_test]
174 fn find_closest_alias() {
175 let root = paths::root();
176 let my_home = root.join("my_home");
177 fs::create_dir(&my_home).unwrap();
178 File::create(&my_home.join("config"))
179 .unwrap()
180 .write_all(
181 br#"
182 [alias]
183 myalias = "build"
184 "#,
185 )
186 .unwrap();
187
188 cargo_process("myalais")
189 .env("CARGO_HOME", &my_home)
190 .with_status(101)
191 .with_stderr_contains(
192 "\
193 error: no such subcommand: `myalais`
194
195 <tab>Did you mean `myalias`?
196 ",
197 )
198 .run();
199
200 // But, if no alias is defined, it must not suggest one!
201 cargo_process("myalais")
202 .with_status(101)
203 .with_stderr_contains(
204 "\
205 error: no such subcommand: `myalais`
206 ",
207 )
208 .with_stderr_does_not_contain(
209 "\
210 <tab>Did you mean `myalias`?
211 ",
212 )
213 .run();
214 }
215
216 // If a subcommand is more than an edit distance of 3 away, we don't make a suggestion.
217 #[cargo_test]
218 fn find_closest_dont_correct_nonsense() {
219 cargo_process("there-is-no-way-that-there-is-a-command-close-to-this")
220 .cwd(&paths::root())
221 .with_status(101)
222 .with_stderr(
223 "[ERROR] no such subcommand: \
224 `there-is-no-way-that-there-is-a-command-close-to-this`
225 ",
226 )
227 .run();
228 }
229
230 #[cargo_test]
231 fn displays_subcommand_on_error() {
232 cargo_process("invalid-command")
233 .with_status(101)
234 .with_stderr("[ERROR] no such subcommand: `invalid-command`\n")
235 .run();
236 }
237
238 #[cargo_test]
239 fn override_cargo_home() {
240 let root = paths::root();
241 let my_home = root.join("my_home");
242 fs::create_dir(&my_home).unwrap();
243 File::create(&my_home.join("config"))
244 .unwrap()
245 .write_all(
246 br#"
247 [cargo-new]
248 name = "foo"
249 email = "bar"
250 git = false
251 "#,
252 )
253 .unwrap();
254
255 cargo_process("new foo")
256 .env("USER", "foo")
257 .env("CARGO_HOME", &my_home)
258 .run();
259
260 let toml = paths::root().join("foo/Cargo.toml");
261 let mut contents = String::new();
262 File::open(&toml)
263 .unwrap()
264 .read_to_string(&mut contents)
265 .unwrap();
266 assert!(contents.contains(r#"authors = ["foo <bar>"]"#));
267 }
268
269 #[cargo_test]
270 fn cargo_subcommand_env() {
271 let src = format!(
272 r#"
273 use std::env;
274
275 fn main() {{
276 println!("{{}}", env::var("{}").unwrap());
277 }}
278 "#,
279 cargo::CARGO_ENV
280 );
281
282 let p = project()
283 .at("cargo-envtest")
284 .file("Cargo.toml", &basic_bin_manifest("cargo-envtest"))
285 .file("src/main.rs", &src)
286 .build();
287
288 let target_dir = p.target_debug_dir();
289
290 p.cargo("build").run();
291 assert!(p.bin("cargo-envtest").is_file());
292
293 let cargo = cargo_exe().canonicalize().unwrap();
294 let mut path = path();
295 path.push(target_dir);
296 let path = env::join_paths(path.iter()).unwrap();
297
298 cargo_process("envtest")
299 .env("PATH", &path)
300 .with_stdout(cargo.to_str().unwrap())
301 .run();
302 }
303
304 #[cargo_test]
305 fn cargo_subcommand_args() {
306 let p = project()
307 .at("cargo-foo")
308 .file("Cargo.toml", &basic_manifest("cargo-foo", "0.0.1"))
309 .file(
310 "src/main.rs",
311 r#"
312 fn main() {
313 let args: Vec<_> = ::std::env::args().collect();
314 println!("{:?}", args);
315 }
316 "#,
317 )
318 .build();
319
320 p.cargo("build").run();
321 let cargo_foo_bin = p.bin("cargo-foo");
322 assert!(cargo_foo_bin.is_file());
323
324 let mut path = path();
325 path.push(p.target_debug_dir());
326 let path = env::join_paths(path.iter()).unwrap();
327
328 cargo_process("foo bar -v --help")
329 .env("PATH", &path)
330 .with_stdout(
331 r#"["[CWD]/cargo-foo/target/debug/cargo-foo[EXE]", "foo", "bar", "-v", "--help"]"#,
332 )
333 .run();
334 }
335
336 #[cargo_test]
337 fn cargo_help() {
338 cargo_process("").run();
339 cargo_process("help").run();
340 cargo_process("-h").run();
341 cargo_process("help build").run();
342 cargo_process("build -h").run();
343 cargo_process("help help").run();
344 }
345
346 #[cargo_test]
347 fn cargo_help_external_subcommand() {
348 Package::new("cargo-fake-help", "1.0.0")
349 .file(
350 "src/main.rs",
351 r#"
352 fn main() {
353 if ::std::env::args().nth(2) == Some(String::from("--help")) {
354 println!("fancy help output");
355 }
356 }"#,
357 )
358 .publish();
359 cargo_process("install cargo-fake-help").run();
360 cargo_process("help fake-help")
361 .with_stdout("fancy help output\n")
362 .run();
363 }
364
365 #[cargo_test]
366 fn explain() {
367 cargo_process("--explain E0001")
368 .with_stdout_contains(
369 "This error suggests that the expression arm corresponding to the noted pattern",
370 )
371 .run();
372 }
373
374 // Test that the output of `cargo -Z help` shows a different help screen with
375 // all the `-Z` flags.
376 #[cargo_test]
377 fn z_flags_help() {
378 cargo_process("-Z help")
379 .with_stdout_contains(
380 " -Z unstable-options -- Allow the usage of unstable options such as --registry",
381 )
382 .run();
383 }