use fidl_fuchsia_feedback::{
CrashReporterMarker, CrashReporterProxy, CrashReporterRequestStream, FileReportResults,
FilingError,
};
use fuchsia_async::Task;
use futures::channel::mpsc;
use futures::future::BoxFuture;
use futures::lock::Mutex;
use futures::prelude::*;
use futures::TryStreamExt;
use std::sync::Arc;
pub use fidl_fuchsia_feedback::CrashReport;
pub trait Hook: Send + Sync {
fn file_report(
&self,
report: CrashReport,
) -> BoxFuture<'static, Result<FileReportResults, FilingError>>;
}
impl<F> Hook for F
where
F: Fn(CrashReport) -> Result<FileReportResults, FilingError> + Send + Sync,
{
fn file_report(
&self,
report: CrashReport,
) -> BoxFuture<'static, Result<FileReportResults, FilingError>> {
future::ready(self(report)).boxed()
}
}
pub struct MockCrashReporterService {
call_hook: Box<dyn Hook>,
}
impl MockCrashReporterService {
pub fn new(hook: impl Hook + 'static) -> Self {
Self { call_hook: Box::new(hook) }
}
pub fn spawn_crash_reporter_service(self: Arc<Self>) -> (CrashReporterProxy, Task<()>) {
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<CrashReporterMarker>().unwrap();
let task = Task::spawn(self.run_crash_reporter_service(stream));
(proxy, task)
}
pub async fn run_crash_reporter_service(
self: Arc<Self>,
mut stream: CrashReporterRequestStream,
) {
while let Some(event) = stream.try_next().await.expect("received CrashReporter request") {
match event {
fidl_fuchsia_feedback::CrashReporterRequest::FileReport { report, responder } => {
let res = self.call_hook.file_report(report).await;
match res {
Err(_) => responder.send(Err(FilingError::InvalidArgsError)).unwrap(),
Ok(_) => responder.send(Ok(&FileReportResults::default())).unwrap(),
}
}
}
}
}
}
pub struct ThrottleHook {
file_response: Result<FileReportResults, FilingError>,
sender: Arc<Mutex<mpsc::Sender<CrashReport>>>,
}
impl ThrottleHook {
pub fn new(
file_response: Result<FileReportResults, FilingError>,
) -> (Self, mpsc::Receiver<CrashReport>) {
let (sender, recv) = mpsc::channel(0);
(Self { file_response, sender: Arc::new(Mutex::new(sender)) }, recv)
}
}
impl Hook for ThrottleHook {
fn file_report(
&self,
report: CrashReport,
) -> BoxFuture<'static, Result<FileReportResults, FilingError>> {
let sender = Arc::clone(&self.sender);
let file_response = self.file_response.clone();
async move {
sender.lock().await.send(report).await.unwrap();
file_response
}
.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_async as fasync;
use std::sync::atomic::{AtomicU32, Ordering};
#[fasync::run_singlethreaded(test)]
async fn test_mock_crash_reporter() {
let mock = Arc::new(MockCrashReporterService::new(|_| Ok(FileReportResults::default())));
let (proxy, _server) = mock.spawn_crash_reporter_service();
let file_result = proxy.file_report(CrashReport::default()).await.expect("made fidl call");
assert_eq!(file_result, Ok(FileReportResults::default()));
}
#[fasync::run_singlethreaded(test)]
async fn test_mock_crash_reporter_fails() {
let mock = Arc::new(MockCrashReporterService::new(|_| Err(FilingError::InvalidArgsError)));
let (proxy, _server) = mock.spawn_crash_reporter_service();
let file_result = proxy.file_report(CrashReport::default()).await.expect("made fidl call");
assert_eq!(file_result, Err(FilingError::InvalidArgsError));
}
#[fasync::run_singlethreaded(test)]
async fn test_mock_crash_reporter_with_external_state() {
let called = Arc::new(AtomicU32::new(0));
let called_clone = Arc::clone(&called);
let mock = Arc::new(MockCrashReporterService::new(move |_| {
called_clone.fetch_add(1, Ordering::SeqCst);
Ok(FileReportResults::default())
}));
let (proxy, _server) = mock.spawn_crash_reporter_service();
let file_result = proxy.file_report(CrashReport::default()).await.expect("made fidl call");
assert_eq!(file_result, Ok(FileReportResults::default()));
assert_eq!(called.load(Ordering::SeqCst), 1);
}
}