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