power_manager_integration_test_lib/mocks/
input_settings_service.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl::endpoints::ServerEnd;
6use fidl_fuchsia_io::DirectoryMarker;
7use fuchsia_component::server::ServiceFs;
8use fuchsia_component_test::LocalComponentHandles;
9use futures::channel::mpsc;
10use futures::lock::Mutex;
11use futures::{StreamExt, TryStreamExt};
12use log::*;
13use std::sync::Arc;
14use {fidl_fuchsia_settings as fsettings, fuchsia_async as fasync};
15
16/// Mocks the fuchsia.settings.Input service to be used in integration tests.
17pub struct MockInputSettingsService {
18    /// Sends new input settings to the mock server. The expected usage is that the test holds the
19    /// sender end to communicate new input settings to the server on the receiver end.
20    settings_sender: Mutex<mpsc::Sender<fsettings::InputSettings>>,
21
22    /// Receiver end for input settings changes. When the server reads the new settings from the
23    /// receiver end, it will send those new settings out to any pending clients that have
24    /// previously called `Watch`.
25    settings_receiver: Mutex<mpsc::Receiver<fsettings::InputSettings>>,
26}
27
28impl MockInputSettingsService {
29    pub fn new() -> Arc<MockInputSettingsService> {
30        let (settings_sender, settings_receiver) = mpsc::channel(1);
31        Arc::new(Self {
32            settings_sender: Mutex::new(settings_sender),
33            settings_receiver: Mutex::new(settings_receiver),
34        })
35    }
36
37    /// Runs the mock using the provided `LocalComponentHandles`.
38    ///
39    /// Expected usage is to call this function from a closure for the
40    /// `local_component_implementation` parameter to `RealmBuilder.add_local_child`.
41    ///
42    /// For example:
43    ///     let mock_input_settings_service = MockInputSettingsService::new();
44    ///     let input_settings_service_child = realm_builder
45    ///         .add_local_child(
46    ///             "input_settings_service",
47    ///             move |handles| {
48    ///                 Box::pin(input_settings_service.clone().run(handles))
49    ///             },
50    ///             ChildOptions::new(),
51    ///         )
52    ///         .await
53    ///         .unwrap();
54    ///
55    pub async fn run(self: Arc<Self>, handles: LocalComponentHandles) -> Result<(), anyhow::Error> {
56        self.run_inner(handles.outgoing_dir).await
57    }
58
59    async fn run_inner(
60        self: Arc<Self>,
61        outgoing_dir: ServerEnd<DirectoryMarker>,
62    ) -> Result<(), anyhow::Error> {
63        let mut fs = ServiceFs::new();
64        fs.dir("svc").add_fidl_service(move |mut stream: fsettings::InputRequestStream| {
65            let this = self.clone();
66
67            // Set the initial settings update value for this connection implement hanging-get
68            let mut initial_settings_update = Some(generate_device_settings(false));
69
70            fasync::Task::local(async move {
71                info!("MockInputSettingsService: new connection");
72                while let Some(fsettings::InputRequest::Watch { responder }) =
73                    stream.try_next().await.unwrap()
74                {
75                    info!("MockInputSettingsService: received Watch request");
76                    let settings = if let Some(settings) = initial_settings_update.take() {
77                        settings
78                    } else {
79                        this.settings_receiver.lock().await.next().await.unwrap()
80                    };
81
82                    info!("MockInputSettingsService: sending input settings: {:?}", settings);
83                    let _ = responder.send(&settings);
84                }
85
86                info!("MockInputSettingsService: closing connection")
87            })
88            .detach();
89        });
90
91        fs.serve_connection(outgoing_dir).unwrap();
92        fs.collect::<()>().await;
93
94        Ok(())
95    }
96
97    pub async fn set_mic_enabled(&self, mic_enabled: bool) {
98        info!("MockInputSettingsService: set mic enabled: {:?}", mic_enabled);
99        let input_settings = generate_device_settings(mic_enabled);
100        self.settings_sender.lock().await.try_send(input_settings).expect("try_send() failed");
101    }
102}
103
104fn generate_device_settings(mic_enabled: bool) -> fsettings::InputSettings {
105    fsettings::InputSettings {
106        devices: Some(vec![fsettings::InputDevice {
107            device_type: Some(fsettings::DeviceType::Microphone),
108            state: Some(fsettings::DeviceState {
109                toggle_flags: Some(if mic_enabled {
110                    fsettings::ToggleStateFlags::AVAILABLE
111                } else {
112                    fsettings::ToggleStateFlags::MUTED
113                }),
114                ..Default::default()
115            }),
116            ..Default::default()
117        }]),
118        ..Default::default()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use fuchsia_component::client::connect_to_protocol_at_dir_svc;
126
127    /// Parses the InputSettings struct to retrieve microphone enabled state.
128    fn parse_is_mic_enabled(settings: fsettings::InputSettings) -> bool {
129        let mic_settings = settings
130            .devices
131            .unwrap()
132            .into_iter()
133            .filter(|device| device.device_type == Some(fsettings::DeviceType::Microphone))
134            .collect::<Vec<_>>();
135
136        assert_eq!(mic_settings.len(), 1);
137
138        let is_enabled = mic_settings[0]
139            .state
140            .as_ref()
141            .unwrap()
142            .toggle_flags
143            .unwrap()
144            .contains(fsettings::ToggleStateFlags::AVAILABLE);
145
146        is_enabled
147    }
148
149    #[fuchsia::test]
150    async fn test_set_mic_enabled() {
151        // Create and serve the mock service
152        let (dir, outgoing_dir) = fidl::endpoints::create_proxy::<DirectoryMarker>();
153        let mock = MockInputSettingsService::new();
154        let _task = fasync::Task::local(mock.clone().run_inner(outgoing_dir));
155
156        // Connect to the mock server
157        let settings_client =
158            connect_to_protocol_at_dir_svc::<fsettings::InputMarker>(&dir).unwrap();
159
160        // mic-enable is initially false
161        let settings = settings_client.watch().await.unwrap();
162        assert_eq!(parse_is_mic_enabled(settings), false);
163
164        // set the mock mic-enable to true and verify the client gets the updated value
165        mock.set_mic_enabled(true).await;
166        let settings = settings_client.watch().await.unwrap();
167        assert_eq!(parse_is_mic_enabled(settings), true);
168    }
169}