blob: 4614b09aef711a6e2acaf78d78b870e200697fe8 [file] [log] [blame]
// Copyright 2020 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 {
event_queue::Event, fidl_fuchsia_update as fidl, proptest::prelude::*,
proptest_derive::Arbitrary, std::convert::TryFrom, thiserror::Error,
};
#[derive(Clone, Debug, PartialEq, Arbitrary)]
pub enum State {
CheckingForUpdates,
ErrorCheckingForUpdate,
NoUpdateAvailable,
InstallationDeferredByPolicy(InstallationDeferredData),
InstallingUpdate(InstallingData),
WaitingForReboot(InstallingData),
InstallationError(InstallationErrorData),
}
impl State {
/// Returns true if this state is an error state.
pub fn is_error(&self) -> bool {
match self {
State::ErrorCheckingForUpdate | State::InstallationError(_) => true,
State::CheckingForUpdates
| State::NoUpdateAvailable
| State::InstallationDeferredByPolicy(_)
| State::InstallingUpdate(_)
| State::WaitingForReboot(_) => false,
}
}
}
impl Event for State {
fn can_merge(&self, other: &State) -> bool {
if self == other {
return true;
}
// Merge states that have the same update info but different installation progress
if let State::InstallingUpdate(InstallingData { update: update0, .. }) = self {
if let State::InstallingUpdate(InstallingData { update: update1, .. }) = other {
return update0 == update1;
}
}
false
}
}
impl Into<fidl::State> for State {
fn into(self) -> fidl::State {
match self {
State::CheckingForUpdates => {
fidl::State::CheckingForUpdates(fidl::CheckingForUpdatesData::EMPTY)
}
State::ErrorCheckingForUpdate => {
fidl::State::ErrorCheckingForUpdate(fidl::ErrorCheckingForUpdateData::EMPTY)
}
State::NoUpdateAvailable => {
fidl::State::NoUpdateAvailable(fidl::NoUpdateAvailableData::EMPTY)
}
State::InstallationDeferredByPolicy(data) => {
fidl::State::InstallationDeferredByPolicy(data.into())
}
State::InstallingUpdate(data) => fidl::State::InstallingUpdate(data.into()),
State::WaitingForReboot(data) => fidl::State::WaitingForReboot(data.into()),
State::InstallationError(data) => fidl::State::InstallationError(data.into()),
}
}
}
impl From<fidl::State> for State {
fn from(fidl_state: fidl::State) -> Self {
match fidl_state {
fidl::State::CheckingForUpdates(_) => State::CheckingForUpdates,
fidl::State::ErrorCheckingForUpdate(_) => State::ErrorCheckingForUpdate,
fidl::State::NoUpdateAvailable(_) => State::NoUpdateAvailable,
fidl::State::InstallationDeferredByPolicy(data) => {
State::InstallationDeferredByPolicy(data.into())
}
fidl::State::InstallingUpdate(data) => State::InstallingUpdate(data.into()),
fidl::State::WaitingForReboot(data) => State::WaitingForReboot(data.into()),
fidl::State::InstallationError(data) => State::InstallationError(data.into()),
}
}
}
#[derive(Clone, Debug, PartialEq, Arbitrary)]
pub struct InstallationErrorData {
pub update: Option<UpdateInfo>,
pub installation_progress: Option<InstallationProgress>,
}
impl Into<fidl::InstallationErrorData> for InstallationErrorData {
fn into(self) -> fidl::InstallationErrorData {
fidl::InstallationErrorData {
update: self.update.map(|ext| ext.into()),
installation_progress: self.installation_progress.map(|ext| ext.into()),
..fidl::InstallationErrorData::EMPTY
}
}
}
impl From<fidl::InstallationErrorData> for InstallationErrorData {
fn from(data: fidl::InstallationErrorData) -> Self {
Self {
update: data.update.map(|o| o.into()),
installation_progress: data.installation_progress.map(|o| o.into()),
}
}
}
#[derive(Clone, Debug, PartialEq, Arbitrary)]
pub struct InstallationProgress {
#[proptest(strategy = "prop::option::of(prop::num::f32::NORMAL)")]
pub fraction_completed: Option<f32>,
}
impl Into<fidl::InstallationProgress> for InstallationProgress {
fn into(self) -> fidl::InstallationProgress {
fidl::InstallationProgress {
fraction_completed: self.fraction_completed,
..fidl::InstallationProgress::EMPTY
}
}
}
impl From<fidl::InstallationProgress> for InstallationProgress {
fn from(progress: fidl::InstallationProgress) -> Self {
Self { fraction_completed: progress.fraction_completed }
}
}
#[derive(Clone, Debug, PartialEq, Arbitrary)]
pub struct InstallingData {
pub update: Option<UpdateInfo>,
pub installation_progress: Option<InstallationProgress>,
}
impl Into<fidl::InstallingData> for InstallingData {
fn into(self) -> fidl::InstallingData {
fidl::InstallingData {
update: self.update.map(|ext| ext.into()),
installation_progress: self.installation_progress.map(|ext| ext.into()),
..fidl::InstallingData::EMPTY
}
}
}
impl From<fidl::InstallingData> for InstallingData {
fn from(data: fidl::InstallingData) -> Self {
Self {
update: data.update.map(|o| o.into()),
installation_progress: data.installation_progress.map(|o| o.into()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct InstallationDeferredData {
pub update: Option<UpdateInfo>,
pub deferral_reason: Option<fidl::InstallationDeferralReason>,
}
impl Into<fidl::InstallationDeferredData> for InstallationDeferredData {
fn into(self) -> fidl::InstallationDeferredData {
fidl::InstallationDeferredData {
update: self.update.map(|ext| ext.into()),
deferral_reason: self.deferral_reason,
..fidl::InstallationDeferredData::EMPTY
}
}
}
impl From<fidl::InstallationDeferredData> for InstallationDeferredData {
fn from(data: fidl::InstallationDeferredData) -> Self {
Self { update: data.update.map(|o| o.into()), deferral_reason: data.deferral_reason }
}
}
// Manually impl Arbitrary because fidl::InstallationDeferralReason does not impl Arbitrary.
// We could have created another wrapper, but we opted not to in order to guarantee the ext
// crate stays in sync with the FIDL for InstallationDeferralReason.
impl Arbitrary for InstallationDeferredData {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
any::<UpdateInfo>()
.prop_flat_map(|info| {
(
proptest::option::of(Just(info)),
proptest::option::of(Just(
fidl::InstallationDeferralReason::CurrentSystemNotCommitted,
)),
)
})
.prop_map(|(update, deferral_reason)| Self { update, deferral_reason })
.boxed()
}
}
#[derive(Clone, Debug, PartialEq, Arbitrary)]
pub struct UpdateInfo {
#[proptest(strategy = "proptest_util::random_version_available()")]
pub version_available: Option<String>,
pub download_size: Option<u64>,
}
impl Into<fidl::UpdateInfo> for UpdateInfo {
fn into(self) -> fidl::UpdateInfo {
fidl::UpdateInfo {
version_available: self.version_available,
download_size: self.download_size,
..fidl::UpdateInfo::EMPTY
}
}
}
impl From<fidl::UpdateInfo> for UpdateInfo {
fn from(info: fidl::UpdateInfo) -> Self {
Self { version_available: info.version_available, download_size: info.download_size }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
pub enum Initiator {
User,
Service,
}
impl From<fidl::Initiator> for Initiator {
fn from(initiator: fidl::Initiator) -> Self {
match initiator {
fidl::Initiator::User => Initiator::User,
fidl::Initiator::Service => Initiator::Service,
}
}
}
impl From<Initiator> for fidl::Initiator {
fn from(initiator: Initiator) -> Self {
match initiator {
Initiator::User => fidl::Initiator::User,
Initiator::Service => fidl::Initiator::Service,
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum CheckOptionsDecodeError {
#[error("missing field 'initiator'")]
MissingInitiator,
}
#[derive(Clone, Debug, PartialEq, Arbitrary)]
pub struct CheckOptions {
pub initiator: Initiator,
pub allow_attaching_to_existing_update_check: bool,
}
impl CheckOptions {
pub fn builder() -> CheckOptionsBuilder {
CheckOptionsBuilder
}
}
impl TryFrom<fidl::CheckOptions> for CheckOptions {
type Error = CheckOptionsDecodeError;
fn try_from(o: fidl::CheckOptions) -> Result<Self, Self::Error> {
Ok(Self {
initiator: o.initiator.ok_or(CheckOptionsDecodeError::MissingInitiator)?.into(),
allow_attaching_to_existing_update_check: o
.allow_attaching_to_existing_update_check
.unwrap_or(false),
})
}
}
impl From<CheckOptions> for fidl::CheckOptions {
fn from(o: CheckOptions) -> Self {
Self {
initiator: Some(o.initiator.into()),
allow_attaching_to_existing_update_check: Some(
o.allow_attaching_to_existing_update_check,
),
..Self::EMPTY
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CheckOptionsBuilder;
#[derive(Clone, Debug, PartialEq)]
pub struct CheckOptionsBuilderWithInitiator {
initiator: Initiator,
allow_attaching_to_existing_update_check: bool,
}
impl CheckOptionsBuilder {
pub fn new() -> Self {
Self
}
pub fn initiator(self, initiator: Initiator) -> CheckOptionsBuilderWithInitiator {
CheckOptionsBuilderWithInitiator {
initiator,
allow_attaching_to_existing_update_check: false,
}
}
}
impl CheckOptionsBuilderWithInitiator {
pub fn allow_attaching_to_existing_update_check(mut self, allow: bool) -> Self {
self.allow_attaching_to_existing_update_check = allow;
self
}
pub fn build(self) -> CheckOptions {
CheckOptions {
initiator: self.initiator,
allow_attaching_to_existing_update_check: self.allow_attaching_to_existing_update_check,
}
}
}
pub mod proptest_util {
use proptest::prelude::*;
prop_compose! {
pub fn random_version_available()(
version_available in proptest::option::of("[0-9A-Z]{10,20}")
) -> Option<String> {
version_available
}
}
}
#[cfg(test)]
mod tests {
use super::*;
proptest! {
// states with the same update info but different progress should merge
#[test]
fn test_can_merge(
update_info: Option<UpdateInfo>,
progress0: Option<InstallationProgress>,
progress1: Option<InstallationProgress>,
) {
let event0 = State::InstallingUpdate(
InstallingData {
update: update_info.clone(),
installation_progress: progress0,
}
);
let event1 = State::InstallingUpdate(
InstallingData {
update: update_info,
installation_progress: progress1,
}
);
prop_assert!(event0.can_merge(&event1));
}
#[test]
fn test_state_roundtrips(state: State) {
let state0: State = state.clone();
let fidl_intermediate: fidl::State = state.into();
let state1: State = fidl_intermediate.into();
prop_assert_eq!(state0, state1);
}
#[test]
fn test_initiator_roundtrips(initiator: Initiator) {
prop_assert_eq!(
Initiator::from(fidl::Initiator::from(initiator.clone())),
initiator
);
}
#[test]
fn test_check_options_roundtrips(check_options: CheckOptions) {
prop_assert_eq!(
CheckOptions::try_from(fidl::CheckOptions::from(check_options.clone())),
Ok(check_options)
);
}
#[test]
fn test_check_options_initiator_required(allow_attaching_to_existing_update_check: bool) {
prop_assert_eq!(
CheckOptions::try_from(fidl::CheckOptions {
initiator: None,
allow_attaching_to_existing_update_check: Some(allow_attaching_to_existing_update_check),
..fidl::CheckOptions::EMPTY
}),
Err(CheckOptionsDecodeError::MissingInitiator)
);
}
}
#[test]
fn check_options_builder() {
assert_eq!(
CheckOptions::builder()
.initiator(Initiator::User)
.allow_attaching_to_existing_update_check(true)
.build(),
CheckOptions {
initiator: Initiator::User,
allow_attaching_to_existing_update_check: true,
}
);
}
}