use std::sync::Arc;
use crate::backends::common::{CommonBuilderConfig, CommonConfig};
use crate::errors::*;
use crate::update::{Release, ReleaseSource, ReleaseUpdate, Releases};
#[must_use]
#[derive(Clone, Default)]
pub struct UpdateBuilder {
source: Option<Arc<dyn ReleaseSource>>,
common: CommonBuilderConfig,
}
impl std::fmt::Debug for UpdateBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UpdateBuilder")
.field("source", &self.source.as_ref().map(|_| "<source>"))
.field("common", &self.common)
.finish()
}
}
impl UpdateBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn source(&mut self, source: impl ReleaseSource + 'static) -> &mut Self {
self.source = Some(Arc::new(source));
self
}
impl_common_builder_setters!(no_auth_token);
pub fn build(&self) -> Result<Box<dyn ReleaseUpdate>> {
let source = self
.source
.clone()
.ok_or_else(|| Error::Config("`source` required".to_string()))?;
Ok(Box::new(Update {
source,
common: self.common.build()?,
}))
}
}
#[non_exhaustive]
pub struct Update {
source: Arc<dyn ReleaseSource>,
common: CommonConfig,
}
impl std::fmt::Debug for Update {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Update")
.field("source", &"<source>")
.field("common", &self.common)
.finish()
}
}
impl Update {
pub fn configure() -> UpdateBuilder {
UpdateBuilder::new()
}
}
impl crate::update::sealed::Sealed for Update {}
impl_update_config_accessors!(Update);
impl ReleaseUpdate for Update {
fn get_latest_release(&self) -> Result<Releases> {
let current_version = crate::update::UpdateConfig::current_version(self).to_owned();
let release = self.source.get_latest_release()?;
Ok(Releases::new(vec![release], current_version))
}
fn get_latest_releases(&self) -> Result<Releases> {
let current_version = crate::update::UpdateConfig::current_version(self).to_owned();
let releases = self.source.get_latest_releases(¤t_version)?;
Ok(Releases::new(releases, current_version))
}
fn get_release_version(&self, ver: &str) -> Result<Release> {
self.source.get_release_version(ver)
}
}
#[cfg(feature = "async")]
#[must_use]
pub struct AsyncUpdateBuilder<S: crate::update::AsyncReleaseSource> {
source: Option<Arc<S>>,
common: CommonBuilderConfig,
}
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> Default for AsyncUpdateBuilder<S> {
fn default() -> Self {
Self {
source: None,
common: CommonBuilderConfig::default(),
}
}
}
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> std::fmt::Debug for AsyncUpdateBuilder<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncUpdateBuilder")
.field("source", &self.source.as_ref().map(|_| "<source>"))
.field("common", &self.common)
.finish()
}
}
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> AsyncUpdateBuilder<S> {
pub fn new() -> Self {
Default::default()
}
pub fn source(&mut self, source: S) -> &mut Self {
self.source = Some(Arc::new(source));
self
}
impl_common_builder_setters!(no_auth_token);
pub fn build_async(&self) -> Result<AsyncUpdate<S>> {
let source = self
.source
.clone()
.ok_or_else(|| Error::Config("`source` required".to_string()))?;
Ok(AsyncUpdate {
source,
common: self.common.build()?,
})
}
}
#[cfg(feature = "async")]
#[non_exhaustive]
pub struct AsyncUpdate<S: crate::update::AsyncReleaseSource> {
source: Arc<S>,
common: CommonConfig,
}
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> std::fmt::Debug for AsyncUpdate<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncUpdate")
.field("source", &"<source>")
.field("common", &self.common)
.finish()
}
}
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> AsyncUpdate<S> {
pub fn configure() -> AsyncUpdateBuilder<S> {
AsyncUpdateBuilder::new()
}
impl_async_update_methods!();
}
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> crate::update::sealed::Sealed for AsyncUpdate<S> {}
#[cfg(feature = "async")]
impl_update_config_accessors!(AsyncUpdate<S>, where (S: crate::update::AsyncReleaseSource));
#[cfg(feature = "async")]
impl<S: crate::update::AsyncReleaseSource> crate::update::AsyncFetch for AsyncUpdate<S> {
async fn get_latest_release_async(&self) -> Result<Releases> {
let current_version = crate::update::UpdateConfig::current_version(self).to_owned();
let release = self.source.get_latest_release().await?;
Ok(Releases::new(vec![release], current_version))
}
async fn get_latest_releases_async(&self) -> Result<Releases> {
let current_version = crate::update::UpdateConfig::current_version(self).to_owned();
let releases = self.source.get_latest_releases(¤t_version).await?;
Ok(Releases::new(releases, current_version))
}
async fn get_release_version_async(&self, ver: &str) -> Result<Release> {
self.source.get_release_version(ver).await
}
}
#[cfg(feature = "async")]
pub struct Blocking<S> {
source: S,
}
#[cfg(feature = "async")]
impl<S> Blocking<S> {
pub fn new(source: S) -> Self {
Self { source }
}
pub fn into_inner(self) -> S {
self.source
}
pub fn as_inner(&self) -> &S {
&self.source
}
}
#[cfg(feature = "async")]
impl<S: ReleaseSource + Clone + 'static> crate::update::AsyncReleaseSource for Blocking<S> {
async fn get_latest_release(&self) -> Result<Release> {
let s = self.source.clone();
tokio::task::spawn_blocking(move || s.get_latest_release())
.await
.map_err(|e| Error::Update(format!("blocking task failed: {e}")))?
}
async fn get_latest_releases(&self, current_version: &str) -> Result<Vec<Release>> {
let s = self.source.clone();
let current_version = current_version.to_owned();
tokio::task::spawn_blocking(move || s.get_latest_releases(¤t_version))
.await
.map_err(|e| Error::Update(format!("blocking task failed: {e}")))?
}
async fn get_release_version(&self, ver: &str) -> Result<Release> {
let s = self.source.clone();
let ver = ver.to_owned();
tokio::task::spawn_blocking(move || s.get_release_version(&ver))
.await
.map_err(|e| Error::Update(format!("blocking task failed: {e}")))?
}
}
#[cfg(test)]
mod tests {
use super::Update;
use crate::update::{Release, ReleaseAsset, ReleaseSource, ReleaseUpdate};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
struct FakeSource {
latest_calls: Arc<AtomicUsize>,
}
impl ReleaseSource for FakeSource {
fn get_latest_release(&self) -> crate::errors::Result<Release> {
self.latest_calls.fetch_add(1, Ordering::SeqCst);
Release::builder()
.version("2.0.0")
.asset(ReleaseAsset::new(
"app-x86_64-unknown-linux-gnu.tar.gz",
"https://example/app-2.0.0.tar.gz",
))
.build()
}
fn get_latest_releases(&self, _current: &str) -> crate::errors::Result<Vec<Release>> {
Ok(vec![self.get_latest_release()?])
}
fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
Release::builder().version(ver).build()
}
}
fn configured(calls: Arc<AtomicUsize>) -> Box<dyn ReleaseUpdate> {
Update::configure()
.source(FakeSource {
latest_calls: calls,
})
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.build()
.unwrap()
}
#[test]
fn build_requires_a_source() {
let res = Update::configure()
.bin_name("app")
.current_version("1.0.0")
.build();
assert!(res.is_err(), "build must fail without a source");
}
#[test]
fn build_is_repeatable() {
let mut builder = Update::configure();
builder
.source(FakeSource {
latest_calls: Arc::new(AtomicUsize::new(0)),
})
.bin_name("app")
.current_version("1.0.0");
builder.build().expect("first build");
builder.build().expect("second build");
}
#[test]
fn fetches_delegate_to_the_source() {
let calls = Arc::new(AtomicUsize::new(0));
let upd = configured(calls.clone());
let latest = upd.get_latest_release().unwrap();
let rel = latest.latest().expect("one-element Releases");
assert_eq!(rel.version, "2.0.0");
assert_eq!(rel.assets.len(), 1);
assert_eq!(
latest.all().len(),
1,
"get_latest_release yields one element"
);
let rels = upd.get_latest_releases().unwrap();
assert_eq!(rels.all().len(), 1);
let tagged = upd.get_release_version("1.5.0").unwrap();
assert_eq!(tagged.version, "1.5.0");
assert_eq!(calls.load(Ordering::SeqCst), 2);
}
#[test]
fn shared_accessors_are_wired() {
let upd = configured(Arc::new(AtomicUsize::new(0)));
assert_eq!(upd.target(), "x86_64-unknown-linux-gnu");
assert_eq!(upd.bin_name(), "app");
assert_eq!(upd.current_version(), "1.0.0");
assert_eq!(upd.auth_token(), None);
}
#[test]
fn is_update_available_true_when_latest_is_newer() {
let upd = configured(Arc::new(AtomicUsize::new(0)));
assert!(
upd.get_latest_releases()
.unwrap()
.is_update_available()
.unwrap(),
"2.0.0 > 1.0.0 => update available"
);
}
#[test]
fn is_update_available_false_when_latest_not_newer() {
let upd = Update::configure()
.source(FakeSource {
latest_calls: Arc::new(AtomicUsize::new(0)),
})
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("2.0.0")
.build()
.unwrap();
assert!(
!upd.get_latest_releases()
.unwrap()
.is_update_available()
.unwrap(),
"latest (2.0.0) is not newer than current (2.0.0) => no update"
);
}
#[test]
fn get_latest_release_carries_current_version_for_the_precheck() {
let upd = configured(Arc::new(AtomicUsize::new(0)));
let releases = upd.get_latest_release().unwrap();
assert_eq!(releases.all().len(), 1);
assert!(
releases.is_update_available().unwrap(),
"2.0.0 > 1.0.0 via the one-element Releases pre-check"
);
}
#[test]
fn selects_asset_from_a_source_release() {
let upd = configured(Arc::new(AtomicUsize::new(0)));
let releases = upd.get_latest_release().unwrap();
let rel = releases.latest().expect("one-element Releases");
let asset = rel
.asset_for("x86_64-unknown-linux-gnu", None)
.expect("asset matches the target");
assert_eq!(asset.download_url, "https://example/app-2.0.0.tar.gz");
}
struct SyncOrchestratedSource;
fn versioned_release_sync(v: &str) -> Release {
Release::builder()
.version(v)
.asset(ReleaseAsset::new(
format!("app-{}.bin", v),
format!("https://example/app-{}.bin", v),
))
.build()
.unwrap()
}
impl ReleaseSource for SyncOrchestratedSource {
fn get_latest_release(&self) -> crate::errors::Result<Release> {
self.get_release_version("9.9.9")
}
fn get_latest_releases(&self, _current: &str) -> crate::errors::Result<Vec<Release>> {
Ok(vec![
versioned_release_sync("1.0.0"),
versioned_release_sync("2.0.0"),
versioned_release_sync("0.9.0"),
versioned_release_sync("1.4.0"),
versioned_release_sync("1.2.0"),
])
}
fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
Ok(versioned_release_sync(ver))
}
}
#[test]
fn update_extended_resolves_explicit_tag_then_selects_that_release() {
let seen: Arc<std::sync::Mutex<Vec<String>>> = Arc::new(std::sync::Mutex::new(vec![]));
let seen_cb = seen.clone();
let upd = Update::configure()
.source(SyncOrchestratedSource)
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.release_tag("7.7.7")
.no_confirm(true)
.show_output(false)
.asset_matcher(move |assets| {
let mut s = seen_cb.lock().unwrap();
for a in assets {
s.push(a.name.clone());
}
None
})
.build()
.unwrap();
let err = upd
.update_extended()
.expect_err("matcher returning None must fail the update");
assert!(
matches!(err, crate::errors::Error::Release(_)),
"explicit-tag path still runs asset selection, got {:?}",
err
);
assert_eq!(
*seen.lock().unwrap(),
vec!["app-7.7.7.bin".to_string()],
"the explicit-tag path must resolve and select the tagged release (7.7.7)"
);
}
#[test]
fn update_extended_selects_newest_compatible_then_fails_at_missing_asset() {
let seen: Arc<std::sync::Mutex<Vec<String>>> = Arc::new(std::sync::Mutex::new(vec![]));
let seen_cb = seen.clone();
let upd = Update::configure()
.source(SyncOrchestratedSource)
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.no_confirm(true)
.show_output(false)
.asset_matcher(move |assets| {
let mut s = seen_cb.lock().unwrap();
for a in assets {
s.push(a.name.clone());
}
None
})
.build()
.unwrap();
let err = upd
.update_extended()
.expect_err("matcher returning None must fail the update");
assert!(
matches!(err, crate::errors::Error::Release(_)),
"no asset selected -> Error::Release, got {:?}",
err
);
assert_eq!(
*seen.lock().unwrap(),
vec!["app-1.4.0.bin".to_string()],
"the sync orchestrator must select the newest compatible release (1.4.0)"
);
}
#[cfg(feature = "async")]
mod async_tests {
use super::super::{AsyncUpdate, Blocking};
use crate::update::{AsyncFetch, AsyncReleaseSource, Release, ReleaseAsset, ReleaseSource};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
struct NativeAsyncSource {
latest_calls: Arc<AtomicUsize>,
releases_calls: Arc<AtomicUsize>,
version_calls: Arc<AtomicUsize>,
}
impl AsyncReleaseSource for NativeAsyncSource {
async fn get_latest_release(&self) -> crate::errors::Result<Release> {
tokio::task::yield_now().await;
self.latest_calls.fetch_add(1, Ordering::SeqCst);
Release::builder()
.version("2.0.0")
.asset(ReleaseAsset::new(
"app-x86_64-unknown-linux-gnu.tar.gz",
"https://example/app-2.0.0.tar.gz",
))
.build()
}
async fn get_latest_releases(
&self,
_current: &str,
) -> crate::errors::Result<Vec<Release>> {
tokio::task::yield_now().await;
self.releases_calls.fetch_add(1, Ordering::SeqCst);
Ok(vec![Release::builder().version("2.0.0").build()?])
}
async fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
tokio::task::yield_now().await;
self.version_calls.fetch_add(1, Ordering::SeqCst);
Release::builder().version(ver).build()
}
}
#[tokio::test]
async fn build_async_requires_a_source() {
let res = AsyncUpdate::<NativeAsyncSource>::configure()
.bin_name("app")
.current_version("1.0.0")
.build_async();
assert!(res.is_err(), "build_async must fail without a source");
}
#[tokio::test]
async fn build_async_is_repeatable() {
let mut builder = AsyncUpdate::configure();
builder
.source(NativeAsyncSource {
latest_calls: Arc::new(AtomicUsize::new(0)),
releases_calls: Arc::new(AtomicUsize::new(0)),
version_calls: Arc::new(AtomicUsize::new(0)),
})
.bin_name("app")
.current_version("1.0.0");
builder.build_async().expect("first build_async");
builder.build_async().expect("second build_async");
}
#[tokio::test]
async fn async_fetches_delegate_to_the_native_source() {
let latest = Arc::new(AtomicUsize::new(0));
let releases = Arc::new(AtomicUsize::new(0));
let version = Arc::new(AtomicUsize::new(0));
let upd = AsyncUpdate::configure()
.source(NativeAsyncSource {
latest_calls: latest.clone(),
releases_calls: releases.clone(),
version_calls: version.clone(),
})
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.build_async()
.unwrap();
let latest_releases = upd.get_latest_release_async().await.unwrap();
let rel = latest_releases.latest().expect("one-element Releases");
assert_eq!(rel.version, "2.0.0");
assert_eq!(rel.assets.len(), 1);
let rels = AsyncFetch::get_latest_releases_async(&upd).await.unwrap();
assert_eq!(rels.all().len(), 1);
let tagged = AsyncFetch::get_release_version_async(&upd, "1.5.0")
.await
.unwrap();
assert_eq!(tagged.version, "1.5.0");
assert_eq!(latest.load(Ordering::SeqCst), 1);
assert_eq!(releases.load(Ordering::SeqCst), 1);
assert_eq!(version.load(Ordering::SeqCst), 1);
}
#[derive(Clone)]
struct SyncSource {
calls: Arc<AtomicUsize>,
}
impl ReleaseSource for SyncSource {
fn get_latest_release(&self) -> crate::errors::Result<Release> {
self.calls.fetch_add(1, Ordering::SeqCst);
Release::builder().version("3.0.0").build()
}
fn get_latest_releases(&self, _current: &str) -> crate::errors::Result<Vec<Release>> {
self.calls.fetch_add(1, Ordering::SeqCst);
Ok(vec![Release::builder().version("3.0.0").build()?])
}
fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
self.calls.fetch_add(1, Ordering::SeqCst);
Release::builder().version(ver).build()
}
}
#[tokio::test]
async fn blocking_adapter_drives_async_update_from_a_sync_source() {
let calls = Arc::new(AtomicUsize::new(0));
let upd = AsyncUpdate::configure()
.source(Blocking::new(SyncSource {
calls: calls.clone(),
}))
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.build_async()
.unwrap();
assert_eq!(
upd.get_latest_release_async()
.await
.unwrap()
.latest()
.unwrap()
.version,
"3.0.0"
);
assert_eq!(
AsyncFetch::get_latest_releases_async(&upd)
.await
.unwrap()
.all()
.len(),
1
);
assert_eq!(
AsyncFetch::get_release_version_async(&upd, "9.9.9")
.await
.unwrap()
.version,
"9.9.9"
);
assert_eq!(
calls.load(Ordering::SeqCst),
3,
"each async fetch must delegate to the sync source exactly once"
);
}
struct OrchestratedSource;
fn versioned_release(v: &str) -> Release {
Release::builder()
.version(v)
.asset(ReleaseAsset::new(
format!("app-{}.bin", v),
format!("https://example/app-{}.bin", v),
))
.build()
.unwrap()
}
impl AsyncReleaseSource for OrchestratedSource {
async fn get_latest_release(&self) -> crate::errors::Result<Release> {
self.get_release_version("9.9.9").await
}
async fn get_latest_releases(
&self,
_current: &str,
) -> crate::errors::Result<Vec<Release>> {
tokio::task::yield_now().await;
Ok(vec![
versioned_release("1.0.0"),
versioned_release("2.0.0"),
versioned_release("0.9.0"),
versioned_release("1.4.0"),
versioned_release("1.2.0"),
])
}
async fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
tokio::task::yield_now().await;
Ok(versioned_release(ver))
}
}
#[tokio::test]
async fn update_extended_async_reports_up_to_date_through_orchestrator() {
struct OldOnly;
impl AsyncReleaseSource for OldOnly {
async fn get_latest_release(&self) -> crate::errors::Result<Release> {
Release::builder().version("1.0.0").build()
}
async fn get_latest_releases(
&self,
_current: &str,
) -> crate::errors::Result<Vec<Release>> {
tokio::task::yield_now().await;
Ok(vec![
Release::builder().version("1.0.0").build()?,
Release::builder().version("0.9.0").build()?,
])
}
async fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
Release::builder().version(ver).build()
}
}
let upd = AsyncUpdate::configure()
.source(OldOnly)
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.no_confirm(true)
.show_output(false)
.build_async()
.unwrap();
let status = upd.update_extended_async().await.unwrap();
assert!(
status.is_up_to_date(),
"only current/older releases -> up-to-date through the async orchestrator"
);
}
#[tokio::test]
async fn update_extended_async_selects_newest_compatible_then_fails_at_missing_asset() {
let seen: Arc<std::sync::Mutex<Vec<String>>> = Arc::new(std::sync::Mutex::new(vec![]));
let seen_cb = seen.clone();
let upd = AsyncUpdate::configure()
.source(OrchestratedSource)
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.no_confirm(true)
.show_output(false)
.asset_matcher(move |assets| {
let mut s = seen_cb.lock().unwrap();
for a in assets {
s.push(a.name.clone());
}
None
})
.build_async()
.unwrap();
let err = upd
.update_extended_async()
.await
.expect_err("matcher returning None must fail the update");
assert!(
matches!(err, crate::errors::Error::Release(_)),
"no asset selected -> Error::Release, got {:?}",
err
);
assert_eq!(
*seen.lock().unwrap(),
vec!["app-1.4.0.bin".to_string()],
"the async orchestrator must select the newest compatible release (1.4.0)"
);
}
#[tokio::test]
async fn update_extended_async_resolves_explicit_tag_then_selects_that_release() {
let seen: Arc<std::sync::Mutex<Vec<String>>> = Arc::new(std::sync::Mutex::new(vec![]));
let seen_cb = seen.clone();
let mut builder = AsyncUpdate::configure();
builder
.source(OrchestratedSource)
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("1.0.0")
.release_tag("7.7.7")
.no_confirm(true)
.show_output(false)
.asset_matcher(move |assets| {
let mut s = seen_cb.lock().unwrap();
for a in assets {
s.push(a.name.clone());
}
None
});
let upd = builder.build_async().unwrap();
let err = upd
.update_extended_async()
.await
.expect_err("matcher returning None must fail the update");
assert!(
matches!(err, crate::errors::Error::Release(_)),
"explicit-tag path still runs asset selection, got {:?}",
err
);
assert_eq!(
*seen.lock().unwrap(),
vec!["app-7.7.7.bin".to_string()],
"the explicit-tag path must resolve and select the tagged release (7.7.7)"
);
}
#[tokio::test]
async fn async_update_works_with_a_non_clone_source() {
struct NonClone {
_lock: std::sync::Mutex<()>,
}
impl AsyncReleaseSource for NonClone {
async fn get_latest_release(&self) -> crate::errors::Result<Release> {
Release::builder().version("1.0.0").build()
}
async fn get_latest_releases(
&self,
_current: &str,
) -> crate::errors::Result<Vec<Release>> {
Ok(vec![Release::builder().version("1.0.0").build()?])
}
async fn get_release_version(&self, ver: &str) -> crate::errors::Result<Release> {
Release::builder().version(ver).build()
}
}
fn assert_not_requiring_clone<S: AsyncReleaseSource>(_: &AsyncUpdate<S>) {}
let upd = AsyncUpdate::configure()
.source(NonClone {
_lock: std::sync::Mutex::new(()),
})
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version("2.0.0")
.no_confirm(true)
.show_output(false)
.build_async()
.unwrap();
assert_not_requiring_clone(&upd);
let status = upd.update_extended_async().await.unwrap();
assert!(status.is_up_to_date());
}
#[tokio::test]
async fn blocking_adapter_propagates_sync_error() {
#[derive(Clone)]
struct FailingSource;
impl ReleaseSource for FailingSource {
fn get_latest_release(&self) -> crate::errors::Result<Release> {
Err(crate::errors::Error::Release("boom".into()))
}
fn get_latest_releases(
&self,
_current: &str,
) -> crate::errors::Result<Vec<Release>> {
Err(crate::errors::Error::HttpStatus {
status: 503,
url: "u".into(),
})
}
fn get_release_version(&self, _ver: &str) -> crate::errors::Result<Release> {
Err(crate::errors::Error::Config("cfg".into()))
}
}
let blk = Blocking::new(FailingSource);
assert!(matches!(
AsyncReleaseSource::get_latest_release(&blk).await,
Err(crate::errors::Error::Release(_))
));
assert!(matches!(
AsyncReleaseSource::get_latest_releases(&blk, "1.0.0").await,
Err(crate::errors::Error::HttpStatus { .. })
));
assert!(matches!(
AsyncReleaseSource::get_release_version(&blk, "1.0.0").await,
Err(crate::errors::Error::Config(_))
));
}
#[test]
fn blocking_new_as_inner_and_into_inner_round_trip() {
#[derive(Clone, PartialEq, Debug)]
struct Marker(u32);
impl ReleaseSource for Marker {
fn get_latest_release(&self) -> crate::errors::Result<Release> {
Release::builder().version("1.0.0").build()
}
fn get_latest_releases(&self, _: &str) -> crate::errors::Result<Vec<Release>> {
Ok(vec![])
}
fn get_release_version(&self, v: &str) -> crate::errors::Result<Release> {
Release::builder().version(v).build()
}
}
let blk = Blocking::new(Marker(7));
assert_eq!(blk.as_inner(), &Marker(7), "as_inner borrows the source");
assert_eq!(blk.into_inner(), Marker(7), "into_inner returns the source");
}
fn assert_fetch_future_is_send<S: AsyncReleaseSource>(s: &S) {
fn is_send<T: Send>(_: &T) {}
is_send(&s.get_latest_release());
}
#[tokio::test]
async fn async_release_source_future_is_send() {
let src = NativeAsyncSource {
latest_calls: Arc::new(AtomicUsize::new(0)),
releases_calls: Arc::new(AtomicUsize::new(0)),
version_calls: Arc::new(AtomicUsize::new(0)),
};
assert_fetch_future_is_send(&src);
assert_eq!(src.get_latest_release().await.unwrap().version, "2.0.0");
}
#[tokio::test]
async fn is_update_available_async_true_then_false() {
let mk = |cur: &str| {
AsyncUpdate::configure()
.source(NativeAsyncSource {
latest_calls: Arc::new(AtomicUsize::new(0)),
releases_calls: Arc::new(AtomicUsize::new(0)),
version_calls: Arc::new(AtomicUsize::new(0)),
})
.bin_name("app")
.target("x86_64-unknown-linux-gnu")
.current_version(cur)
.build_async()
.unwrap()
};
assert!(
mk("1.0.0")
.get_latest_releases_async()
.await
.unwrap()
.is_update_available()
.unwrap(),
"2.0.0 > 1.0.0 => update available"
);
assert!(
!mk("2.0.0")
.get_latest_releases_async()
.await
.unwrap()
.is_update_available()
.unwrap(),
"2.0.0 not newer than 2.0.0 => no update"
);
}
}
}