Skip to main content

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 std::sync::atomic::{AtomicU32, Ordering};
73
74    #[fuchsia::test]
75    async fn test_mock_reboot() {
76        let reboot_service = Arc::new(MockRebootService::new(Box::new(|_| Ok(()))));
77
78        let reboot_service_clone = Arc::clone(&reboot_service);
79        let proxy = reboot_service_clone.spawn_reboot_service();
80
81        proxy
82            .shutdown(&ShutdownOptions {
83                action: Some(ShutdownAction::Reboot),
84                reasons: Some(vec![ShutdownReason::SystemUpdate]),
85                ..Default::default()
86            })
87            .await
88            .expect("made shutdown call")
89            .expect("shutdown call succeeded");
90    }
91
92    #[fuchsia::test]
93    async fn test_mock_reboot_fails() {
94        let reboot_service =
95            Arc::new(MockRebootService::new(Box::new(|_| Err(zx::Status::INTERNAL.into_raw()))));
96
97        let reboot_service_clone = Arc::clone(&reboot_service);
98        let proxy = reboot_service_clone.spawn_reboot_service();
99
100        let shutdown_result = proxy
101            .shutdown(&ShutdownOptions {
102                action: Some(ShutdownAction::Reboot),
103                reasons: Some(vec![ShutdownReason::SystemUpdate]),
104                ..Default::default()
105            })
106            .await
107            .expect("made shutdown call");
108        assert_eq!(shutdown_result, Err(zx::Status::INTERNAL.into_raw()));
109    }
110
111    #[fuchsia::test]
112    async fn test_mock_reboot_fails_on_no_action() {
113        let reboot_service = Arc::new(MockRebootService::new(Box::new(|_| Ok(()))));
114
115        let reboot_service_clone = Arc::clone(&reboot_service);
116        let proxy = reboot_service_clone.spawn_reboot_service();
117
118        let shutdown_result = proxy
119            .shutdown(&ShutdownOptions {
120                reasons: Some(vec![ShutdownReason::SystemUpdate]),
121                ..Default::default()
122            })
123            .await
124            .expect("made shutdown call");
125        assert_eq!(shutdown_result, Err(zx::Status::INVALID_ARGS.into_raw()));
126    }
127
128    #[fuchsia::test]
129    async fn test_mock_reboot_call_hook() {
130        let reboot_service = Arc::new(MockRebootService::new(Box::new(|options| {
131            if let Some(reasons) = options.reasons {
132                match &reasons[..] {
133                    [ShutdownReason::DeveloperRequest] => Ok(()),
134                    _ => Err(zx::Status::NOT_SUPPORTED.into_raw()),
135                }
136            } else {
137                Err(zx::Status::NOT_SUPPORTED.into_raw())
138            }
139        })));
140
141        let reboot_service_clone = Arc::clone(&reboot_service);
142        let proxy = reboot_service_clone.spawn_reboot_service();
143
144        // Succeed when given expected shutdown reason.
145        let () = proxy
146            .shutdown(&ShutdownOptions {
147                action: Some(ShutdownAction::Reboot),
148                reasons: Some(vec![ShutdownReason::DeveloperRequest]),
149                ..Default::default()
150            })
151            .await
152            .expect("made shutdown call")
153            .expect("shutdown call succeeded");
154
155        // Error when given unexpected shutdown reason.
156        let error_reboot_result = proxy
157            .shutdown(&ShutdownOptions {
158                action: Some(ShutdownAction::Reboot),
159                reasons: Some(vec![ShutdownReason::SystemUpdate]),
160                ..Default::default()
161            })
162            .await
163            .expect("made shutdown call");
164        assert_eq!(error_reboot_result, Err(zx::Status::NOT_SUPPORTED.into_raw()));
165    }
166
167    #[fuchsia::test]
168    async fn test_mock_reboot_with_external_state() {
169        let called = Arc::new(AtomicU32::new(0));
170        let called_clone = Arc::clone(&called);
171        let reboot_service = Arc::new(MockRebootService::new(Box::new(move |_| {
172            called_clone.fetch_add(1, Ordering::SeqCst);
173            Ok(())
174        })));
175
176        let reboot_service_clone = Arc::clone(&reboot_service);
177        let proxy = reboot_service_clone.spawn_reboot_service();
178
179        proxy
180            .shutdown(&ShutdownOptions {
181                action: Some(ShutdownAction::Reboot),
182                reasons: Some(vec![ShutdownReason::SystemUpdate]),
183                ..Default::default()
184            })
185            .await
186            .expect("made shutdown call")
187            .expect("shutdown call succeeded");
188        assert_eq!(called.load(Ordering::SeqCst), 1);
189    }
190}