summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2025-02-18 19:41:09 +0100
committerLinus Färnstrand <linus@mullvad.net>2025-02-18 19:41:09 +0100
commit1c07d5980724df08b3b0fc3b7aaa09f197dc113d (patch)
treee740d6585e2d5591a9aa2d3106c69845783454c9
parent447ec20b79adbda18d6e954a3d30178ffeb35a67 (diff)
parent35fb1310a4dd348d33e1ed2454b47bb2c2819077 (diff)
downloadmullvadvpn-1c07d5980724df08b3b0fc3b7aaa09f197dc113d.tar.xz
mullvadvpn-1c07d5980724df08b3b0fc3b7aaa09f197dc113d.zip
Merge branch 'fix-alpha-builds-are-crashing-droid-1693'
-rw-r--r--mullvad-daemon/src/version_check.rs72
-rw-r--r--mullvad-setup/src/main.rs13
-rw-r--r--mullvad-types/src/version.rs176
-rw-r--r--mullvad-version/src/lib.rs312
-rw-r--r--mullvad-version/src/main.rs39
-rw-r--r--test/test-manager/src/tests/install.rs6
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(&current_alpha, &latest_stable, latest_beta, false),
+ None,
+ );
+ assert_eq!(
+ suggested_upgrade(&current_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: &regex::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