Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use channel_versions::{overrides, ChannelVersion, ChannelVersions};
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about)]
|
||||
struct Args {
|
||||
/// Name of the operating system to parse the version for.
|
||||
#[arg(long, value_enum)]
|
||||
target_os: channel_versions::overrides::TargetOS,
|
||||
|
||||
/// The file containing a JSON-serialied [`ChannelVersions`] struct to
|
||||
/// apply overrides for.
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
/// Reads in a JSON-serialized [`ChannelVersions`] from a file, applies any
|
||||
/// defined overrides that match a given target OS, and prints out the updated
|
||||
/// JSON (omitting changelogs).
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let contents = fs::read_to_string(&args.file)?;
|
||||
|
||||
// Deserialize the JSON data into the expected format.
|
||||
let versions: ChannelVersions = serde_json::from_str(contents.as_str())?;
|
||||
|
||||
let context = overrides::Context {
|
||||
target_os: Some(args.target_os),
|
||||
};
|
||||
|
||||
let dev_version_info = versions.dev.version_info_for_execution_context(&context);
|
||||
let preview_version_info = versions
|
||||
.preview
|
||||
.version_info_for_execution_context(&context);
|
||||
let stable_version_info = versions.stable.version_info_for_execution_context(&context);
|
||||
|
||||
let transformed_versions = ChannelVersions {
|
||||
dev: ChannelVersion::new(dev_version_info),
|
||||
preview: ChannelVersion::new(preview_version_info),
|
||||
stable: ChannelVersion::new(stable_version_info),
|
||||
changelogs: None,
|
||||
};
|
||||
|
||||
// Print out the transformed version info.
|
||||
println!("{}", serde_json::to_string_pretty(&transformed_versions)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use std::process::exit;
|
||||
|
||||
use anyhow::Result;
|
||||
use channel_versions::ParsedVersion;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about)]
|
||||
struct Args {
|
||||
#[arg(long)]
|
||||
version_to_roll_out: String,
|
||||
|
||||
#[arg(long)]
|
||||
current_version: String,
|
||||
}
|
||||
|
||||
/// Compares two versions and exits with a non-zero exit code if the version to rollout is older than the current version.
|
||||
/// Used within the `channel-versions` repo to ensure that we always specify the `is_rollback` field when rolling back.
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let version_to_roll_out = args.version_to_roll_out;
|
||||
let current_version = args.current_version;
|
||||
|
||||
let parsed_version_to_roll_out = ParsedVersion::try_from(version_to_roll_out.as_str())?;
|
||||
let parsed_current_version = ParsedVersion::try_from(current_version.as_str())?;
|
||||
|
||||
match parsed_version_to_roll_out.cmp(&parsed_current_version) {
|
||||
std::cmp::Ordering::Less => {
|
||||
println!("Current version ({current_version}) is newer than the version to roll out ({version_to_roll_out})");
|
||||
exit(1);
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
println!("Version to rollout ({version_to_roll_out}) is equal to the current version ({current_version})");
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
println!("Version to rollout ({version_to_roll_out}) is newer than the current version ({current_version})");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_version_string() {
|
||||
let version_string = "v0.2023.05.15.08.04.stable_01";
|
||||
let parsed_version: ParsedVersion = version_string
|
||||
.try_into()
|
||||
.expect("version string is parsable");
|
||||
assert_eq!(parsed_version.major, 0);
|
||||
assert_eq!(
|
||||
parsed_version.date,
|
||||
NaiveDate::from_ymd_opt(2023, 5, 15)
|
||||
.unwrap()
|
||||
.and_hms_opt(8, 4, 0)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(parsed_version.patch, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_major_versions_compare_correctly() {
|
||||
let older_version: ParsedVersion = "v0.2023.05.15.08.04.stable_01"
|
||||
.try_into()
|
||||
.expect("older_version is parsable");
|
||||
let newer_version: ParsedVersion = "v1.2023.05.15.08.04.stable_01"
|
||||
.try_into()
|
||||
.expect("newer_version is parsable");
|
||||
assert!(newer_version > older_version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dates_compare_correctly() {
|
||||
let older_version: ParsedVersion = "v0.2023.05.15.08.04.stable_01"
|
||||
.try_into()
|
||||
.expect("older_version is parsable");
|
||||
let newer_version: ParsedVersion = "v0.2023.05.22.08.04.stable_00"
|
||||
.try_into()
|
||||
.expect("newer_version is parsable");
|
||||
assert!(newer_version > older_version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_patches_compare_correctly() {
|
||||
let older_version: ParsedVersion = "v0.2023.05.15.08.04.stable_00"
|
||||
.try_into()
|
||||
.expect("older_version is parsable");
|
||||
let newer_version: ParsedVersion = "v0.2023.05.15.08.04.stable_01"
|
||||
.try_into()
|
||||
.expect("newer_version is parsable");
|
||||
assert!(newer_version > older_version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignores_unknown_channels() {
|
||||
// We no longer support or parse-out beta and canary versions, but we
|
||||
// need to be able to parse a JSON file that still contains them.
|
||||
let channel_version_string = r#"{
|
||||
"beta": {
|
||||
"version": "v0.2024.01.30.16.52.beta_00"
|
||||
},
|
||||
"canary": {
|
||||
"version": "v0.2022.09.29.08.08.canary_00"
|
||||
},
|
||||
"dev": {
|
||||
"version": "v0.2024.01.30.20.34.dev_00"
|
||||
},
|
||||
"preview": {
|
||||
"version": "v0.2024.01.30.20.34.preview_00"
|
||||
},
|
||||
"stable": {
|
||||
"version": "v0.2024.01.16.16.31.stable_01"
|
||||
}
|
||||
}"#;
|
||||
|
||||
let channel_versions: ChannelVersions = serde_json::from_str(channel_version_string)
|
||||
.expect("Should be able to parse channel versions");
|
||||
assert_eq!(
|
||||
channel_versions.stable.version_info().version,
|
||||
"v0.2024.01.16.16.31.stable_01"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
pub mod overrides;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
use lazy_static::lazy_static;
|
||||
use memo_map::MemoMap;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use overrides::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ChannelVersions {
|
||||
pub dev: ChannelVersion,
|
||||
pub preview: ChannelVersion,
|
||||
pub stable: ChannelVersion,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub changelogs: Option<ChannelChangelogs>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ChannelVersions {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"dev: {:?}; preview: {:?}; stable: {:?}",
|
||||
self.dev, self.preview, self.stable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref VERSION_RE: Regex = Regex::new(r"v(\d+)\.(.+)\.(.+)_(\d+)").unwrap();
|
||||
|
||||
// Cached mapping of version strings to semantic versions.
|
||||
static ref PARSED_VERSIONS_CACHE: MemoMap<String, ParsedVersion> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||
pub struct ParsedVersion {
|
||||
major: usize,
|
||||
date: NaiveDateTime,
|
||||
patch: usize,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for ParsedVersion {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self> {
|
||||
PARSED_VERSIONS_CACHE
|
||||
.get_or_try_insert(value, || {
|
||||
VERSION_RE
|
||||
.captures(value)
|
||||
.and_then(|captures| {
|
||||
let date_str = captures.get(2)?.as_str();
|
||||
let date =
|
||||
NaiveDateTime::parse_from_str(date_str, "%Y.%m.%d.%H.%M").ok()?;
|
||||
Some(ParsedVersion {
|
||||
major: captures.get(1)?.as_str().parse().ok()?,
|
||||
date,
|
||||
patch: captures.get(4)?.as_str().parse().ok()?,
|
||||
})
|
||||
})
|
||||
.context("Can't parse string into Version")
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ParsedVersion {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(self.major, self.date, self.patch).cmp(&(other.major, other.date, other.patch))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ParsedVersion {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ChannelVersion {
|
||||
#[serde(flatten)]
|
||||
version_info: VersionInfo,
|
||||
/// Any overrides which should be applied for this channel.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
overrides: Vec<VersionOverride>,
|
||||
}
|
||||
|
||||
impl ChannelVersion {
|
||||
pub fn new(version_info: VersionInfo) -> Self {
|
||||
Self {
|
||||
version_info,
|
||||
overrides: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the version information, with any applicable overrides applied
|
||||
/// based on the current execution environment.
|
||||
pub fn version_info(&self) -> VersionInfo {
|
||||
let context = overrides::Context::from_env();
|
||||
self.version_info
|
||||
.with_overrides_applied(&self.overrides, &context)
|
||||
}
|
||||
|
||||
/// Returns the version information, with any applicable overrides applied
|
||||
/// based on the provided context.
|
||||
pub fn version_info_for_execution_context(&self, context: &overrides::Context) -> VersionInfo {
|
||||
self.version_info
|
||||
.with_overrides_applied(&self.overrides, context)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VersionInfo {
|
||||
pub version: String,
|
||||
/// The version to download for new users from the download page. This is not used on the client
|
||||
/// other than in the `apply_overrides` binary used from the `channel-versions` repo.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub version_for_new_users: Option<String>,
|
||||
/// The time by which the client needs to be updated, after which
|
||||
/// the user sees a warning banner.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub update_by: Option<DateTime<FixedOffset>>,
|
||||
/// If specified, this field indicates the oldest version of the client that is still
|
||||
/// supported. Any version before this version is not supported and the user should update.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub soft_cutoff: Option<String>,
|
||||
/// If specified, this field indicates the latest client version that has a prominent update.
|
||||
/// Versions before `prominent_update` should display the prominent update UI.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub last_prominent_update: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub is_rollback: Option<bool>,
|
||||
/// The version to use for CLI downloads, falling back to `version` if not set.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cli_version: Option<String>,
|
||||
}
|
||||
|
||||
impl VersionInfo {
|
||||
pub fn new(version: String) -> Self {
|
||||
Self {
|
||||
version,
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
version_for_new_users: None,
|
||||
is_rollback: None,
|
||||
cli_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the CLI version, falling back to the app version if not set.
|
||||
pub fn cli_version(&self) -> &str {
|
||||
self.cli_version.as_deref().unwrap_or(&self.version)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ChannelChangelogs {
|
||||
// Maps of changelogs by version
|
||||
pub dev: HashMap<String, Changelog>,
|
||||
pub preview: HashMap<String, Changelog>,
|
||||
pub stable: HashMap<String, Changelog>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Changelog {
|
||||
pub date: DateTime<FixedOffset>,
|
||||
pub sections: Vec<Section>,
|
||||
#[serde(default = "default_markdown_sections")]
|
||||
pub markdown_sections: Vec<MarkdownSection>,
|
||||
#[serde(default)]
|
||||
pub image_url: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub oz_updates: Vec<String>,
|
||||
}
|
||||
|
||||
// Default value for when the changelog JSON doesn't have the markdown_sections field
|
||||
fn default_markdown_sections() -> Vec<MarkdownSection> {
|
||||
vec![
|
||||
MarkdownSection {
|
||||
title: "New features".to_string(),
|
||||
markdown: "".to_string(),
|
||||
},
|
||||
MarkdownSection {
|
||||
title: "Improvements".to_string(),
|
||||
markdown: "".to_string(),
|
||||
},
|
||||
MarkdownSection {
|
||||
title: "Coming soon".to_string(),
|
||||
markdown: "".to_string(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Changelog {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
self.sections
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, item| {
|
||||
let _ = write!(output, "{item}\n\n");
|
||||
output
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Section {
|
||||
pub title: String,
|
||||
pub items: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MarkdownSection {
|
||||
pub title: String,
|
||||
pub markdown: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Section {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}:\n{}",
|
||||
self.title,
|
||||
self.items.iter().fold(String::new(), |mut output, item| {
|
||||
let _ = writeln!(output, "- {item}");
|
||||
output
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "channel_versions_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,137 @@
|
||||
//! Functionality relating to overrides of the default per-channel version
|
||||
//! info.
|
||||
//!
|
||||
//! For example, we may want to roll out a hotfix release for Linux, but
|
||||
//! not want macOS and Windows users to need to perform another update.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::VersionInfo;
|
||||
|
||||
/// The set of contextual information that is relevant for applying per-version
|
||||
/// overrides.
|
||||
pub struct Context {
|
||||
pub target_os: Option<TargetOS>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn from_env() -> Self {
|
||||
Context {
|
||||
target_os: TargetOS::current(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "cli", derive(clap::ValueEnum), clap(rename_all = "lower"))]
|
||||
pub enum TargetOS {
|
||||
#[serde(rename = "macos")]
|
||||
MacOS,
|
||||
#[serde(rename = "linux")]
|
||||
Linux,
|
||||
#[serde(rename = "windows")]
|
||||
Windows,
|
||||
#[serde(rename = "web")]
|
||||
Web,
|
||||
|
||||
// Catch-all in case we we can't deserialize an unrecognized enum variant.
|
||||
// We need [value(skip)] here to tell `clap` to ignore this variant.
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "cli", value(skip))]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl TargetOS {
|
||||
/// Returns the current operating system, based on the build-time target_os
|
||||
/// cfg variable, or None if it is not supported.
|
||||
pub fn current() -> Option<Self> {
|
||||
if cfg!(target_family = "wasm") {
|
||||
Some(TargetOS::Web)
|
||||
} else if cfg!(target_os = "macos") {
|
||||
Some(TargetOS::MacOS)
|
||||
} else if cfg!(target_os = "linux") {
|
||||
Some(TargetOS::Linux)
|
||||
} else if cfg!(target_os = "windows") {
|
||||
Some(TargetOS::Windows)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name of the [`TargetOS`], or None if it is unknown.
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let name = match self {
|
||||
TargetOS::MacOS => "MacOS".to_owned(),
|
||||
TargetOS::Linux => "Linux".to_owned(),
|
||||
TargetOS::Windows => "Windows".to_owned(),
|
||||
TargetOS::Web => "Web".to_owned(),
|
||||
_ => return None,
|
||||
};
|
||||
Some(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
enum OverridePredicate {
|
||||
#[serde(rename = "target_os")]
|
||||
TargetOS(TargetOS),
|
||||
}
|
||||
|
||||
impl OverridePredicate {
|
||||
fn matches(&self, context: &Context) -> bool {
|
||||
match self {
|
||||
OverridePredicate::TargetOS(os) => {
|
||||
if let Some(target_os) = &context.target_os {
|
||||
os == target_os
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VersionOverride {
|
||||
/// The predicate which determines whether or not this override should be
|
||||
/// applied.
|
||||
predicate: OverridePredicate,
|
||||
/// The overridden version info.
|
||||
version_info: VersionInfo,
|
||||
}
|
||||
|
||||
impl VersionInfo {
|
||||
/// Returns a copy of this [`VersionInfo`] with the first matching override
|
||||
/// applied, if any match.
|
||||
pub fn with_overrides_applied(&self, overrides: &[VersionOverride], context: &Context) -> Self {
|
||||
let mut new = self.clone();
|
||||
for version_override in overrides {
|
||||
if version_override.predicate.matches(context) {
|
||||
new.apply_override(version_override.version_info.clone());
|
||||
// We only apply the first matching override, and skip the rest.
|
||||
break;
|
||||
}
|
||||
}
|
||||
new
|
||||
}
|
||||
|
||||
fn apply_override(&mut self, other: VersionInfo) {
|
||||
self.version = other.version;
|
||||
if let Some(soft_cutoff) = other.soft_cutoff {
|
||||
self.soft_cutoff = Some(soft_cutoff);
|
||||
}
|
||||
if let Some(update_by) = other.update_by {
|
||||
self.update_by = Some(update_by);
|
||||
}
|
||||
if let Some(last_prominent_update) = other.last_prominent_update {
|
||||
self.last_prominent_update = Some(last_prominent_update);
|
||||
}
|
||||
if let Some(cli_version) = other.cli_version {
|
||||
self.cli_version = Some(cli_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "overrides_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,223 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{ChannelVersion, ChannelVersions};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_only_first_override_is_applied() {
|
||||
#[cfg(target_os = "macos")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::MacOS);
|
||||
#[cfg(target_os = "linux")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::Linux);
|
||||
#[cfg(target_os = "windows")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::Windows);
|
||||
|
||||
let version = ChannelVersion {
|
||||
version_info: VersionInfo {
|
||||
version: "base_version".to_string(),
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: None,
|
||||
},
|
||||
overrides: vec![
|
||||
VersionOverride {
|
||||
predicate: predicate.clone(),
|
||||
version_info: VersionInfo {
|
||||
version: "override_version".to_string(),
|
||||
update_by: Some(DateTime::<Utc>::MIN_UTC.fixed_offset()),
|
||||
soft_cutoff: Some("override_cutoff".to_string()),
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: None,
|
||||
},
|
||||
},
|
||||
VersionOverride {
|
||||
predicate,
|
||||
version_info: VersionInfo {
|
||||
// This should not be applied; as we only apply the first
|
||||
// matching override.
|
||||
version: "second_override_version".to_string(),
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let version_info = version.version_info.clone();
|
||||
let version_info_with_overrides = version.version_info();
|
||||
assert_ne!(version_info.version, version_info_with_overrides.version);
|
||||
assert_eq!(version_info_with_overrides.version, "override_version");
|
||||
assert_ne!(
|
||||
version_info.update_by,
|
||||
version_info_with_overrides.update_by
|
||||
);
|
||||
assert_eq!(
|
||||
version_info_with_overrides.update_by,
|
||||
Some(DateTime::<Utc>::MIN_UTC.fixed_offset())
|
||||
);
|
||||
assert_ne!(
|
||||
version_info.soft_cutoff,
|
||||
version_info_with_overrides.soft_cutoff
|
||||
);
|
||||
assert_eq!(
|
||||
version_info_with_overrides.soft_cutoff,
|
||||
Some("override_cutoff".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_target_is_ignored() {
|
||||
let channel_version_string = r#"{
|
||||
"beta": {
|
||||
"version": "v0.2024.01.30.16.52.beta_00"
|
||||
},
|
||||
"canary": {
|
||||
"version": "v0.2022.09.29.08.08.canary_00"
|
||||
},
|
||||
"dev": {
|
||||
"soft_cutoff": "v0.2023.05.12.08.03.dev_00",
|
||||
"version": "v0.2024.01.30.20.34.dev_00"
|
||||
},
|
||||
"preview": {
|
||||
"version": "v0.2024.01.30.20.34.preview_00"
|
||||
},
|
||||
"stable": {
|
||||
"soft_cutoff": "v0.2023.11.28.08.02.stable_00",
|
||||
"version": "v0.2024.01.16.16.31.stable_01",
|
||||
"overrides": [
|
||||
{
|
||||
"predicate": {
|
||||
"target_os": "gibberish"
|
||||
},
|
||||
"version_info": {
|
||||
"version": "v0.2024.01.30.16.52.stable_00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
// We should still be able to deserialize even if the target OS isn't recognized.
|
||||
let channel_versions: ChannelVersions = serde_json::from_str(channel_version_string)
|
||||
.expect("Should be able to parse channel versions");
|
||||
assert_eq!(
|
||||
channel_versions.stable.version_info().version,
|
||||
"v0.2024.01.16.16.31.stable_01"
|
||||
);
|
||||
|
||||
// The override should have no effect, as the target OS is gibberish.
|
||||
let version_with_overrides = channel_versions
|
||||
.stable
|
||||
.version_info()
|
||||
.with_overrides_applied(&channel_versions.stable.overrides, &Context::from_env());
|
||||
assert_eq!(
|
||||
version_with_overrides.version,
|
||||
"v0.2024.01.16.16.31.stable_01"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_version_override_is_applied() {
|
||||
#[cfg(target_os = "macos")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::MacOS);
|
||||
#[cfg(target_os = "linux")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::Linux);
|
||||
#[cfg(target_os = "windows")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::Windows);
|
||||
|
||||
let version = ChannelVersion {
|
||||
version_info: VersionInfo {
|
||||
version: "base_version".to_string(),
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: Some("base_cli_version".to_string()),
|
||||
},
|
||||
overrides: vec![VersionOverride {
|
||||
predicate,
|
||||
version_info: VersionInfo {
|
||||
version: "override_version".to_string(),
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: Some("override_cli_version".to_string()),
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
let version_info_with_overrides = version.version_info();
|
||||
assert_eq!(
|
||||
version_info_with_overrides.cli_version,
|
||||
Some("override_cli_version".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
version_info_with_overrides.cli_version(),
|
||||
"override_cli_version"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_version_preserved_when_override_omits_it() {
|
||||
#[cfg(target_os = "macos")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::MacOS);
|
||||
#[cfg(target_os = "linux")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::Linux);
|
||||
#[cfg(target_os = "windows")]
|
||||
let predicate = OverridePredicate::TargetOS(TargetOS::Windows);
|
||||
|
||||
let version = ChannelVersion {
|
||||
version_info: VersionInfo {
|
||||
version: "base_version".to_string(),
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: Some("base_cli_version".to_string()),
|
||||
},
|
||||
overrides: vec![VersionOverride {
|
||||
predicate,
|
||||
version_info: VersionInfo {
|
||||
version: "override_version".to_string(),
|
||||
update_by: None,
|
||||
soft_cutoff: None,
|
||||
last_prominent_update: None,
|
||||
is_rollback: None,
|
||||
version_for_new_users: None,
|
||||
cli_version: None,
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
let version_info_with_overrides = version.version_info();
|
||||
// cli_version should be preserved from the base since the override doesn't set it.
|
||||
assert_eq!(
|
||||
version_info_with_overrides.cli_version,
|
||||
Some("base_cli_version".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
version_info_with_overrides.cli_version(),
|
||||
"base_cli_version"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_version_falls_back_to_version() {
|
||||
let info = VersionInfo::new("app_version".to_string());
|
||||
assert_eq!(info.cli_version, None);
|
||||
assert_eq!(info.cli_version(), "app_version");
|
||||
}
|
||||
Reference in New Issue
Block a user