diff --git a/Cargo.lock b/Cargo.lock index 1511fbccee7..497222682f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,6 +472,7 @@ dependencies = [ "serde", "serde-untagged", "serde-value", + "snapbox", "thiserror", "toml", "unicode-xid", diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 0166672c0d0..1b72fd30bf1 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -20,3 +20,6 @@ url.workspace = true [lints] workspace = true + +[dev-dependencies] +snapbox.workspace = true diff --git a/crates/cargo-util-schemas/src/core/partial_version.rs b/crates/cargo-util-schemas/src/core/partial_version.rs index b9c1db82ed1..5057d6046e1 100644 --- a/crates/cargo-util-schemas/src/core/partial_version.rs +++ b/crates/cargo-util-schemas/src/core/partial_version.rs @@ -83,9 +83,6 @@ impl std::str::FromStr for PartialVersion { type Err = PartialVersionError; fn from_str(value: &str) -> Result { - if is_req(value) { - return Err(ErrorKind::VersionReq.into()); - } match semver::Version::parse(value) { Ok(ver) => Ok(ver.into()), Err(_) => { @@ -96,9 +93,16 @@ impl std::str::FromStr for PartialVersion { Err(_) if value.contains('+') => return Err(ErrorKind::BuildMetadata.into()), Err(_) => return Err(ErrorKind::Unexpected.into()), }; - assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req"); + if version_req.comparators.len() != 1 { + return Err(ErrorKind::VersionReq.into()); + } let comp = version_req.comparators.pop().unwrap(); - assert_eq!(comp.op, semver::Op::Caret, "guaranteed by is_req"); + if comp.op != semver::Op::Caret { + return Err(ErrorKind::VersionReq.into()); + } else if value.starts_with('^') { + // Can't distinguish between `^` present or not + return Err(ErrorKind::VersionReq.into()); + } let pre = if comp.pre.is_empty() { None } else { @@ -179,9 +183,65 @@ enum ErrorKind { Unexpected, } -fn is_req(value: &str) -> bool { - let Some(first) = value.chars().next() else { - return false; - }; - "<>=^~".contains(first) || value.contains('*') || value.contains(',') +#[cfg(test)] +mod test { + use super::*; + use snapbox::str; + + #[test] + fn parse_success() { + let cases = &[ + // Valid pre-release + ("1.43.0-beta.1", str!["1.43.0-beta.1"]), + // Valid pre-release with wildcard + ("1.43.0-beta.1.x", str!["1.43.0-beta.1.x"]), + ]; + for (input, expected) in cases { + let actual: Result = input.parse(); + let actual = match actual { + Ok(result) => result.to_string(), + Err(err) => format!("didn't pass: {err}"), + }; + snapbox::assert_eq(expected.clone(), actual); + } + } + + #[test] + fn parse_errors() { + let cases = &[ + // Disallow caret + ( + "^1.43", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + // Bad pre-release + ( + "1.43-beta.1", + str![[r#"unexpected prerelease field, expected a version like "1.32""#]], + ), + // Weird wildcard + ( + "x", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + ( + "1.x", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + ( + "1.1.x", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + // Non-sense + ("foodaddle", str![[r#"expected a version like "1.32""#]]), + ]; + for (input, expected) in cases { + let actual: Result = input.parse(); + let actual = match actual { + Ok(result) => format!("didn't fail: {result:?}"), + Err(err) => err.to_string(), + }; + snapbox::assert_eq(expected.clone(), actual); + } + } } diff --git a/crates/cargo-util-schemas/src/manifest/rust_version.rs b/crates/cargo-util-schemas/src/manifest/rust_version.rs index 03ba94b3e71..5c40097737f 100644 --- a/crates/cargo-util-schemas/src/manifest/rust_version.rs +++ b/crates/cargo-util-schemas/src/manifest/rust_version.rs @@ -106,6 +106,7 @@ enum RustVersionErrorKind { #[cfg(test)] mod test { use super::*; + use snapbox::str; #[test] fn is_compatible_with_rustc() { @@ -170,4 +171,48 @@ mod test { } assert!(passed); } + + #[test] + fn parse_errors() { + let cases = &[ + // Disallow caret + ( + "^1.43", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + // Valid pre-release + ( + "1.43.0-beta.1", + str![[r#"unexpected prerelease field, expected a version like "1.32""#]], + ), + // Bad pre-release + ( + "1.43-beta.1", + str![[r#"unexpected prerelease field, expected a version like "1.32""#]], + ), + // Weird wildcard + ( + "x", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + ( + "1.x", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + ( + "1.1.x", + str![[r#"unexpected version requirement, expected a version like "1.32""#]], + ), + // Non-sense + ("foodaddle", str![[r#"expected a version like "1.32""#]]), + ]; + for (input, expected) in cases { + let actual: Result = input.parse(); + let actual = match actual { + Ok(result) => format!("didn't fail: {result:?}"), + Err(err) => err.to_string(), + }; + snapbox::assert_eq(expected.clone(), actual); + } + } } diff --git a/tests/testsuite/rust_version.rs b/tests/testsuite/rust_version.rs index 01f49e32ff8..1706bf4b485 100644 --- a/tests/testsuite/rust_version.rs +++ b/tests/testsuite/rust_version.rs @@ -26,7 +26,7 @@ fn rust_version_satisfied() { } #[cargo_test] -fn rust_version_bad_caret() { +fn rust_version_error() { project() .file( "Cargo.toml", @@ -58,105 +58,6 @@ fn rust_version_bad_caret() { .run(); } -#[cargo_test] -fn rust_version_good_pre_release() { - project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - edition = "2015" - authors = [] - rust-version = "1.43.0-beta.1" - [[bin]] - name = "foo" - "#, - ) - .file("src/main.rs", "fn main() {}") - .build() - .cargo("check") - .with_status(101) - .with_stderr( - "\ -[ERROR] unexpected prerelease field, expected a version like \"1.32\" - --> Cargo.toml:7:28 - | -7 | rust-version = \"1.43.0-beta.1\" - | ^^^^^^^^^^^^^^^ - | -", - ) - .run(); -} - -#[cargo_test] -fn rust_version_bad_pre_release() { - project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - edition = "2015" - authors = [] - rust-version = "1.43-beta.1" - [[bin]] - name = "foo" - "#, - ) - .file("src/main.rs", "fn main() {}") - .build() - .cargo("check") - .with_status(101) - .with_stderr( - "\ -[ERROR] unexpected prerelease field, expected a version like \"1.32\" - --> Cargo.toml:7:28 - | -7 | rust-version = \"1.43-beta.1\" - | ^^^^^^^^^^^^^ - | -", - ) - .run(); -} - -#[cargo_test] -fn rust_version_bad_nonsense() { - project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - edition = "2015" - authors = [] - rust-version = "foodaddle" - [[bin]] - name = "foo" - "#, - ) - .file("src/main.rs", "fn main() {}") - .build() - .cargo("check") - .with_status(101) - .with_stderr( - "\ -[ERROR] expected a version like \"1.32\" - --> Cargo.toml:7:28 - | -7 | rust-version = \"foodaddle\" - | ^^^^^^^^^^^ - | -", - ) - .run(); -} - #[cargo_test] fn rust_version_older_than_edition() { project()