blob: 12cb7d671fc0319359cb6519a28fb4952653334f [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::Error;
use fidl::endpoints::{create_endpoints, ClientEnd};
use fidl_fuchsia_io as fio;
use fidl_fuchsia_test_manager as ftest_manager;
use futures::stream::{FusedStream, StreamExt, TryStreamExt};
use log::warn;
const ITERATOR_BATCH_SIZE: usize = 10;
/// Serves the |DebugDataIterator| protocol by serving all the files contained under
/// |dir_path|.
///
/// The contents under |dir_path| are assumed to not change while the iterator is served.
pub async fn serve_iterator(
dir_path: &str,
mut iterator: ftest_manager::DebugDataIteratorRequestStream,
) -> Result<(), Error> {
let directory =
io_util::open_directory_in_namespace(dir_path, io_util::OpenFlags::RIGHT_READABLE)?;
let mut file_stream = files_async::readdir_recursive(&directory, None)
.filter_map(|entry_result| {
let result = match entry_result {
Ok(files_async::DirEntry { name, kind }) => match kind {
files_async::DirentKind::File => Some(name),
_ => None,
},
Err(e) => {
warn!("Error reading directory in {}: {:?}", dir_path, e);
None
}
};
futures::future::ready(result)
})
.fuse();
while let Some(request) = iterator.try_next().await? {
let ftest_manager::DebugDataIteratorRequest::GetNext { responder } = request;
let next_files = match file_stream.is_terminated() {
true => vec![],
false => file_stream.by_ref().take(ITERATOR_BATCH_SIZE).collect().await,
};
let debug_data = next_files
.into_iter()
.map(|file_name| {
let (file, server) = create_endpoints::<fio::NodeMarker>()?;
directory.open(io_util::OpenFlags::RIGHT_READABLE, 0, &file_name, server)?;
Ok(ftest_manager::DebugData {
file: Some(ClientEnd::new(file.into_channel())),
name: file_name.into(),
..ftest_manager::DebugData::EMPTY
})
})
.collect::<Result<Vec<_>, Error>>()?;
let _ = responder.send(&mut debug_data.into_iter());
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use fidl::endpoints::create_proxy_and_stream;
use futures::future::TryFutureExt;
use std::{
collections::HashMap,
fs::{DirBuilder, File},
io::Write,
};
use tempfile::tempdir;
async fn iterator_test<F, Fut>(dir: &tempfile::TempDir, test_fn: F)
where
F: FnOnce(ftest_manager::DebugDataIteratorProxy) -> Fut,
Fut: futures::Future<Output = ()>,
{
let (proxy, stream) =
create_proxy_and_stream::<ftest_manager::DebugDataIteratorMarker>().unwrap();
futures::future::join(
serve_iterator(dir.path().as_os_str().to_str().unwrap(), stream)
.unwrap_or_else(|e| panic!("Iterator server failed: {:?}", e)),
test_fn(proxy),
)
.await;
}
#[fuchsia::test]
async fn serve_empty_dir() {
let dir = tempdir().unwrap();
iterator_test(&dir, |proxy| async move {
let results = proxy.get_next().await.expect("get next");
assert!(results.is_empty());
})
.await;
}
#[fuchsia::test]
async fn serve_single_file_dir() {
let dir = tempdir().unwrap();
let mut f = File::create(dir.path().join("test-file")).expect("create file");
writeln!(f, "test file content").expect("write to test file");
drop(f);
iterator_test(&dir, |proxy| async move {
let results = proxy.get_next().await.expect("get next");
assert_eq!(results.len(), 1);
let result = results.into_iter().next().unwrap();
assert_eq!(result.name.unwrap(), "test-file");
let file_proxy = result.file.unwrap().into_proxy().expect("create proxy");
assert_eq!(
io_util::read_file(&file_proxy).await.expect("read file"),
"test file content\n"
);
})
.await;
}
#[fuchsia::test]
async fn serve_single_nested_file_dir() {
let dir = tempdir().unwrap();
DirBuilder::new().create(dir.path().join("subdir")).expect("create dir");
let mut f = File::create(dir.path().join("subdir").join("test-file")).expect("create file");
writeln!(f, "test file content").expect("write to test file");
drop(f);
iterator_test(&dir, |proxy| async move {
let results = proxy.get_next().await.expect("get next");
assert_eq!(results.len(), 1);
let result = results.into_iter().next().unwrap();
assert_eq!(result.name.unwrap(), "subdir/test-file");
let file_proxy = result.file.unwrap().into_proxy().expect("create proxy");
assert_eq!(
io_util::read_file(&file_proxy).await.expect("read file"),
"test file content\n"
);
})
.await;
}
#[fuchsia::test]
async fn serve_multiple() {
let dir = tempdir().unwrap();
DirBuilder::new().create(dir.path().join("subdir")).expect("create dir");
let mut expected_files = HashMap::new();
for i in 0..ITERATOR_BATCH_SIZE + 1 {
let file_name = format!("test-file-{:?}", i);
let contents = format!("test file {:?} content\n", i);
let mut f = File::create(dir.path().join(&file_name)).expect("create file");
write!(f, "{}", &contents).expect("write to test file");
expected_files.insert(file_name, contents);
}
for i in 0..ITERATOR_BATCH_SIZE + 1 {
let file_name = format!("test-file-{:?}", i);
let contents = format!("test subdir file {:?} content\n", i);
let mut f =
File::create(dir.path().join("subdir").join(&file_name)).expect("create file");
write!(f, "{}", &contents).expect("write to test file");
expected_files.insert(format!("subdir/{}", file_name), contents);
}
iterator_test(&dir, move |proxy| async move {
let mut raw_results = vec![];
loop {
let next = proxy.get_next().await.expect("get next");
if next.is_empty() {
break;
} else {
raw_results.extend(next);
}
}
let actual_file_and_contents: HashMap<_, _> = futures::stream::iter(raw_results)
.then(|debug_data| async move {
let contents =
io_util::read_file(&debug_data.file.unwrap().into_proxy().unwrap())
.await
.expect("read file");
(debug_data.name.unwrap(), contents)
})
.collect()
.await;
assert_eq!(expected_files, actual_file_and_contents);
})
.await;
}
}