mock_installer/
lib.rs
1use 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
26impl 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 pub fn states(self, states: Vec<State>) -> Self {
46 Self { states, ..self }
47 }
48
49 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}