blob: a776261f60c5e7b3bb5c52fe73b80f1c5445795d [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 std::collections::HashMap;
use std::option::Option::Some;
use std::sync::Arc;
use fidl_fuchsia_settings::{LightError, LightMarker, LightProxy};
use futures::lock::Mutex;
use crate::agent::restore_agent;
use crate::registry::device_storage::testing::*;
use crate::registry::device_storage::DeviceStorage;
use crate::switchboard::base::SettingType;
use crate::switchboard::light_types::{
ColorRgb, LightGroup, LightInfo, LightState, LightType, LightValue,
};
use crate::tests::fakes::hardware_light_service::HardwareLightService;
use crate::tests::fakes::service_registry::ServiceRegistry;
use crate::EnvironmentBuilder;
type HardwareLightServiceHandle = Arc<Mutex<HardwareLightService>>;
const ENV_NAME: &str = "settings_service_light_test_environment";
const CONTEXT_ID: u64 = 0;
const LIGHT_NAME_1: &str = "light_name_1";
const LIGHT_NAME_2: &str = "light_name_2";
fn get_test_light_info() -> LightInfo {
let mut light_groups = HashMap::new();
light_groups.insert(
LIGHT_NAME_1.to_string(),
LightGroup {
name: LIGHT_NAME_1.to_string(),
enabled: true,
light_type: LightType::Brightness,
lights: vec![LightState { value: Some(LightValue::Brightness(42)) }],
hardware_index: vec![0],
},
);
light_groups.insert(
LIGHT_NAME_2.to_string(),
LightGroup {
name: LIGHT_NAME_2.to_string(),
enabled: true,
light_type: LightType::Simple,
lights: vec![LightState { value: Some(LightValue::Simple(true)) }],
hardware_index: vec![1],
},
);
LightInfo { light_groups }
}
/// Populates the given HardwareLightService fake with the lights from a LightInfo.
///
/// Assumes that each light group has one light, as if the light groups were read from the
/// underlying fuchsia.hardware.light API.
async fn populate_single_test_lights(
hardware_light_service_handle: HardwareLightServiceHandle,
light_info: LightInfo,
) {
for (name, group) in light_info.light_groups.into_iter() {
hardware_light_service_handle
.lock()
.await
.insert_light(
group.hardware_index[0],
name,
group.light_type,
group.lights[0].value.clone().unwrap(),
)
.await;
}
}
/// Populates the given HardwareLightService fake with the lights from a LightInfo.
///
/// For each light group, adds a new light with the light's index in the LightGroup appended to it.
/// For example a light group with name RED and 3 lights would result in RED_1, RED_2, RED_3 being
/// inserted into the fake service.
async fn populate_multiple_test_lights(
hardware_light_service_handle: HardwareLightServiceHandle,
light_info: LightInfo,
) {
for (name, group) in light_info.light_groups.into_iter() {
for i in 0..group.hardware_index.len() {
hardware_light_service_handle
.lock()
.await
.insert_light(
group.hardware_index[i],
format!("{}_{}", name, i),
group.light_type.clone(),
group.lights[i].value.clone().unwrap(),
)
.await;
}
}
}
/// Creates a test environment for light.
///
/// If starting_light_info is provided, the device storage will be initialized with the given value.
/// If hardware_light_service_handle is provided, it will be used as the fake service in the service
/// registry, otherwise one will be constructed.
async fn create_test_light_env_with_service(
starting_light_info: Option<LightInfo>,
hardware_light_service_handle: Option<HardwareLightServiceHandle>,
) -> (LightProxy, Arc<Mutex<DeviceStorage<LightInfo>>>) {
let service_registry = ServiceRegistry::create();
let service_handle = match hardware_light_service_handle.clone() {
Some(service) => service,
None => Arc::new(Mutex::new(HardwareLightService::new())),
};
service_registry.lock().await.register_service(service_handle.clone());
let storage_factory = InMemoryStorageFactory::create();
let store = storage_factory
.lock()
.await
.get_device_storage::<LightInfo>(StorageAccessContext::Test, CONTEXT_ID);
if let Some(info) = starting_light_info {
store.lock().await.write(&info, false).await.expect("write starting values");
if hardware_light_service_handle.is_none() {
// If a fake hardware light service wasn't provided for us, populate the initial lights.
populate_single_test_lights(service_handle.clone(), info).await;
}
}
let env = EnvironmentBuilder::new(storage_factory)
.service(Box::new(ServiceRegistry::serve(service_registry)))
.agents(&[restore_agent::blueprint::create()])
.settings(&[SettingType::Light])
.spawn_and_get_nested_environment(ENV_NAME)
.await
.unwrap();
let light_service = env.connect_to_service::<LightMarker>().unwrap();
(light_service, store)
}
async fn set_light_value(service: &LightProxy, light_group: LightGroup) {
service
.set_light_group_values(
light_group.name.as_str(),
&mut light_group.lights.into_iter().map(LightState::into),
)
.await
.expect("set completed")
.expect("set successful");
}
/// Compares a vector of light group from the settings FIDL API and the light groups from a
/// service-internal LightInfo object for equality.
fn assert_lights_eq(mut groups: Vec<fidl_fuchsia_settings::LightGroup>, info: LightInfo) {
// Watch returns vector, internally we use a HashMap, so convert into a vector for comparison.
let mut expected_value = info
.light_groups
.into_iter()
.map(|(_, value)| fidl_fuchsia_settings::LightGroup::from(value))
.collect::<Vec<_>>();
// Sort by names for stability
groups.sort_by_key(|group: &fidl_fuchsia_settings::LightGroup| group.name.clone());
expected_value.sort_by_key(|group| group.name.clone());
assert_eq!(groups, expected_value);
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_light_restore() {
// Populate the fake service with the initial lights that will be restored.
let hardware_light_service_handle = Arc::new(Mutex::new(HardwareLightService::new()));
populate_single_test_lights(hardware_light_service_handle.clone(), get_test_light_info()).await;
let (light_service, store) =
create_test_light_env_with_service(None, Some(hardware_light_service_handle)).await;
let expected_light_group = get_test_light_info().light_groups.remove(LIGHT_NAME_1).unwrap();
// Verify that the restored value is persisted.
let mut store_lock = store.lock().await;
let retrieved_struct = store_lock.get().await;
assert_eq!(
&expected_light_group.clone(),
retrieved_struct.light_groups.get(LIGHT_NAME_1).unwrap()
);
// Verify that the restored value is returned on a watch call.
let settings: fidl_fuchsia_settings::LightGroup =
light_service.watch_light_group(LIGHT_NAME_1).await.expect("watch completed");
assert_eq!(fidl_fuchsia_settings::LightGroup::from(expected_light_group), settings);
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_light_restores_on_watch() {
let hardware_light_service_handle = Arc::new(Mutex::new(HardwareLightService::new()));
let (light_service, _) =
create_test_light_env_with_service(None, Some(hardware_light_service_handle.clone())).await;
// Don't populate the fake service with lights until after the service starts.
let expected_light_info = get_test_light_info();
populate_single_test_lights(hardware_light_service_handle.clone(), expected_light_info.clone())
.await;
// Upon a watch call, light controller will read the underlying value from the fake service.
let settings = light_service.watch_light_groups().await.expect("watch completed");
assert_lights_eq(settings, expected_light_info);
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_light_store_and_watch() {
let mut expected_light_info = get_test_light_info();
let mut changed_light_group =
expected_light_info.light_groups.get(LIGHT_NAME_1).unwrap().clone();
changed_light_group.lights = vec![LightState { value: Some(LightValue::Brightness(128)) }];
expected_light_info.light_groups.insert(LIGHT_NAME_1.to_string(), changed_light_group.clone());
let (light_service, store) =
create_test_light_env_with_service(Some(get_test_light_info()), None).await;
// Set a light value.
set_light_value(&light_service, changed_light_group).await;
// Verify the value we set is persisted in DeviceStorage.
assert_eq!(expected_light_info, store.lock().await.get().await);
// Ensure value from Watch matches set value.
let settings: Vec<fidl_fuchsia_settings::LightGroup> =
light_service.watch_light_groups().await.expect("watch completed");
assert_lights_eq(settings, expected_light_info);
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_light_set_wrong_size() {
let (light_service, _) =
create_test_light_env_with_service(Some(get_test_light_info()), None).await;
// Light group only has one light, attempt to set two lights.
light_service
.set_light_group_values(
LIGHT_NAME_1,
&mut vec![
LightState { value: Some(LightValue::Brightness(128)) },
LightState { value: Some(LightValue::Brightness(11)) },
]
.into_iter()
.map(LightState::into),
)
.await
.expect("set completed")
.expect_err("set failed");
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_light_set_none() {
const TEST_LIGHT_NAME: &str = "multiple_lights";
const LIGHT_1_VAL: u8 = 42;
const LIGHT_2_START_VAL: u8 = 24;
const LIGHT_2_CHANGED_VAL: u8 = 11;
// For this test, create a light group with two lights to test setting one light at a time.
let original_light_group = LightGroup {
name: TEST_LIGHT_NAME.to_string(),
enabled: true,
light_type: LightType::Brightness,
lights: vec![
LightState { value: Some(LightValue::Brightness(LIGHT_1_VAL)) },
LightState { value: Some(LightValue::Brightness(LIGHT_2_START_VAL)) },
],
hardware_index: vec![0, 1],
};
// When changing the light group, specify None for the first light.
let mut changed_light_group = original_light_group.clone();
changed_light_group.lights = vec![
LightState { value: None },
LightState { value: Some(LightValue::Brightness(LIGHT_2_CHANGED_VAL)) },
];
// Only the second light shoudl change.
let mut expected_light_group = original_light_group.clone();
expected_light_group.lights = vec![
LightState { value: Some(LightValue::Brightness(LIGHT_1_VAL)) },
LightState { value: Some(LightValue::Brightness(LIGHT_2_CHANGED_VAL)) },
];
let mut light_groups = HashMap::new();
light_groups.insert(TEST_LIGHT_NAME.to_string(), original_light_group);
let starting_light_info = LightInfo { light_groups };
let mut expected_light_info = starting_light_info.clone();
expected_light_info
.light_groups
.insert(TEST_LIGHT_NAME.to_string(), expected_light_group.clone());
let hardware_light_service_handle = Arc::new(Mutex::new(HardwareLightService::new()));
let (light_service, store) = create_test_light_env_with_service(
Some(starting_light_info.clone()),
Some(hardware_light_service_handle.clone()),
)
.await;
// Populate the fake service with the initial lights so the set calls aren't rejected.
populate_multiple_test_lights(hardware_light_service_handle, starting_light_info).await;
set_light_value(&light_service, changed_light_group).await;
// Verify the value we set is persisted in DeviceStorage.
assert_eq!(expected_light_info, store.lock().await.get().await);
// Ensure value from Watch matches set value.
let mut settings: Vec<fidl_fuchsia_settings::LightGroup> =
light_service.watch_light_groups().await.expect("watch completed");
settings.sort_by_key(|group: &fidl_fuchsia_settings::LightGroup| group.name.clone());
let settings = light_service.watch_light_group(TEST_LIGHT_NAME).await.expect("watch completed");
assert_eq!(settings, expected_light_group.into());
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_individual_light_group() {
let test_light_info = get_test_light_info();
let light_group_1 = test_light_info.light_groups.get(LIGHT_NAME_1).unwrap().clone();
let mut light_group_1_updated = light_group_1.clone();
light_group_1_updated.lights = vec![LightState { value: Some(LightValue::Brightness(128)) }];
let light_group_2 = test_light_info.light_groups.get(LIGHT_NAME_2).unwrap().clone();
let mut light_group_2_updated = light_group_2.clone();
light_group_2_updated.lights = vec![LightState { value: Some(LightValue::Simple(false)) }];
let (light_service, _) =
create_test_light_env_with_service(Some(get_test_light_info()), None).await;
// Ensure values from Watch matches set values.
let settings = light_service.watch_light_group(LIGHT_NAME_1).await.expect("watch completed");
assert_eq!(settings, light_group_1.into());
let settings = light_service.watch_light_group(LIGHT_NAME_2).await.expect("watch completed");
assert_eq!(settings, light_group_2.into());
// Set updated values for the two lights.
set_light_value(&light_service, light_group_1_updated.clone()).await;
let settings = light_service.watch_light_group(LIGHT_NAME_1).await.expect("watch completed");
assert_eq!(settings, light_group_1_updated.into());
set_light_value(&light_service, light_group_2_updated.clone()).await;
let settings = light_service.watch_light_group(LIGHT_NAME_2).await.expect("watch completed");
assert_eq!(settings, light_group_2_updated.into());
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_watch_unknown_light_group_name() {
let (light_service, _) =
create_test_light_env_with_service(Some(get_test_light_info()), None).await;
// Unknown name should be rejected.
light_service.watch_light_group("unknown_name").await.expect_err("watch should fail");
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_set_unknown_light_group_name() {
let (light_service, _) =
create_test_light_env_with_service(Some(get_test_light_info()), None).await;
// Unknown name should be rejected.
let result = light_service
.set_light_group_values("unknown_name", &mut vec![].into_iter())
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidName));
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_set_wrong_state_length() {
let test_light_info = get_test_light_info();
let (light_service, _) = create_test_light_env_with_service(Some(test_light_info), None).await;
// Set with no light state should fail.
let result = light_service
.set_light_group_values(LIGHT_NAME_1, &mut vec![].into_iter())
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidValue));
// Set with an extra light state should fail.
let extra_state = vec![
fidl_fuchsia_settings::LightState { value: None },
fidl_fuchsia_settings::LightState { value: None },
];
let result = light_service
.set_light_group_values(LIGHT_NAME_1, &mut extra_state.into_iter())
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidValue));
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_set_wrong_value_type() {
const TEST_LIGHT_NAME: &str = "multiple_lights";
const LIGHT_START_VAL: u8 = 24;
const LIGHT_CHANGED_VAL: u8 = 11;
let original_light_group = LightGroup {
name: TEST_LIGHT_NAME.to_string(),
enabled: true,
light_type: LightType::Brightness,
lights: vec![
LightState { value: Some(LightValue::Brightness(LIGHT_START_VAL)) },
LightState { value: Some(LightValue::Brightness(LIGHT_START_VAL)) },
],
hardware_index: vec![0, 1],
};
let mut light_groups = HashMap::new();
light_groups.insert(TEST_LIGHT_NAME.to_string(), original_light_group);
let starting_light_info = LightInfo { light_groups };
let hardware_light_service_handle = Arc::new(Mutex::new(HardwareLightService::new()));
let (light_service, _) = create_test_light_env_with_service(
Some(starting_light_info.clone()),
Some(hardware_light_service_handle.clone()),
)
.await;
// One of the light values is On instead of brightness, the set should fail.
let result = light_service
.set_light_group_values(
TEST_LIGHT_NAME,
&mut vec![
fidl_fuchsia_settings::LightState { value: None },
fidl_fuchsia_settings::LightState {
value: Some(fidl_fuchsia_settings::LightValue::On(true)),
},
]
.into_iter(),
)
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidValue));
// One of the values is the right type, but the other is still wrong, the set should fail.
let result = light_service
.set_light_group_values(
TEST_LIGHT_NAME,
&mut vec![
fidl_fuchsia_settings::LightState {
value: Some(fidl_fuchsia_settings::LightValue::Brightness(LIGHT_CHANGED_VAL)),
},
fidl_fuchsia_settings::LightState {
value: Some(fidl_fuchsia_settings::LightValue::On(true)),
},
]
.into_iter(),
)
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidValue));
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_set_invalid_rgb_values() {
const TEST_LIGHT_NAME: &str = "light";
const LIGHT_START_VAL: f32 = 0.25;
const INVALID_VAL_1: f32 = 1.1;
const INVALID_VAL_2: f32 = -0.1;
let original_light_group = LightGroup {
name: TEST_LIGHT_NAME.to_string(),
enabled: true,
light_type: LightType::Rgb,
lights: vec![LightState {
value: Some(LightValue::Rgb(ColorRgb {
red: LIGHT_START_VAL,
green: LIGHT_START_VAL,
blue: LIGHT_START_VAL,
})),
}],
hardware_index: vec![0],
};
let mut light_groups = HashMap::new();
light_groups.insert(TEST_LIGHT_NAME.to_string(), original_light_group);
let starting_light_info = LightInfo { light_groups };
let hardware_light_service_handle = Arc::new(Mutex::new(HardwareLightService::new()));
let (light_service, _) = create_test_light_env_with_service(
Some(starting_light_info.clone()),
Some(hardware_light_service_handle.clone()),
)
.await;
// One of the RGB components is too big, the set should fail.
let result = light_service
.set_light_group_values(
TEST_LIGHT_NAME,
&mut vec![fidl_fuchsia_settings::LightState {
value: Some(fidl_fuchsia_settings::LightValue::Color(
fidl_fuchsia_ui_types::ColorRgb {
red: LIGHT_START_VAL,
green: LIGHT_START_VAL,
blue: INVALID_VAL_1,
},
)),
}]
.into_iter(),
)
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidValue));
// One of the RGB components is negative, the set should fail.
let result = light_service
.set_light_group_values(
TEST_LIGHT_NAME,
&mut vec![fidl_fuchsia_settings::LightState {
value: Some(fidl_fuchsia_settings::LightValue::Color(
fidl_fuchsia_ui_types::ColorRgb {
red: LIGHT_START_VAL,
green: INVALID_VAL_2,
blue: LIGHT_START_VAL,
},
)),
}]
.into_iter(),
)
.await
.expect("set returns");
assert_eq!(result, Err(LightError::InvalidValue));
}