]>
Commit | Line | Data |
---|---|---|
83571aee EH |
1 | //! Tests for custom cargo commands and other global command features. |
2 | ||
ee5e24ff | 3 | use std::env; |
a6dad622 | 4 | use std::fs::{self, File}; |
7274307a | 5 | use std::io::Read; |
a6dad622 | 6 | use std::path::{Path, PathBuf}; |
7274307a | 7 | use std::process::Stdio; |
8cce8996 | 8 | use std::str; |
8cce8996 | 9 | |
9115b2c3 AC |
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}; | |
a3f6a404 | 14 | |
1e682848 | 15 | #[cfg_attr(windows, allow(dead_code))] |
974d5834 JB |
16 | enum FakeKind<'a> { |
17 | Executable, | |
1e682848 | 18 | Symlink { target: &'a Path }, |
974d5834 JB |
19 | } |
20 | ||
f7c91ba6 AR |
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. | |
6d1d3a68 | 24 | fn fake_file(proj: Project, dir: &Path, name: &str, kind: &FakeKind<'_>) -> Project { |
85984a87 DW |
25 | let path = proj |
26 | .root() | |
1e682848 AC |
27 | .join(dir) |
28 | .join(&format!("{}{}", name, env::consts::EXE_SUFFIX)); | |
763ba535 | 29 | path.parent().unwrap().mkdir_p(); |
23591fe5 | 30 | match *kind { |
974d5834 JB |
31 | FakeKind::Executable => { |
32 | File::create(&path).unwrap(); | |
33 | make_executable(&path); | |
1e682848 AC |
34 | } |
35 | FakeKind::Symlink { target } => { | |
36 | make_symlink(&path, target); | |
974d5834 JB |
37 | } |
38 | } | |
a6dad622 AC |
39 | return proj; |
40 | ||
41 | #[cfg(unix)] | |
42 | fn make_executable(p: &Path) { | |
43 | use std::os::unix::prelude::*; | |
44 | ||
53cc3ce8 | 45 | let mut perms = fs::metadata(p).unwrap().permissions(); |
a6dad622 AC |
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) {} | |
974d5834 JB |
52 | #[cfg(unix)] |
53 | fn make_symlink(p: &Path, t: &Path) { | |
1e682848 | 54 | ::std::os::unix::fs::symlink(t, p).expect("Failed to create symlink"); |
974d5834 JB |
55 | } |
56 | #[cfg(windows)] | |
57 | fn make_symlink(_: &Path, _: &Path) { | |
58 | panic!("Not supported") | |
59 | } | |
a3f6a404 | 60 | } |
61 | ||
a6dad622 | 62 | fn path() -> Vec<PathBuf> { |
23591fe5 | 63 | env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect() |
db3823a8 | 64 | } |
ee5e24ff | 65 | |
0e0d9688 | 66 | #[cargo_test] |
af2c3555 DW |
67 | fn list_commands_with_descriptions() { |
68 | let p = project().build(); | |
a173fc0a | 69 | p.cargo("--list") |
fecb7246 AC |
70 | .with_stdout_contains( |
71 | " build Compile a local package and all of its dependencies", | |
72 | ) | |
f7c91ba6 AR |
73 | // Assert that `read-manifest` prints the right one-line description followed by another |
74 | // command, indented. | |
fecb7246 AC |
75 | .with_stdout_contains( |
76 | " read-manifest Print a JSON representation of a Cargo.toml manifest.", | |
77 | ) | |
a173fc0a | 78 | .run(); |
af2c3555 DW |
79 | } |
80 | ||
7b16c7c1 C |
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 | ||
0e0d9688 | 92 | #[cargo_test] |
6950bbb0 | 93 | fn list_command_looks_at_path() { |
f8c9928c | 94 | let proj = project().build(); |
1e682848 AC |
95 | let proj = fake_file( |
96 | proj, | |
97 | Path::new("path-test"), | |
98 | "cargo-1", | |
99 | &FakeKind::Executable, | |
100 | ); | |
a3f6a404 | 101 | |
5d0cb3f2 | 102 | let mut path = path(); |
db3823a8 | 103 | path.push(proj.root().join("path-test")); |
ee5e24ff | 104 | let path = env::join_paths(path.iter()).unwrap(); |
fecb7246 AC |
105 | let output = cargo_process("-v --list") |
106 | .env("PATH", &path) | |
107 | .exec_with_output() | |
108 | .unwrap(); | |
63b34b64 DW |
109 | let output = str::from_utf8(&output.stdout).unwrap(); |
110 | assert!( | |
111 | output.contains("\n 1 "), | |
112 | "missing 1: {}", | |
113 | output | |
114 | ); | |
6950bbb0 | 115 | } |
12f5de8e | 116 | |
f7c91ba6 | 117 | // Windows and symlinks don't currently mix well. |
974d5834 | 118 | #[cfg(unix)] |
0e0d9688 | 119 | #[cargo_test] |
6950bbb0 | 120 | fn list_command_resolves_symlinks() { |
f8c9928c | 121 | let proj = project().build(); |
1e682848 AC |
122 | let proj = fake_file( |
123 | proj, | |
124 | Path::new("path-test"), | |
125 | "cargo-2", | |
126 | &FakeKind::Symlink { | |
127 | target: &cargo_exe(), | |
128 | }, | |
129 | ); | |
974d5834 JB |
130 | |
131 | let mut path = path(); | |
132 | path.push(proj.root().join("path-test")); | |
133 | let path = env::join_paths(path.iter()).unwrap(); | |
fecb7246 AC |
134 | let output = cargo_process("-v --list") |
135 | .env("PATH", &path) | |
136 | .exec_with_output() | |
137 | .unwrap(); | |
63b34b64 DW |
138 | let output = str::from_utf8(&output.stdout).unwrap(); |
139 | assert!( | |
140 | output.contains("\n 2 "), | |
141 | "missing 2: {}", | |
142 | output | |
143 | ); | |
6950bbb0 | 144 | } |
974d5834 | 145 | |
0e0d9688 | 146 | #[cargo_test] |
6950bbb0 | 147 | fn find_closest_biuld_to_build() { |
85984a87 DW |
148 | cargo_process("biuld") |
149 | .with_status(101) | |
150 | .with_stderr_contains( | |
1e682848 | 151 | "\ |
a1735c7a AK |
152 | error: no such subcommand: `biuld` |
153 | ||
154 | <tab>Did you mean `build`? | |
1e682848 | 155 | ", |
fecb7246 AC |
156 | ) |
157 | .run(); | |
a1735c7a AK |
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#" | |
6f8c7d5a EH |
165 | fn main() { |
166 | println!("Similar, but not identical to, build"); | |
167 | } | |
168 | "#, | |
fecb7246 AC |
169 | ) |
170 | .publish(); | |
85984a87 DW |
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", | |
fecb7246 AC |
179 | ) |
180 | .with_stdout_contains(" biuld\n") | |
85984a87 | 181 | .run(); |
6950bbb0 | 182 | } |
12f5de8e | 183 | |
ff3e880c ZL |
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(); | |
4ae79d2f EH |
189 | fs::write( |
190 | &my_home.join("config"), | |
191 | r#" | |
192 | [alias] | |
193 | myalias = "build" | |
194 | "#, | |
195 | ) | |
196 | .unwrap(); | |
ff3e880c ZL |
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 | ||
f7c91ba6 | 226 | // If a subcommand is more than an edit distance of 3 away, we don't make a suggestion. |
0e0d9688 | 227 | #[cargo_test] |
6950bbb0 | 228 | fn find_closest_dont_correct_nonsense() { |
85984a87 DW |
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( | |
1e682848 | 233 | "[ERROR] no such subcommand: \ |
1671630b | 234 | `there-is-no-way-that-there-is-a-command-close-to-this` |
1e682848 | 235 | ", |
fecb7246 AC |
236 | ) |
237 | .run(); | |
79858995 KA |
238 | } |
239 | ||
0e0d9688 | 240 | #[cargo_test] |
79858995 | 241 | fn displays_subcommand_on_error() { |
85984a87 DW |
242 | cargo_process("invalid-command") |
243 | .with_status(101) | |
244 | .with_stderr("[ERROR] no such subcommand: `invalid-command`\n") | |
245 | .run(); | |
6950bbb0 | 246 | } |
2badab8c | 247 | |
0e0d9688 | 248 | #[cargo_test] |
015a08a0 | 249 | fn cargo_subcommand_env() { |
1e682848 AC |
250 | let src = format!( |
251 | r#" | |
015a08a0 VK |
252 | use std::env; |
253 | ||
254 | fn main() {{ | |
255 | println!("{{}}", env::var("{}").unwrap()); | |
256 | }} | |
1e682848 AC |
257 | "#, |
258 | cargo::CARGO_ENV | |
259 | ); | |
015a08a0 | 260 | |
85984a87 DW |
261 | let p = project() |
262 | .at("cargo-envtest") | |
015a08a0 | 263 | .file("Cargo.toml", &basic_bin_manifest("cargo-envtest")) |
d43ee1dd NK |
264 | .file("src/main.rs", &src) |
265 | .build(); | |
015a08a0 VK |
266 | |
267 | let target_dir = p.target_debug_dir(); | |
268 | ||
85984a87 | 269 | p.cargo("build").run(); |
570fe892 | 270 | assert!(p.bin("cargo-envtest").is_file()); |
015a08a0 | 271 | |
015a08a0 VK |
272 | let cargo = cargo_exe().canonicalize().unwrap(); |
273 | let mut path = path(); | |
274 | path.push(target_dir); | |
275 | let path = env::join_paths(path.iter()).unwrap(); | |
276 | ||
85984a87 DW |
277 | cargo_process("envtest") |
278 | .env("PATH", &path) | |
279 | .with_stdout(cargo.to_str().unwrap()) | |
280 | .run(); | |
015a08a0 VK |
281 | } |
282 | ||
0e0d9688 | 283 | #[cargo_test] |
deb1c1e1 | 284 | fn cargo_subcommand_args() { |
85984a87 DW |
285 | let p = project() |
286 | .at("cargo-foo") | |
ab19c483 | 287 | .file("Cargo.toml", &basic_manifest("cargo-foo", "0.0.1")) |
deb1c1e1 AK |
288 | .file( |
289 | "src/main.rs", | |
290 | r#" | |
6f8c7d5a EH |
291 | fn main() { |
292 | let args: Vec<_> = ::std::env::args().collect(); | |
293 | println!("{:?}", args); | |
294 | } | |
295 | "#, | |
fecb7246 AC |
296 | ) |
297 | .build(); | |
deb1c1e1 | 298 | |
85984a87 | 299 | p.cargo("build").run(); |
deb1c1e1 | 300 | let cargo_foo_bin = p.bin("cargo-foo"); |
570fe892 | 301 | assert!(cargo_foo_bin.is_file()); |
deb1c1e1 AK |
302 | |
303 | let mut path = path(); | |
304 | path.push(p.target_debug_dir()); | |
305 | let path = env::join_paths(path.iter()).unwrap(); | |
306 | ||
85984a87 DW |
307 | cargo_process("foo bar -v --help") |
308 | .env("PATH", &path) | |
092f7bae DW |
309 | .with_stdout( |
310 | r#"["[CWD]/cargo-foo/target/debug/cargo-foo[EXE]", "foo", "bar", "-v", "--help"]"#, | |
311 | ) | |
49f73b9c | 312 | .run(); |
deb1c1e1 AK |
313 | } |
314 | ||
0e0d9688 | 315 | #[cargo_test] |
6950bbb0 | 316 | fn explain() { |
85984a87 DW |
317 | cargo_process("--explain E0001") |
318 | .with_stdout_contains( | |
b0c181d9 | 319 | "This error suggests that the expression arm corresponding to the noted pattern", |
fecb7246 AC |
320 | ) |
321 | .run(); | |
6950bbb0 | 322 | } |
a4104914 | 323 | |
7274307a EH |
324 | #[cargo_test] |
325 | fn closed_output_ok() { | |
326 | // Checks that closed output doesn't cause an error. | |
327 | let mut p = cargo_process("--list").build_command(); | |
328 | p.stdout(Stdio::piped()).stderr(Stdio::piped()); | |
329 | let mut child = p.spawn().unwrap(); | |
330 | // Close stdout | |
331 | drop(child.stdout.take()); | |
332 | // Read stderr | |
333 | let mut s = String::new(); | |
334 | child | |
335 | .stderr | |
336 | .as_mut() | |
337 | .unwrap() | |
338 | .read_to_string(&mut s) | |
339 | .unwrap(); | |
340 | let status = child.wait().unwrap(); | |
341 | assert!(status.success()); | |
f5a3d559 | 342 | assert!(s.is_empty(), "{}", s); |
7274307a | 343 | } |