mfio_rt/
test_suite.rs

1//! mfio-rt test suite.
2//!
3//! This module contains all blocks needed for testing [`Fs`] and [`Tcp`] implementations in a
4//! standard, extensive way. The main entry point for using the test suite are the
5//! [`test_suite!`](crate::test_suite!) and [`net_test_suite!`](crate::net_test_suite!) macros.
6
7use crate::util::diff_paths;
8pub use crate::{DirHandle, DirHandleExt, Fs, OpenOptions, Path, Shutdown};
9pub use alloc::{
10    collections::BTreeSet,
11    format,
12    string::{String, ToString},
13    vec,
14    vec::Vec,
15};
16pub use core::future::Future;
17pub use futures::StreamExt;
18pub use mfio::backend::IoBackendExt;
19pub use mfio::traits::{IoRead, IoWrite};
20pub use once_cell::sync::Lazy;
21pub use tempdir::TempDir;
22
23#[cfg(feature = "std")]
24pub use crate::{Tcp, TcpListenerHandle, TcpStreamHandle};
25#[cfg(feature = "std")]
26pub use std::fs;
27
28const FILES: &[(&str, &str)] = &[
29    ("Cargo.toml", include_str!("../Cargo.toml")),
30    ("src/lib.rs", include_str!("lib.rs")),
31    ("src/util.rs", include_str!("util.rs")),
32    ("src/native/mod.rs", include_str!("native/mod.rs")),
33    (
34        "src/native/impls/mod.rs",
35        include_str!("native/impls/mod.rs"),
36    ),
37    (
38        "src/native/impls/thread.rs",
39        include_str!("native/impls/thread.rs"),
40    ),
41    (
42        "src/native/impls/unix_extra.rs",
43        include_str!("native/impls/unix_extra.rs"),
44    ),
45    ("p1/p2/p3/a.txt", "TEST TEST TEST"),
46];
47
48const DIRECTORIES: &[&str] = &[
49    "src/native/impls/io_uring",
50    "src/native/impls/mio",
51    "p1/p2/p3/p4/p5/p6",
52];
53
54pub static CTX: Lazy<TestCtx> = Lazy::new(TestCtx::new);
55
56#[cfg(not(miri))]
57const fn hash(mut x: u64) -> u64 {
58    x = (x ^ (x >> 30)).wrapping_mul(0xbf58476d1ce4e5b9u64);
59    x = (x ^ (x >> 27)).wrapping_mul(0x94d049bb133111ebu64);
60    x ^ (x >> 31)
61}
62
63pub struct TestCtx {
64    files: Vec<(String, Vec<u8>)>,
65    dirs: Vec<String>,
66}
67
68impl Default for TestCtx {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl TestCtx {
75    pub fn new() -> Self {
76        let mut files = vec![];
77
78        for (p, c) in FILES {
79            files.push((p.to_string(), c.to_string().into_bytes()));
80        }
81
82        let mut dirs = vec![];
83
84        for d in DIRECTORIES {
85            dirs.push(d.to_string());
86        }
87
88        // Create a few "large" random files
89
90        #[cfg(not(miri))]
91        for f in 0..4 {
92            files.push((
93                format!("large/{f}"),
94                (0..0x4000000)
95                    .map(|v| hash((v as u64) << (f * 8)) as u8)
96                    .collect::<Vec<_>>(),
97            ))
98        }
99
100        Self { files, dirs }
101    }
102
103    pub fn list(&self, path: &str) -> BTreeSet<String> {
104        let path = path.trim_start_matches('.').trim_start_matches('/');
105        self.files
106            .iter()
107            .map(|v| v.0.as_str())
108            .filter_map(move |v| v.strip_prefix(path))
109            .map(|v| v.trim_start_matches('/'))
110            .map(|v| v.split_once('/').map(|(a, _)| a).unwrap_or(v))
111            .chain(
112                self.dirs
113                    .iter()
114                    .map(|v| v.as_str())
115                    .filter_map(move |v| v.strip_prefix(path))
116                    .map(|v| v.trim_start_matches('/'))
117                    .map(|v| v.split_once('/').map(|(a, _)| a).unwrap_or(v)),
118            )
119            .filter(|s| !s.is_empty())
120            .map(|s| s.to_string())
121            .collect()
122    }
123
124    pub fn dirs(&self) -> &[String] {
125        &self.dirs
126    }
127
128    pub fn files(&self) -> &[(String, Vec<u8>)] {
129        &self.files
130    }
131
132    pub fn all_dirs(&self) -> BTreeSet<String> {
133        let mut dirs = BTreeSet::new();
134
135        for dir in &self.dirs {
136            let mut d = ".".to_string();
137            dirs.insert(d.clone());
138            for p in dir.split('/') {
139                d.push('/');
140                d.push_str(p);
141                dirs.insert(d.clone());
142            }
143        }
144
145        for dir in self
146            .files
147            .iter()
148            .filter_map(|(a, _)| a.rsplit_once('/').map(|(a, _)| a))
149        {
150            let mut d = ".".to_string();
151            dirs.insert(d.clone());
152            for p in dir.split('/') {
153                d.push('/');
154                d.push_str(p);
155                dirs.insert(d.clone());
156            }
157        }
158
159        dirs
160    }
161
162    #[cfg(feature = "std")]
163    pub fn build_in_path(&self, path: &Path) {
164        for d in &self.dirs {
165            let _ = fs::create_dir_all(path.join(d));
166        }
167
168        for (p, data) in &self.files {
169            if let Some((d, _)) = p.rsplit_once('/') {
170                let _ = fs::create_dir_all(path.join(d));
171            }
172            fs::write(path.join(p), data).unwrap();
173        }
174    }
175
176    pub async fn build_in_fs(&self, fs: &impl Fs) {
177        let cdir = fs.current_dir();
178
179        for d in &self.dirs {
180            #[cfg(feature = "std")]
181            println!("Dir: {d:?}");
182            let _ = cdir.create_dir_all(d).await;
183        }
184
185        for (p, data) in &self.files {
186            #[cfg(feature = "std")]
187            println!("File: {p:?}");
188            if let Some((d, _)) = p.rsplit_once('/') {
189                let _ = cdir.create_dir_all(d).await;
190            }
191            let file = cdir
192                .open_file(p, OpenOptions::new().create_new(true).write(true))
193                .await
194                .unwrap();
195            file.write_all(0, &data[..]).await.unwrap();
196        }
197    }
198}
199
200#[cfg(feature = "std")]
201pub mod net {
202    use super::*;
203    use async_semaphore::Semaphore;
204    use core::pin::pin;
205
206    /// Maximum number of concurrent TCP tests (listener, client) pairs at a time.
207    static TCP_SEM: Semaphore = Semaphore::new(16);
208
209    pub struct NetTestRun<'a, T> {
210        ctx: &'static TestCtx,
211        rt: &'a T,
212    }
213
214    impl<'a, T: Tcp> NetTestRun<'a, T> {
215        pub fn new(rt: &'a T) -> Self {
216            Self { ctx: &*CTX, rt }
217        }
218
219        pub async fn tcp_connect(&self) {
220            use std::net::TcpListener;
221
222            let _sem = TCP_SEM.acquire().await;
223
224            let listener = TcpListener::bind("127.0.0.1:0").unwrap();
225            let addr = listener.local_addr().unwrap();
226
227            let jh = std::thread::spawn(move || {
228                let _ = listener.accept().unwrap();
229            });
230
231            self.rt.connect(addr).await.unwrap();
232
233            jh.join().unwrap();
234        }
235
236        pub async fn tcp_listen(&self) {
237            use std::net::TcpStream;
238
239            let _sem = TCP_SEM.acquire().await;
240
241            let listener = self.rt.bind("127.0.0.1:0").await.unwrap();
242            let addr = listener.local_addr().unwrap();
243
244            let jh = std::thread::spawn(move || {
245                let _ = TcpStream::connect(addr).unwrap();
246            });
247
248            let mut listener = pin!(listener);
249
250            let _ = listener.next().await.unwrap();
251
252            jh.join().unwrap();
253        }
254
255        pub fn tcp_receive(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
256            use mfio::io::NoPos;
257            use std::net::TcpListener;
258
259            self.ctx.files.iter().map(move |(name, data)| async move {
260                let _sem = TCP_SEM.acquire().await;
261
262                let listener = TcpListener::bind("127.0.0.1:0").unwrap();
263                let addr = listener.local_addr().unwrap();
264
265                let (tx, rx) = flume::bounded(1);
266
267                let jh = std::thread::spawn(move || {
268                    use std::io::Write;
269                    let (mut sock, _) = listener.accept().unwrap();
270                    sock.write_all(data).unwrap();
271                    let _ = sock.shutdown(std::net::Shutdown::Both);
272                    let _ = tx.send(());
273                });
274
275                let conn = self.rt.connect(addr).await.unwrap();
276
277                let mut out = vec![];
278                conn.read_to_end(NoPos::new(), &mut out).await.unwrap();
279                assert!(
280                    &out[..] == data,
281                    "{name} does not match ({} vs {})",
282                    out.len(),
283                    data.len()
284                );
285
286                let _ = rx.recv_async().await;
287                jh.join().unwrap();
288            })
289        }
290
291        pub fn tcp_send(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
292            use mfio::io::NoPos;
293            use std::net::TcpListener;
294
295            self.ctx.files.iter().map(move |(name, data)| async move {
296                let _sem = TCP_SEM.acquire().await;
297
298                let listener = TcpListener::bind("127.0.0.1:0").unwrap();
299                let addr = listener.local_addr().unwrap();
300
301                let (tx, rx) = flume::bounded(1);
302
303                let jh = std::thread::spawn(move || {
304                    use std::io::Read;
305                    let (mut sock, _) = listener.accept().unwrap();
306                    let mut out = vec![];
307                    sock.read_to_end(&mut out).unwrap();
308                    let _ = tx.send(());
309                    out
310                });
311
312                {
313                    let conn = self.rt.connect(addr).await.unwrap();
314                    conn.write_all(NoPos::new(), &data[..]).await.unwrap();
315                    core::mem::drop(conn);
316                }
317
318                let _ = rx.recv_async().await;
319
320                let ret = jh.join().unwrap();
321                assert!(
322                    &ret == data,
323                    "{name} does not match ({} vs {})",
324                    ret.len(),
325                    data.len()
326                );
327            })
328        }
329
330        pub fn tcp_echo_client(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
331            use mfio::io::NoPos;
332            use std::net::TcpListener;
333
334            self.ctx.files.iter().map(move |(name, data)| async move {
335                let _sem = TCP_SEM.acquire().await;
336
337                let listener = TcpListener::bind("127.0.0.1:0").unwrap();
338                let addr = listener.local_addr().unwrap();
339
340                let (tx, rx) = flume::bounded(1);
341
342                let jh = std::thread::spawn(move || {
343                    let (sock, _) = listener.accept().unwrap();
344                    log::trace!("Echo STD server start");
345                    std::io::copy(&mut &sock, &mut &sock).unwrap();
346                    log::trace!("Echo STD server end");
347                    let _ = tx.send(());
348                });
349
350                let ret = {
351                    let conn = self.rt.connect(addr).await.unwrap();
352
353                    let write = async {
354                        conn.write_all(NoPos::new(), &data[..]).await.unwrap();
355                        log::trace!("Written");
356                        conn.shutdown(Shutdown::Write).unwrap();
357                    };
358
359                    let read = async {
360                        let mut ret = vec![];
361                        conn.read_to_end(NoPos::new(), &mut ret).await.unwrap();
362                        ret
363                    };
364
365                    futures::join!(write, read).1
366                };
367
368                let _ = rx.recv_async().await;
369                jh.join().unwrap();
370
371                assert!(
372                    &ret == data,
373                    "{name} does not match ({} vs {})",
374                    ret.len(),
375                    data.len()
376                );
377            })
378        }
379
380        pub fn tcp_echo_server(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
381            use core::mem::MaybeUninit;
382            use flume::SendError;
383            use mfio::io::{Read, StreamIoExt, VecPacket};
384            use std::net::TcpStream;
385
386            self.ctx.files.iter().map(move |(name, data)| async move {
387                let _sem = TCP_SEM.acquire().await;
388
389                let listener = self.rt.bind("127.0.0.1:0").await.unwrap();
390                let addr = listener.local_addr().unwrap();
391
392                let (tx, rx) = flume::bounded(1);
393
394                let jh = std::thread::spawn(move || {
395                    use std::io::{Read, Write};
396                    let sock = TcpStream::connect(addr).unwrap();
397                    let sock = std::sync::Arc::new(sock);
398
399                    let jh = std::thread::spawn({
400                        let sock = sock.clone();
401                        move || {
402                            (&mut &*sock).write_all(data).unwrap();
403                            sock.shutdown(Shutdown::Write.into()).unwrap();
404                        }
405                    });
406
407                    let mut out = vec![];
408                    let _ = (&mut &*sock).read_to_end(&mut out);
409
410                    jh.join().unwrap();
411                    let _ = tx.send(());
412
413                    out
414                });
415
416                let mut listener = pin!(listener);
417
418                let (sock, _) = listener.next().await.unwrap();
419
420                // TODO: we need a much simpler std::io::copy equivalent...
421                let (rtx, rrx) = flume::bounded(4);
422                let (mtx, mrx) = flume::bounded(4);
423
424                for i in 0..rtx.capacity().unwrap() {
425                    let _ = rtx.send_async((i, vec![MaybeUninit::uninit(); 64])).await;
426                }
427
428                let read = {
429                    let sock = &sock;
430                    async move {
431                        rrx.stream()
432                            .then(|(i, mut v)| async move {
433                                v.resize(v.len() * 2, MaybeUninit::uninit());
434                                let p = VecPacket::from(v);
435                                let p = sock.stream_io(p).await;
436                                let len = p.simple_contiguous_slice().unwrap().len();
437                                let mut v = p.take();
438                                let v = unsafe {
439                                    v.set_len(len);
440                                    core::mem::transmute::<Vec<MaybeUninit<u8>>, Vec<u8>>(v)
441                                };
442                                if v.is_empty() {
443                                    log::trace!("Empty @ {i}");
444                                    Err(SendError((i, v)))
445                                } else {
446                                    log::trace!("Forward {i}");
447                                    Ok((i, v))
448                                }
449                            })
450                            .take_while(|v| core::future::ready(v.is_ok()))
451                            .forward(mtx.sink())
452                            .await
453                    }
454                };
455
456                let write = async {
457                    let sock = &sock;
458                    let rtx = &rtx;
459                    mrx.stream()
460                        .for_each(|(i, v)| async move {
461                            let p = VecPacket::<Read>::from(v);
462                            let p = sock.stream_io(p).await;
463                            let mut v = p.take();
464                            let v = unsafe {
465                                v.set_len(v.capacity());
466                                core::mem::transmute::<Vec<u8>, Vec<MaybeUninit<u8>>>(v)
467                            };
468                            log::trace!("Forward back {i}");
469                            // We can't use stream.forward(), because that might cancel the .then() in
470                            // unfinished state.
471                            let _ = rtx.send_async((i, v)).await;
472                        })
473                        .await
474                };
475
476                let _ = futures::join!(read, write);
477                log::trace!("Echo server");
478                core::mem::drop(sock);
479
480                let _ = rx.recv_async().await;
481                let ret = jh.join().unwrap();
482
483                assert!(
484                    &ret == data,
485                    "{name} does not match ({} vs {})",
486                    ret.len(),
487                    data.len()
488                );
489            })
490        }
491    }
492}
493
494pub struct TestRun<'a, T, D> {
495    ctx: &'a TestCtx,
496    rt: &'a T,
497    _drop_guard: D,
498}
499
500#[cfg(feature = "std")]
501impl<'a, T: Fs> TestRun<'a, T, TempDir> {
502    pub fn new(rt: &'a T, dir: TempDir) -> Self {
503        CTX.build_in_path(dir.path());
504        Self {
505            rt,
506            _drop_guard: dir,
507            ctx: &CTX,
508        }
509    }
510}
511
512impl<'a, T: Fs, D> TestRun<'a, T, D> {
513    pub async fn built_by_rt(rt: &'a T, drop_guard: D) -> TestRun<'a, T, D> {
514        CTX.build_in_fs(rt).await;
515        Self {
516            rt,
517            _drop_guard: drop_guard,
518            ctx: &CTX,
519        }
520    }
521
522    pub fn assume_built(rt: &'a T, drop_guard: D) -> TestRun<'a, T, D> {
523        Self {
524            rt,
525            _drop_guard: drop_guard,
526            ctx: &CTX,
527        }
528    }
529
530    pub fn files_equal(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
531        self.ctx.files.iter().map(move |(p, data)| async move {
532            let cur_dir = self.rt.current_dir().path().await.unwrap();
533            let path = &cur_dir.join(p);
534            let fh = self
535                .rt
536                .open(path, OpenOptions::new().read(true))
537                .await
538                .unwrap();
539            let mut buf = vec![];
540            fh.read_to_end(0, &mut buf).await.unwrap();
541            assert!(&buf == data, "File {p} does not match!");
542        })
543    }
544
545    pub fn files_equal_rel(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
546        self.ctx.files.iter().map(move |(p, data)| async move {
547            let cur_dir = self.rt.current_dir().path().await.unwrap();
548            let path = &cur_dir.join(p);
549            let dh = self
550                .rt
551                .current_dir()
552                .open_dir(path.parent().unwrap())
553                .await
554                .unwrap();
555            let fh = dh
556                .open_file(path.file_name().unwrap(), OpenOptions::new().read(true))
557                .await
558                .unwrap();
559            let mut buf = vec![];
560            fh.read_to_end(0, &mut buf).await.unwrap();
561            if &buf != data && buf.len() == data.len() {
562                for (i, (a, b)) in buf.iter().zip(data.iter()).enumerate() {
563                    if a != b {
564                        panic!(
565                            "File {p} does not match at {i}\n{:?}\n{:?}",
566                            &buf[i..(i + 20)],
567                            &data[i..(i + 20)]
568                        );
569                    }
570                }
571            }
572            //assert!(&buf == data, "File {p} does not match! ({:x} vs {:x}) ({:?} {:?})", buf.len(), data.len(), &buf[..128], &data[..128]);
573        })
574    }
575
576    pub fn writes_equal<'b>(
577        &'b self,
578        tdir: &'b Path,
579    ) -> impl Iterator<Item = impl Future<Output = ()> + 'b> + 'b {
580        self.ctx.files.iter().map(move |(p, data)| async move {
581            let tdir = self.rt.current_dir().path().await.unwrap().join(tdir);
582            let path = &tdir.join(p);
583            self.rt
584                .current_dir()
585                .create_dir_all(path.parent().unwrap())
586                .await
587                .unwrap();
588
589            let fh = self
590                .rt
591                .open(path, OpenOptions::new().create(true).write(true))
592                .await
593                .unwrap();
594
595            fh.write_all(0, &data[..]).await.unwrap();
596
597            core::mem::drop(fh);
598
599            let fh = self
600                .rt
601                .open(path, OpenOptions::new().read(true))
602                .await
603                .unwrap();
604
605            let mut buf = vec![];
606            fh.read_to_end(0, &mut buf).await.unwrap();
607
608            assert_eq!(core::str::from_utf8(&buf), core::str::from_utf8(data));
609            assert!(&buf == data, "File {p} does not match!");
610        })
611    }
612
613    pub fn writes_equal_rel<'b>(
614        &'b self,
615        tdir: &'b Path,
616    ) -> impl Iterator<Item = impl Future<Output = ()> + 'b> + 'b {
617        self.ctx.files.iter().map(move |(p, data)| async move {
618            let tdir = self.rt.current_dir().path().await.unwrap().join(tdir);
619            let path = &tdir.join(p);
620            self.rt
621                .current_dir()
622                .create_dir_all(path.parent().unwrap())
623                .await
624                .unwrap();
625
626            let dh = self
627                .rt
628                .current_dir()
629                .open_dir(path.parent().unwrap())
630                .await
631                .unwrap();
632
633            let filename = path.file_name().unwrap();
634
635            let fh = dh
636                .open_file(filename, OpenOptions::new().create(true).write(true))
637                .await
638                .unwrap();
639
640            fh.write_all(0, &data[..]).await.unwrap();
641
642            core::mem::drop(fh);
643
644            let fh = dh
645                .open_file(filename, OpenOptions::new().read(true))
646                .await
647                .unwrap();
648
649            let mut buf = vec![];
650            fh.read_to_end(0, &mut buf).await.unwrap();
651
652            assert!(&buf == data, "File {p} does not match!");
653        })
654    }
655
656    pub fn dirs_equal(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
657        let all_dirs = self.ctx.all_dirs();
658        log::error!("{all_dirs:?}");
659        all_dirs.into_iter().map(move |d| async move {
660            let cur_path = self.rt.current_dir().path().await.unwrap();
661            log::error!("Join with {d}");
662            let path = &cur_path.join(&d);
663            log::error!("Open dir: {path:?}");
664            let dh = self
665                .rt
666                .current_dir()
667                .open_dir(path)
668                .await
669                .unwrap_or_else(|_| panic!("{path:?}"));
670            log::error!("Opened dir: {path:?}");
671            let dir = dh
672                .read_dir()
673                .await
674                .unwrap()
675                .map(|v| v.unwrap().name)
676                .collect::<BTreeSet<String>>()
677                .await;
678            assert_eq!(dir, self.ctx.list(&d), "{d}");
679        })
680    }
681
682    pub fn walk_dirs(&self) -> impl Iterator<Item = impl Future<Output = ()> + '_> + '_ {
683        let curdir = self.rt.current_dir();
684        self.ctx.all_dirs().into_iter().flat_map(move |d1| {
685            self.ctx.all_dirs().into_iter().map(move |d2| {
686                let d1 = d1.clone();
687                async move {
688                    let cur_path = curdir.path().await.unwrap();
689                    let path1 = &cur_path.join(&d1);
690                    let path2 = &cur_path.join(&d2);
691
692                    let relpath1 = diff_paths(path1, path2).unwrap();
693                    let relpath2 = diff_paths(&d1, &d2).unwrap();
694                    assert_eq!(&relpath1, &relpath2);
695
696                    let dh1 = curdir.open_dir(path1).await.unwrap();
697                    let dh2 = curdir.open_dir(path2).await.unwrap();
698
699                    let relpath3 =
700                        diff_paths(dh1.path().await.unwrap(), dh2.path().await.unwrap()).unwrap();
701
702                    assert_eq!(&relpath1, &relpath3);
703                }
704            })
705        })
706    }
707}
708
709async fn seq(i: impl Iterator<Item = impl Future<Output = ()> + '_> + '_) {
710    for i in i {
711        i.await;
712    }
713}
714
715async fn con(i: impl Iterator<Item = impl Future<Output = ()> + '_> + '_) {
716    let unordered = i.collect::<futures::stream::FuturesUnordered<_>>();
717    unordered.count().await;
718}
719
720pub mod fs_tests {
721    use super::*;
722
723    pub async fn all_tests_seq(run: TestRun<'_, impl Fs, impl Sized>) {
724        seq(run.files_equal()).await;
725        seq(run.files_equal_rel()).await;
726        seq(run.dirs_equal()).await;
727        seq(run.walk_dirs()).await;
728        let tdir = Path::new("mfio-testsuite-writes");
729        let tdir2 = Path::new("mfio-testsuite-writes2");
730        seq(run.writes_equal(tdir)).await;
731        seq(run.writes_equal_rel(tdir2)).await;
732    }
733
734    pub async fn all_tests_con(run: TestRun<'_, impl Fs, impl Sized>) {
735        futures::join! {
736            con(run.files_equal()),
737            con(run.files_equal_rel()),
738            con(run.dirs_equal()),
739            con(run.walk_dirs()),
740        };
741        // We cannot put writes concurrently, because we are creating new directories, which are
742        // not expected by other tests.
743        let tdir = Path::new("mfio-testsuite-writes");
744        let tdir2 = Path::new("mfio-testsuite-writes2");
745        futures::join! {
746            seq(run.writes_equal(tdir)),
747            seq(run.writes_equal_rel(tdir2)),
748        };
749    }
750
751    pub async fn files_equal(run: TestRun<'_, impl Fs, impl Sized>) {
752        seq(run.files_equal()).await;
753    }
754
755    pub async fn files_equal_rel(run: TestRun<'_, impl Fs, impl Sized>) {
756        seq(run.files_equal_rel()).await;
757    }
758
759    pub async fn dirs_equal(run: TestRun<'_, impl Fs, impl Sized>) {
760        seq(run.dirs_equal()).await;
761    }
762
763    pub async fn walk_dirs(run: TestRun<'_, impl Fs, impl Sized>) {
764        seq(run.walk_dirs()).await;
765    }
766
767    pub async fn writes_equal(run: TestRun<'_, impl Fs, impl Sized>) {
768        let tdir = Path::new("mfio-testsuite-writes");
769        seq(run.writes_equal(tdir)).await;
770    }
771
772    pub async fn writes_equal_rel(run: TestRun<'_, impl Fs, impl Sized>) {
773        let tdir = Path::new("mfio-testsuite-writes");
774        seq(run.writes_equal_rel(tdir)).await;
775    }
776}
777
778/// Builds filesystem test suite.
779///
780/// Unlike [`test_suite!`](crate::test_suite!), this function does not include any tests, and they
781/// must be added manually. Please see the [`fs_tests`] module for a list of available tests.
782///
783/// The first parameter of the macro is the name of the generated module, while the second one
784/// contains a closure containing the test name and an asynchronous closure to be executed.
785///
786/// The third argument an onwards is a comma separated list of tests to run.
787///
788/// The closure that runs contains the entire test_suite modules. It accepts a `&'static mut T`,
789/// where `T: Fs`. To get a static ref, use the `staticify` function. It is unsound, but necessary
790/// to make test suite generation code ergonomic.
791///
792/// # Examples
793///
794/// ```no_run
795/// use mfio_rt::*;
796///
797/// test_suite_base!(tests_default, |test_name, closure| {
798///     let _ = ::env_logger::builder().is_test(true).try_init();
799///     let mut rt = NativeRt::default();
800///     let rt = staticify(&mut rt);
801///     let dir = TempDir::new(test_name).unwrap();
802///     rt.set_cwd(dir.path().to_path_buf());
803///     rt.run(move |rt| {
804///         let run = TestRun::new(rt, dir);
805///         closure(run)
806///     });
807/// },
808///     dirs_equal,
809///     files_equal
810/// );
811/// ```
812#[macro_export]
813macro_rules! test_suite_base {
814    ($test_ident:ident, $fs_builder:expr, $($(#[cfg($meta:meta)])* $test:ident),*) => {
815        #[cfg(test)]
816        #[allow(clippy::redundant_closure_call)]
817        mod $test_ident {
818            use $crate::test_suite::*;
819
820            fn staticify<T>(val: &mut T) -> &'static mut T {
821                unsafe { core::mem::transmute(val) }
822            }
823
824            macro_rules! impl_test {
825                ($name:ident) => {
826                    #[test]
827                    fn $name() {
828                        let builder: fn(&'static str, fn(TestRun<'static, _, _>) -> _) = $fs_builder;
829                        builder("mfio-testsuite", fs_tests::$name);
830                    }
831                }
832            }
833
834            $(
835                $(#[cfg($meta)])*
836                impl_test!($test);
837            )*
838        }
839    };
840}
841
842/// Builds filesystem test suite.
843///
844/// This includes all default tests, if you wish to not do that, please use
845/// [`test_suite_base!`](crate::test_suite_base!) macro.
846///
847/// The first parameter of the macro is the name of the generated module, while the second one
848/// contains a closure containing the test name and an asynchronous closure to be executed.
849///
850/// The closure that runs contains the entire test_suite modules. It accepts a `&'static mut T`,
851/// where `T: Fs`. To get a static ref, use the `staticify` function. It is unsound, but necessary
852/// to make test suite generation code ergonomic.
853///
854///
855/// # Examples
856///
857/// ```no_run
858/// use mfio_rt::*;
859///
860/// test_suite!(tests_default, |test_name, closure| {
861///     let _ = ::env_logger::builder().is_test(true).try_init();
862///     let mut rt = NativeRt::default();
863///     let rt = staticify(&mut rt);
864///     let dir = TempDir::new(test_name).unwrap();
865///     rt.set_cwd(dir.path().to_path_buf());
866///     rt.run(move |rt| {
867///         let run = TestRun::new(rt, dir);
868///         closure(run)
869///     });
870/// });
871/// ```
872#[macro_export]
873macro_rules! test_suite {
874    ($test_ident:ident, $fs_builder:expr) => {
875        $crate::test_suite_base!(
876            $test_ident,
877            $fs_builder,
878            #[cfg(not(miri))]
879            all_tests_seq,
880            #[cfg(not(miri))]
881            all_tests_con,
882            files_equal,
883            files_equal_rel,
884            dirs_equal,
885            walk_dirs,
886            writes_equal,
887            writes_equal_rel
888        );
889    };
890}
891
892/// Builds network test suite.
893///
894/// The first parameter of the macro is the name of the generated module, while the second one
895/// contains a closure containing the test name and an asynchronous closure to be executed.
896///
897/// The closure that runs contains the entire test_suite and net modules. It accepts a `&'static
898/// mut T` where `T: Tcp`. To get a static ref, use the `staticify` function. It is unsound, but
899/// necessary to make test suite generation code ergonomic.
900///
901/// # Examples
902///
903/// ```no_run
904/// use mfio_rt::*;
905///
906/// net_test_suite!(net_tests_default, |closure| {
907///     let _ = ::env_logger::builder().is_test(true).try_init();
908///     let mut rt = NativeRt::default();
909///     let rt = staticify(&mut rt);
910///     rt.run(closure);
911/// });
912/// ```
913#[cfg(feature = "std")]
914#[macro_export]
915macro_rules! net_test_suite {
916    ($test_ident:ident, $fs_builder:expr) => {
917        #[cfg(test)]
918        #[allow(clippy::redundant_closure_call)]
919        mod $test_ident {
920            use net::*;
921            use $crate::test_suite::*;
922
923            async fn seq(i: impl Iterator<Item = impl Future<Output = ()> + '_> + '_) {
924                for i in i {
925                    i.await;
926                }
927            }
928
929            async fn con(i: impl Iterator<Item = impl Future<Output = ()> + '_> + '_) {
930                let unordered = i.collect::<futures::stream::FuturesUnordered<_>>();
931                unordered.count().await;
932            }
933
934            fn staticify<T>(val: &mut T) -> &'static mut T {
935                unsafe { core::mem::transmute(val) }
936            }
937
938            #[cfg(not(miri))]
939            #[test]
940            fn tcp_connect() {
941                $fs_builder(|rt| async move {
942                    let run = NetTestRun::new(rt);
943                    run.tcp_connect().await;
944                });
945            }
946
947            #[cfg(not(miri))]
948            #[test]
949            fn tcp_listen() {
950                $fs_builder(|rt| async move {
951                    let run = NetTestRun::new(rt);
952                    run.tcp_listen().await;
953                });
954            }
955
956            #[cfg(not(miri))]
957            #[test]
958            fn tcp_send_seq() {
959                $fs_builder(|rt| async move {
960                    let run = NetTestRun::new(rt);
961                    seq(run.tcp_send()).await;
962                });
963            }
964
965            #[cfg(not(miri))]
966            #[test]
967            fn tcp_send_con() {
968                $fs_builder(|rt| async move {
969                    let run = NetTestRun::new(rt);
970                    con(run.tcp_send()).await;
971                });
972            }
973
974            #[cfg(not(miri))]
975            #[test]
976            fn tcp_receive_seq() {
977                $fs_builder(|rt| async move {
978                    let run = NetTestRun::new(rt);
979                    seq(run.tcp_receive()).await;
980                });
981            }
982
983            #[cfg(not(miri))]
984            #[test]
985            fn tcp_receive_con() {
986                $fs_builder(|rt| async move {
987                    let run = NetTestRun::new(rt);
988                    con(run.tcp_receive()).await;
989                });
990            }
991
992            #[cfg(not(miri))]
993            #[test]
994            fn tcp_echo_client_seq() {
995                $fs_builder(|rt| async move {
996                    let run = NetTestRun::new(rt);
997                    seq(run.tcp_echo_client()).await;
998                });
999            }
1000
1001            #[cfg(not(miri))]
1002            #[test]
1003            fn tcp_echo_client_con() {
1004                $fs_builder(|rt| async move {
1005                    let run = NetTestRun::new(rt);
1006                    con(run.tcp_echo_client()).await;
1007                });
1008            }
1009
1010            #[cfg(not(miri))]
1011            #[test]
1012            fn tcp_echo_server_seq() {
1013                $fs_builder(|rt| async move {
1014                    let run = NetTestRun::new(rt);
1015                    seq(run.tcp_echo_server()).await;
1016                });
1017            }
1018
1019            #[cfg(not(miri))]
1020            #[test]
1021            fn tcp_echo_server_con() {
1022                $fs_builder(|rt| async move {
1023                    let run = NetTestRun::new(rt);
1024                    con(run.tcp_echo_server()).await;
1025                });
1026            }
1027        }
1028    };
1029}