use anyhow::Error;
use fidl_fuchsia_io as fio;
use rand::{distributions, Rng};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EntryDistribution {
depth: u32,
max_depth: u32,
}
impl EntryDistribution {
pub fn new(max_depth: u32) -> EntryDistribution {
EntryDistribution { depth: 1, max_depth }
}
fn next_level(&self) -> EntryDistribution {
debug_assert!(
self.depth <= self.max_depth,
"directory tree exceeded max depth ({} vs max of {}). programming error.",
self.depth,
self.max_depth
);
EntryDistribution { depth: self.depth + 1, max_depth: self.max_depth }
}
fn directory_distribution(&self) -> distributions::Bernoulli {
distributions::Bernoulli::from_ratio(self.max_depth - self.depth, self.max_depth).unwrap()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FileEntry {
name: u64,
contents: Vec<u8>,
}
impl distributions::Distribution<FileEntry> for distributions::Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> FileEntry {
let size = rng.gen_range(1..1 << 16);
let mut contents = vec![0; size];
rng.fill(contents.as_mut_slice());
FileEntry { name: rng.gen(), contents }
}
}
impl FileEntry {
async fn write_file_at(self, root: &fio::DirectoryProxy) -> Result<(), Error> {
let file = fuchsia_fs::directory::open_file(
root,
&self.name.to_string(),
fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
)
.await?;
fuchsia_fs::file::write(&file, &self.contents).await?;
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DirectoryEntry {
name: u64,
entries: Vec<Entry>,
}
impl distributions::Distribution<DirectoryEntry> for EntryDistribution {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DirectoryEntry {
let num_entries = rng.gen_range(0..6);
let mut entries = vec![];
let entry_dist = self.next_level();
for _ in 0..num_entries {
entries.push(rng.sample(&entry_dist));
}
DirectoryEntry { name: rng.gen(), entries }
}
}
impl DirectoryEntry {
pub fn get_name(&self) -> String {
self.name.to_string()
}
pub fn write_tree_at<'a>(
self,
root: &'a fio::DirectoryProxy,
) -> futures::future::BoxFuture<'a, Result<(), Error>> {
Box::pin(async {
let this = fuchsia_fs::directory::create_directory(
root,
&self.get_name(),
fio::PERM_READABLE | fio::PERM_WRITABLE,
)
.await?;
for entry in self.entries {
match entry {
Entry::File(file_entry) => file_entry.write_file_at(&this).await?,
Entry::Directory(dir_entry) => dir_entry.write_tree_at(&this).await?,
}
}
Ok(())
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Entry {
File(FileEntry),
Directory(DirectoryEntry),
}
impl distributions::Distribution<Entry> for EntryDistribution {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Entry {
if rng.sample(self.directory_distribution()) {
Entry::Directory(rng.sample(self))
} else {
Entry::File(rng.sample(distributions::Standard))
}
}
}
#[cfg(test)]
mod tests {
use super::{DirectoryEntry, Entry, EntryDistribution, FileEntry};
use fs_management::Minfs;
use fuchsia_async as fasync;
use ramdevice_client::RamdiskClient;
use rand::rngs::mock::StepRng;
use rand::Rng as _;
fn get_fixture(initial: u64, increment: u64, depth: u32) -> DirectoryEntry {
assert_eq!(initial, 0xdeadbeef, "initial value changed - update fixture");
assert_eq!(increment, 1, "increment value changed - update fixture");
assert_eq!(depth, 2, "depth value changed - update fixture");
DirectoryEntry {
name: 3735928580,
entries: vec![
Entry::File(FileEntry { name: 3735928563, contents: vec![242] }),
Entry::File(FileEntry { name: 3735928567, contents: vec![246] }),
Entry::File(FileEntry { name: 3735928571, contents: vec![250] }),
Entry::File(FileEntry { name: 3735928575, contents: vec![254] }),
Entry::File(FileEntry { name: 3735928579, contents: vec![2] }),
],
}
}
#[test]
fn gen_tree() {
let initial = 0xdeadbeef;
let increment = 1;
let depth = 2;
let mut rng = StepRng::new(initial, increment);
let dist = EntryDistribution::new(depth);
let tree: DirectoryEntry = rng.sample(dist);
let fixture = get_fixture(initial, increment, depth);
assert_eq!(fixture, tree);
for i in 0..fixture.entries.len() {
match &fixture.entries[i] {
Entry::File(fixture_file_entry) => match &tree.entries[i] {
Entry::File(tree_file_entry) => {
assert_eq!(fixture_file_entry.name, tree_file_entry.name)
}
_ => panic!("expected a file in generated tree"),
},
_ => panic!("expected a file in fixture tree"),
}
}
}
#[test]
fn same_rng_same_tree() {
let dist = EntryDistribution::new(5);
let tree1: DirectoryEntry = StepRng::new(1337, 1).sample(dist);
let tree2: DirectoryEntry = StepRng::new(1337, 1).sample(dist);
assert_eq!(tree1, tree2);
}
#[fasync::run_singlethreaded(test)]
async fn write_tree() {
let root = "/test-root";
let initial = 0xdeadbeef;
let increment = 1;
let depth = 2;
let mut rng = StepRng::new(initial, increment);
let dist = EntryDistribution::new(depth);
let tree: DirectoryEntry = rng.sample(dist);
let mut ramdisk =
RamdiskClient::create(512, 1 << 16).await.expect("failed to make ramdisk");
let controller = ramdisk.take_controller().expect("invalid controller");
let mut minfs = Minfs::new(controller);
minfs.format().await.expect("failed to format minfs");
let mut minfs = minfs.serve().await.expect("failed to mount minfs");
minfs.bind_to_path(root).expect("failed to bind path");
tree.write_tree_at(minfs.root()).await.expect("failed to write tree");
let fixture = get_fixture(initial, increment, depth);
let path = std::path::PathBuf::from(format!("{}/{}", root, fixture.name));
assert!(path.is_dir(), "{}", path.display());
for (i, entry) in std::fs::read_dir(&path).expect("failed to read directory").enumerate() {
let entry = entry.expect("failed to read entry");
let file_type = entry.file_type().expect("failed to get file type");
assert!(file_type.is_file());
let file_name =
entry.file_name().into_string().expect("failed to convert file name to string");
let expected_name = match &fixture.entries[i] {
Entry::File(f) => f.name,
Entry::Directory(_) => panic!("expected a file in the fixture tree"),
};
assert_eq!(file_name, expected_name.to_string());
}
minfs.shutdown().await.expect("failed to unmount minfs");
ramdisk.destroy().await.expect("failed to destroy ramdisk");
}
}