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