diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2025-02-18 19:41:09 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2025-02-18 19:41:09 +0100 |
| commit | 1c07d5980724df08b3b0fc3b7aaa09f197dc113d (patch) | |
| tree | e740d6585e2d5591a9aa2d3106c69845783454c9 | |
| parent | 447ec20b79adbda18d6e954a3d30178ffeb35a67 (diff) | |
| parent | 35fb1310a4dd348d33e1ed2454b47bb2c2819077 (diff) | |
| download | mullvadvpn-1c07d5980724df08b3b0fc3b7aaa09f197dc113d.tar.xz mullvadvpn-1c07d5980724df08b3b0fc3b7aaa09f197dc113d.zip | |
Merge branch 'fix-alpha-builds-are-crashing-droid-1693'
| -rw-r--r-- | mullvad-daemon/src/version_check.rs | 72 | ||||
| -rw-r--r-- | mullvad-setup/src/main.rs | 13 | ||||
| -rw-r--r-- | mullvad-types/src/version.rs | 176 | ||||
| -rw-r--r-- | mullvad-version/src/lib.rs | 312 | ||||
| -rw-r--r-- | mullvad-version/src/main.rs | 39 | ||||
| -rw-r--r-- | test/test-manager/src/tests/install.rs | 6 |
6 files changed, 303 insertions, 315 deletions
diff --git a/mullvad-daemon/src/version_check.rs b/mullvad-daemon/src/version_check.rs index 1ee879ca1f..c5da930c27 100644 --- a/mullvad-daemon/src/version_check.rs +++ b/mullvad-daemon/src/version_check.rs @@ -5,10 +5,10 @@ use futures::{ FutureExt, SinkExt, StreamExt, TryFutureExt, }; use mullvad_api::{availability::ApiAvailability, rest::MullvadRestHandle, AppVersionProxy}; -use mullvad_types::version::{AppVersionInfo, ParsedAppVersion}; +use mullvad_types::version::AppVersionInfo; +use mullvad_version::Version; use serde::{Deserialize, Serialize}; use std::{ - cmp::max, future::Future, io, path::{Path, PathBuf}, @@ -24,8 +24,8 @@ use tokio::{fs::File, io::AsyncReadExt}; const VERSION_INFO_FILENAME: &str = "version-info.json"; -static APP_VERSION: LazyLock<ParsedAppVersion> = - LazyLock::new(|| ParsedAppVersion::from_str(mullvad_version::VERSION).unwrap()); +static APP_VERSION: LazyLock<Version> = + LazyLock::new(|| Version::from_str(mullvad_version::VERSION).unwrap()); static IS_DEV_BUILD: LazyLock<bool> = LazyLock::new(|| APP_VERSION.is_dev()); const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(15); @@ -535,26 +535,38 @@ fn dev_version_cache() -> AppVersionInfo { suggested_upgrade: None, } } + /// If current_version is not the latest, return a string containing the latest version. fn suggested_upgrade( - current_version: &ParsedAppVersion, + current_version: &Version, latest_stable: &Option<String>, latest_beta: &str, show_beta: bool, ) -> Option<String> { let stable_version = latest_stable .as_ref() - .and_then(|stable| ParsedAppVersion::from_str(stable).ok()); + .and_then(|stable| Version::from_str(stable).ok()); let beta_version = if show_beta { - ParsedAppVersion::from_str(latest_beta).ok() + Version::from_str(latest_beta).ok() } else { None }; - let latest_version = max(stable_version, beta_version)?; + let latest_version = match (&stable_version, &beta_version) { + (Some(_), None) => stable_version, + (None, Some(_)) => beta_version, + (Some(stable), Some(beta)) => { + if beta > stable { + beta_version + } else { + stable_version + } + } + (None, None) => None, + }?; - if current_version < &latest_version { + if &latest_version > current_version { Some(latest_version.to_string()) } else { None @@ -736,13 +748,17 @@ mod test { let latest_stable = Some("2020.4".to_string()); let latest_beta = "2020.5-beta3"; - let older_stable = ParsedAppVersion::from_str("2020.3").unwrap(); - let current_stable = ParsedAppVersion::from_str("2020.4").unwrap(); - let newer_stable = ParsedAppVersion::from_str("2021.5").unwrap(); + let older_stable = Version::from_str("2020.3").unwrap(); + let current_stable = Version::from_str("2020.4").unwrap(); + let newer_stable = Version::from_str("2021.5").unwrap(); - let older_beta = ParsedAppVersion::from_str("2020.3-beta3").unwrap(); - let current_beta = ParsedAppVersion::from_str("2020.5-beta3").unwrap(); - let newer_beta = ParsedAppVersion::from_str("2021.5-beta3").unwrap(); + let older_beta = Version::from_str("2020.3-beta3").unwrap(); + let current_beta = Version::from_str("2020.5-beta3").unwrap(); + let newer_beta = Version::from_str("2021.5-beta3").unwrap(); + + let older_alpha = Version::from_str("2020.3-alpha3").unwrap(); + let current_alpha = Version::from_str("2020.5-alpha3").unwrap(); + let newer_alpha = Version::from_str("2021.5-alpha3").unwrap(); assert_eq!( suggested_upgrade(&older_stable, &latest_stable, latest_beta, false), @@ -768,6 +784,7 @@ mod test { suggested_upgrade(&newer_stable, &latest_stable, latest_beta, true), None ); + assert_eq!( suggested_upgrade(&older_beta, &latest_stable, latest_beta, false), Some("2020.4".to_owned()) @@ -792,5 +809,30 @@ mod test { suggested_upgrade(&newer_beta, &latest_stable, latest_beta, true), None ); + + assert_eq!( + suggested_upgrade(&older_alpha, &latest_stable, latest_beta, false), + Some("2020.4".to_owned()) + ); + assert_eq!( + suggested_upgrade(&older_alpha, &latest_stable, latest_beta, true), + Some("2020.5-beta3".to_owned()) + ); + assert_eq!( + suggested_upgrade(¤t_alpha, &latest_stable, latest_beta, false), + None, + ); + assert_eq!( + suggested_upgrade(¤t_alpha, &latest_stable, latest_beta, true), + Some("2020.5-beta3".to_owned()) + ); + assert_eq!( + suggested_upgrade(&newer_alpha, &latest_stable, latest_beta, false), + None + ); + assert_eq!( + suggested_upgrade(&newer_alpha, &latest_stable, latest_beta, true), + None + ); } } diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs index e525d5cb88..166364cf92 100644 --- a/mullvad-setup/src/main.rs +++ b/mullvad-setup/src/main.rs @@ -1,15 +1,14 @@ use clap::Parser; -use std::{path::PathBuf, process, str::FromStr, sync::LazyLock, time::Duration}; - use mullvad_api::{proxy::ApiConnectionMode, ApiEndpoint, DEVICE_NOT_FOUND}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::version::ParsedAppVersion; +use mullvad_version::Version; +use std::{path::PathBuf, process, str::FromStr, sync::LazyLock, time::Duration}; use talpid_core::firewall::{self, Firewall}; use talpid_future::retry::{retry_future, ConstantInterval}; use talpid_types::ErrorExt; -static APP_VERSION: LazyLock<ParsedAppVersion> = - LazyLock::new(|| ParsedAppVersion::from_str(mullvad_version::VERSION).unwrap()); +static APP_VERSION: LazyLock<Version> = + LazyLock::new(|| Version::from_str(mullvad_version::VERSION).unwrap()); const DEVICE_REMOVAL_STRATEGY: ConstantInterval = ConstantInterval::new(Duration::ZERO, Some(5)); @@ -114,9 +113,9 @@ async fn main() { fn is_older_version(old_version: &str) -> Result<ExitStatus, Error> { let parsed_version = - ParsedAppVersion::from_str(old_version).map_err(|_| Error::ParseVersionStringError)?; + Version::from_str(old_version).map_err(|_| Error::ParseVersionStringError)?; - Ok(if parsed_version < *APP_VERSION { + Ok(if *APP_VERSION > parsed_version { ExitStatus::Ok } else { ExitStatus::VersionNotOlder diff --git a/mullvad-types/src/version.rs b/mullvad-types/src/version.rs index 1ca5785147..6338909feb 100644 --- a/mullvad-types/src/version.rs +++ b/mullvad-types/src/version.rs @@ -1,17 +1,4 @@ -use regex::Regex; use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - fmt::{self, Formatter}, - str::FromStr, - sync::LazyLock, -}; - -static STABLE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(\d{4})\.(\d+)$").unwrap()); -static BETA_REGEX: LazyLock<Regex> = - LazyLock::new(|| Regex::new(r"^(\d{4})\.(\d+)-beta(\d+)$").unwrap()); -static DEV_REGEX: LazyLock<Regex> = - LazyLock::new(|| Regex::new(r"^(\d{4})\.(\d+)(\.\d+)?(-beta(\d+))?-dev-(\w+)$").unwrap()); /// AppVersionInfo represents the current stable and the current latest release versions of the /// Mullvad VPN app. @@ -35,166 +22,3 @@ pub struct AppVersionInfo { } pub type AppVersion = String; - -/// Parses a version string into a type that can be used for comparisons. -#[derive(Eq, PartialEq, Debug, Clone)] -pub enum ParsedAppVersion { - Stable(u32, u32), - Beta(u32, u32, u32), - Dev(u32, u32, Option<u32>, String), -} - -impl FromStr for ParsedAppVersion { - type Err = (); - fn from_str(version: &str) -> Result<Self, Self::Err> { - let get_int = |cap: ®ex::Captures<'_>, idx| cap.get(idx)?.as_str().parse().ok(); - - if let Some(caps) = STABLE_REGEX.captures(version) { - let year = get_int(&caps, 1).ok_or(())?; - let version = get_int(&caps, 2).ok_or(())?; - Ok(Self::Stable(year, version)) - } else if let Some(caps) = BETA_REGEX.captures(version) { - let year = get_int(&caps, 1).ok_or(())?; - let version = get_int(&caps, 2).ok_or(())?; - let beta_version = get_int(&caps, 3).ok_or(())?; - Ok(Self::Beta(year, version, beta_version)) - } else if let Some(caps) = DEV_REGEX.captures(version) { - let year = get_int(&caps, 1).ok_or(())?; - let version = get_int(&caps, 2).ok_or(())?; - let beta_version = caps.get(4).map(|_| get_int(&caps, 5).unwrap()); - let dev_hash = caps.get(6).ok_or(())?.as_str().to_string(); - Ok(Self::Dev(year, version, beta_version, dev_hash)) - } else { - Err(()) - } - } -} - -impl ParsedAppVersion { - pub fn is_dev(&self) -> bool { - matches!(self, ParsedAppVersion::Dev(..)) - } -} - -impl Ord for ParsedAppVersion { - fn cmp(&self, other: &Self) -> Ordering { - use ParsedAppVersion::*; - match (self, other) { - (Stable(year, version), Stable(other_year, other_version)) => { - year.cmp(other_year).then(version.cmp(other_version)) - } - // A stable version of the same year and version is always greater than a beta - (Stable(year, version), Beta(other_year, other_version, _)) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(Ordering::Greater), - // We assume that a dev version of the same year and version is newer - (Stable(year, version), Dev(other_year, other_version, ..)) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(Ordering::Less), - - ( - Beta(year, version, beta_version), - Beta(other_year, other_version, other_beta_version), - ) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(beta_version.cmp(other_beta_version)), - (Beta(year, version, _beta_version), Stable(other_year, other_version)) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(Ordering::Less), - // We assume that a dev version of the same year and version is newer - (Beta(year, version, _), Dev(other_year, other_version, ..)) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(Ordering::Less), - - // Dev versions of the same year and version are assumed to be equal - (Dev(year, version, ..), Dev(other_year, other_version, ..)) => { - year.cmp(other_year).then(version.cmp(other_version)) - } - (Dev(year, version, ..), Stable(other_year, other_version)) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(Ordering::Greater), - (Dev(year, version, ..), Beta(other_year, other_version, _)) => year - .cmp(other_year) - .then(version.cmp(other_version)) - .then(Ordering::Greater), - } - } -} - -impl PartialOrd for ParsedAppVersion { - fn partial_cmp(&self, other: &ParsedAppVersion) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl fmt::Display for ParsedAppVersion { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Stable(year, version) => write!(f, "{year}.{version}"), - Self::Beta(year, version, beta_version) => { - write!(f, "{year}.{version}-beta{beta_version}") - } - Self::Dev(year, version, beta_version, hash) => { - if let Some(beta_version) = beta_version { - write!(f, "{year}.{version}-beta{beta_version}-dev-{hash}") - } else { - write!(f, "{year}.{version}-dev-{hash}") - } - } - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_version_regex() { - assert!(STABLE_REGEX.is_match("2020.4")); - assert!(!STABLE_REGEX.is_match("2020.4-beta3")); - assert!(BETA_REGEX.is_match("2020.4-beta3")); - assert!(!STABLE_REGEX.is_match("2020.5-beta1-dev-f16be4")); - assert!(!STABLE_REGEX.is_match("2020.5-dev-f16be4")); - assert!(!BETA_REGEX.is_match("2020.5-beta1-dev-f16be4")); - assert!(!BETA_REGEX.is_match("2020.5-dev-f16be4")); - assert!(!BETA_REGEX.is_match("2020.4")); - assert!(DEV_REGEX.is_match("2020.5-dev-f16be4")); - assert!(DEV_REGEX.is_match("2020.5-beta1-dev-f16be4")); - assert!(!DEV_REGEX.is_match("2020.5")); - assert!(!DEV_REGEX.is_match("2020.5-beta1")); - } - - #[test] - fn test_version_parsing() { - let tests = vec![ - ("2020.4", Some(ParsedAppVersion::Stable(2020, 4))), - ("2020.4-beta3", Some(ParsedAppVersion::Beta(2020, 4, 3))), - ( - "2020.15-beta1-dev-f16be4", - Some(ParsedAppVersion::Dev( - 2020, - 15, - Some(1), - "f16be4".to_string(), - )), - ), - ( - "2020.15-dev-f16be4", - Some(ParsedAppVersion::Dev(2020, 15, None, "f16be4".to_string())), - ), - ("2020.15-9000", None), - ("", None), - ]; - - for (input, expected_output) in tests { - assert_eq!(ParsedAppVersion::from_str(input).ok(), expected_output,); - } - } -} diff --git a/mullvad-version/src/lib.rs b/mullvad-version/src/lib.rs index cb9ecde750..29ecb78a1e 100644 --- a/mullvad-version/src/lib.rs +++ b/mullvad-version/src/lib.rs @@ -1,8 +1,8 @@ +use std::cmp::Ordering; use std::fmt::Display; use std::str::FromStr; use std::sync::LazyLock; -use crate::PreStableType::{Alpha, Beta}; use regex::Regex; /// The Mullvad VPN app product version @@ -10,42 +10,72 @@ pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-versio #[derive(Debug, Clone, PartialEq)] pub struct Version { - /// The last two digits of the version's year - pub year: String, - pub incremental: String, - /// A version can have an optional pre-stable type, e.g. alpha or beta. If `pre_stable` - /// and `dev` both are None the version is stable. + pub year: u32, + pub incremental: u32, + /// A version can have an optional pre-stable type, e.g. alpha or beta. pub pre_stable: Option<PreStableType>, /// All versions may have an optional -dev-[commit hash] suffix. pub dev: Option<String>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum PreStableType { - Alpha(String), - Beta(String), + Alpha(u32), + Beta(u32), } -impl Version { - pub fn parse(version: &str) -> Version { - Version::from_str(version).unwrap() +impl Ord for PreStableType { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (PreStableType::Alpha(a), PreStableType::Alpha(b)) => a.cmp(b), + (PreStableType::Beta(a), PreStableType::Beta(b)) => a.cmp(b), + (PreStableType::Alpha(_), PreStableType::Beta(_)) => Ordering::Less, + (PreStableType::Beta(_), PreStableType::Alpha(_)) => Ordering::Greater, + } } +} - pub fn is_stable(&self) -> bool { - self.pre_stable.is_none() && self.dev.is_none() +impl PartialOrd for PreStableType { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) } +} - pub fn alpha(&self) -> Option<&str> { - match &self.pre_stable { - Some(PreStableType::Alpha(v)) => Some(v), - _ => None, - } +impl Version { + /// Returns true if this version has a -dev suffix, e.g. 2025.2-beta1-dev-123abc + pub fn is_dev(&self) -> bool { + self.dev.is_some() } +} - pub fn beta(&self) -> Option<&str> { - match &self.pre_stable { - Some(PreStableType::Beta(beta)) => Some(beta), - _ => None, +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + let type_ordering = match (&self.pre_stable, &other.pre_stable) { + (None, None) => Ordering::Equal, + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (Some(self_pre_stable), Some(other_pre_stable)) => { + self_pre_stable.cmp(other_pre_stable) + } + }; + + // The dev vs non-dev ordering. For a version of a given type, if all else is equal + // a dev version is greater than a non-dev version. + let dev_ordering = match (self.is_dev(), other.is_dev()) { + (true, false) => Some(Ordering::Greater), + (false, true) => Some(Ordering::Less), + (_, _) => None, + }; + + let release_ordering = self + .year + .cmp(&other.year) + .then(self.incremental.cmp(&other.incremental)) + .then(type_ordering); + + match release_ordering { + Ordering::Equal => dev_ordering, + _ => Some(release_ordering), } } } @@ -60,7 +90,7 @@ impl Display for Version { dev, } = &self; - write!(f, "20{year}.{incremental}")?; + write!(f, "{year}.{incremental}")?; match pre_stable { Some(PreStableType::Alpha(version)) => write!(f, "-alpha{version}")?, @@ -76,14 +106,10 @@ impl Display for Version { } } -impl FromStr for Version { - type Err = String; - - fn from_str(version: &str) -> Result<Self, Self::Err> { - static VERSION_REGEX: LazyLock<Regex> = LazyLock::new(|| { - Regex::new( - r"(?x) # enable insignificant whitespace mode - 20(?<year>\d{2})\. # the last two digits of the year +static VERSION_REGEX: LazyLock<Regex> = LazyLock::new(|| { + Regex::new( + r"(?x) # enable insignificant whitespace mode + (?<year>\d{4})\. # the year (?<incremental>[1-9]\d?) # the incrementing version number (?: # (optional) alpha or beta or dev -alpha(?<alpha>[1-9]\d?\d?)| @@ -93,34 +119,35 @@ impl FromStr for Version { -dev-(?<dev>[0-9a-f]+) )?$ ", - ) - .unwrap() - }); + ) + .unwrap() +}); +impl FromStr for Version { + type Err = String; + + fn from_str(version: &str) -> Result<Self, Self::Err> { let captures = VERSION_REGEX .captures(version) .ok_or_else(|| format!("Version does not match expected format: {version}"))?; - let year = captures - .name("year") - .expect("Missing year") - .as_str() - .to_owned(); + let year = captures.name("year").unwrap().as_str().parse().unwrap(); let incremental = captures .name("incremental") - .ok_or("Missing incremental")? + .unwrap() .as_str() - .to_owned(); + .parse() + .unwrap(); - let alpha = captures.name("alpha").map(|m| m.as_str().to_owned()); - let beta = captures.name("beta").map(|m| m.as_str().to_owned()); + let alpha = captures.name("alpha").map(|m| m.as_str().parse().unwrap()); + let beta = captures.name("beta").map(|m| m.as_str().parse().unwrap()); let dev = captures.name("dev").map(|m| m.as_str().to_owned()); let pre_stable = match (alpha, beta) { (None, None) => None, - (Some(v), None) => Some(Alpha(v)), - (None, Some(v)) => Some(Beta(v)), + (Some(v), None) => Some(PreStableType::Alpha(v)), + (None, Some(v)) => Some(PreStableType::Beta(v)), _ => return Err(format!("Invalid version: {version}")), }; @@ -138,102 +165,201 @@ mod tests { use super::*; #[test] + fn test_version_ordering() { + // Test year comparison + assert!(parse("2022.1") > parse("2021.1"),); + + // Test incremental comparison + assert!(parse("2021.2") > parse("2021.1"),); + + // Test stable vs pre-release + assert!(parse("2021.1") > parse("2021.1-beta1"),); + assert!(parse("2021.1") > parse("2021.1-alpha1"),); + + // Test beta vs alpha + assert!(parse("2021.1-beta1") > parse("2021.1-alpha1"),); + assert!(parse("2021.1-beta1") > parse("2021.1-alpha2"),); + assert!(parse("2021.2-alpha1") > parse("2021.1-beta2"),); + + // Test version numbers within same type + assert!(parse("2021.1-beta2") > parse("2021.1-beta1"),); + assert!(parse("2021.1-alpha2") > parse("2021.1-alpha1"),); + + // Test dev versions + assert!(parse("2021.1-dev-abc") > parse("2021.1"),); + assert!(parse("2021.2") > parse("2021.1-dev-abc"),); + assert!(parse("2021.1-dev-abc") > parse("2021.1-beta1"),); + assert!(parse("2021.1-dev-abc") > parse("2021.1-alpha1"),); + assert!(parse("2025.1-dev-abc") > parse("2025.1-beta1-dev-abc"),); + assert!(parse("2025.1-dev-abc") > parse("2025.1-beta2-dev-abc"),); + assert!(parse("2025.1-dev-abc") > parse("2025.1-alpha2-dev-abc"),); + assert!(parse("2025.1-beta1-dev-abc") > parse("2025.1-alpha7-dev-abc"),); + assert!(parse("2025.2-alpha1-dev-abc") > parse("2025.1-beta7-dev-abc"),); + + // Test version equality + assert_eq!(parse("2021.1"), parse("2021.1")); + assert_eq!(parse("2021.1-beta1"), parse("2021.1-beta1")); + assert_eq!(parse("2021.1-alpha7"), parse("2021.1-alpha7")); + assert_eq!(parse("2021.1-dev-abc123"), parse("2021.1-dev-abc123")); + assert_ne!(parse("2021.1-dev-abc123"), parse("2021.1-dev-def123")); + } + + #[test] + fn test_version_ordering_and_equality_dev() { + let v1 = parse("2021.3-dev-abc"); + let v2 = parse("2021.3-dev-def"); + + // Exactly the same version are equal, but has no ordering + assert_eq!(v1, v1); + assert!(v1.partial_cmp(&v2).is_none()); + + // Equal down to the dev suffix are not equal, and has no ordering + assert_ne!(v1, v2); + assert!(v1.partial_cmp(&v2).is_none()); + } + + #[test] fn test_parse() { let version = "2021.34"; - let parsed = Version::parse(version); - assert_eq!(parsed.year, "21"); - assert_eq!(parsed.incremental, "34"); - assert_eq!(parsed.alpha(), None); - assert_eq!(parsed.beta(), None); + let parsed = parse(version); + assert_eq!(parsed.year, 2021); + assert_eq!(parsed.incremental, 34); + assert_eq!(alpha(&parsed), None); + assert_eq!(beta(&parsed), None); assert_eq!(parsed.dev, None); - assert!(parsed.is_stable()); + assert!(is_stable(&parsed)); } #[test] fn test_parse_with_alpha() { let version = "2023.1-alpha77"; - let parsed = Version::parse(version); - assert_eq!(parsed.year, "23"); - assert_eq!(parsed.incremental, "1"); - assert_eq!(parsed.alpha(), Some("77")); - assert_eq!(parsed.beta(), None); + let parsed = parse(version); + assert_eq!(parsed.year, 2023); + assert_eq!(parsed.incremental, 1); + assert_eq!(alpha(&parsed), Some(77)); + assert_eq!(beta(&parsed), None); assert_eq!(parsed.dev, None); - assert!(!parsed.is_stable()); + assert!(!is_stable(&parsed)); let version = "2021.34-alpha777"; - let parsed = Version::parse(version); - assert_eq!(parsed.alpha(), Some("777")); + let parsed = parse(version); + assert_eq!(alpha(&parsed), Some(777)); } #[test] fn test_parse_with_beta() { let version = "2021.34-beta5"; - let parsed = Version::parse(version); - assert_eq!(parsed.year, "21"); - assert_eq!(parsed.incremental, "34"); - assert_eq!(parsed.alpha(), None); - assert_eq!(parsed.beta(), Some("5")); + let parsed = parse(version); + assert_eq!(parsed.year, 2021); + assert_eq!(parsed.incremental, 34); + assert_eq!(alpha(&parsed), None); + assert_eq!(beta(&parsed), Some(5)); assert_eq!(parsed.dev, None); - assert!(!parsed.is_stable()); + assert!(!is_stable(&parsed)); let version = "2021.34-beta453"; - let parsed = Version::parse(version); - assert_eq!(parsed.beta(), Some("453")); + let parsed = parse(version); + assert_eq!(beta(&parsed), Some(453)); } #[test] fn test_parse_with_dev() { let version = "2021.34-dev-0b60e4d87"; - let parsed = Version::parse(version); - assert_eq!(parsed.year, "21"); - assert_eq!(parsed.incremental, "34"); - assert!(!parsed.is_stable()); + let parsed = parse(version); + assert_eq!(parsed.year, 2021); + assert_eq!(parsed.incremental, 34); + assert!(!is_stable(&parsed)); assert_eq!(parsed.dev, Some("0b60e4d87".to_string())); - assert_eq!(parsed.alpha(), None); - assert_eq!(parsed.beta(), None); + assert_eq!(alpha(&parsed), None); + assert_eq!(beta(&parsed), None); } #[test] fn test_parse_both_beta_and_dev() { let version = "2024.8-beta1-dev-e5483d"; - let parsed = Version::parse(version); - assert_eq!(parsed.year, "24"); - assert_eq!(parsed.incremental, "8"); - assert_eq!(parsed.alpha(), None); - assert_eq!(parsed.beta(), Some("1")); + let parsed = parse(version); + assert_eq!(parsed.year, 2024); + assert_eq!(parsed.incremental, 8); + assert_eq!(alpha(&parsed), None); + assert_eq!(beta(&parsed), Some(1)); assert_eq!(parsed.dev, Some("e5483d".to_string())); - assert!(!parsed.is_stable()); + assert!(!is_stable(&parsed)); + } + + #[test] + fn test_returns_error_on_invalid_version() { + assert!("2021".parse::<Version>().is_err()); + assert!("not-a-version".parse::<Version>().is_err()); + assert!("".parse::<Version>().is_err()); } #[test] - #[should_panic] - fn test_panics_on_invalid_version() { - Version::parse("2021"); + fn test_returns_error_on_invalid_incremental() { + assert!("2021.2a".parse::<Version>().is_err()); } #[test] - #[should_panic] - fn test_panics_on_invalid_version_type_number() { - Version::parse("2021.1-beta001"); + fn test_returns_error_on_invalid_version_type() { + assert!("2021.2-omega".parse::<Version>().is_err()); } #[test] - #[should_panic] - fn test_panics_on_alpha_and_beta_in_same_version() { - Version::parse("2021.1-beta5-alpha2"); + fn test_returns_error_on_invalid_version_type_number() { + assert!("2021.1-beta001".parse::<Version>().is_err()); } #[test] - #[should_panic] - fn test_panics_on_dev_without_commit_hash() { - Version::parse("2021.1-dev"); + fn test_returns_error_on_alpha_and_beta_in_same_version() { + assert!("2021.1-beta5-alpha2".parse::<Version>().is_err()); + } + + #[test] + fn test_returns_error_on_dev_without_commit_hash() { + assert!("2021.1-dev".parse::<Version>().is_err()) + } + + fn parse(version: &str) -> Version { + version.parse().unwrap() } #[test] fn test_version_display() { let version = "2024.8-beta1-dev-e5483d"; - let parsed = Version::parse(version); + let parsed: Version = version.parse().unwrap(); + + assert_eq!(format!("{parsed}"), version); + + let version = "2024.8-beta1"; + let parsed: Version = version.parse().unwrap(); assert_eq!(format!("{parsed}"), version); + + let version = "2024.8-alpha77-dev-85483d"; + let parsed: Version = version.parse().unwrap(); + + assert_eq!(format!("{parsed}"), version); + + let version = "2024.12"; + let parsed: Version = version.parse().unwrap(); + + assert_eq!(format!("{parsed}"), version); + } + + fn alpha(version: &Version) -> Option<u32> { + match version.pre_stable { + Some(PreStableType::Alpha(alpha)) => Some(alpha), + _ => None, + } + } + + fn beta(version: &Version) -> Option<u32> { + match version.pre_stable { + Some(PreStableType::Beta(beta)) => Some(beta), + _ => None, + } + } + + fn is_stable(version: &Version) -> bool { + version.pre_stable.is_none() && !version.is_dev() } } diff --git a/mullvad-version/src/main.rs b/mullvad-version/src/main.rs index 1317260a2f..9d5cf6d152 100644 --- a/mullvad-version/src/main.rs +++ b/mullvad-version/src/main.rs @@ -62,51 +62,42 @@ fn to_semver(version: &str) -> String { /// Version: 2021.34-dev /// versionCode: 21349000 fn to_android_version_code(version: &str) -> String { - let version = Version::parse(version); + let version: Version = version.parse().unwrap(); let (build_type, build_number) = if version.dev.is_some() { - ("9", "000") + ("9", "000".to_string()) } else { match &version.pre_stable { - Some(PreStableType::Alpha(v)) => ("0", v.as_str()), - Some(PreStableType::Beta(v)) => ("1", v.as_str()), + Some(PreStableType::Alpha(v)) => ("0", v.to_string()), + Some(PreStableType::Beta(v)) => ("1", v.to_string()), // Stable version - None => ("9", "000"), + None => ("9", "000".to_string()), } }; + let year_last_two_digits = version.year % 100; + format!( "{}{:0>2}{}{:0>3}", - version.year, version.incremental, build_type, build_number, + year_last_two_digits, version.incremental, build_type, build_number, ) } fn to_windows_h_format(version_str: &str) -> String { - let version = Version::parse(version_str); - assert!( - is_valid_windows_version(&version), - "Invalid Windows version: {version:?}" - ); + let version = version_str.parse().unwrap(); let Version { year, incremental, .. } = version; format!( - "#define MAJOR_VERSION 20{year} + "#define MAJOR_VERSION {year} #define MINOR_VERSION {incremental} #define PATCH_VERSION 0 #define PRODUCT_VERSION \"{version_str}\"" ) } -/// On Windows we currently support the following versions: stable, beta and dev. -fn is_valid_windows_version(version: &Version) -> bool { - version.is_stable() - || version.beta().is_some() - || (version.dev.is_some() && version.alpha().is_none()) -} - #[cfg(test)] mod tests { use super::*; @@ -132,8 +123,12 @@ mod tests { } #[test] - #[should_panic] - fn test_invalid_windows_version_code() { - to_windows_h_format("2021.34-alpha1"); + fn test_windows_version_h() { + let version_h = to_windows_h_format("2025.4-beta2-dev-abcdef"); + let expected_version_h = "#define MAJOR_VERSION 2025 +#define MINOR_VERSION 4 +#define PATCH_VERSION 0 +#define PRODUCT_VERSION \"2025.4-beta2-dev-abcdef\""; + assert_eq!(expected_version_h, version_h); } } diff --git a/test/test-manager/src/tests/install.rs b/test/test-manager/src/tests/install.rs index d640b26089..8c257d1899 100644 --- a/test/test-manager/src/tests/install.rs +++ b/test/test-manager/src/tests/install.rs @@ -1,4 +1,5 @@ use anyhow::{bail, ensure, Context}; +use std::str::FromStr; use std::time::Duration; use mullvad_management_interface::MullvadProxyClient; @@ -109,8 +110,9 @@ pub async fn test_upgrade_app( // Verify that the correct version was installed let running_daemon_version = rpc.mullvad_daemon_version().await?; - let running_daemon_version = - mullvad_version::Version::parse(&running_daemon_version).to_string(); + let running_daemon_version = mullvad_version::Version::from_str(&running_daemon_version) + .unwrap() + .to_string(); ensure!( &TEST_CONFIG .app_package_filename |
