From 43333adf0f1f1fe4a333982b7afac5313e060222 Mon Sep 17 00:00:00 2001 From: rami3l Date: Thu, 20 Jun 2024 16:30:18 +0800 Subject: [PATCH 1/3] fix(dist): throw an error when a `PartialVersion` string doesn't start with an ASCII digit --- src/dist/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/dist/mod.rs b/src/dist/mod.rs index c72b92176b..b9e84e302e 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -208,6 +208,17 @@ impl fmt::Display for PartialVersion { impl FromStr for PartialVersion { type Err = anyhow::Error; fn from_str(ver: &str) -> Result { + // `semver::Comparator::from_str` supports an optional operator + // (e.g. `=`, `>`, `>=`, `<`, `<=`, `~`, `^`, `*`) before the + // partial version, so we should exclude that case first. + if let Some(ch) = ver.chars().nth(0) { + if !ch.is_ascii_digit() { + return Err(anyhow!( + "expected ASCII digit at the beginning of `{ver}`, found `{ch}`" + ) + .context("error parsing `PartialVersion`")); + } + } let (ver, pre) = ver.split_once('-').unwrap_or((ver, "")); let comparator = semver::Comparator::from_str(ver).context("error parsing `PartialVersion`")?; From 4289a459643924af15d66c17a09534aea92572cc Mon Sep 17 00:00:00 2001 From: rami3l Date: Thu, 20 Jun 2024 17:14:14 +0800 Subject: [PATCH 2/3] chore(dist): add some doc comments --- src/dist/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/dist/mod.rs b/src/dist/mod.rs index b9e84e302e..acf6adeda4 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -125,11 +125,11 @@ struct ParsedToolchainDesc { target: Option, } -// A toolchain descriptor from rustup's perspective. These contain -// 'partial target triples', which allow toolchain names like -// 'stable-msvc' to work. Partial target triples though are parsed -// from a hardcoded set of known triples, whereas target triples -// are nearly-arbitrary strings. +/// A toolchain descriptor from rustup's perspective. These contain +/// 'partial target triples', which allow toolchain names like +/// 'stable-msvc' to work. Partial target triples though are parsed +/// from a hardcoded set of known triples, whereas target triples +/// are nearly-arbitrary strings. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct PartialToolchainDesc { pub channel: Channel, @@ -137,11 +137,11 @@ pub struct PartialToolchainDesc { pub target: PartialTargetTriple, } -// Fully-resolved toolchain descriptors. These always have full target -// triples attached to them and are used for canonical identification, -// such as naming their installation directory. -// -// as strings they look like stable-x86_64-pc-windows-msvc or +/// Fully-resolved toolchain descriptors. These always have full target +/// triples attached to them and are used for canonical identification, +/// such as naming their installation directory. +/// +/// As strings they look like stable-x86_64-pc-windows-msvc or /// 1.55-x86_64-pc-windows-msvc #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct ToolchainDesc { @@ -181,6 +181,8 @@ impl FromStr for Channel { } } +/// A possibly incomplete Rust toolchain version that +/// can be converted from and to its string form. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] pub struct PartialVersion { pub major: u64, From 08a0cc2ba033030d0598ced502ce4d178d469ece Mon Sep 17 00:00:00 2001 From: rami3l Date: Thu, 20 Jun 2024 17:44:35 +0800 Subject: [PATCH 3/3] test(dist): add simple tests for `PartialVersion` --- src/dist/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/dist/mod.rs b/src/dist/mod.rs index acf6adeda4..53eaf8bf03 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -1205,6 +1205,8 @@ fn date_from_manifest_date(date_str: &str) -> Option { #[cfg(test)] mod tests { + use proptest::prelude::*; + use super::*; #[test] @@ -1316,6 +1318,54 @@ mod tests { } } + #[test] + fn partial_version_from_str() -> Result<()> { + assert_eq!( + PartialVersion::from_str("0.12")?, + PartialVersion { + major: 0, + minor: Some(12), + patch: None, + pre: semver::Prerelease::EMPTY, + }, + ); + assert_eq!( + PartialVersion::from_str("1.23-beta")?, + PartialVersion { + major: 1, + minor: Some(23), + patch: None, + pre: semver::Prerelease::new("beta").unwrap(), + }, + ); + assert_eq!( + PartialVersion::from_str("1.23.0-beta.4")?, + PartialVersion { + major: 1, + minor: Some(23), + patch: Some(0), + pre: semver::Prerelease::new("beta.4").unwrap(), + }, + ); + + assert!(PartialVersion::from_str("1.01").is_err()); // no leading zeros + assert!(PartialVersion::from_str("^1.23").is_err()); // no comparing operators + assert!(PartialVersion::from_str(">=1").is_err()); + assert!(PartialVersion::from_str("*").is_err()); + assert!(PartialVersion::from_str("stable").is_err()); + + Ok(()) + } + + proptest! { + #[test] + fn partial_version_from_str_to_str( + ver in r"[0-9]{1}(\.(0|[1-9][0-9]{0,2}))(\.(0|[1-9][0-9]{0,1}))?(-beta(\.(0|[1-9][1-9]{0,1}))?)?" + ) { + prop_assert_eq!(PartialVersion::from_str(&ver).unwrap().to_string(), ver); + } + } + #[test] fn compatible_host_triples() { static CASES: &[(&str, &[&str], &[&str])] = &[