mock_crash_reporter/
lib.rs

1// Copyright 2021 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 fidl_fuchsia_feedback::{
6    CrashReporterMarker, CrashReporterProxy, CrashReporterRequestStream, FileReportResults,
7    FilingError,
8};
9use fuchsia_async::Task;
10use futures::channel::mpsc;
11use futures::future::BoxFuture;
12use futures::lock::Mutex;
13use futures::prelude::*;
14use futures::TryStreamExt;
15use std::sync::Arc;
16
17pub use fidl_fuchsia_feedback::CrashReport;
18
19/// A call hook that can be used to inject responses into the CrashReporter service.
20pub trait Hook: Send + Sync {
21    /// Describes what the file_report call will return.
22    fn file_report(
23        &self,
24        report: CrashReport,
25    ) -> BoxFuture<'static, Result<FileReportResults, FilingError>>;
26}
27
28impl<F> Hook for F
29where
30    F: Fn(CrashReport) -> Result<FileReportResults, FilingError> + Send + Sync,
31{
32    fn file_report(
33        &self,
34        report: CrashReport,
35    ) -> BoxFuture<'static, Result<FileReportResults, FilingError>> {
36        future::ready(self(report)).boxed()
37    }
38}
39
40pub struct MockCrashReporterService {
41    call_hook: Box<dyn Hook>,
42}
43
44impl MockCrashReporterService {
45    /// Creates a new MockCrashReporterService with a given callback to run per call to the service.
46    pub fn new(hook: impl Hook + 'static) -> Self {
47        Self { call_hook: Box::new(hook) }
48    }
49
50    /// Spawns an `fasync::Task` which serves fuchsia.feedback/CrashReporter.
51    pub fn spawn_crash_reporter_service(self: Arc<Self>) -> (CrashReporterProxy, Task<()>) {
52        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<CrashReporterMarker>();
53
54        let task = Task::spawn(self.run_crash_reporter_service(stream));
55
56        (proxy, task)
57    }
58
59    /// Serves fuchsia.feedback/CrashReporter.FileReport requests on the given request stream.
60    pub async fn run_crash_reporter_service(
61        self: Arc<Self>,
62        mut stream: CrashReporterRequestStream,
63    ) {
64        while let Some(event) = stream.try_next().await.expect("received CrashReporter request") {
65            match event {
66                fidl_fuchsia_feedback::CrashReporterRequest::FileReport { report, responder } => {
67                    let res = self.call_hook.file_report(report).await;
68                    match res {
69                        Err(_) => responder.send(Err(FilingError::InvalidArgsError)).unwrap(),
70                        Ok(_) => responder.send(Ok(&FileReportResults::default())).unwrap(),
71                    }
72                }
73            }
74        }
75    }
76}
77
78/// Hook that can be used to yield control of the `file_report` call to the caller. The caller can
79/// control when `file_report` calls complete by pulling from the mpsc::Receiver.
80pub struct ThrottleHook {
81    file_response: Result<FileReportResults, FilingError>,
82    sender: Arc<Mutex<mpsc::Sender<CrashReport>>>,
83}
84
85impl ThrottleHook {
86    pub fn new(
87        file_response: Result<FileReportResults, FilingError>,
88    ) -> (Self, mpsc::Receiver<CrashReport>) {
89        // We deliberately give a capacity of 1 so that the caller must pull from the
90        // receiver in order to unblock the `file_report` call.
91        let (sender, recv) = mpsc::channel(0);
92        (Self { file_response, sender: Arc::new(Mutex::new(sender)) }, recv)
93    }
94}
95impl Hook for ThrottleHook {
96    fn file_report(
97        &self,
98        report: CrashReport,
99    ) -> BoxFuture<'static, Result<FileReportResults, FilingError>> {
100        let sender = Arc::clone(&self.sender);
101        let file_response = self.file_response.clone();
102
103        async move {
104            sender.lock().await.send(report).await.unwrap();
105            file_response
106        }
107        .boxed()
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use fuchsia_async as fasync;
115    use std::sync::atomic::{AtomicU32, Ordering};
116
117    #[fasync::run_singlethreaded(test)]
118    async fn test_mock_crash_reporter() {
119        let mock = Arc::new(MockCrashReporterService::new(|_| Ok(FileReportResults::default())));
120        let (proxy, _server) = mock.spawn_crash_reporter_service();
121
122        let file_result = proxy.file_report(CrashReport::default()).await.expect("made fidl call");
123
124        assert_eq!(file_result, Ok(FileReportResults::default()));
125    }
126
127    #[fasync::run_singlethreaded(test)]
128    async fn test_mock_crash_reporter_fails() {
129        let mock = Arc::new(MockCrashReporterService::new(|_| Err(FilingError::InvalidArgsError)));
130        let (proxy, _server) = mock.spawn_crash_reporter_service();
131
132        let file_result = proxy.file_report(CrashReport::default()).await.expect("made fidl call");
133
134        assert_eq!(file_result, Err(FilingError::InvalidArgsError));
135    }
136
137    #[fasync::run_singlethreaded(test)]
138    async fn test_mock_crash_reporter_with_external_state() {
139        let called = Arc::new(AtomicU32::new(0));
140        let called_clone = Arc::clone(&called);
141        let mock = Arc::new(MockCrashReporterService::new(move |_| {
142            called_clone.fetch_add(1, Ordering::SeqCst);
143            Ok(FileReportResults::default())
144        }));
145        let (proxy, _server) = mock.spawn_crash_reporter_service();
146
147        let file_result = proxy.file_report(CrashReport::default()).await.expect("made fidl call");
148
149        assert_eq!(file_result, Ok(FileReportResults::default()));
150        assert_eq!(called.load(Ordering::SeqCst), 1);
151    }
152}