summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/cargo_command.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/cargo_command.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/cargo_command.rs535
1 files changed, 535 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/testsuite/cargo_command.rs b/src/tools/cargo/tests/testsuite/cargo_command.rs
new file mode 100644
index 000000000..62869387f
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/cargo_command.rs
@@ -0,0 +1,535 @@
+//! Tests for custom cargo commands and other global command features.
+
+use std::env;
+use std::fs;
+use std::io::Read;
+use std::path::{Path, PathBuf};
+use std::process::Stdio;
+use std::str;
+
+use cargo_test_support::basic_manifest;
+use cargo_test_support::paths::CargoPathExt;
+use cargo_test_support::registry::Package;
+use cargo_test_support::tools::echo_subcommand;
+use cargo_test_support::{
+ basic_bin_manifest, cargo_exe, cargo_process, paths, project, project_in_home,
+};
+use cargo_util::paths::join_paths;
+
+fn path() -> Vec<PathBuf> {
+ env::split_paths(&env::var_os("PATH").unwrap_or_default()).collect()
+}
+
+#[cargo_test]
+fn list_commands_with_descriptions() {
+ let p = project().build();
+ p.cargo("--list")
+ .with_stdout_contains(
+ " build Compile a local package and all of its dependencies",
+ )
+ // Assert that `read-manifest` prints the right one-line description followed by another
+ // command, indented.
+ .with_stdout_contains(
+ " read-manifest Print a JSON representation of a Cargo.toml manifest.",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn list_builtin_aliases_with_descriptions() {
+ let p = project().build();
+ p.cargo("--list")
+ .with_stdout_contains(" b alias: build")
+ .with_stdout_contains(" c alias: check")
+ .with_stdout_contains(" r alias: run")
+ .with_stdout_contains(" t alias: test")
+ .run();
+}
+
+#[cargo_test]
+fn list_custom_aliases_with_descriptions() {
+ let p = project_in_home("proj")
+ .file(
+ &paths::home().join(".cargo").join("config"),
+ r#"
+ [alias]
+ myaliasstr = "foo --bar"
+ myaliasvec = ["foo", "--bar"]
+ "#,
+ )
+ .build();
+
+ p.cargo("--list")
+ .with_stdout_contains(" myaliasstr alias: foo --bar")
+ .with_stdout_contains(" myaliasvec alias: foo --bar")
+ .run();
+}
+
+#[cargo_test]
+fn list_dedupe() {
+ let p = project()
+ .executable(Path::new("path-test-1").join("cargo-dupe"), "")
+ .executable(Path::new("path-test-2").join("cargo-dupe"), "")
+ .build();
+
+ let mut path = path();
+ path.push(p.root().join("path-test-1"));
+ path.push(p.root().join("path-test-2"));
+ let path = env::join_paths(path.iter()).unwrap();
+
+ p.cargo("--list")
+ .env("PATH", &path)
+ .with_stdout_contains_n(" dupe", 1)
+ .run();
+}
+
+#[cargo_test]
+fn list_command_looks_at_path() {
+ let proj = project()
+ .executable(Path::new("path-test").join("cargo-1"), "")
+ .build();
+
+ let mut path = path();
+ path.push(proj.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+ let output = cargo_process("-v --list")
+ .env("PATH", &path)
+ .exec_with_output()
+ .unwrap();
+ let output = str::from_utf8(&output.stdout).unwrap();
+ assert!(
+ output.contains("\n 1 "),
+ "missing 1: {}",
+ output
+ );
+}
+
+#[cfg(windows)]
+#[cargo_test]
+fn list_command_looks_at_path_case_mismatch() {
+ let proj = project()
+ .executable(Path::new("path-test").join("cargo-1"), "")
+ .build();
+
+ let mut path = path();
+ path.push(proj.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+
+ // See issue #11814: Environment variable names are case-insensitive on Windows.
+ // We need to check that having "Path" instead of "PATH" is okay.
+ let output = cargo_process("-v --list")
+ .env("Path", &path)
+ .env_remove("PATH")
+ .exec_with_output()
+ .unwrap();
+ let output = str::from_utf8(&output.stdout).unwrap();
+ assert!(
+ output.contains("\n 1 "),
+ "missing 1: {}",
+ output
+ );
+}
+
+#[cargo_test]
+fn list_command_handles_known_external_commands() {
+ let p = project()
+ .executable(Path::new("path-test").join("cargo-fmt"), "")
+ .build();
+
+ let fmt_desc = " fmt Formats all bin and lib files of the current crate using rustfmt.";
+
+ // Without path - fmt isn't there
+ p.cargo("--list")
+ .env("PATH", "")
+ .with_stdout_does_not_contain(fmt_desc)
+ .run();
+
+ // With path - fmt is there with known description
+ let mut path = path();
+ path.push(p.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+
+ p.cargo("--list")
+ .env("PATH", &path)
+ .with_stdout_contains(fmt_desc)
+ .run();
+}
+
+#[cargo_test]
+fn list_command_resolves_symlinks() {
+ let proj = project()
+ .symlink(cargo_exe(), Path::new("path-test").join("cargo-2"))
+ .build();
+
+ let mut path = path();
+ path.push(proj.root().join("path-test"));
+ let path = env::join_paths(path.iter()).unwrap();
+ let output = cargo_process("-v --list")
+ .env("PATH", &path)
+ .exec_with_output()
+ .unwrap();
+ let output = str::from_utf8(&output.stdout).unwrap();
+ assert!(
+ output.contains("\n 2 "),
+ "missing 2: {}",
+ output
+ );
+}
+
+#[cargo_test]
+fn find_closest_capital_c_to_c() {
+ cargo_process("C")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `C`
+
+<tab>Did you mean `c`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn find_closest_capital_b_to_b() {
+ cargo_process("B")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `B`
+
+<tab>Did you mean `b`?
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn find_closest_biuld_to_build() {
+ cargo_process("biuld")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `biuld`
+
+<tab>Did you mean `build`?
+",
+ )
+ .run();
+
+ // But, if we actually have `biuld`, it must work!
+ // https://github.com/rust-lang/cargo/issues/5201
+ Package::new("cargo-biuld", "1.0.0")
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("Similar, but not identical to, build");
+ }
+ "#,
+ )
+ .publish();
+
+ cargo_process("install cargo-biuld").run();
+ cargo_process("biuld")
+ .with_stdout("Similar, but not identical to, build\n")
+ .run();
+ cargo_process("--list")
+ .with_stdout_contains(
+ " build Compile a local package and all of its dependencies\n",
+ )
+ .with_stdout_contains(" biuld\n")
+ .run();
+}
+
+#[cargo_test]
+fn find_closest_alias() {
+ let root = paths::root();
+ let my_home = root.join("my_home");
+ fs::create_dir(&my_home).unwrap();
+ fs::write(
+ &my_home.join("config"),
+ r#"
+ [alias]
+ myalias = "build"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("myalais")
+ .env("CARGO_HOME", &my_home)
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `myalais`
+
+<tab>Did you mean `myalias`?
+",
+ )
+ .run();
+
+ // But, if no alias is defined, it must not suggest one!
+ cargo_process("myalais")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+error: no such command: `myalais`
+",
+ )
+ .with_stderr_does_not_contain(
+ "\
+<tab>Did you mean `myalias`?
+",
+ )
+ .run();
+}
+
+// If a subcommand is more than an edit distance of 3 away, we don't make a suggestion.
+#[cargo_test]
+fn find_closest_dont_correct_nonsense() {
+ cargo_process("there-is-no-way-that-there-is-a-command-close-to-this")
+ .cwd(&paths::root())
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no such command: `there-is-no-way-that-there-is-a-command-close-to-this`
+
+<tab>View all installed commands with `cargo --list`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn displays_subcommand_on_error() {
+ cargo_process("invalid-command")
+ .with_status(101)
+ .with_stderr(
+ "\
+[ERROR] no such command: `invalid-command`
+
+<tab>View all installed commands with `cargo --list`",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn override_cargo_home() {
+ let root = paths::root();
+ let my_home = root.join("my_home");
+ fs::create_dir(&my_home).unwrap();
+ fs::write(
+ &my_home.join("config"),
+ r#"
+ [cargo-new]
+ vcs = "none"
+ "#,
+ )
+ .unwrap();
+
+ cargo_process("new foo").env("CARGO_HOME", &my_home).run();
+
+ assert!(!paths::root().join("foo/.git").is_dir());
+
+ cargo_process("new foo2").run();
+
+ assert!(paths::root().join("foo2/.git").is_dir());
+}
+
+#[cargo_test]
+fn cargo_subcommand_env() {
+ let src = format!(
+ r#"
+ use std::env;
+
+ fn main() {{
+ println!("{{}}", env::var("{}").unwrap());
+ }}
+ "#,
+ cargo::CARGO_ENV
+ );
+
+ let p = project()
+ .at("cargo-envtest")
+ .file("Cargo.toml", &basic_bin_manifest("cargo-envtest"))
+ .file("src/main.rs", &src)
+ .build();
+
+ let target_dir = p.target_debug_dir();
+
+ p.cargo("build").run();
+ assert!(p.bin("cargo-envtest").is_file());
+
+ let cargo = cargo_exe().canonicalize().unwrap();
+ let mut path = path();
+ path.push(target_dir.clone());
+ let path = env::join_paths(path.iter()).unwrap();
+
+ cargo_process("envtest")
+ .env("PATH", &path)
+ .with_stdout(cargo.to_str().unwrap())
+ .run();
+
+ // Check that subcommands inherit an overridden $CARGO
+ let envtest_bin = target_dir
+ .join("cargo-envtest")
+ .with_extension(std::env::consts::EXE_EXTENSION)
+ .canonicalize()
+ .unwrap();
+ let envtest_bin = envtest_bin.to_str().unwrap();
+ cargo_process("envtest")
+ .env("PATH", &path)
+ .env(cargo::CARGO_ENV, &envtest_bin)
+ .with_stdout(envtest_bin)
+ .run();
+}
+
+#[cargo_test]
+fn cargo_cmd_bins_vs_explicit_path() {
+ // Set up `cargo-foo` binary in two places: inside `$HOME/.cargo/bin` and outside of it
+ //
+ // Return paths to both places
+ fn set_up_cargo_foo() -> (PathBuf, PathBuf) {
+ let p = project()
+ .at("cargo-foo")
+ .file("Cargo.toml", &basic_manifest("cargo-foo", "1.0.0"))
+ .file(
+ "src/bin/cargo-foo.rs",
+ r#"fn main() { println!("INSIDE"); }"#,
+ )
+ .file(
+ "src/bin/cargo-foo2.rs",
+ r#"fn main() { println!("OUTSIDE"); }"#,
+ )
+ .build();
+ p.cargo("build").run();
+ let cargo_bin_dir = paths::home().join(".cargo/bin");
+ cargo_bin_dir.mkdir_p();
+ let root_bin_dir = paths::root().join("bin");
+ root_bin_dir.mkdir_p();
+ let exe_name = format!("cargo-foo{}", env::consts::EXE_SUFFIX);
+ fs::rename(p.bin("cargo-foo"), cargo_bin_dir.join(&exe_name)).unwrap();
+ fs::rename(p.bin("cargo-foo2"), root_bin_dir.join(&exe_name)).unwrap();
+
+ (root_bin_dir, cargo_bin_dir)
+ }
+
+ let (outside_dir, inside_dir) = set_up_cargo_foo();
+
+ // If `$CARGO_HOME/bin` is not in a path, prefer it over anything in `$PATH`.
+ //
+ // This is the historical behavior we don't want to break.
+ cargo_process("foo").with_stdout_contains("INSIDE").run();
+
+ // When `$CARGO_HOME/bin` is in the `$PATH`
+ // use only `$PATH` so the user-defined ordering is respected.
+ {
+ cargo_process("foo")
+ .env(
+ "PATH",
+ join_paths(&[&inside_dir, &outside_dir], "PATH").unwrap(),
+ )
+ .with_stdout_contains("INSIDE")
+ .run();
+
+ cargo_process("foo")
+ // Note: trailing slash
+ .env(
+ "PATH",
+ join_paths(&[inside_dir.join(""), outside_dir.join("")], "PATH").unwrap(),
+ )
+ .with_stdout_contains("INSIDE")
+ .run();
+
+ cargo_process("foo")
+ .env(
+ "PATH",
+ join_paths(&[&outside_dir, &inside_dir], "PATH").unwrap(),
+ )
+ .with_stdout_contains("OUTSIDE")
+ .run();
+
+ cargo_process("foo")
+ // Note: trailing slash
+ .env(
+ "PATH",
+ join_paths(&[outside_dir.join(""), inside_dir.join("")], "PATH").unwrap(),
+ )
+ .with_stdout_contains("OUTSIDE")
+ .run();
+ }
+}
+
+#[cargo_test]
+fn cargo_subcommand_args() {
+ let p = echo_subcommand();
+ let cargo_foo_bin = p.bin("cargo-echo");
+ assert!(cargo_foo_bin.is_file());
+
+ let mut path = path();
+ path.push(p.target_debug_dir());
+ let path = env::join_paths(path.iter()).unwrap();
+
+ cargo_process("echo bar -v --help")
+ .env("PATH", &path)
+ .with_stdout("echo bar -v --help")
+ .run();
+}
+
+#[cargo_test]
+fn explain() {
+ cargo_process("--explain E0001")
+ .with_stdout_contains(
+ "This error suggests that the expression arm corresponding to the noted pattern",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn closed_output_ok() {
+ // Checks that closed output doesn't cause an error.
+ let mut p = cargo_process("--list").build_command();
+ p.stdout(Stdio::piped()).stderr(Stdio::piped());
+ let mut child = p.spawn().unwrap();
+ // Close stdout
+ drop(child.stdout.take());
+ // Read stderr
+ let mut s = String::new();
+ child
+ .stderr
+ .as_mut()
+ .unwrap()
+ .read_to_string(&mut s)
+ .unwrap();
+ let status = child.wait().unwrap();
+ assert!(status.success());
+ assert!(s.is_empty(), "{}", s);
+}
+
+#[cargo_test]
+fn subcommand_leading_plus_output_contains() {
+ cargo_process("+nightly")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no such command: `+nightly`
+
+<tab>Cargo does not handle `+toolchain` directives.
+<tab>Did you mean to invoke `cargo` through `rustup` instead?",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn full_did_you_mean() {
+ cargo_process("bluid")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: no such command: `bluid`
+
+<tab>Did you mean `build`?
+
+<tab>View all installed commands with `cargo --list`",
+ )
+ .run();
+}