#![deny(missing_docs)]
use display_types::INVALID_DISP_ID;
use fidl::endpoints::{ClientEnd, ServerEnd};
use fidl_fuchsia_hardware_display::{
self as display, CoordinatorListenerMarker, CoordinatorListenerProxy, CoordinatorMarker,
CoordinatorRequestStream, VsyncAckCookie,
};
use fidl_fuchsia_hardware_display_types as display_types;
use itertools::Itertools;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MockCoordinatorError {
#[error("duplicate IDs provided")]
DuplicateIds,
#[error("FIDL error: {0}")]
FidlError(#[from] fidl::Error),
}
pub type Result<T> = std::result::Result<T, MockCoordinatorError>;
pub struct MockCoordinator {
#[allow(unused)]
coordinator_stream: CoordinatorRequestStream,
listener_proxy: CoordinatorListenerProxy,
displays: HashMap<DisplayId, display::Info>,
}
#[derive(Eq, Hash, Ord, PartialOrd, PartialEq)]
struct DisplayId(u64);
impl MockCoordinator {
pub fn new(
coordinator_server_end: ServerEnd<CoordinatorMarker>,
listener_client_end: ClientEnd<CoordinatorListenerMarker>,
) -> Result<MockCoordinator> {
let coordinator_stream = coordinator_server_end.into_stream()?;
let listener_proxy = listener_client_end.into_proxy()?;
Ok(MockCoordinator { coordinator_stream, listener_proxy, displays: HashMap::new() })
}
pub fn assign_displays(&mut self, displays: Vec<display::Info>) -> Result<()> {
let mut map = HashMap::new();
if !displays.into_iter().all(|info| map.insert(DisplayId(info.id.value), info).is_none()) {
return Err(MockCoordinatorError::DuplicateIds);
}
let added: Vec<_> = map
.iter()
.sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
.map(|(_, info)| info.clone())
.collect();
let removed: Vec<display_types::DisplayId> =
self.displays.iter().map(|(_, info)| info.id).collect();
self.displays = map;
self.listener_proxy.on_displays_changed(&added, &removed)?;
Ok(())
}
pub fn emit_vsync_event(
&self,
display_id_value: u64,
stamp: display_types::ConfigStamp,
) -> Result<()> {
self.listener_proxy
.on_vsync(
&display_types::DisplayId { value: display_id_value },
zx::MonotonicInstant::get().into_nanos(),
&stamp,
&VsyncAckCookie { value: INVALID_DISP_ID },
)
.map_err(MockCoordinatorError::from)
}
}
pub fn create_proxy_and_mock(
) -> Result<(display::CoordinatorProxy, display::CoordinatorListenerRequestStream, MockCoordinator)>
{
let (coordinator_proxy, coordinator_server) =
fidl::endpoints::create_proxy::<CoordinatorMarker>()?;
let (listener_client, listener_requests) =
fidl::endpoints::create_request_stream::<CoordinatorListenerMarker>()?;
Ok((
coordinator_proxy,
listener_requests,
MockCoordinator::new(coordinator_server, listener_client)?,
))
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::{Context, Result};
use fidl_fuchsia_hardware_display as display;
use futures::{future, TryStreamExt};
async fn wait_for_displays_changed_event(
listener_requests: &mut display::CoordinatorListenerRequestStream,
) -> Result<(Vec<display::Info>, Vec<display_types::DisplayId>)> {
let mut stream = listener_requests.try_filter_map(|event| match event {
display::CoordinatorListenerRequest::OnDisplaysChanged {
added,
removed,
control_handle: _,
} => future::ok(Some((added, removed))),
_ => future::ok(None),
});
stream.try_next().await?.context("failed to listen to coordinator listener requests")
}
#[fuchsia::test]
async fn assign_displays_fails_with_duplicate_display_ids() {
let displays = vec![
display::Info {
id: display_types::DisplayId { value: 1 },
modes: Vec::new(),
pixel_format: Vec::new(),
manufacturer_name: "Foo".to_string(),
monitor_name: "what".to_string(),
monitor_serial: "".to_string(),
horizontal_size_mm: 0,
vertical_size_mm: 0,
using_fallback_size: false,
},
display::Info {
id: display_types::DisplayId { value: 1 },
modes: Vec::new(),
pixel_format: Vec::new(),
manufacturer_name: "Bar".to_string(),
monitor_name: "who".to_string(),
monitor_serial: "".to_string(),
horizontal_size_mm: 0,
vertical_size_mm: 0,
using_fallback_size: false,
},
];
let (_proxy, _listener_requests, mut mock) =
create_proxy_and_mock().expect("failed to create MockCoordinator");
let result = mock.assign_displays(displays);
assert!(result.is_err());
}
#[fuchsia::test]
async fn assign_displays_displays_added() -> Result<()> {
let displays = vec![
display::Info {
id: display_types::DisplayId { value: 1 },
modes: Vec::new(),
pixel_format: Vec::new(),
manufacturer_name: "Foo".to_string(),
monitor_name: "what".to_string(),
monitor_serial: "".to_string(),
horizontal_size_mm: 0,
vertical_size_mm: 0,
using_fallback_size: false,
},
display::Info {
id: display_types::DisplayId { value: 2 },
modes: Vec::new(),
pixel_format: Vec::new(),
manufacturer_name: "Bar".to_string(),
monitor_name: "who".to_string(),
monitor_serial: "".to_string(),
horizontal_size_mm: 0,
vertical_size_mm: 0,
using_fallback_size: false,
},
];
let (_proxy, mut listener_requests, mut mock) =
create_proxy_and_mock().expect("failed to create MockCoordinator");
mock.assign_displays(displays.clone())?;
let (added, removed) = wait_for_displays_changed_event(&mut listener_requests).await?;
assert_eq!(added, displays);
assert_eq!(removed, vec![]);
Ok(())
}
#[fuchsia::test]
async fn assign_displays_displays_removed() -> Result<()> {
let displays = vec![display::Info {
id: display_types::DisplayId { value: 1 },
modes: Vec::new(),
pixel_format: Vec::new(),
manufacturer_name: "Foo".to_string(),
monitor_name: "what".to_string(),
monitor_serial: "".to_string(),
horizontal_size_mm: 0,
vertical_size_mm: 0,
using_fallback_size: false,
}];
let (_proxy, mut listener_requests, mut mock) =
create_proxy_and_mock().expect("failed to create MockCoordinator");
mock.assign_displays(displays)?;
let _ = wait_for_displays_changed_event(&mut listener_requests).await?;
mock.assign_displays(vec![])?;
let (added, removed) = wait_for_displays_changed_event(&mut listener_requests).await?;
assert_eq!(added, vec![]);
assert_eq!(removed, vec![display_types::DisplayId { value: 1 }]);
Ok(())
}
}