From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- src/tools/cargo/tests/testsuite/cargo_command.rs | 535 +++++++++++++++++++++++ 1 file changed, 535 insertions(+) create mode 100644 src/tools/cargo/tests/testsuite/cargo_command.rs (limited to 'src/tools/cargo/tests/testsuite/cargo_command.rs') 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 { + 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` + +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` + +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` + +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` + +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( + "\ +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` + +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` + +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` + +Cargo does not handle `+toolchain` directives. +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` + +Did you mean `build`? + +View all installed commands with `cargo --list`", + ) + .run(); +} -- cgit v1.2.3