| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::config::Config; |
| use crate::update_manager::UpdateManagerControlHandle; |
| use crate::update_monitor::StateNotifier; |
| use fidl_fuchsia_update::CheckNotStartedReason; |
| use fidl_fuchsia_update_ext::{CheckOptions, Initiator}; |
| use fuchsia_async as fasync; |
| use fuchsia_syslog::fx_log_info; |
| use futures::prelude::*; |
| |
| pub fn run_periodic_update_check<N>( |
| mut manager: UpdateManagerControlHandle<N>, |
| config: &Config, |
| ) -> impl Future<Output = ()> |
| where |
| N: StateNotifier, |
| { |
| let timer = config.poll_frequency().map(|duration| fasync::Interval::new(duration.into())); |
| |
| async move { |
| let mut timer = match timer { |
| Some(timer) => timer, |
| None => return, |
| }; |
| |
| while let Some(()) = timer.next().await { |
| let options = CheckOptions::builder().initiator(Initiator::Service).build(); |
| match manager.try_start_update(options, None).await { |
| Ok(()) => {} |
| Err(CheckNotStartedReason::Throttled) => { |
| fx_log_info!("Service initiated update check throttled"); |
| } |
| Err(CheckNotStartedReason::AlreadyInProgress) => { |
| fx_log_info!("Update in progress, automatic update check skipped"); |
| } |
| Err(CheckNotStartedReason::Internal) => { |
| fx_log_info!("Internal error, will try again later"); |
| } |
| Err(CheckNotStartedReason::InvalidOptions) => { |
| fx_log_info!("Invalid options, update check skipped"); |
| } |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::config::ConfigBuilder; |
| use crate::update_manager::{ |
| tests::{ |
| FakeCommitQuerier, FakeCurrentChannelUpdater, FakeTargetChannelUpdater, |
| FakeUpdateChecker, FakeUpdateManagerControlHandle, StateChangeCollector, |
| UnreachableUpdateApplier, |
| }, |
| UpdateManager, UpdateManagerRequest, |
| }; |
| use fidl_fuchsia_update_ext::{CheckOptions, State}; |
| use fuchsia_async::DurationExt; |
| use fuchsia_zircon::DurationNum; |
| use futures::task::Poll; |
| use matches::assert_matches; |
| use std::sync::Arc; |
| |
| #[test] |
| fn test_disabled_periodic_update_check() { |
| let mut executor = fasync::Executor::new_with_fake_time().unwrap(); |
| |
| let (manager, mut requests) = FakeUpdateManagerControlHandle::<StateChangeCollector>::new(); |
| |
| let mut cron = run_periodic_update_check(manager, &Config::default()).boxed(); |
| |
| assert_eq!(Poll::Ready(()), executor.run_until_stalled(&mut cron)); |
| assert_matches!(requests.next(), None); |
| assert_eq!(None, executor.wake_next_timer()); |
| } |
| |
| #[test] |
| fn test_periodic_update_check() { |
| let mut executor = fasync::Executor::new_with_fake_time().unwrap(); |
| |
| let (manager, mut requests) = FakeUpdateManagerControlHandle::<StateChangeCollector>::new(); |
| |
| let period = 10.minutes(); |
| let config = ConfigBuilder::new().poll_frequency(period).build(); |
| let mut cron = run_periodic_update_check(manager, &config).boxed(); |
| |
| // Let the cron task set up the timer, but nothing interesting happens until time advances. |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| assert_matches!(requests.next(), None); |
| |
| // Not time yet. |
| executor.set_fake_time(8.minutes().after_now()); |
| assert!(!executor.wake_expired_timers()); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| assert_matches!(requests.next(), None); |
| |
| // Verify the timer performs the correct operations after elapsing. |
| executor.set_fake_time(2.minutes().after_now()); |
| assert!(executor.wake_expired_timers()); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| assert_matches!( |
| requests.next(), |
| Some(UpdateManagerRequest::TryStartUpdate { |
| options: CheckOptions { |
| initiator: Initiator::Service, |
| allow_attaching_to_existing_update_check: false, |
| }, |
| callback: None, |
| responder: _, |
| }) |
| ); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| assert_matches!(requests.next(), None); |
| |
| // Verify the timer fires more than once. |
| executor.set_fake_time(period.after_now()); |
| assert!(executor.wake_expired_timers()); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| assert_matches!( |
| requests.next(), |
| Some(UpdateManagerRequest::TryStartUpdate { |
| options: CheckOptions { |
| initiator: Initiator::Service, |
| allow_attaching_to_existing_update_check: false, |
| }, |
| callback: None, |
| responder: _, |
| }) |
| ); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| assert_matches!(requests.next(), None); |
| } |
| |
| #[test] |
| fn test_simultaneous_user_update_check_and_periodic_update_check() { |
| let mut executor = fasync::Executor::new_with_fake_time().unwrap(); |
| |
| let checker = FakeUpdateChecker::new_up_to_date(); |
| let update_blocked = checker.block().unwrap(); |
| let callback = StateChangeCollector::new(); |
| let mut fut = |
| UpdateManager::<_, _, _, _, StateChangeCollector, _>::from_checker_and_applier( |
| Arc::new(FakeTargetChannelUpdater::new()), |
| Arc::new(FakeCurrentChannelUpdater::new()), |
| checker.clone(), |
| UnreachableUpdateApplier, |
| FakeCommitQuerier::new(), |
| ) |
| .boxed(); |
| let mut manager = match executor.run_until_stalled(&mut fut) { |
| Poll::Ready(manager) => manager, |
| Poll::Pending => panic!("manager not ready"), |
| } |
| .spawn(); |
| |
| let period = 24.hours(); |
| let config = ConfigBuilder::new().poll_frequency(period).build(); |
| let mut cron = run_periodic_update_check(manager.clone(), &config).boxed(); |
| |
| // Let the cron task set up the timer, but nothing interesting happens until time advances. |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| |
| // User wins, and only 1 update check happens. |
| let options = CheckOptions::builder().initiator(Initiator::User).build(); |
| let mut fut = manager.try_start_update(options, Some(callback.clone())).boxed(); |
| assert_eq!(executor.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| // The automatic update is skipped because an update is already in progress. |
| executor.set_fake_time(period.after_now()); |
| assert!(executor.wake_expired_timers()); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| // Let the user-initiated update check complete. The update does not run. |
| std::mem::drop(update_blocked); |
| assert_eq!(Poll::Pending, executor.run_until_stalled(&mut cron)); |
| |
| assert_eq!(checker.call_count(), 1); |
| assert_eq!( |
| callback.take_states(), |
| vec![State::CheckingForUpdates, State::NoUpdateAvailable] |
| ); |
| } |
| } |