blob: 304071474e7d4490bf8b59d0e386dcfd0fb39615 [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.
#[cfg(test)]
use {
crate::agent::restore_agent,
crate::internal::handler::{message, Address, Payload},
crate::message::base::{Audience, MessageEvent, MessengerType},
crate::registry::base::{Command, ContextBuilder, State},
crate::registry::device_storage::DeviceStorageFactory,
crate::registry::device_storage::{testing::*, DeviceStorageCompatible},
crate::registry::setting_handler::persist::WriteResult,
crate::registry::setting_handler::{
controller, persist, persist::controller as data_controller, persist::write,
persist::ClientProxy as DataClientProxy, persist::Handler as DataHandler, persist::Storage,
BoxedController, ClientImpl, ClientProxy, ControllerError, GenerateController, Handler,
},
crate::switchboard::accessibility_types::AccessibilityInfo,
crate::switchboard::base::{
get_all_setting_types, ControllerStateResult, DoNotDisturbInfo, SettingRequest,
SettingResponseResult, SettingType, SwitchboardError,
},
crate::EnvironmentBuilder,
async_trait::async_trait,
futures::channel::mpsc::{unbounded, UnboundedSender},
futures::StreamExt,
std::collections::HashMap,
std::marker::PhantomData,
};
const ENV_NAME: &str = "settings_service_setting_handler_test_environment";
const CONTEXT_ID: u64 = 0;
macro_rules! gen_controller {
($name:ident, $succeed:expr) => {
/// Controller is a simple controller test implementation that refers to a
/// Control type for how to behave.
struct $name {}
#[async_trait]
impl controller::Create for $name {
async fn create(_: ClientProxy) -> Result<Self, ControllerError> {
if $succeed {
Ok(Self {})
} else {
Err(ControllerError::InitFailure { description: "failure".to_string() })
}
}
}
#[async_trait]
impl controller::Handle for $name {
async fn handle(&self, _: SettingRequest) -> Option<SettingResponseResult> {
return None;
}
async fn change_state(&mut self, _: State) -> Option<ControllerStateResult> {
return None;
}
}
};
}
gen_controller!(SucceedController, true);
gen_controller!(FailController, false);
macro_rules! gen_data_controller {
($name:ident, $succeed:expr) => {
/// The DataController is a controller implementation with storage that
/// defers to a Control type for how to behave.
struct $name<S: Storage> {
_storage: PhantomData<S>,
}
#[async_trait]
impl<S: Storage> data_controller::Create<S> for $name<S> {
async fn create(_: DataClientProxy<S>) -> Result<Self, ControllerError> {
if $succeed {
Ok(Self { _storage: PhantomData })
} else {
Err(ControllerError::InitFailure { description: "failure".to_string() })
}
}
}
#[async_trait]
impl<S: Storage> controller::Handle for $name<S> {
async fn handle(&self, _: SettingRequest) -> Option<SettingResponseResult> {
return None;
}
async fn change_state(&mut self, _: State) -> Option<ControllerStateResult> {
return None;
}
}
};
}
gen_data_controller!(SucceedDataController, true);
gen_data_controller!(FailDataController, false);
macro_rules! verify_handle {
($spawn:expr) => {
assert!(EnvironmentBuilder::new(InMemoryStorageFactory::create())
.handler(SettingType::Unknown, Box::new($spawn))
.agents(&[restore_agent::blueprint::create()])
.settings(&[SettingType::Unknown])
.spawn_nested(ENV_NAME)
.await
.is_ok());
};
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_spawn() {
// Exercises successful spawn of a simple controller.
verify_handle!(Handler::<SucceedController>::spawn);
// Exercises failed spawn of a simple controller.
verify_handle!(Handler::<FailController>::spawn);
// Exercises successful spawn of a data controller.
verify_handle!(DataHandler::<DoNotDisturbInfo, SucceedDataController<DoNotDisturbInfo>>::spawn);
// Exercises failed spawn of a data controller.
verify_handle!(DataHandler::<DoNotDisturbInfo, FailDataController<DoNotDisturbInfo>>::spawn);
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_write_notify() {
let factory = message::create_hub();
let (handler_messenger, handler_receptor) =
factory.create(MessengerType::Unbound).await.unwrap();
let context = ContextBuilder::new(
SettingType::Accessibility,
InMemoryStorageFactory::create(),
handler_messenger,
handler_receptor,
CONTEXT_ID,
)
.build();
let (client_tx, mut client_rx) =
futures::channel::mpsc::unbounded::<persist::ClientProxy<AccessibilityInfo>>();
let storage = context
.environment
.storage_factory_handle
.lock()
.await
.get_store::<AccessibilityInfo>(context.id);
let setting_type = context.setting_type;
ClientImpl::create(
context,
Box::new(move |proxy| {
let client_tx = client_tx.clone();
let storage = storage.clone();
Box::pin(async move {
client_tx
.unbounded_send(
persist::ClientProxy::<AccessibilityInfo>::new(
proxy,
storage,
setting_type,
)
.await,
)
.ok();
Ok(Box::new(BlankController {}) as BoxedController)
})
}),
)
.await
.expect("client should be created");
// Get the proxy.
let mut proxy = client_rx.next().await.unwrap();
verify_write_behavior(
&mut proxy,
AccessibilityInfo {
audio_description: None,
screen_reader: None,
color_inversion: None,
enable_magnification: None,
color_correction: None,
captions_settings: None,
},
false,
)
.await;
verify_write_behavior(
&mut proxy,
AccessibilityInfo {
audio_description: Some(true),
screen_reader: None,
color_inversion: None,
enable_magnification: None,
color_correction: None,
captions_settings: None,
},
true,
)
.await;
}
async fn verify_write_behavior<S: DeviceStorageCompatible + Send + Sync>(
proxy: &mut persist::ClientProxy<S>,
value: S,
notified: bool,
) {
let result = write(proxy, value, false).await;
assert_eq!(notified, result.notified());
assert!(result.is_ok() && result.into_response_result().is_ok());
}
/// StateController allows for exposing incoming handler state to an outside
/// listener.
struct StateController {
state_reporter: UnboundedSender<State>,
invocation_counts: HashMap<State, u8>,
invocation_counts_reporter: UnboundedSender<HashMap<State, u8>>,
}
impl StateController {
pub fn create_generator(
reporter: UnboundedSender<State>,
invocation_counts_reporter: UnboundedSender<HashMap<State, u8>>,
) -> GenerateController {
Box::new(move |_| {
let reporter = reporter.clone();
let invocation_counts_reporter = invocation_counts_reporter.clone();
Box::pin(async move {
let mut invocation_counts = HashMap::new();
invocation_counts.insert(State::Startup, 0);
invocation_counts.insert(State::Listen, 0);
invocation_counts.insert(State::EndListen, 0);
invocation_counts.insert(State::Teardown, 0);
Ok(Box::new(StateController {
state_reporter: reporter.clone(),
invocation_counts,
invocation_counts_reporter: invocation_counts_reporter.clone(),
}) as BoxedController)
})
})
}
}
#[async_trait]
impl controller::Handle for StateController {
async fn handle(&self, _: SettingRequest) -> Option<SettingResponseResult> {
None
}
async fn change_state(&mut self, state: State) -> Option<ControllerStateResult> {
self.invocation_counts.entry(state).and_modify(|e| *e += 1);
self.invocation_counts_reporter.unbounded_send(self.invocation_counts.clone()).ok();
self.state_reporter.unbounded_send(state).ok();
None
}
}
struct BlankController {}
#[async_trait]
impl controller::Handle for BlankController {
async fn handle(&self, _: SettingRequest) -> Option<SettingResponseResult> {
return None;
}
async fn change_state(&mut self, _: State) -> Option<ControllerStateResult> {
return None;
}
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_event_propagation() {
let factory = message::create_hub();
let setting_type = SettingType::Unknown;
let (messenger, _) =
factory.create(MessengerType::Addressable(Address::Registry)).await.unwrap();
let (event_tx, mut event_rx) = unbounded::<State>();
let (invocations_tx, _invocations_rx) = unbounded::<HashMap<State, u8>>();
let (handler_messenger, handler_receptor) =
factory.create(MessengerType::Unbound).await.unwrap();
let signature = handler_messenger.get_signature();
let context = ContextBuilder::new(
setting_type,
InMemoryStorageFactory::create(),
handler_messenger,
handler_receptor,
CONTEXT_ID,
)
.build();
assert!(ClientImpl::create(
context,
StateController::create_generator(event_tx, invocations_tx)
)
.await
.is_ok());
messenger
.message(
Payload::Command(Command::ChangeState(State::Startup)),
Audience::Messenger(signature.clone()),
)
.send()
.ack();
assert_eq!(Some(State::Startup), event_rx.next().await);
messenger
.message(
Payload::Command(Command::ChangeState(State::Listen)),
Audience::Messenger(signature.clone()),
)
.send()
.ack();
assert_eq!(Some(State::Listen), event_rx.next().await);
messenger
.message(
Payload::Command(Command::ChangeState(State::EndListen)),
Audience::Messenger(signature.clone()),
)
.send()
.ack();
assert_eq!(Some(State::EndListen), event_rx.next().await);
messenger
.message(
Payload::Command(Command::ChangeState(State::Teardown)),
Audience::Messenger(signature.clone()),
)
.send()
.ack();
assert_eq!(Some(State::Teardown), event_rx.next().await);
}
// Test that the controller state is entered [n] times.
async fn verify_controller_state(state: State, n: u8) {
let factory = message::create_hub();
let setting_type = SettingType::Audio;
let (messenger, _) =
factory.create(MessengerType::Addressable(Address::Registry)).await.unwrap();
let (event_tx, mut event_rx) = unbounded::<State>();
let (invocations_tx, mut invocations_rx) = unbounded::<HashMap<State, u8>>();
let (handler_messenger, handler_receptor) =
factory.create(MessengerType::Unbound).await.unwrap();
let signature = handler_messenger.get_signature();
let context = ContextBuilder::new(
setting_type,
InMemoryStorageFactory::create(),
handler_messenger,
handler_receptor,
CONTEXT_ID,
)
.build();
ClientImpl::create(context, StateController::create_generator(event_tx, invocations_tx))
.await
.expect("Unable to create ClientImpl");
messenger
.message(
Payload::Command(Command::ChangeState(state)),
Audience::Messenger(signature.clone()),
)
.send()
.ack();
assert_eq!(Some(state), event_rx.next().await);
if let Some(invocation_counts) = invocations_rx.next().await {
assert_eq!(*invocation_counts.get(&state).unwrap(), n);
} else {
panic!("Should have receiced message from receptor");
}
}
#[fuchsia_async::run_until_stalled(test)]
// Test that the setting handler calls ChangeState(State::Startup) on controller.
async fn test_startup_state() {
verify_controller_state(State::Startup, 1).await;
}
#[fuchsia_async::run_until_stalled(test)]
// Test that the setting handler calls ChangeState(State::Teardown) on controller.
async fn test_teardown_state() {
verify_controller_state(State::Teardown, 1).await;
}
/// Empty controller that handles no commands or events.
/// TODO(fxb/50217): Clean up test controllers.
struct StubController {}
impl StubController {
pub fn create_generator() -> GenerateController {
Box::new(move |_| {
Box::pin(async move { Ok(Box::new(StubController {}) as BoxedController) })
})
}
}
#[async_trait]
impl controller::Handle for StubController {
async fn handle(&self, _: SettingRequest) -> Option<SettingResponseResult> {
return None;
}
async fn change_state(&mut self, _: State) -> Option<ControllerStateResult> {
None
}
}
/// Ensures that the correct unimplemented error is returned when the controller
/// doesn't properly handle a given command.
#[fuchsia_async::run_until_stalled(test)]
async fn test_unimplemented_error() {
for setting_type in get_all_setting_types() {
let factory = message::create_hub();
let (messenger, _) =
factory.create(MessengerType::Addressable(Address::Registry)).await.unwrap();
let (handler_messenger, handler_receptor) =
factory.create(MessengerType::Unbound).await.unwrap();
let signature = handler_messenger.get_signature();
let context = ContextBuilder::new(
setting_type,
InMemoryStorageFactory::create(),
handler_messenger,
handler_receptor,
CONTEXT_ID,
)
.build();
assert!(ClientImpl::create(context, StubController::create_generator()).await.is_ok());
let mut receptor = messenger
.message(
Payload::Command(Command::HandleRequest(SettingRequest::Get)),
Audience::Messenger(signature),
)
.send();
while let Some(message_event) = receptor.next().await {
if let MessageEvent::Message(incoming_payload, _) = message_event {
if let Payload::Result(Err(SwitchboardError::UnimplementedRequest {
setting_type: incoming_type,
request: _,
})) = incoming_payload
{
assert_eq!(incoming_type, setting_type);
return;
} else {
panic!("should have received a result");
}
}
}
}
}