From dc7d1f82ad416250cdd99c02243a68437b02d809 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Tue, 16 Dec 2025 22:21:16 +0100 Subject: [PATCH 1/7] test(add): add the test case for `cargo add X@latest` --- .../cargo_add/add_latest/in/Cargo.toml | 9 +++++ .../cargo_add/add_latest/in/src/lib.rs | 0 tests/testsuite/cargo_add/add_latest/mod.rs | 38 +++++++++++++++++++ .../cargo_add/add_latest/out/Cargo.toml | 9 +++++ .../cargo_add/add_latest/stderr.term.svg | 33 ++++++++++++++++ tests/testsuite/cargo_add/mod.rs | 1 + 6 files changed, 90 insertions(+) create mode 100644 tests/testsuite/cargo_add/add_latest/in/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest/in/src/lib.rs create mode 100644 tests/testsuite/cargo_add/add_latest/mod.rs create mode 100644 tests/testsuite/cargo_add/add_latest/out/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest/stderr.term.svg diff --git a/tests/testsuite/cargo_add/add_latest/in/Cargo.toml b/tests/testsuite/cargo_add/add_latest/in/Cargo.toml new file mode 100644 index 00000000000..db55ed24962 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest/in/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" + +[dependencies] +my-package = "0.4" diff --git a/tests/testsuite/cargo_add/add_latest/in/src/lib.rs b/tests/testsuite/cargo_add/add_latest/in/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/add_latest/mod.rs b/tests/testsuite/cargo_add/add_latest/mod.rs new file mode 100644 index 00000000000..a789fce72a1 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest/mod.rs @@ -0,0 +1,38 @@ +use crate::prelude::*; +use cargo_test_support::Project; +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::str; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "0.4.2+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver).publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .arg_line("my-package@latest") + .current_dir(cwd) + .assert() + .failure() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/add_latest/out/Cargo.toml b/tests/testsuite/cargo_add/add_latest/out/Cargo.toml new file mode 100644 index 00000000000..db55ed24962 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest/out/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" + +[dependencies] +my-package = "0.4" diff --git a/tests/testsuite/cargo_add/add_latest/stderr.term.svg b/tests/testsuite/cargo_add/add_latest/stderr.term.svg new file mode 100644 index 00000000000..9505c56dd85 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest/stderr.term.svg @@ -0,0 +1,33 @@ + + + + + + + error: invalid version requirement `latest` + + + + Caused by: + + unexpected character 'l' while parsing major version number + + + + + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index bd9024466ac..c1ac29febd1 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -1,4 +1,5 @@ mod add_basic; +mod add_latest; mod add_multiple; mod add_no_vendored_package_with_alter_registry; mod add_no_vendored_package_with_vendor; From 4630254f64b0a7cfde91a0791621b8724e1fe508 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Mon, 6 Apr 2026 21:55:50 +0200 Subject: [PATCH 2/7] test(add): add the adding latest case with MSRV --- .../add_latest_rust_version/in/Cargo.toml | 7 ++++ .../add_latest_rust_version/in/src/lib.rs | 0 .../cargo_add/add_latest_rust_version/mod.rs | 32 ++++++++++++++++++ .../add_latest_rust_version/out/Cargo.toml | 7 ++++ .../add_latest_rust_version/stderr.term.svg | 33 +++++++++++++++++++ tests/testsuite/cargo_add/mod.rs | 1 + 6 files changed, 80 insertions(+) create mode 100644 tests/testsuite/cargo_add/add_latest_rust_version/in/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_rust_version/in/src/lib.rs create mode 100644 tests/testsuite/cargo_add/add_latest_rust_version/mod.rs create mode 100644 tests/testsuite/cargo_add/add_latest_rust_version/out/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_rust_version/stderr.term.svg diff --git a/tests/testsuite/cargo_add/add_latest_rust_version/in/Cargo.toml b/tests/testsuite/cargo_add/add_latest_rust_version/in/Cargo.toml new file mode 100644 index 00000000000..644f41d9f6c --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_rust_version/in/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" +rust-version = "1.70" diff --git a/tests/testsuite/cargo_add/add_latest_rust_version/in/src/lib.rs b/tests/testsuite/cargo_add/add_latest_rust_version/in/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/add_latest_rust_version/mod.rs b/tests/testsuite/cargo_add/add_latest_rust_version/mod.rs new file mode 100644 index 00000000000..99d2f7e8d6d --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_rust_version/mod.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; +use cargo_test_support::Project; +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::str; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("rust-version-user", "0.1.0") + .rust_version("1.66") + .publish(); + cargo_test_support::registry::Package::new("rust-version-user", "0.2.1") + .rust_version("1.72") + .publish(); + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .arg_line("rust-version-user@latest") + .current_dir(cwd) + .assert() + .failure() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/add_latest_rust_version/out/Cargo.toml b/tests/testsuite/cargo_add/add_latest_rust_version/out/Cargo.toml new file mode 100644 index 00000000000..644f41d9f6c --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_rust_version/out/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" +rust-version = "1.70" diff --git a/tests/testsuite/cargo_add/add_latest_rust_version/stderr.term.svg b/tests/testsuite/cargo_add/add_latest_rust_version/stderr.term.svg new file mode 100644 index 00000000000..9505c56dd85 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_rust_version/stderr.term.svg @@ -0,0 +1,33 @@ + + + + + + + error: invalid version requirement `latest` + + + + Caused by: + + unexpected character 'l' while parsing major version number + + + + + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index c1ac29febd1..3de9474dbc6 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -1,5 +1,6 @@ mod add_basic; mod add_latest; +mod add_latest_rust_version; mod add_multiple; mod add_no_vendored_package_with_alter_registry; mod add_no_vendored_package_with_vendor; From 9f4e782343bb750d31942d423a61915a5f967998 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Mon, 6 Apr 2026 22:37:52 +0200 Subject: [PATCH 3/7] test(add): add the adding latest case without existing dep --- .../add_latest_no_existing/in/Cargo.toml | 6 +++ .../add_latest_no_existing/in/src/lib.rs | 0 .../cargo_add/add_latest_no_existing/mod.rs | 38 +++++++++++++++++++ .../add_latest_no_existing/out/Cargo.toml | 6 +++ .../add_latest_no_existing/stderr.term.svg | 33 ++++++++++++++++ tests/testsuite/cargo_add/mod.rs | 1 + 6 files changed, 84 insertions(+) create mode 100644 tests/testsuite/cargo_add/add_latest_no_existing/in/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_no_existing/in/src/lib.rs create mode 100644 tests/testsuite/cargo_add/add_latest_no_existing/mod.rs create mode 100644 tests/testsuite/cargo_add/add_latest_no_existing/out/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_no_existing/stderr.term.svg diff --git a/tests/testsuite/cargo_add/add_latest_no_existing/in/Cargo.toml b/tests/testsuite/cargo_add/add_latest_no_existing/in/Cargo.toml new file mode 100644 index 00000000000..946b7c86bf0 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_no_existing/in/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/add_latest_no_existing/in/src/lib.rs b/tests/testsuite/cargo_add/add_latest_no_existing/in/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/add_latest_no_existing/mod.rs b/tests/testsuite/cargo_add/add_latest_no_existing/mod.rs new file mode 100644 index 00000000000..a789fce72a1 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_no_existing/mod.rs @@ -0,0 +1,38 @@ +use crate::prelude::*; +use cargo_test_support::Project; +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::str; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "0.4.2+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver).publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .arg_line("my-package@latest") + .current_dir(cwd) + .assert() + .failure() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/add_latest_no_existing/out/Cargo.toml b/tests/testsuite/cargo_add/add_latest_no_existing/out/Cargo.toml new file mode 100644 index 00000000000..946b7c86bf0 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_no_existing/out/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/add_latest_no_existing/stderr.term.svg b/tests/testsuite/cargo_add/add_latest_no_existing/stderr.term.svg new file mode 100644 index 00000000000..9505c56dd85 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_no_existing/stderr.term.svg @@ -0,0 +1,33 @@ + + + + + + + error: invalid version requirement `latest` + + + + Caused by: + + unexpected character 'l' while parsing major version number + + + + + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index 3de9474dbc6..3fe4e85ce7f 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -1,5 +1,6 @@ mod add_basic; mod add_latest; +mod add_latest_no_existing; mod add_latest_rust_version; mod add_multiple; mod add_no_vendored_package_with_alter_registry; From 5dde137e4750f8fbddf2e93174a107cf810af356 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Mon, 13 Apr 2026 22:16:14 +0200 Subject: [PATCH 4/7] test(add): add alt-registry latest case Signed-off-by: 0xPoe --- .../add_latest_alt_registry/in/Cargo.toml | 6 +++ .../add_latest_alt_registry/in/src/lib.rs | 1 + .../cargo_add/add_latest_alt_registry/mod.rs | 40 +++++++++++++++++++ .../add_latest_alt_registry/out/Cargo.toml | 6 +++ .../add_latest_alt_registry/stderr.term.svg | 33 +++++++++++++++ tests/testsuite/cargo_add/mod.rs | 1 + 6 files changed, 87 insertions(+) create mode 100644 tests/testsuite/cargo_add/add_latest_alt_registry/in/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_alt_registry/in/src/lib.rs create mode 100644 tests/testsuite/cargo_add/add_latest_alt_registry/mod.rs create mode 100644 tests/testsuite/cargo_add/add_latest_alt_registry/out/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_alt_registry/stderr.term.svg diff --git a/tests/testsuite/cargo_add/add_latest_alt_registry/in/Cargo.toml b/tests/testsuite/cargo_add/add_latest_alt_registry/in/Cargo.toml new file mode 100644 index 00000000000..946b7c86bf0 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_alt_registry/in/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/add_latest_alt_registry/in/src/lib.rs b/tests/testsuite/cargo_add/add_latest_alt_registry/in/src/lib.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_alt_registry/in/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_add/add_latest_alt_registry/mod.rs b/tests/testsuite/cargo_add/add_latest_alt_registry/mod.rs new file mode 100644 index 00000000000..b360d5f73ab --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_alt_registry/mod.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; +use cargo_test_support::Project; +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::str; + +#[cargo_test] +fn case() { + cargo_test_support::registry::alt_init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "0.4.2+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver) + .alternative(true) + .publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .arg_line("my-package@latest --registry alternative") + .current_dir(cwd) + .assert() + .failure() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/add_latest_alt_registry/out/Cargo.toml b/tests/testsuite/cargo_add/add_latest_alt_registry/out/Cargo.toml new file mode 100644 index 00000000000..946b7c86bf0 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_alt_registry/out/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/add_latest_alt_registry/stderr.term.svg b/tests/testsuite/cargo_add/add_latest_alt_registry/stderr.term.svg new file mode 100644 index 00000000000..9505c56dd85 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_alt_registry/stderr.term.svg @@ -0,0 +1,33 @@ + + + + + + + error: invalid version requirement `latest` + + + + Caused by: + + unexpected character 'l' while parsing major version number + + + + + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index 3fe4e85ce7f..2302884b567 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -1,5 +1,6 @@ mod add_basic; mod add_latest; +mod add_latest_alt_registry; mod add_latest_no_existing; mod add_latest_rust_version; mod add_multiple; From f398e9078fbe4e0277676b17a2abe43f750108e7 Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Mon, 13 Apr 2026 22:36:22 +0200 Subject: [PATCH 5/7] test(add): add git latest conflict case Signed-off-by: 0xPoe --- .../cargo_add/git_conflicts_namever/mod.rs | 22 +++++++++++++ .../stderr_latest.term.svg | 33 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/testsuite/cargo_add/git_conflicts_namever/stderr_latest.term.svg diff --git a/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs b/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs index bf1cd694983..64e0d84ac3e 100644 --- a/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs +++ b/tests/testsuite/cargo_add/git_conflicts_namever/mod.rs @@ -39,3 +39,25 @@ fn case() { assert_ui().subset_matches(current_dir!().join("out"), &project_root); } + +#[cargo_test] +fn latest() { + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .args([ + "my-package@latest", + "--git", + "https://github.com/dcjanus/invalid", + ]) + .current_dir(cwd) + .assert() + .code(101) + .stdout_eq(str![""]) + .stderr_eq(file!["stderr_latest.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/git_conflicts_namever/stderr_latest.term.svg b/tests/testsuite/cargo_add/git_conflicts_namever/stderr_latest.term.svg new file mode 100644 index 00000000000..9505c56dd85 --- /dev/null +++ b/tests/testsuite/cargo_add/git_conflicts_namever/stderr_latest.term.svg @@ -0,0 +1,33 @@ + + + + + + + error: invalid version requirement `latest` + + + + Caused by: + + unexpected character 'l' while parsing major version number + + + + + + From afb273a8770eba2c7e13a2bc7f893ffe2cab512a Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Tue, 21 Apr 2026 22:36:20 +0200 Subject: [PATCH 6/7] test(add): add the workspace dependency case --- .../add_latest_workspace/in/Cargo.toml | 5 +++ .../add_latest_workspace/in/bar/Cargo.toml | 4 ++ .../add_latest_workspace/in/bar/src/lib.rs | 0 .../cargo_add/add_latest_workspace/mod.rs | 38 +++++++++++++++++++ .../add_latest_workspace/out/Cargo.toml | 5 +++ .../add_latest_workspace/out/bar/Cargo.toml | 4 ++ .../add_latest_workspace/out/bar/src/lib.rs | 0 .../add_latest_workspace/stderr.term.svg | 33 ++++++++++++++++ tests/testsuite/cargo_add/mod.rs | 1 + 9 files changed, 90 insertions(+) create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/in/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/in/bar/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/in/bar/src/lib.rs create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/mod.rs create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/out/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/out/bar/Cargo.toml create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/out/bar/src/lib.rs create mode 100644 tests/testsuite/cargo_add/add_latest_workspace/stderr.term.svg diff --git a/tests/testsuite/cargo_add/add_latest_workspace/in/Cargo.toml b/tests/testsuite/cargo_add/add_latest_workspace/in/Cargo.toml new file mode 100644 index 00000000000..b3beb88fba7 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_workspace/in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["bar"] + +[workspace.dependencies] +my-package = "0.4" diff --git a/tests/testsuite/cargo_add/add_latest_workspace/in/bar/Cargo.toml b/tests/testsuite/cargo_add/add_latest_workspace/in/bar/Cargo.toml new file mode 100644 index 00000000000..fb520246281 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_workspace/in/bar/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "bar" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/add_latest_workspace/in/bar/src/lib.rs b/tests/testsuite/cargo_add/add_latest_workspace/in/bar/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/add_latest_workspace/mod.rs b/tests/testsuite/cargo_add/add_latest_workspace/mod.rs new file mode 100644 index 00000000000..bc88128519d --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_workspace/mod.rs @@ -0,0 +1,38 @@ +use crate::prelude::*; +use cargo_test_support::Project; +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::str; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "0.4.2+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver).publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .arg_line("my-package@latest -p bar") + .current_dir(cwd) + .assert() + .failure() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/add_latest_workspace/out/Cargo.toml b/tests/testsuite/cargo_add/add_latest_workspace/out/Cargo.toml new file mode 100644 index 00000000000..b3beb88fba7 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_workspace/out/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = ["bar"] + +[workspace.dependencies] +my-package = "0.4" diff --git a/tests/testsuite/cargo_add/add_latest_workspace/out/bar/Cargo.toml b/tests/testsuite/cargo_add/add_latest_workspace/out/bar/Cargo.toml new file mode 100644 index 00000000000..fb520246281 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_workspace/out/bar/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "bar" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/add_latest_workspace/out/bar/src/lib.rs b/tests/testsuite/cargo_add/add_latest_workspace/out/bar/src/lib.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/add_latest_workspace/stderr.term.svg b/tests/testsuite/cargo_add/add_latest_workspace/stderr.term.svg new file mode 100644 index 00000000000..9505c56dd85 --- /dev/null +++ b/tests/testsuite/cargo_add/add_latest_workspace/stderr.term.svg @@ -0,0 +1,33 @@ + + + + + + + error: invalid version requirement `latest` + + + + Caused by: + + unexpected character 'l' while parsing major version number + + + + + + diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index 2302884b567..bab857cd40b 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -3,6 +3,7 @@ mod add_latest; mod add_latest_alt_registry; mod add_latest_no_existing; mod add_latest_rust_version; +mod add_latest_workspace; mod add_multiple; mod add_no_vendored_package_with_alter_registry; mod add_no_vendored_package_with_vendor; From d350679a3e67f269bf6f086b53e80d66c5c34d8f Mon Sep 17 00:00:00 2001 From: 0xPoe Date: Mon, 6 Apr 2026 22:39:16 +0200 Subject: [PATCH 7/7] feat(add): improve `cargo add X@latest` error message Signed-off-by: 0xPoe --- src/cargo/ops/cargo_add/crate_spec.rs | 74 ++++++++++++++----- src/cargo/ops/cargo_add/mod.rs | 65 ++++++++++++++-- .../cargo_add/add_latest/stderr.term.svg | 15 ++-- .../add_latest_alt_registry/stderr.term.svg | 9 ++- .../add_latest_no_existing/stderr.term.svg | 9 ++- .../add_latest_rust_version/stderr.term.svg | 18 +++-- .../add_latest_workspace/stderr.term.svg | 8 +- .../stderr_latest.term.svg | 12 +-- 8 files changed, 150 insertions(+), 60 deletions(-) diff --git a/src/cargo/ops/cargo_add/crate_spec.rs b/src/cargo/ops/cargo_add/crate_spec.rs index d7aeddd0615..00d106f1f18 100644 --- a/src/cargo/ops/cargo_add/crate_spec.rs +++ b/src/cargo/ops/cargo_add/crate_spec.rs @@ -7,6 +7,24 @@ use crate::CargoResult; use crate::util::toml_mut::dependency::RegistrySource; use cargo_util_schemas::manifest::PackageName; +/// A user-provided version selector from `@`. +#[derive(Debug)] +pub(super) enum VersionSpec { + /// A semver requirement that can be written to the manifest. + Requirement(String), + /// The special `@latest` selector, used for diagnostics only. + Latest, +} + +impl std::fmt::Display for VersionSpec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Requirement(req) => req.fmt(f), + Self::Latest => "latest".fmt(f), + } + } +} + /// User-specified crate /// /// This can be a @@ -16,8 +34,8 @@ use cargo_util_schemas::manifest::PackageName; pub struct CrateSpec { /// Crate name name: String, - /// Optional version requirement - version_req: Option, + /// Optional version selector + version: Option, } impl CrateSpec { @@ -46,22 +64,34 @@ impl CrateSpec { package_name?; - if let Some(version) = version { - semver::VersionReq::parse(version).with_context(|| { - if let Some(stripped) = version.strip_prefix("v") { - return format!( - "the version provided, `{version}` is not a \ - valid SemVer requirement\n\n\ - help: changing the package to `{name}@{stripped}`", - ); - } - format!("invalid version requirement `{version}`") - })?; - } + let version = if let Some(version) = version { + // `latest` is the only supported special version selector. It is + // not a SemVer requirement. + // + // We intentionally keep it case-sensitive to match other package + // managers we may be helping users transition from. + if version == "latest" { + Some(VersionSpec::Latest) + } else { + semver::VersionReq::parse(version).with_context(|| { + if let Some(stripped) = version.strip_prefix("v") { + return format!( + "the version provided, `{version}` is not a \ + valid SemVer requirement\n\n\ + help: changing the package to `{name}@{stripped}`", + ); + } + format!("invalid version requirement `{version}`") + })?; + Some(VersionSpec::Requirement(version.to_owned())) + } + } else { + None + }; let id = Self { name: name.to_owned(), - version_req: version.map(|s| s.to_owned()), + version, }; Ok(id) @@ -70,8 +100,14 @@ impl CrateSpec { /// Generate a dependency entry for this crate specifier pub fn to_dependency(&self) -> CargoResult { let mut dep = Dependency::new(self.name()); - if let Some(version_req) = self.version_req() { - dep = dep.set_source(RegistrySource::new(version_req)); + match self.version.as_ref() { + Some(VersionSpec::Latest) => { + anyhow::bail!("`latest` is not a valid dependency requirement") + } + Some(VersionSpec::Requirement(req)) => { + dep = dep.set_source(RegistrySource::new(req)); + } + None => {} } Ok(dep) } @@ -80,7 +116,7 @@ impl CrateSpec { &self.name } - pub fn version_req(&self) -> Option<&str> { - self.version_req.as_deref() + pub(crate) fn version(&self) -> Option<&VersionSpec> { + self.version.as_ref() } } diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs index 929d704c677..a3f5b7989c2 100644 --- a/src/cargo/ops/cargo_add/mod.rs +++ b/src/cargo/ops/cargo_add/mod.rs @@ -47,6 +47,7 @@ use crate::util::toml_mut::dependency::WorkspaceSource; use crate::util::toml_mut::manifest::DepTable; use crate::util::toml_mut::manifest::LocalManifest; use crate_spec::CrateSpec; +use crate_spec::VersionSpec; const MAX_FEATURE_PRINTS: usize = 30; @@ -349,6 +350,10 @@ fn resolve_dependency( .as_deref() .map(CrateSpec::resolve) .transpose()?; + let request_latest = crate_spec + .as_ref() + .is_some_and(|crate_spec| matches!(crate_spec.version(), Some(VersionSpec::Latest))); + let mut selected_dep = if let Some(url) = &arg.git { let mut src = GitSource::new(url); if let Some(branch) = &arg.branch { @@ -362,9 +367,9 @@ fn resolve_dependency( } let selected = if let Some(crate_spec) = &crate_spec { - if let Some(v) = crate_spec.version_req() { + if let Some(version) = crate_spec.version() { // crate specifier includes a version (e.g. `docopt@0.8`) - anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{v}`)."); + anyhow::bail!("cannot specify a git URL (`{url}`) with a version (`{version}`)."); } let dependency = crate_spec.to_dependency()?.set_source(src); let selected = select_package(&dependency, gctx, registry)?; @@ -399,9 +404,9 @@ fn resolve_dependency( } let selected = if let Some(crate_spec) = &crate_spec { - if let Some(v) = crate_spec.version_req() { + if let Some(version) = crate_spec.version() { // crate specifier includes a version (e.g. `docopt@0.8`) - anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`)."); + anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{version}`)."); } let dependency = crate_spec.to_dependency()?.set_source(src); let selected = select_package(&dependency, gctx, registry)?; @@ -423,7 +428,14 @@ fn resolve_dependency( }; selected } else if let Some(crate_spec) = &crate_spec { - crate_spec.to_dependency()? + if request_latest { + // `latest` is not a dependency requirement we can write to the manifest. + // Build an unconstrained dependency and let the dedicated diagnostics below + // explain what the user should do instead. + Dependency::new(crate_spec.name()) + } else { + crate_spec.to_dependency()? + } } else { anyhow::bail!("dependency name is required"); }; @@ -513,6 +525,49 @@ fn resolve_dependency( dependency = dependency.clear_version(); } + // Check if user tried to use @latest and provide helpful error. + if request_latest { + // The diagnostics below compare against the resolved and latest published registry + // versions, so they only apply to registry dependencies. + if !matches!(dependency.source(), Some(Source::Registry(_))) { + anyhow::bail!("invalid version requirement `latest`"); + } + + // Get the exact version that `cargo add ` would resolve to, + // respecting MSRV and existing version constraints. + let resolved = + get_latest_dependency(spec, &dependency, honor_rust_version, gctx, registry)?; + let resolved_version = resolved + .version() + .expect("resolved dependency should have version"); + // Get the actual latest non-prerelease, non-yanked version from the registry, + // ignoring MSRV and existing version constraints. + // Only name + registry matter; `Dependency::query` ignores other fields. + let mut unconstrained_dep = Dependency::new(&dependency.name); + if let Some(registry_name) = dependency.registry() { + unconstrained_dep = unconstrained_dep.set_registry(registry_name); + } + let latest = get_latest_dependency(spec, &unconstrained_dep, Some(false), gctx, registry)?; + let latest_version = latest + .version() + .expect("latest dependency should have version"); + if resolved_version == latest_version { + anyhow::bail!( + "invalid version requirement `latest`\n\n\ + help: to add the latest version `{latest_version}`, run `cargo add {}`", + dependency.name, + ); + } else { + anyhow::bail!( + "invalid version requirement `latest`\n\n\ + help: to use `{resolved_version}`, run `cargo add {}`\n\ + help: to use the latest version, run `cargo add {}@{latest_version}`", + dependency.name, + dependency.name, + ); + } + } + let query = query_dependency(ws, gctx, &mut dependency)?; let dependency = populate_available_features(dependency, &query, registry)?; diff --git a/tests/testsuite/cargo_add/add_latest/stderr.term.svg b/tests/testsuite/cargo_add/add_latest/stderr.term.svg index 9505c56dd85..c5d8b666d80 100644 --- a/tests/testsuite/cargo_add/add_latest/stderr.term.svg +++ b/tests/testsuite/cargo_add/add_latest/stderr.term.svg @@ -1,7 +1,8 @@ - +