1use crate::{Benchmark, CacheClearableFilesystem, Filesystem, OperationDuration, OperationTimer};
6use async_trait::async_trait;
7use std::collections::VecDeque;
8use std::ffi::{CStr, CString};
9use std::mem::MaybeUninit;
10use std::ops::Range;
11use std::os::fd::RawFd;
12use std::os::unix::ffi::OsStringExt;
13use std::path::{Path, PathBuf};
14
15#[derive(Copy, Clone)]
17pub struct DirectoryTreeStructure {
18 pub files_per_directory: u64,
20
21 pub directories_per_directory: u64,
24
25 pub max_depth: u32,
27}
28
29impl DirectoryTreeStructure {
30 fn max_pending(&self) -> u64 {
33 self.directories_per_directory.pow(self.max_depth)
34 }
35
36 fn create_directory_tree(&self, root: PathBuf) {
38 struct DirInfo {
39 path: PathBuf,
40 depth: u32,
41 }
42 let mut pending = VecDeque::new();
43 pending.push_back(DirInfo { path: root, depth: 0 });
44 while let Some(dir) = pending.pop_front() {
45 for i in 0..self.files_per_directory {
46 let path = dir.path.join(file_name(i));
47 std::fs::File::create(path).unwrap();
48 }
49 if self.max_depth == dir.depth {
50 continue;
51 }
52 for i in 0..self.directories_per_directory {
53 let path = dir.path.join(dir_name(i));
54 std::fs::create_dir(&path).unwrap();
55 pending.push_back(DirInfo { path, depth: dir.depth + 1 });
56 }
57 }
58 }
59
60 fn enumerate_paths(&self) -> Vec<PathBuf> {
62 let mut paths = Vec::new();
63 self.enumerate_paths_impl(Path::new(""), &mut paths);
64 paths
65 }
66
67 fn enumerate_paths_impl(&self, base: &Path, paths: &mut Vec<PathBuf>) {
68 for i in 0..self.files_per_directory {
69 paths.push(base.join(file_name(i)));
70 }
71 if self.max_depth == 0 {
72 return;
73 }
74 for i in 0..self.directories_per_directory {
75 let path = base.join(dir_name(i));
76 paths.push(path.clone());
77 Self { max_depth: self.max_depth - 1, ..*self }.enumerate_paths_impl(&path, paths);
78 }
79 }
80}
81
82#[derive(Clone)]
85pub struct WalkDirectoryTreeCold {
86 dts: DirectoryTreeStructure,
87 iterations: u64,
88}
89
90impl WalkDirectoryTreeCold {
91 pub fn new(dts: DirectoryTreeStructure, iterations: u64) -> Self {
92 Self { dts, iterations }
93 }
94}
95
96#[async_trait]
97impl<T: CacheClearableFilesystem> Benchmark<T> for WalkDirectoryTreeCold {
98 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
99 storage_trace::duration!(
100 "benchmark",
101 "WalkDirectoryTreeCold",
102 "files_per_directory" => self.dts.files_per_directory,
103 "directories_per_directory" => self.dts.directories_per_directory,
104 "max_depth" => self.dts.max_depth
105 );
106 let root = fs.benchmark_dir().to_path_buf();
107
108 self.dts.create_directory_tree(root.clone());
109 let max_pending = self.dts.max_pending() as usize;
110 let mut durations = Vec::new();
111 for i in 0..self.iterations {
112 fs.clear_cache().await;
113 storage_trace::duration!("benchmark", "WalkDirectoryTree", "iteration" => i);
114 let timer = OperationTimer::start();
115 walk_directory_tree(root.clone(), max_pending);
116 durations.push(timer.stop());
117 }
118 durations
119 }
120
121 fn name(&self) -> String {
122 format!(
123 "WalkDirectoryTreeCold/Files{}/Dirs{}/Depth{}",
124 self.dts.files_per_directory, self.dts.directories_per_directory, self.dts.max_depth
125 )
126 }
127}
128
129#[derive(Clone)]
132pub struct WalkDirectoryTreeWarm {
133 dts: DirectoryTreeStructure,
134 iterations: u64,
135}
136
137impl WalkDirectoryTreeWarm {
138 pub fn new(dts: DirectoryTreeStructure, iterations: u64) -> Self {
139 Self { dts, iterations }
140 }
141}
142
143#[async_trait]
144impl<T: Filesystem> Benchmark<T> for WalkDirectoryTreeWarm {
145 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
146 storage_trace::duration!(
147 "benchmark",
148 "WalkDirectoryTreeWarm",
149 "files_per_directory" => self.dts.files_per_directory,
150 "directories_per_directory" => self.dts.directories_per_directory,
151 "max_depth" => self.dts.max_depth
152 );
153 let root = fs.benchmark_dir().to_path_buf();
154
155 self.dts.create_directory_tree(root.clone());
156 let max_pending = self.dts.max_pending() as usize;
157 let mut durations = Vec::new();
158 for i in 0..self.iterations {
159 storage_trace::duration!("benchmark", "WalkDirectoryTree", "iteration" => i);
160 let timer = OperationTimer::start();
161 walk_directory_tree(root.clone(), max_pending);
162 durations.push(timer.stop());
163 }
164 durations
165 }
166
167 fn name(&self) -> String {
168 format!(
169 "WalkDirectoryTreeWarm/Files{}/Dirs{}/Depth{}",
170 self.dts.files_per_directory, self.dts.directories_per_directory, self.dts.max_depth
171 )
172 }
173}
174
175fn walk_directory_tree(root: PathBuf, max_pending: usize) {
178 let mut pending = VecDeque::new();
179 pending.reserve(max_pending);
180 pending.push_back(root);
181 while let Some(dir) = pending.pop_front() {
182 storage_trace::duration!("benchmark", "read_dir");
183 for entry in std::fs::read_dir(&dir).unwrap() {
184 let entry = entry.unwrap();
185 if entry.file_type().unwrap().is_dir() {
186 pending.push_back(entry.path());
187 }
188 }
189 }
190}
191
192#[derive(Clone)]
195pub struct StatPath {
196 file_count: u64,
197}
198
199impl StatPath {
200 pub fn new() -> Self {
201 Self { file_count: 100 }
202 }
203}
204
205#[async_trait]
206impl<T: Filesystem> Benchmark<T> for StatPath {
207 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
208 storage_trace::duration!("benchmark", "StatPath");
209
210 let root = fs.benchmark_dir().to_path_buf();
211 for i in 0..self.file_count {
212 std::fs::File::create(root.join(file_name(i))).unwrap();
213 }
214
215 let root_fd =
216 open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
217
218 let mut durations = Vec::with_capacity(self.file_count as usize);
219 for i in 0..self.file_count {
220 let path = path_buf_to_c_string(file_name(i));
221 storage_trace::duration!("benchmark", "stat", "file" => i);
222 let timer = OperationTimer::start();
223 stat_path_at(&root_fd, &path).unwrap();
224 durations.push(timer.stop());
225 }
226 durations
227 }
228
229 fn name(&self) -> String {
230 "StatPath".to_string()
231 }
232}
233
234#[derive(Clone)]
237pub struct OpenFile {
238 file_count: u64,
239}
240
241impl OpenFile {
242 pub fn new() -> Self {
243 Self { file_count: 100 }
244 }
245}
246
247#[async_trait]
248impl<T: Filesystem> Benchmark<T> for OpenFile {
249 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
250 storage_trace::duration!("benchmark", "OpenFile");
251
252 let root = fs.benchmark_dir().to_path_buf();
253 for i in 0..self.file_count {
254 std::fs::File::create(root.join(file_name(i))).unwrap();
255 }
256
257 let root_fd =
258 open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
259
260 let mut durations = Vec::with_capacity(self.file_count as usize);
261 for i in 0..self.file_count {
262 let path = path_buf_to_c_string(file_name(i));
263 let _file = {
265 storage_trace::duration!("benchmark", "open", "file" => i);
266 let timer = OperationTimer::start();
267 let file = open_path_at(&root_fd, &path, libc::O_RDWR);
268 durations.push(timer.stop());
269 file
270 };
271 }
272 durations
273 }
274
275 fn name(&self) -> String {
276 "OpenFile".to_string()
277 }
278}
279
280#[derive(Clone)]
282pub struct CreateFile {
283 file_count: u64,
284}
285
286impl CreateFile {
287 pub fn new() -> Self {
288 Self { file_count: 100 }
289 }
290}
291
292#[async_trait]
293impl<T: Filesystem> Benchmark<T> for CreateFile {
294 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
295 storage_trace::duration!("benchmark", "CreateFile");
296
297 let root = fs.benchmark_dir().to_path_buf();
298 let root_fd =
299 open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
300
301 let mut durations = Vec::with_capacity(self.file_count as usize);
302 for i in 0..self.file_count {
303 let path = path_buf_to_c_string(file_name(i));
304 let _file = {
306 storage_trace::duration!("benchmark", "create", "file" => i);
307 let timer = OperationTimer::start();
308 let file = open_path_at(&root_fd, &path, libc::O_CREAT | libc::O_RDWR);
309 durations.push(timer.stop());
310 file
311 };
312 }
313 durations
314 }
315
316 fn name(&self) -> String {
317 "CreateFile".to_string()
318 }
319}
320
321#[derive(Clone)]
323pub struct UnlinkFile {
324 file_count: u64,
325}
326
327impl UnlinkFile {
328 pub fn new() -> Self {
329 Self { file_count: 100 }
330 }
331}
332
333#[async_trait]
334impl<T: Filesystem> Benchmark<T> for UnlinkFile {
335 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
336 storage_trace::duration!("benchmark", "UnlinkFile");
337
338 let root = fs.benchmark_dir().to_path_buf();
339 let root_fd =
340 open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
341
342 for i in 0..self.file_count {
343 let path = path_buf_to_c_string(file_name(i));
344 let _file = open_path_at(&root_fd, &path, libc::O_CREAT | libc::O_RDWR).unwrap();
345 }
346
347 let mut durations = Vec::with_capacity(self.file_count as usize);
348 for i in 0..self.file_count {
349 let path = path_buf_to_c_string(file_name(i));
350 storage_trace::duration!("benchmark", "unlink", "file" => i);
351 let timer = OperationTimer::start();
352 unlink_path_at(&root_fd, &path).unwrap();
353 durations.push(timer.stop());
354 }
355 durations
356 }
357
358 fn name(&self) -> String {
359 "UnlinkFile".to_string()
360 }
361}
362
363#[derive(Clone)]
366pub struct OpenDeeplyNestedFile {
367 file_count: u64,
368 depth: u64,
369}
370
371impl OpenDeeplyNestedFile {
372 pub fn new() -> Self {
373 Self { file_count: 100, depth: 5 }
374 }
375
376 fn dir_path(&self, n: u64) -> PathBuf {
377 let mut path = PathBuf::new();
378 for i in 0..self.depth {
379 path = path.join(format!("dir-{:03}-{:03}", n, i));
380 }
381 path
382 }
383}
384
385#[async_trait]
386impl<T: Filesystem> Benchmark<T> for OpenDeeplyNestedFile {
387 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
388 storage_trace::duration!("benchmark", "OpenDeeplyNestedFile");
389
390 let root = fs.benchmark_dir().to_path_buf();
391 for i in 0..self.file_count {
392 let dir_path = root.join(self.dir_path(i));
393 std::fs::create_dir_all(&dir_path).unwrap();
394 std::fs::File::create(dir_path.join(file_name(i))).unwrap();
395 }
396
397 let root_fd =
398 open_path(&path_buf_to_c_string(root), libc::O_DIRECTORY | libc::O_RDONLY).unwrap();
399
400 let mut durations = Vec::with_capacity(self.file_count as usize);
401 for i in 0..self.file_count {
402 let path = path_buf_to_c_string(self.dir_path(i).join(file_name(i)));
403 let _file = {
405 storage_trace::duration!("benchmark", "open", "file" => i);
406 let timer = OperationTimer::start();
407 let file = open_path_at(&root_fd, &path, libc::O_RDWR);
408 durations.push(timer.stop());
409 file
410 };
411 }
412 durations
413 }
414
415 fn name(&self) -> String {
416 "OpenDeeplyNestedFile".to_string()
417 }
418}
419
420#[derive(Clone)]
422pub struct GitStatus {
423 dts: DirectoryTreeStructure,
424 iterations: u64,
425
426 stat_threads: usize,
428}
429
430impl GitStatus {
431 pub fn new() -> Self {
432 let dts = DirectoryTreeStructure {
435 files_per_directory: 4,
436 directories_per_directory: 2,
437 max_depth: 7,
438 };
439
440 Self { dts, iterations: 5, stat_threads: 1 }
443 }
444
445 fn stat_paths(&self, root: &OpenFd, paths: &Vec<CString>) {
448 storage_trace::duration!("benchmark", "GitStatus::stat_paths");
449 std::thread::scope(|scope| {
450 for thread in 0..self.stat_threads {
451 scope.spawn(move || {
452 let paths = &paths;
453 for path in batch_range(paths.len(), self.stat_threads, thread) {
454 storage_trace::duration!("benchmark", "stat");
455 stat_path_at(root, &paths[path]).unwrap();
456 }
457 });
458 }
459 });
460 }
461
462 fn walk_repo(&self, dir: &Path) {
464 storage_trace::duration!("benchmark", "GitStatus::walk_repo");
465 for entry in std::fs::read_dir(&dir).unwrap() {
466 let entry = entry.unwrap();
467 if entry.file_type().unwrap().is_dir() {
468 self.walk_repo(&entry.path());
469 }
470 }
471 }
472}
473
474#[async_trait]
475impl<T: Filesystem> Benchmark<T> for GitStatus {
476 async fn run(&self, fs: &mut T) -> Vec<OperationDuration> {
477 let root = &fs.benchmark_dir().to_path_buf();
478
479 self.dts.create_directory_tree(root.clone());
480 let paths = self.dts.enumerate_paths().into_iter().map(path_buf_to_c_string).collect();
481
482 let root_fd =
483 open_path(&path_buf_to_c_string(root.clone()), libc::O_DIRECTORY | libc::O_RDONLY)
484 .unwrap();
485
486 let mut durations = Vec::new();
487 for i in 0..self.iterations {
488 storage_trace::duration!("benchmark", "GitStatus", "iteration" => i);
489 let timer = OperationTimer::start();
490 self.stat_paths(&root_fd, &paths);
491 self.walk_repo(&root);
492 durations.push(timer.stop());
493 }
494 durations
495 }
496
497 fn name(&self) -> String {
498 "GitStatus".to_owned()
499 }
500}
501
502pub fn file_name(n: u64) -> PathBuf {
503 format!("file-{:03}.txt", n).into()
504}
505
506pub fn dir_name(n: u64) -> PathBuf {
507 format!("dir-{:03}", n).into()
508}
509
510pub fn path_buf_to_c_string(path: PathBuf) -> CString {
511 CString::new(path.into_os_string().into_vec()).unwrap()
512}
513
514pub fn stat_path_at(dir: &OpenFd, path: &CStr) -> Result<libc::stat, std::io::Error> {
515 unsafe {
516 let mut stat = MaybeUninit::uninit();
517 let result =
520 libc::fstatat(dir.0, path.as_ptr(), stat.as_mut_ptr(), libc::AT_SYMLINK_NOFOLLOW);
521
522 if result == 0 { Ok(stat.assume_init()) } else { Err(std::io::Error::last_os_error()) }
523 }
524}
525
526fn batch_range(item_count: usize, batch_count: usize, batch_num: usize) -> Range<usize> {
529 let items_per_batch = (item_count + (batch_count - 1)) / batch_count;
530 let start = items_per_batch * batch_num;
531 let end = std::cmp::min(item_count, start + items_per_batch);
532 start..end
533}
534
535pub struct OpenFd(RawFd);
536
537impl Drop for OpenFd {
538 fn drop(&mut self) {
539 unsafe { libc::close(self.0) };
540 }
541}
542
543pub fn open_path(path: &CStr, flags: libc::c_int) -> Result<OpenFd, std::io::Error> {
544 let result = unsafe { libc::open(path.as_ptr(), flags) };
545 if result >= 0 { Ok(OpenFd(result)) } else { Err(std::io::Error::last_os_error()) }
546}
547
548pub fn open_path_at(
549 dir: &OpenFd,
550 path: &CStr,
551 flags: libc::c_int,
552) -> Result<OpenFd, std::io::Error> {
553 let result = unsafe { libc::openat(dir.0, path.as_ptr(), flags) };
554 if result >= 0 { Ok(OpenFd(result)) } else { Err(std::io::Error::last_os_error()) }
555}
556
557pub fn unlink_path_at(dir: &OpenFd, path: &CStr) -> Result<(), std::io::Error> {
558 let result = unsafe { libc::unlinkat(dir.0, path.as_ptr(), 0) };
559 if result == 0 { Ok(()) } else { Err(std::io::Error::last_os_error()) }
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565 use crate::testing::TestFilesystem;
566
567 const ITERATION_COUNT: u64 = 3;
568
569 #[derive(PartialEq, Debug)]
570 struct DirectoryTree {
571 files: u64,
572 directories: Vec<DirectoryTree>,
573 }
574
575 fn read_in_directory_tree(root: PathBuf) -> DirectoryTree {
576 let mut tree = DirectoryTree { files: 0, directories: vec![] };
577 for entry in std::fs::read_dir(root).unwrap() {
578 let entry = entry.unwrap();
579 if entry.file_type().unwrap().is_dir() {
580 tree.directories.push(read_in_directory_tree(entry.path()));
581 } else {
582 tree.files += 1;
583 }
584 }
585 tree
586 }
587
588 #[fuchsia::test]
589 async fn create_directory_tree_with_depth_of_zero_test() {
590 let test_fs = Box::new(TestFilesystem::new());
591 let dts = DirectoryTreeStructure {
592 files_per_directory: 3,
593 directories_per_directory: 2,
594 max_depth: 0,
595 };
596 let root = test_fs.benchmark_dir().to_owned();
597 dts.create_directory_tree(root.clone());
598
599 let directory_tree = read_in_directory_tree(root);
600 assert_eq!(directory_tree, DirectoryTree { files: 3, directories: vec![] });
601 test_fs.shutdown().await;
602 }
603
604 #[fuchsia::test]
605 async fn create_directory_tree_with_depth_of_two_test() {
606 let test_fs = Box::new(TestFilesystem::new());
607 let dts = DirectoryTreeStructure {
608 files_per_directory: 4,
609 directories_per_directory: 2,
610 max_depth: 2,
611 };
612 let root = test_fs.benchmark_dir().to_owned();
613 dts.create_directory_tree(root.clone());
614
615 let directory_tree = read_in_directory_tree(root);
616 assert_eq!(
617 directory_tree,
618 DirectoryTree {
619 files: 4,
620 directories: vec![
621 DirectoryTree {
622 files: 4,
623 directories: vec![
624 DirectoryTree { files: 4, directories: vec![] },
625 DirectoryTree { files: 4, directories: vec![] }
626 ]
627 },
628 DirectoryTree {
629 files: 4,
630 directories: vec![
631 DirectoryTree { files: 4, directories: vec![] },
632 DirectoryTree { files: 4, directories: vec![] }
633 ]
634 }
635 ]
636 }
637 );
638 test_fs.shutdown().await;
639 }
640
641 #[fuchsia::test]
642 fn enumerate_paths_test() {
643 let dts = DirectoryTreeStructure {
644 files_per_directory: 2,
645 directories_per_directory: 2,
646 max_depth: 0,
647 };
648 let paths = dts.enumerate_paths();
649 assert_eq!(paths, vec![PathBuf::from("file-000.txt"), PathBuf::from("file-001.txt")],);
650
651 let dts = DirectoryTreeStructure {
652 files_per_directory: 2,
653 directories_per_directory: 2,
654 max_depth: 1,
655 };
656 let paths = dts.enumerate_paths();
657 assert_eq!(
658 paths,
659 vec![
660 PathBuf::from("file-000.txt"),
661 PathBuf::from("file-001.txt"),
662 PathBuf::from("dir-000"),
663 PathBuf::from("dir-000/file-000.txt"),
664 PathBuf::from("dir-000/file-001.txt"),
665 PathBuf::from("dir-001"),
666 PathBuf::from("dir-001/file-000.txt"),
667 PathBuf::from("dir-001/file-001.txt"),
668 ],
669 );
670 }
671
672 #[fuchsia::test]
673 fn batch_range_test() {
674 assert_eq!(batch_range(10, 1, 0), 0..10);
675
676 assert_eq!(batch_range(10, 3, 0), 0..4);
677 assert_eq!(batch_range(10, 3, 1), 4..8);
678 assert_eq!(batch_range(10, 3, 2), 8..10);
679
680 assert_eq!(batch_range(10, 4, 3), 9..10);
681 assert_eq!(batch_range(12, 4, 3), 9..12);
682 }
683
684 #[fuchsia::test]
685 async fn walk_directory_tree_cold_test() {
686 let mut test_fs = Box::new(TestFilesystem::new());
687 let dts = DirectoryTreeStructure {
688 files_per_directory: 2,
689 directories_per_directory: 2,
690 max_depth: 2,
691 };
692 let results = WalkDirectoryTreeCold::new(dts, ITERATION_COUNT).run(test_fs.as_mut()).await;
693
694 assert_eq!(results.len(), ITERATION_COUNT as usize);
695 assert_eq!(test_fs.clear_cache_count().await, ITERATION_COUNT);
696 test_fs.shutdown().await;
697 }
698
699 #[fuchsia::test]
700 async fn walk_directory_tree_warm_test() {
701 let mut test_fs = Box::new(TestFilesystem::new());
702 let dts = DirectoryTreeStructure {
703 files_per_directory: 2,
704 directories_per_directory: 2,
705 max_depth: 2,
706 };
707 let results = WalkDirectoryTreeWarm::new(dts, ITERATION_COUNT).run(test_fs.as_mut()).await;
708
709 assert_eq!(results.len(), ITERATION_COUNT as usize);
710 assert_eq!(test_fs.clear_cache_count().await, 0);
711 test_fs.shutdown().await;
712 }
713
714 #[fuchsia::test]
715 async fn stat_path_test() {
716 let mut test_fs = Box::new(TestFilesystem::new());
717 let benchmark = StatPath { file_count: 5 };
718 let results = benchmark.run(test_fs.as_mut()).await;
719 assert_eq!(results.len(), 5);
720 assert_eq!(test_fs.clear_cache_count().await, 0);
721 test_fs.shutdown().await;
722 }
723
724 #[fuchsia::test]
725 async fn open_file_test() {
726 let mut test_fs = Box::new(TestFilesystem::new());
727 let benchmark = OpenFile { file_count: 5 };
728 let results = benchmark.run(test_fs.as_mut()).await;
729 assert_eq!(results.len(), 5);
730 assert_eq!(test_fs.clear_cache_count().await, 0);
731 test_fs.shutdown().await;
732 }
733
734 #[fuchsia::test]
735 async fn create_file_test() {
736 let mut test_fs = Box::new(TestFilesystem::new());
737 let benchmark = CreateFile { file_count: 5 };
738 let results = benchmark.run(test_fs.as_mut()).await;
739 assert_eq!(results.len(), 5);
740 assert_eq!(test_fs.clear_cache_count().await, 0);
741 test_fs.shutdown().await;
742 }
743
744 #[fuchsia::test]
745 async fn unlink_file_test() {
746 let mut test_fs = Box::new(TestFilesystem::new());
747 let benchmark = UnlinkFile { file_count: 5 };
748 let results = benchmark.run(test_fs.as_mut()).await;
749 assert_eq!(results.len(), 5);
750 assert_eq!(test_fs.clear_cache_count().await, 0);
751 test_fs.shutdown().await;
752 }
753
754 #[fuchsia::test]
755 async fn open_deeply_nested_file_test() {
756 let mut test_fs = Box::new(TestFilesystem::new());
757 let benchmark = OpenDeeplyNestedFile { file_count: 5, depth: 3 };
758 let results = benchmark.run(test_fs.as_mut()).await;
759 assert_eq!(results.len(), 5);
760 assert_eq!(test_fs.clear_cache_count().await, 0);
761 test_fs.shutdown().await;
762 }
763
764 #[fuchsia::test]
765 async fn git_status_test() {
766 let mut test_fs = Box::new(TestFilesystem::new());
767 let dts = DirectoryTreeStructure {
768 files_per_directory: 2,
769 directories_per_directory: 2,
770 max_depth: 2,
771 };
772 let benchmark = GitStatus { dts, iterations: ITERATION_COUNT, stat_threads: 1 };
773 let results = benchmark.run(test_fs.as_mut()).await;
774
775 assert_eq!(results.len(), ITERATION_COUNT as usize);
776 assert_eq!(test_fs.clear_cache_count().await, 0);
777 test_fs.shutdown().await;
778 }
779}