Unify daemon app version types
Previously we had two types in the code base that dealt with version parsing. This commit unifies these types so that we only use the Version struct that is defines in the mullvad-version crate. This also solves a bug where the daemon code would crash on alpha versions, as the previous version parsing code didn't handle them.
This commit is contained in:
parent
447ec20b79
commit
35fb1310a4
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PreStableType {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub fn parse(version: &str) -> Version {
|
||||
Version::from_str(version).unwrap()
|
||||
/// 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 is_stable(&self) -> bool {
|
||||
self.pre_stable.is_none() && self.dev.is_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)
|
||||
}
|
||||
};
|
||||
|
||||
pub fn alpha(&self) -> Option<&str> {
|
||||
match &self.pre_stable {
|
||||
Some(PreStableType::Alpha(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
};
|
||||
|
||||
pub fn beta(&self) -> Option<&str> {
|
||||
match &self.pre_stable {
|
||||
Some(PreStableType::Beta(beta)) => Some(beta),
|
||||
_ => 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}")),
|
||||
};
|
||||
|
||||
@ -137,103 +164,202 @@ impl FromStr for Version {
|
||||
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]
|
||||
#[should_panic]
|
||||
fn test_panics_on_invalid_version() {
|
||||
Version::parse("2021");
|
||||
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_type_number() {
|
||||
Version::parse("2021.1-beta001");
|
||||
fn test_returns_error_on_invalid_incremental() {
|
||||
assert!("2021.2a".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() {
|
||||
assert!("2021.2-omega".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_invalid_version_type_number() {
|
||||
assert!("2021.1-beta001".parse::<Version>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user