1use anyhow::Error;
17use fidl_fuchsia_io as fio;
18use rand::distr::{Bernoulli, Distribution, StandardUniform};
19use rand::{Rng, RngExt as _};
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 ramdevice_client::RamdiskClient;
167 use rand::RngExt as _;
168
169 struct StepRng {
170 state: u64,
171 increment: u64,
172 }
173
174 impl StepRng {
175 pub fn new(initial: u64, increment: u64) -> Self {
176 Self { state: initial, increment: increment }
177 }
178 }
179
180 impl rand::TryRng for StepRng {
181 type Error = core::convert::Infallible;
182
183 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
184 Ok(self.try_next_u64()? as u32)
185 }
186
187 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
188 let r = self.state;
189 self.state = self.state.wrapping_add(self.increment);
190 Ok(r)
191 }
192
193 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
194 rand::rand_core::utils::fill_bytes_via_next_word(dst, || self.try_next_u64())
195 }
196 }
197
198 fn get_fixture(initial: u64, increment: u64, depth: u32) -> DirectoryEntry {
201 assert_eq!(initial, 0xFFFF0000, "initial value changed - update fixture");
203 assert_eq!(increment, 1, "increment value changed - update fixture");
204 assert_eq!(depth, 2, "depth value changed - update fixture");
205 DirectoryEntry {
206 name: 4294901785,
207 entries: vec![
208 Entry::File(FileEntry { name: 4294901764, contents: vec![3, 0] }),
209 Entry::File(FileEntry { name: 4294901768, contents: vec![7, 0, 255, 255, 0, 0] }),
210 Entry::File(FileEntry {
211 name: 4294901773,
212 contents: vec![11, 0, 255, 255, 0, 0, 0, 0, 12, 0],
213 }),
214 Entry::File(FileEntry {
215 name: 4294901778,
216 contents: vec![16, 0, 255, 255, 0, 0, 0, 0, 17, 0, 255, 255, 0, 0, 0],
217 }),
218 Entry::File(FileEntry {
219 name: 4294901784,
220 contents: vec![
221 21, 0, 255, 255, 0, 0, 0, 0, 22, 0, 255, 255, 0, 0, 0, 0, 23, 0, 255, 255,
222 ],
223 }),
224 ],
225 }
226 }
227
228 #[test]
229 fn gen_tree() {
230 let initial = 0xFFFF0000;
231 let increment = 1;
232 let depth = 2;
233 let mut rng = StepRng::new(initial, increment);
235 let dist = EntryDistribution::new(depth);
236 let tree: DirectoryEntry = rng.sample(dist);
237 let fixture = get_fixture(initial, increment, depth);
240 assert_eq!(fixture, tree);
241 for i in 0..fixture.entries.len() {
242 match &fixture.entries[i] {
243 Entry::File(fixture_file_entry) => match &tree.entries[i] {
244 Entry::File(tree_file_entry) => {
245 assert_eq!(fixture_file_entry.name, tree_file_entry.name)
246 }
247 _ => panic!("expected a file in generated tree"),
248 },
249 _ => panic!("expected a file in fixture tree"),
250 }
251 }
252 }
253
254 #[test]
255 fn same_rng_same_tree() {
256 let dist = EntryDistribution::new(5);
259
260 let tree1: DirectoryEntry = StepRng::new(1337, 1).sample(dist);
261 let tree2: DirectoryEntry = StepRng::new(1337, 1).sample(dist);
262
263 assert_eq!(tree1, tree2);
264 }
265
266 #[fuchsia::test]
267 async fn write_tree() {
268 let root = "/test-root";
269 let initial = 0xFFFF0000;
270 let increment = 1;
271 let depth = 2;
272
273 let mut rng = StepRng::new(initial, increment);
274 let dist = EntryDistribution::new(depth);
275 let tree: DirectoryEntry = rng.sample(dist);
276
277 let ramdisk = RamdiskClient::create(512, 1 << 16).await.expect("failed to make ramdisk");
278 let controller = ramdisk.connector().expect("invalid controller");
279 let mut minfs = fs_management::filesystem::Filesystem::from_boxed_config(
280 controller,
281 Box::new(Minfs::default()),
282 );
283
284 minfs.format().await.expect("failed to format minfs");
285 let mut minfs = minfs.serve().await.expect("failed to mount minfs");
286 minfs.bind_to_path(root).expect("failed to bind path");
287
288 tree.write_tree_at(minfs.root()).await.expect("failed to write tree");
289
290 let fixture = get_fixture(initial, increment, depth);
291 let path = std::path::PathBuf::from(format!("{}/{}", root, fixture.name));
292 assert!(path.is_dir(), "{}", path.display());
293 for (i, entry) in std::fs::read_dir(&path).expect("failed to read directory").enumerate() {
294 let entry = entry.expect("failed to read entry");
295 let file_type = entry.file_type().expect("failed to get file type");
296 assert!(file_type.is_file());
297 let file_name =
298 entry.file_name().into_string().expect("failed to convert file name to string");
299 let expected_name = match &fixture.entries[i] {
300 Entry::File(f) => f.name,
301 Entry::Directory(_) => panic!("expected a file in the fixture tree"),
302 };
303 assert_eq!(file_name, expected_name.to_string());
304 }
305
306 minfs.shutdown().await.expect("failed to unmount minfs");
307 }
308}