1use anyhow::Error;
6use fidl_fuchsia_hardware_power_statecontrol::{
7 AdminProxy, AdminRequest, AdminRequestStream, AdminShutdownResult, ShutdownOptions,
8};
9use fuchsia_async as fasync;
10use futures::{TryFutureExt, TryStreamExt};
11use std::sync::Arc;
12
13pub struct MockRebootService {
14 call_hook: Box<dyn Fn(ShutdownOptions) -> AdminShutdownResult + Send + Sync>,
15}
16
17impl MockRebootService {
18 pub fn new(
22 call_hook: Box<dyn Fn(ShutdownOptions) -> AdminShutdownResult + Send + Sync>,
23 ) -> Self {
24 Self { call_hook }
25 }
26
27 pub async fn run_reboot_service(
30 self: Arc<Self>,
31 mut stream: AdminRequestStream,
32 ) -> Result<(), Error> {
33 while let Some(event) = stream.try_next().await.expect("received request") {
34 match event {
35 AdminRequest::Shutdown { options, responder } => {
36 let result = if options.action.is_none() {
37 Err(zx::Status::INVALID_ARGS.into_raw())
38 } else {
39 (self.call_hook)(options)
40 };
41 responder.send(result)?;
42 }
43 _ => {
44 panic!("unhandled RebootService method {event:?}");
45 }
46 }
47 }
48 Ok(())
49 }
50
51 pub fn spawn_reboot_service(self: Arc<Self>) -> AdminProxy {
54 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<
55 fidl_fuchsia_hardware_power_statecontrol::AdminMarker,
56 >();
57
58 fasync::Task::spawn(
59 self.run_reboot_service(stream)
60 .unwrap_or_else(|e| panic!("error running reboot service: {e:?}")),
61 )
62 .detach();
63
64 proxy
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use fidl_fuchsia_hardware_power_statecontrol::{ShutdownAction, ShutdownReason};
72 use fuchsia_async as fasync;
73 use std::sync::atomic::{AtomicU32, Ordering};
74
75 #[fasync::run_singlethreaded(test)]
76 async fn test_mock_reboot() {
77 let reboot_service = Arc::new(MockRebootService::new(Box::new(|_| Ok(()))));
78
79 let reboot_service_clone = Arc::clone(&reboot_service);
80 let proxy = reboot_service_clone.spawn_reboot_service();
81
82 proxy
83 .shutdown(&ShutdownOptions {
84 action: Some(ShutdownAction::Reboot),
85 reasons: Some(vec![ShutdownReason::SystemUpdate]),
86 ..Default::default()
87 })
88 .await
89 .expect("made shutdown call")
90 .expect("shutdown call succeeded");
91 }
92
93 #[fasync::run_singlethreaded(test)]
94 async fn test_mock_reboot_fails() {
95 let reboot_service =
96 Arc::new(MockRebootService::new(Box::new(|_| Err(zx::Status::INTERNAL.into_raw()))));
97
98 let reboot_service_clone = Arc::clone(&reboot_service);
99 let proxy = reboot_service_clone.spawn_reboot_service();
100
101 let shutdown_result = proxy
102 .shutdown(&ShutdownOptions {
103 action: Some(ShutdownAction::Reboot),
104 reasons: Some(vec![ShutdownReason::SystemUpdate]),
105 ..Default::default()
106 })
107 .await
108 .expect("made shutdown call");
109 assert_eq!(shutdown_result, Err(zx::Status::INTERNAL.into_raw()));
110 }
111
112 #[fasync::run_singlethreaded(test)]
113 async fn test_mock_reboot_fails_on_no_action() {
114 let reboot_service = Arc::new(MockRebootService::new(Box::new(|_| Ok(()))));
115
116 let reboot_service_clone = Arc::clone(&reboot_service);
117 let proxy = reboot_service_clone.spawn_reboot_service();
118
119 let shutdown_result = proxy
120 .shutdown(&ShutdownOptions {
121 reasons: Some(vec![ShutdownReason::SystemUpdate]),
122 ..Default::default()
123 })
124 .await
125 .expect("made shutdown call");
126 assert_eq!(shutdown_result, Err(zx::Status::INVALID_ARGS.into_raw()));
127 }
128
129 #[fasync::run_singlethreaded(test)]
130 async fn test_mock_reboot_call_hook() {
131 let reboot_service = Arc::new(MockRebootService::new(Box::new(|options| {
132 if let Some(reasons) = options.reasons {
133 match &reasons[..] {
134 [ShutdownReason::DeveloperRequest] => Ok(()),
135 _ => Err(zx::Status::NOT_SUPPORTED.into_raw()),
136 }
137 } else {
138 Err(zx::Status::NOT_SUPPORTED.into_raw())
139 }
140 })));
141
142 let reboot_service_clone = Arc::clone(&reboot_service);
143 let proxy = reboot_service_clone.spawn_reboot_service();
144
145 let () = proxy
147 .shutdown(&ShutdownOptions {
148 action: Some(ShutdownAction::Reboot),
149 reasons: Some(vec![ShutdownReason::DeveloperRequest]),
150 ..Default::default()
151 })
152 .await
153 .expect("made shutdown call")
154 .expect("shutdown call succeeded");
155
156 let error_reboot_result = proxy
158 .shutdown(&ShutdownOptions {
159 action: Some(ShutdownAction::Reboot),
160 reasons: Some(vec![ShutdownReason::SystemUpdate]),
161 ..Default::default()
162 })
163 .await
164 .expect("made shutdown call");
165 assert_eq!(error_reboot_result, Err(zx::Status::NOT_SUPPORTED.into_raw()));
166 }
167
168 #[fasync::run_singlethreaded(test)]
169 async fn test_mock_reboot_with_external_state() {
170 let called = Arc::new(AtomicU32::new(0));
171 let called_clone = Arc::clone(&called);
172 let reboot_service = Arc::new(MockRebootService::new(Box::new(move |_| {
173 called_clone.fetch_add(1, Ordering::SeqCst);
174 Ok(())
175 })));
176
177 let reboot_service_clone = Arc::clone(&reboot_service);
178 let proxy = reboot_service_clone.spawn_reboot_service();
179
180 proxy
181 .shutdown(&ShutdownOptions {
182 action: Some(ShutdownAction::Reboot),
183 reasons: Some(vec![ShutdownReason::SystemUpdate]),
184 ..Default::default()
185 })
186 .await
187 .expect("made shutdown call")
188 .expect("shutdown call succeeded");
189 assert_eq!(called.load(Ordering::SeqCst), 1);
190 }
191}