blob: f1e58f7cc09c46f29b4b2dd4583f58d58941060f [file] [log] [blame]
// 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 {
anyhow::anyhow,
event_queue::{ControlHandle, EventQueue, Notify},
fidl_fuchsia_update_ext::State,
fuchsia_inspect_contrib::inspectable::InspectableDebugString,
fuchsia_syslog::{fx_log_err, fx_log_warn},
futures::prelude::*,
};
pub trait StateNotifier: Notify<State> + Send + Sync + 'static {}
impl<T> StateNotifier for T where T: Notify<State> + Send + Sync + 'static {}
#[derive(Debug)]
pub struct UpdateMonitor<N>
where
N: StateNotifier,
{
temporary_queue: ControlHandle<N, State>,
update_state: InspectableDebugString<Option<State>>,
version_available: InspectableDebugString<Option<String>>,
inspect_node: fuchsia_inspect::Node,
}
impl<N> UpdateMonitor<N>
where
N: StateNotifier,
{
pub fn from_inspect_node(node: fuchsia_inspect::Node) -> (impl Future<Output = ()>, Self) {
let (temporary_fut, temporary_queue) = EventQueue::new();
(
temporary_fut,
UpdateMonitor {
temporary_queue,
update_state: InspectableDebugString::new(None, &node, "update-state"),
version_available: InspectableDebugString::new(None, &node, "version-available"),
inspect_node: node,
},
)
}
#[cfg(test)]
pub fn new() -> (impl Future<Output = ()>, Self) {
Self::from_inspect_node(
fuchsia_inspect::Inspector::new().root().create_child("test-update-monitor-root-node"),
)
}
pub async fn add_temporary_callback(&mut self, callback: N) {
if let Err(e) = self.temporary_queue.add_client(callback).await {
fx_log_err!("error adding client to temporary queue: {:#}", anyhow!(e))
}
}
pub async fn advance_update_state(&mut self, next_update_state: Option<State>) {
*self.update_state.get_mut() = next_update_state;
if *self.update_state == None {
*self.version_available.get_mut() = None;
}
self.send_on_state().await;
if *self.update_state == None {
if let Err(e) = self.temporary_queue.clear().await {
fx_log_warn!("error clearing clients of temporary queue: {:#}", anyhow!(e))
}
}
}
pub fn set_version_available(&mut self, version_available: String) {
*self.version_available.get_mut() = Some(version_available);
}
#[cfg(test)]
pub fn get_version_available(&self) -> Option<String> {
self.version_available.clone()
}
pub fn update_state(&self) -> Option<State> {
self.update_state.clone()
}
pub async fn send_on_state(&mut self) {
if let Some(state) = self.update_state() {
if let Err(e) = self.temporary_queue.queue_event(state).await {
fx_log_warn!("error sending state to temporary queue: {:#}", anyhow!(e))
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use event_queue::{ClosedClient, Notify};
use fidl_fuchsia_update_ext::{
random_installation_deferred_data, random_installation_error_data, random_installing_data,
random_version_available,
};
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use futures::future::BoxFuture;
use parking_lot::Mutex;
use proptest::prelude::*;
use std::sync::Arc;
const VERSION_AVAILABLE: &str = "fake-version-available";
#[derive(Clone, Debug)]
struct FakeStateNotifier {
states: Arc<Mutex<Vec<State>>>,
}
impl FakeStateNotifier {
fn new() -> Self {
Self { states: Arc::new(Mutex::new(vec![])) }
}
}
impl Notify<State> for FakeStateNotifier {
fn notify(&self, state: State) -> BoxFuture<'static, Result<(), ClosedClient>> {
self.states.lock().push(state);
future::ready(Ok(())).boxed()
}
}
prop_compose! {
fn random_update_state()
(
installation_deferred_data in random_installation_deferred_data(),
installing_data in random_installing_data(),
installation_error_data in random_installation_error_data()
)
(
state in prop_oneof![
Just(Some(State::CheckingForUpdates)),
Just(Some(State::ErrorCheckingForUpdate)),
Just(Some(State::NoUpdateAvailable)),
Just(Some(State::InstallationDeferredByPolicy(installation_deferred_data))),
Just(Some(State::InstallingUpdate(installing_data.clone()))),
Just(Some(State::WaitingForReboot(installing_data))),
Just(Some(State::InstallationError(installation_error_data))),
Just(None),
]) -> Option<State>
{
state
}
}
async fn random_update_monitor(
update_state: Option<State>,
version_available: Option<String>,
) -> UpdateMonitor<FakeStateNotifier> {
let (fut, mut mms) = UpdateMonitor::<FakeStateNotifier>::new();
fasync::spawn(fut);
version_available.map(|s| mms.set_version_available(s));
mms.advance_update_state(update_state).await;
mms
}
async fn wait_for_states(callback: &FakeStateNotifier, len: usize) {
while callback.states.lock().len() != len {
fasync::Timer::new(fasync::Time::after(zx::Duration::from_millis(10))).await;
}
}
proptest! {
#[test]
fn test_adding_callback_sends_current_state(
update_state in random_update_state(),
version_available in random_version_available(),
) {
fasync::Executor::new().unwrap().run_singlethreaded(async {
let mut update_monitor = random_update_monitor(update_state.clone(), version_available).await;
let expected_states: Vec<_> = update_state.into_iter().collect();
let temporary_callback = FakeStateNotifier::new();
update_monitor.add_temporary_callback(temporary_callback.clone()).await;
wait_for_states(&temporary_callback, expected_states.len()).await;
prop_assert_eq!(
&temporary_callback.states.lock().clone(),
&expected_states
);
Ok(())
}).unwrap();
}
#[test]
fn test_advance_update_state_calls_callbacks(
initial_state in random_update_state(),
version_available in random_version_available(),
next_state in random_update_state(),
) {
fasync::Executor::new().unwrap().run_singlethreaded(async {
let mut update_monitor = random_update_monitor(initial_state.clone(), version_available).await;
let temporary_callback = FakeStateNotifier::new();
let expected_states: Vec<_> = vec![initial_state, next_state.clone()].into_iter().filter_map(|o|o).collect();
update_monitor.add_temporary_callback(temporary_callback.clone()).await;
update_monitor.advance_update_state(next_state).await;
wait_for_states(&temporary_callback, expected_states.len()).await;
prop_assert_eq!(
&temporary_callback.states.lock().clone(),
&expected_states
);
Ok(())
}).unwrap();
}
#[test]
fn test_advance_updater_state_to_none_clears_version_available(
update_state in random_update_state(),
version_available in random_version_available(),
) {
fasync::Executor::new().unwrap().run_singlethreaded(async {
let mut update_monitor = random_update_monitor(update_state, version_available).await;
update_monitor.set_version_available(VERSION_AVAILABLE.to_string());
update_monitor.advance_update_state(None).await;
prop_assert_eq!(
update_monitor.get_version_available(),
None
);
Ok(())
}).unwrap();
}
#[test]
fn test_advance_update_state_to_idle_clears_temporary_callbacks(
update_state in random_update_state(),
version_available in random_version_available(),
) {
fasync::Executor::new().unwrap().run_singlethreaded(async {
let mut update_monitor = random_update_monitor(update_state, version_available).await;
let temporary_callback = FakeStateNotifier::new();
update_monitor.add_temporary_callback(temporary_callback.clone()).await;
update_monitor.advance_update_state(None).await;
temporary_callback.states.lock().clear();
update_monitor.advance_update_state(Some(State::CheckingForUpdates)).await;
prop_assert_eq!(temporary_callback.states.lock().clone(), vec![]);
Ok(())
}).unwrap();
}
}
}
#[cfg(test)]
mod test_inspect {
use super::*;
use event_queue::{ClosedClient, Notify};
use fuchsia_async as fasync;
use fuchsia_inspect::assert_inspect_tree;
use futures::future::BoxFuture;
#[derive(Clone, Debug)]
struct FakeStateNotifier;
impl Notify<State> for FakeStateNotifier {
fn notify(&self, _state: State) -> BoxFuture<'static, Result<(), ClosedClient>> {
future::ready(Ok(())).boxed()
}
}
#[test]
fn test_inspect_initial_state() {
let inspector = fuchsia_inspect::Inspector::new();
let _update_monitor = UpdateMonitor::<FakeStateNotifier>::from_inspect_node(
inspector.root().create_child("update-monitor"),
);
assert_inspect_tree!(
inspector,
root: {
"update-monitor": {
"update-state": "None",
"version-available": "None",
}
}
);
}
#[fasync::run_singlethreaded(test)]
async fn test_inspect_update_state() {
let inspector = fuchsia_inspect::Inspector::new();
let (fut, mut update_monitor) = UpdateMonitor::<FakeStateNotifier>::from_inspect_node(
inspector.root().create_child("update-monitor"),
);
fasync::spawn(fut);
update_monitor.advance_update_state(Some(State::CheckingForUpdates)).await;
assert_inspect_tree!(
inspector,
root: {
"update-monitor": {
"update-state": format!("{:?}", Some(State::CheckingForUpdates)),
"version-available": "None",
}
}
);
}
#[test]
fn test_inspect_version_available() {
let inspector = fuchsia_inspect::Inspector::new();
let (_fut, mut update_monitor) = UpdateMonitor::<FakeStateNotifier>::from_inspect_node(
inspector.root().create_child("update-monitor"),
);
let version_available = "fake-version-available";
update_monitor.set_version_available(version_available.to_string());
assert_inspect_tree!(
inspector,
root: {
"update-monitor": {
"update-state": "None",
"version-available": format!("{:?}", Some(version_available)),
}
}
);
}
}