mock_installer/
lib.rs

1// Copyright 2020 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 assert_matches::assert_matches;
6use fidl_fuchsia_update_installer::{
7    InstallerMarker, InstallerProxy, InstallerRequest, InstallerRequestStream, MonitorProxy,
8    Options, RebootControllerRequest, UpdateNotStartedReason,
9};
10use fidl_fuchsia_update_installer_ext::{State, StateId};
11use fuchsia_async as fasync;
12use fuchsia_sync::Mutex;
13use futures::channel::mpsc;
14use futures::prelude::*;
15use pretty_assertions::assert_eq;
16use std::sync::Arc;
17
18#[derive(PartialEq, Debug)]
19pub enum CapturedUpdateInstallerRequest {
20    StartUpdate { url: String, options: Options, reboot_controller_present: bool },
21    SuspendUpdate { attempt_id: Option<String> },
22    ResumeUpdate { attempt_id: Option<String> },
23    CancelUpdate { attempt_id: Option<String> },
24}
25
26// Options does not impl Eq, but it is semantically Eq.
27impl Eq for CapturedUpdateInstallerRequest {}
28
29#[derive(Eq, PartialEq, Debug)]
30pub enum CapturedRebootControllerRequest {
31    Unblock,
32    Detach,
33}
34
35pub struct MockUpdateInstallerServiceBuilder {
36    states: Vec<State>,
37    states_receiver: Option<mpsc::Receiver<State>>,
38    start_update_response: Result<String, UpdateNotStartedReason>,
39}
40
41impl MockUpdateInstallerServiceBuilder {
42    /// The mock installer will sends these states to the caller of StartUpdate.
43    /// It will only work for the first StartUpdate call.
44    /// Ignored if states_receiver exists.
45    pub fn states(self, states: Vec<State>) -> Self {
46        Self { states, ..self }
47    }
48
49    /// When the mock installer receives a state it will forward it to the caller of StartUpdate.
50    /// It will only work for the first StartUpdate call.
51    pub fn states_receiver(self, states_receiver: mpsc::Receiver<State>) -> Self {
52        Self { states_receiver: Some(states_receiver), ..self }
53    }
54
55    pub fn start_update_response(
56        self,
57        start_update_response: Result<String, UpdateNotStartedReason>,
58    ) -> Self {
59        Self { start_update_response, ..self }
60    }
61
62    pub fn build(self) -> MockUpdateInstallerService {
63        let Self { states, states_receiver, start_update_response } = self;
64        let states_receiver = match states_receiver {
65            Some(states_receiver) => states_receiver,
66            None => {
67                let (mut sender, receiver) = mpsc::channel(0);
68                fasync::Task::spawn(async move {
69                    for state in states {
70                        sender.send(state).await.unwrap();
71                    }
72                })
73                .detach();
74                receiver
75            }
76        };
77        MockUpdateInstallerService {
78            start_update_response: Mutex::new(start_update_response),
79            states_receiver: Mutex::new(Some(states_receiver)),
80            captured_args: Mutex::new(vec![]),
81            reboot_controller_requests: Mutex::new(vec![]),
82        }
83    }
84}
85
86pub struct MockUpdateInstallerService {
87    start_update_response: Mutex<Result<String, UpdateNotStartedReason>>,
88    states_receiver: Mutex<Option<mpsc::Receiver<State>>>,
89    captured_args: Mutex<Vec<CapturedUpdateInstallerRequest>>,
90    reboot_controller_requests: Mutex<Vec<CapturedRebootControllerRequest>>,
91}
92
93impl MockUpdateInstallerService {
94    pub fn builder() -> MockUpdateInstallerServiceBuilder {
95        MockUpdateInstallerServiceBuilder {
96            states: vec![],
97            start_update_response: Ok("id".into()),
98            states_receiver: None,
99        }
100    }
101
102    pub fn with_states(states: Vec<State>) -> Self {
103        Self::builder().states(states).build()
104    }
105
106    pub fn with_response(start_update_response: Result<String, UpdateNotStartedReason>) -> Self {
107        Self::builder().start_update_response(start_update_response).build()
108    }
109
110    pub async fn run_service(self: Arc<Self>, mut stream: InstallerRequestStream) {
111        while let Some(req) = stream.try_next().await.unwrap() {
112            match req {
113                InstallerRequest::StartUpdate {
114                    url,
115                    options,
116                    monitor,
117                    reboot_controller,
118                    responder,
119                } => {
120                    self.captured_args.lock().push(CapturedUpdateInstallerRequest::StartUpdate {
121                        url: url.url,
122                        options,
123                        reboot_controller_present: reboot_controller.is_some(),
124                    });
125                    let proxy =
126                        MonitorProxy::new(fasync::Channel::from_channel(monitor.into_channel()));
127                    fasync::Task::spawn(Arc::clone(&self).send_states(proxy)).detach();
128                    if let Some(reboot_controller) = reboot_controller {
129                        let service = Arc::clone(&self);
130                        fasync::Task::spawn(async move {
131                            let mut stream = reboot_controller.into_stream();
132
133                            while let Some(request) = stream.try_next().await.unwrap() {
134                                let request = match request {
135                                    RebootControllerRequest::Unblock { .. } => {
136                                        CapturedRebootControllerRequest::Unblock
137                                    }
138                                    RebootControllerRequest::Detach { .. } => {
139                                        CapturedRebootControllerRequest::Detach
140                                    }
141                                };
142                                service.reboot_controller_requests.lock().push(request);
143                            }
144                        })
145                        .detach();
146                    }
147                    let response = self.start_update_response.lock();
148                    responder.send(response.as_deref().map_err(|e| *e)).unwrap();
149                }
150                InstallerRequest::SuspendUpdate { attempt_id, responder } => {
151                    self.captured_args
152                        .lock()
153                        .push(CapturedUpdateInstallerRequest::SuspendUpdate { attempt_id });
154                    responder.send(Ok(())).unwrap();
155                }
156                InstallerRequest::ResumeUpdate { attempt_id, responder } => {
157                    self.captured_args
158                        .lock()
159                        .push(CapturedUpdateInstallerRequest::ResumeUpdate { attempt_id });
160                    responder.send(Ok(())).unwrap();
161                }
162                InstallerRequest::CancelUpdate { attempt_id, responder } => {
163                    self.captured_args
164                        .lock()
165                        .push(CapturedUpdateInstallerRequest::CancelUpdate { attempt_id });
166                    responder.send(Ok(())).unwrap();
167                }
168                InstallerRequest::MonitorUpdate { .. } => {
169                    panic!("unexpected request: {req:?}");
170                }
171            }
172        }
173    }
174
175    async fn send_states(self: Arc<Self>, monitor: MonitorProxy) {
176        let mut receiver = self
177            .states_receiver
178            .lock()
179            .take()
180            .expect("mock installer only supports a single StartUpdate call");
181
182        while let Some(state) = receiver.next().await {
183            Self::send_state(state, &monitor).await;
184        }
185    }
186
187    async fn send_state(state: State, monitor: &MonitorProxy) {
188        let is_reboot = state.id() == StateId::Reboot;
189        let result = monitor.on_state(&state.into()).await;
190        if is_reboot {
191            assert_matches!(result, Err(_));
192        } else {
193            assert_matches!(result, Ok(()));
194        }
195    }
196
197    pub fn assert_installer_called_with(&self, expected_args: Vec<CapturedUpdateInstallerRequest>) {
198        assert_eq!(*self.captured_args.lock(), expected_args);
199    }
200
201    pub fn assert_reboot_controller_called_with(
202        &self,
203        expected_requests: Vec<CapturedRebootControllerRequest>,
204    ) {
205        assert_eq!(*self.reboot_controller_requests.lock(), expected_requests);
206    }
207
208    pub fn spawn_installer_service(self: Arc<Self>) -> InstallerProxy {
209        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
210
211        fasync::Task::spawn(self.run_service(stream)).detach();
212
213        proxy
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use fidl_fuchsia_update_installer::{Initiator, MonitorMarker, MonitorRequest};
221    use pretty_assertions::assert_eq;
222
223    #[fasync::run_singlethreaded(test)]
224    async fn test_mock_installer() {
225        let installer_service =
226            Arc::new(MockUpdateInstallerService::with_states(vec![State::Prepare]));
227        let proxy = Arc::clone(&installer_service).spawn_installer_service();
228        let url =
229            fidl_fuchsia_pkg::PackageUrl { url: "fuchsia-pkg://fuchsia.com/update".to_string() };
230        let options = Options {
231            initiator: Some(Initiator::User),
232            should_write_recovery: Some(true),
233            allow_attach_to_existing_attempt: Some(true),
234            ..Default::default()
235        };
236        let (monitor_client_end, stream) =
237            fidl::endpoints::create_request_stream::<MonitorMarker>();
238        proxy
239            .start_update(&url, &options, monitor_client_end, None)
240            .await
241            .expect("made start_update call")
242            .expect("start_update call succeeded");
243        assert_eq!(
244            vec![State::Prepare],
245            stream
246                .map_ok(|MonitorRequest::OnState { state, responder }| {
247                    responder.send().unwrap();
248                    State::try_from(state).unwrap()
249                })
250                .try_collect::<Vec<_>>()
251                .await
252                .unwrap()
253        );
254    }
255}