mock_reboot/
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 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    /// Creates a new MockRebootService with a given callback to run per call to the service.
19    /// `call_hook` must return a `Result` for each call, which will be sent to
20    /// the caller as the result of the reboot call.
21    pub fn new(
22        call_hook: Box<dyn Fn(ShutdownOptions) -> AdminShutdownResult + Send + Sync>,
23    ) -> Self {
24        Self { call_hook }
25    }
26
27    /// Serves only the reboot portion of the fuchsia.hardware.power.statecontrol protocol on the
28    /// given request stream.
29    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    /// Spawns and detaches a Fuchsia async Task which serves the reboot portion of the
52    /// fuchsia.hardware.power.statecontrol protocol, returning a proxy directly.
53    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        // Succeed when given expected shutdown reason.
146        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        // Error when given unexpected shutdown reason.
157        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}