1use anyhow::Error;
17use fidl_fuchsia_io as fio;
18use rand::Rng;
19use rand::distr::{Bernoulli, Distribution, StandardUniform};
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct EntryDistribution {
26 depth: u32,
27 max_depth: u32,
28}
29
30impl EntryDistribution {
31 pub fn new(max_depth: u32) -> EntryDistribution {
34 EntryDistribution { depth: 1, max_depth }
35 }
36
37 fn next_level(&self) -> EntryDistribution {
39 debug_assert!(
40 self.depth <= self.max_depth,
41 "directory tree exceeded max depth ({} vs max of {}). programming error.",
42 self.depth,
43 self.max_depth
44 );
45 EntryDistribution { depth: self.depth + 1, max_depth: self.max_depth }
46 }
47
48 fn directory_distribution(&self) -> Bernoulli {
51 Bernoulli::from_ratio(self.max_depth - self.depth, self.max_depth).unwrap()
52 }
53}
54
55#[derive(Debug, Clone, Eq, PartialEq)]
58pub struct FileEntry {
59 name: u64,
60 contents: Vec<u8>,
61}
62
63impl Distribution<FileEntry> for StandardUniform {
64 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> FileEntry {
65 let size = usize::from(rng.random::<u16>());
66 let mut contents = vec![0; size];
72 rng.fill(contents.as_mut_slice());
73 FileEntry { name: rng.random(), contents }
74 }
75}
76
77impl FileEntry {
78 async fn write_file_at(self, root: &fio::DirectoryProxy) -> Result<(), Error> {
79 let file = fuchsia_fs::directory::open_file(
80 root,
81 &self.name.to_string(),
82 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_WRITABLE,
83 )
84 .await?;
85 fuchsia_fs::file::write(&file, &self.contents).await?;
86 Ok(())
87 }
88}
89
90#[derive(Clone, Debug, Eq, PartialEq)]
95pub struct DirectoryEntry {
96 name: u64,
97 entries: Vec<Entry>,
98}
99
100impl Distribution<DirectoryEntry> for EntryDistribution {
101 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DirectoryEntry {
102 let num_entries = rng.random_range(0..6);
104 let mut entries = vec![];
105 let entry_dist = self.next_level();
106 for _ in 0..num_entries {
107 entries.push(rng.sample(&entry_dist));
108 }
109 DirectoryEntry { name: rng.random(), entries }
110 }
111}
112
113impl DirectoryEntry {
114 pub fn get_name(&self) -> String {
116 self.name.to_string()
117 }
118
119 pub fn write_tree_at<'a>(
121 self,
122 root: &'a fio::DirectoryProxy,
123 ) -> futures::future::BoxFuture<'a, Result<(), Error>> {
124 Box::pin(async {
125 let this = fuchsia_fs::directory::create_directory(
126 root,
127 &self.get_name(),
128 fio::PERM_READABLE | fio::PERM_WRITABLE,
129 )
130 .await?;
131 for entry in self.entries {
132 match entry {
133 Entry::File(file_entry) => file_entry.write_file_at(&this).await?,
134 Entry::Directory(dir_entry) => dir_entry.write_tree_at(&this).await?,
135 }
136 }
137 Ok(())
138 })
139 }
140}
141
142#[derive(Clone, Debug, Eq, PartialEq)]
145pub enum Entry {
146 File(FileEntry),
148 Directory(DirectoryEntry),
150}
151
152impl Distribution<Entry> for EntryDistribution {
153 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Entry {
154 if rng.sample(self.directory_distribution()) {
155 Entry::Directory(rng.sample(self))
156 } else {
157 Entry::File(rng.sample(StandardUniform))
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::{DirectoryEntry, Entry, EntryDistribution, FileEntry};
165 use fs_management::Minfs;
166 use fuchsia_async as fasync;
167 use ramdevice_client::RamdiskClient;
168 use rand::{Rng as _, RngCore};
169
170 struct StepRng {
171 state: u64,
172 increment: u64,
173 }
174
175 impl StepRng {
176 pub fn new(initial: u64, increment: u64) -> Self {
177 Self { state: initial, increment: increment }
178 }
179 }
180
181 impl RngCore for StepRng {
182 fn next_u32(&mut self) -> u32 {
183 self.next_u64() as u32
184 }
185
186 fn next_u64(&mut self) -> u64 {
187 let r = self.state;
188 self.state = self.state.wrapping_add(self.increment);
189 r
190 }
191
192 fn fill_bytes(&mut self, dst: &mut [u8]) {
193 rand_core::impls::fill_bytes_via_next(self, dst)
194 }
195 }
196
197 fn get_fixture(initial: u64, increment: u64, depth: u32) -> DirectoryEntry {
200 assert_eq!(initial, 0xFFFF0000, "initial value changed - update fixture");
202 assert_eq!(increment, 1, "increment value changed - update fixture");
203 assert_eq!(depth, 2, "depth value changed - update fixture");
204 DirectoryEntry {
205 name: 4294901785,
206 entries: vec![
207 Entry::File(FileEntry { name: 4294901764, contents: vec![3, 0] }),
208 Entry::File(FileEntry { name: 4294901768, contents: vec![7, 0, 255, 255, 0, 0] }),
209 Entry::File(FileEntry {
210 name: 4294901773,
211 contents: vec![11, 0, 255, 255, 0, 0, 0, 0, 12, 0],
212 }),
213 Entry::File(FileEntry {
214 name: 4294901778,
215 contents: vec![16, 0, 255, 255, 0, 0, 0, 0, 17, 0, 255, 255, 0, 0, 0],
216 }),
217 Entry::File(FileEntry {
218 name: 4294901784,
219 contents: vec![
220 21, 0, 255, 255, 0, 0, 0, 0, 22, 0, 255, 255, 0, 0, 0, 0, 23, 0, 255, 255,
221 ],
222 }),
223 ],
224 }
225 }
226
227 #[test]
228 fn gen_tree() {
229 let initial = 0xFFFF0000;
230 let increment = 1;
231 let depth = 2;
232 let mut rng = StepRng::new(initial, increment);
234 let dist = EntryDistribution::new(depth);
235 let tree: DirectoryEntry = rng.sample(dist);
236 let fixture = get_fixture(initial, increment, depth);
239 assert_eq!(fixture, tree);
240 for i in 0..fixture.entries.len() {
241 match &fixture.entries[i] {
242 Entry::File(fixture_file_entry) => match &tree.entries[i] {
243 Entry::File(tree_file_entry) => {
244 assert_eq!(fixture_file_entry.name, tree_file_entry.name)
245 }
246 _ => panic!("expected a file in generated tree"),
247 },
248 _ => panic!("expected a file in fixture tree"),
249 }
250 }
251 }
252
253 #[test]
254 fn same_rng_same_tree() {
255 let dist = EntryDistribution::new(5);
258
259 let tree1: DirectoryEntry = StepRng::new(1337, 1).sample(dist);
260 let tree2: DirectoryEntry = StepRng::new(1337, 1).sample(dist);
261
262 assert_eq!(tree1, tree2);
263 }
264
265 #[fasync::run_singlethreaded(test)]
266 async fn write_tree() {
267 let root = "/test-root";
268 let initial = 0xFFFF0000;
269 let increment = 1;
270 let depth = 2;
271
272 let mut rng = StepRng::new(initial, increment);
273 let dist = EntryDistribution::new(depth);
274 let tree: DirectoryEntry = rng.sample(dist);
275
276 let ramdisk = RamdiskClient::create(512, 1 << 16).await.expect("failed to make ramdisk");
277 let controller = ramdisk.open_controller().expect("invalid controller");
278 let mut minfs = Minfs::new(controller);
279
280 minfs.format().await.expect("failed to format minfs");
281 let mut minfs = minfs.serve().await.expect("failed to mount minfs");
282 minfs.bind_to_path(root).expect("failed to bind path");
283
284 tree.write_tree_at(minfs.root()).await.expect("failed to write tree");
285
286 let fixture = get_fixture(initial, increment, depth);
287 let path = std::path::PathBuf::from(format!("{}/{}", root, fixture.name));
288 assert!(path.is_dir(), "{}", path.display());
289 for (i, entry) in std::fs::read_dir(&path).expect("failed to read directory").enumerate() {
290 let entry = entry.expect("failed to read entry");
291 let file_type = entry.file_type().expect("failed to get file type");
292 assert!(file_type.is_file());
293 let file_name =
294 entry.file_name().into_string().expect("failed to convert file name to string");
295 let expected_name = match &fixture.entries[i] {
296 Entry::File(f) => f.name,
297 Entry::Directory(_) => panic!("expected a file in the fixture tree"),
298 };
299 assert_eq!(file_name, expected_name.to_string());
300 }
301
302 minfs.shutdown().await.expect("failed to unmount minfs");
303 ramdisk.destroy().await.expect("failed to destroy ramdisk");
304 }
305}