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    AdminPerformRebootResult, AdminProxy, AdminRequest, AdminRequestStream, RebootOptions,
8};
9use fuchsia_async as fasync;
10use futures::{TryFutureExt, TryStreamExt};
11use std::sync::Arc;
12
13pub struct MockRebootService {
14    call_hook: Box<dyn Fn(RebootOptions) -> AdminPerformRebootResult + 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(RebootOptions) -> AdminPerformRebootResult + 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::PerformReboot { options, responder } => {
36                    let result = (self.call_hook)(options);
37                    responder.send(result)?;
38                }
39                _ => {
40                    panic!("unhandled RebootService method {event:?}");
41                }
42            }
43        }
44        Ok(())
45    }
46
47    /// Spawns and detaches a Fuchsia async Task which serves the reboot portion of the
48    /// fuchsia.hardware.power.statecontrol protocol, returning a proxy directly.
49    pub fn spawn_reboot_service(self: Arc<Self>) -> AdminProxy {
50        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<
51            fidl_fuchsia_hardware_power_statecontrol::AdminMarker,
52        >();
53
54        fasync::Task::spawn(
55            self.run_reboot_service(stream)
56                .unwrap_or_else(|e| panic!("error running reboot service: {e:?}")),
57        )
58        .detach();
59
60        proxy
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use fidl_fuchsia_hardware_power_statecontrol::RebootReason2;
68    use fuchsia_async as fasync;
69    use std::sync::atomic::{AtomicU32, Ordering};
70
71    #[fasync::run_singlethreaded(test)]
72    async fn test_mock_reboot() {
73        let reboot_service = Arc::new(MockRebootService::new(Box::new(|_| Ok(()))));
74
75        let reboot_service_clone = Arc::clone(&reboot_service);
76        let proxy = reboot_service_clone.spawn_reboot_service();
77
78        proxy
79            .perform_reboot(&RebootOptions {
80                reasons: Some(vec![RebootReason2::SystemUpdate]),
81                ..Default::default()
82            })
83            .await
84            .expect("made reboot call")
85            .expect("reboot call succeeded");
86    }
87
88    #[fasync::run_singlethreaded(test)]
89    async fn test_mock_reboot_fails() {
90        let reboot_service =
91            Arc::new(MockRebootService::new(Box::new(|_| Err(zx::Status::INTERNAL.into_raw()))));
92
93        let reboot_service_clone = Arc::clone(&reboot_service);
94        let proxy = reboot_service_clone.spawn_reboot_service();
95
96        let reboot_result = proxy
97            .perform_reboot(&RebootOptions {
98                reasons: Some(vec![RebootReason2::SystemUpdate]),
99                ..Default::default()
100            })
101            .await
102            .expect("made reboot call");
103        assert_eq!(reboot_result, Err(zx::Status::INTERNAL.into_raw()));
104    }
105
106    #[fasync::run_singlethreaded(test)]
107    async fn test_mock_reboot_call_hook() {
108        let reboot_service = Arc::new(MockRebootService::new(Box::new(|options| {
109            if let Some(reasons) = options.reasons {
110                match &reasons[..] {
111                    [RebootReason2::UserRequest] => Ok(()),
112                    _ => Err(zx::Status::NOT_SUPPORTED.into_raw()),
113                }
114            } else {
115                Err(zx::Status::NOT_SUPPORTED.into_raw())
116            }
117        })));
118
119        let reboot_service_clone = Arc::clone(&reboot_service);
120        let proxy = reboot_service_clone.spawn_reboot_service();
121
122        // Succeed when given expected reboot reason.
123        let () = proxy
124            .perform_reboot(&RebootOptions {
125                reasons: Some(vec![RebootReason2::UserRequest]),
126                ..Default::default()
127            })
128            .await
129            .expect("made reboot call")
130            .expect("reboot call succeeded");
131
132        // Error when given unexpected reboot reason.
133        let error_reboot_result = proxy
134            .perform_reboot(&RebootOptions {
135                reasons: Some(vec![RebootReason2::SystemUpdate]),
136                ..Default::default()
137            })
138            .await
139            .expect("made reboot call");
140        assert_eq!(error_reboot_result, Err(zx::Status::NOT_SUPPORTED.into_raw()));
141    }
142
143    #[fasync::run_singlethreaded(test)]
144    async fn test_mock_reboot_with_external_state() {
145        let called = Arc::new(AtomicU32::new(0));
146        let called_clone = Arc::clone(&called);
147        let reboot_service = Arc::new(MockRebootService::new(Box::new(move |_| {
148            called_clone.fetch_add(1, Ordering::SeqCst);
149            Ok(())
150        })));
151
152        let reboot_service_clone = Arc::clone(&reboot_service);
153        let proxy = reboot_service_clone.spawn_reboot_service();
154
155        proxy
156            .perform_reboot(&RebootOptions {
157                reasons: Some(vec![RebootReason2::SystemUpdate]),
158                ..Default::default()
159            })
160            .await
161            .expect("made reboot call")
162            .expect("reboot call succeeded");
163        assert_eq!(called.load(Ordering::SeqCst), 1);
164    }
165}