wasmer_wast/
wasi_wast.rs

1use std::{
2    fs::{self, File, OpenOptions, ReadDir, read_dir},
3    future::Future,
4    io::{self, Read, SeekFrom},
5    path::{Path, PathBuf},
6    pin::Pin,
7    sync::{Arc, Mutex, mpsc},
8    task::{Context, Poll},
9};
10
11use anyhow::{Context as _, anyhow};
12use fs_extra::dir::{self, copy};
13use tempfile::TempDir;
14use tokio::runtime::Handle;
15use virtual_fs::{
16    AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt, FileSystem, Pipe, ReadBuf,
17    RootFileSystemBuilder, host_fs, mem_fs, passthru_fs, tmp_fs, union_fs,
18};
19use wasmer::{FunctionEnv, Imports, Module, Store};
20use wasmer_types::ModuleHash;
21use wasmer_wasix::runtime::task_manager::{block_on, tokio::TokioTaskManager};
22use wasmer_wasix::types::wasi::{Filesize, Timestamp};
23use wasmer_wasix::{
24    FsError, PluggableRuntime, VirtualFile, WasiEnv, WasiEnvBuilder, WasiVersion,
25    generate_import_object_from_env, get_wasi_version,
26};
27use wast::parser::{self, Parse, ParseBuffer, Parser};
28
29/// The kind of filesystem `WasiTest` is going to use.
30#[derive(Debug)]
31pub enum WasiFileSystemKind {
32    /// Instruct the test runner to use `virtual_fs::host_fs`.
33    Host,
34
35    /// Instruct the test runner to use `virtual_fs::mem_fs`.
36    InMemory,
37
38    /// Instruct the test runner to use `virtual_fs::tmp_fs`
39    Tmp,
40
41    /// Instruct the test runner to use `virtual_fs::passtru_fs`
42    PassthruMemory,
43
44    /// Instruct the test runner to use `virtual_fs::union_fs<host_fs, mem_fs>`
45    UnionHostMemory,
46
47    /// Instruct the test runner to use the TempFs returned by `virtual_fs::builder::RootFileSystemBuilder`
48    RootFileSystemBuilder,
49}
50
51/// Crate holding metadata parsed from the WASI WAST about the test to be run.
52#[derive(Debug, Clone, Hash)]
53pub struct WasiTest<'a> {
54    wasm_path: &'a str,
55    args: Vec<&'a str>,
56    envs: Vec<(&'a str, &'a str)>,
57    dirs: Vec<&'a str>,
58    mapped_dirs: Vec<(&'a str, &'a str)>,
59    temp_dirs: Vec<&'a str>,
60    assert_return: Option<AssertReturn>,
61    stdin: Option<Stdin<'a>>,
62    assert_stdout: Option<AssertStdout<'a>>,
63    assert_stderr: Option<AssertStderr<'a>>,
64}
65
66// TODO: add `test_fs` here to sandbox better
67const TEMP_ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../tmp/");
68const BASE_TEST_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../wasi-wast/wasi/");
69
70fn get_stdio_output(rx: &mpsc::Receiver<Vec<u8>>) -> anyhow::Result<String> {
71    let mut stdio = Vec::new();
72    while let Ok(mut buf) = rx.try_recv() {
73        stdio.append(&mut buf);
74    }
75    let stdout_str = std::str::from_utf8(&stdio[..])?;
76    #[cfg(target_os = "windows")]
77    // normalize line endings
78    return Ok(stdout_str.replace("\r\n", "\n"));
79
80    #[cfg(not(target_os = "windows"))]
81    return Ok(stdout_str.to_string());
82}
83
84#[allow(dead_code)]
85impl<'a> WasiTest<'a> {
86    /// Turn a WASI WAST string into a list of tokens.
87    pub fn lex_string(wast: &'a str) -> parser::Result<ParseBuffer<'a>> {
88        ParseBuffer::new(wast)
89    }
90
91    /// Turn a WASI WAST list of tokens into a `WasiTest` struct.
92    pub fn parse_tokens(tokens: &'a ParseBuffer<'a>) -> parser::Result<Self> {
93        parser::parse(tokens)
94    }
95
96    /// Execute the WASI test and assert.
97    pub fn run(
98        &self,
99        mut store: &mut Store,
100        base_path: &str,
101        filesystem_kind: WasiFileSystemKind,
102    ) -> anyhow::Result<bool> {
103        use anyhow::Context;
104
105        #[cfg(not(target_arch = "wasm32"))]
106        let runtime = tokio::runtime::Builder::new_multi_thread()
107            .enable_all()
108            .build()?;
109        #[cfg(not(target_arch = "wasm32"))]
110        let handle = runtime.handle().clone();
111        #[cfg(not(target_arch = "wasm32"))]
112        let _guard = handle.enter();
113        #[cfg(not(target_arch = "wasm32"))]
114        let mut rt = PluggableRuntime::new(Arc::new(TokioTaskManager::new(runtime)));
115        #[cfg(target_arch = "wasm32")]
116        let mut rt = PluggableRuntime::new(Arc::new(TokioTaskManager::default()));
117        rt.set_engine(store.engine().clone());
118
119        let mut pb = PathBuf::from(base_path);
120        pb.push(self.wasm_path);
121        let wasm_bytes = {
122            let mut wasm_module = File::open(pb)?;
123            let mut out = vec![];
124            wasm_module.read_to_end(&mut out)?;
125            out
126        };
127        let module_hash = ModuleHash::xxhash(&wasm_bytes);
128
129        let module = Module::new(store, wasm_bytes)?;
130        let (builder, _tempdirs, mut stdin_tx, stdout_rx, stderr_rx) =
131            { block_on(async { self.create_wasi_env(filesystem_kind).await }) }?;
132
133        let (instance, _wasi_env) =
134            builder
135                .runtime(Arc::new(rt))
136                .instantiate_ext(module, module_hash, store)?;
137
138        let start = instance.exports.get_function("_start")?;
139
140        if let Some(stdin) = &self.stdin {
141            // let mut wasi_stdin = { wasi_env.data(store).stdin().unwrap().unwrap() };
142            // Then we can write to it!
143            let data = stdin.stream.to_string();
144            block_on(async move {
145                stdin_tx.write_all(data.as_bytes()).await?;
146                stdin_tx.shutdown().await?;
147
148                Ok::<_, anyhow::Error>(())
149            })?;
150        } else {
151            std::mem::drop(stdin_tx);
152        }
153
154        // TODO: handle errors here when the error fix gets shipped
155        match start.call(&mut store, &[]) {
156            Ok(_) => {}
157            Err(e) => {
158                let stdout_str = get_stdio_output(&stdout_rx)?;
159                let stderr_str = get_stdio_output(&stderr_rx)?;
160                Err(e).with_context(|| {
161                    format!(
162                        "failed to run WASI `_start` function: failed with stdout: \"{stdout_str}\"\nstderr: \"{stderr_str}\"",
163                    )
164                })?;
165            }
166        }
167
168        if let Some(expected_stdout) = &self.assert_stdout {
169            let stdout_str = get_stdio_output(&stdout_rx)?;
170            //dbg!(&expected_stdout, &stdout_str);
171            assert_eq!(stdout_str, expected_stdout.expected);
172        }
173
174        if let Some(expected_stderr) = &self.assert_stderr {
175            let stderr_str = get_stdio_output(&stderr_rx)?;
176            assert_eq!(stderr_str, expected_stderr.expected);
177        }
178
179        Ok(true)
180    }
181
182    /// Create the wasi env with the given metadata.
183    #[allow(clippy::type_complexity)]
184    async fn create_wasi_env(
185        &self,
186        filesystem_kind: WasiFileSystemKind,
187    ) -> anyhow::Result<(
188        WasiEnvBuilder,
189        Vec<tempfile::TempDir>,
190        Pipe,
191        mpsc::Receiver<Vec<u8>>,
192        mpsc::Receiver<Vec<u8>>,
193    )> {
194        let mut builder = WasiEnv::builder(self.wasm_path);
195
196        let (stdin_tx, stdin_rx) = Pipe::channel();
197        builder.set_stdin(Box::new(stdin_rx));
198
199        for (name, value) in &self.envs {
200            builder.add_env(name, value);
201        }
202
203        let mut host_temp_dirs_to_not_drop = vec![];
204
205        match filesystem_kind {
206            WasiFileSystemKind::Host => {
207                // Use a temporary folder, otherwise other file systems will spot the artifacts of this FS.
208                let mut source = PathBuf::from(BASE_TEST_DIR);
209                source.push("test_fs");
210
211                fs::create_dir_all(TEMP_ROOT)
212                    .with_context(|| anyhow!("cannot create root tmp folder for WASI tests"))?;
213
214                let root_dir = TempDir::with_prefix_in("host_fs_copy-", TEMP_ROOT)
215                    .with_context(|| anyhow!("cannot create temporary directory"))?;
216                copy(source.as_path(), root_dir.path(), &dir::CopyOptions::new())
217                    .with_context(|| anyhow!("cannot copy to the temporary directory"))?;
218                let base_dir = root_dir.path().to_path_buf();
219                host_temp_dirs_to_not_drop.push(root_dir);
220
221                let fs = host_fs::FileSystem::new(Handle::current(), base_dir.clone()).unwrap();
222
223                for (alias, real_dir) in &self.mapped_dirs {
224                    let mut dir = base_dir.clone();
225                    dir.push(real_dir);
226                    builder.add_map_dir(alias, dir)?;
227                }
228
229                // due to the structure of our code, all preopen dirs must be mapped now
230                for dir in &self.dirs {
231                    let mut new_dir = base_dir.clone();
232                    new_dir.push(dir);
233                    builder.add_map_dir(dir, new_dir)?;
234                }
235
236                for alias in &self.temp_dirs {
237                    let temp_dir = tempfile::tempdir_in(&base_dir)?;
238                    builder.add_map_dir(alias, temp_dir.path())?;
239                    host_temp_dirs_to_not_drop.push(temp_dir);
240                }
241
242                builder.set_fs(Arc::new(fs) as Arc<dyn FileSystem + Send + Sync>);
243            }
244
245            other => {
246                let fs: Arc<dyn FileSystem + Send + Sync> = match other {
247                    WasiFileSystemKind::InMemory => Arc::<mem_fs::FileSystem>::default(),
248                    WasiFileSystemKind::Tmp => Arc::<tmp_fs::TmpFileSystem>::default(),
249                    WasiFileSystemKind::PassthruMemory => {
250                        let fs = Arc::<mem_fs::FileSystem>::default();
251                        Arc::new(passthru_fs::PassthruFileSystem::new_arc(fs))
252                    }
253                    WasiFileSystemKind::RootFileSystemBuilder => {
254                        Arc::new(RootFileSystemBuilder::new().build())
255                    }
256                    WasiFileSystemKind::UnionHostMemory => {
257                        let a = mem_fs::FileSystem::default();
258                        let b = mem_fs::FileSystem::default();
259                        let c = mem_fs::FileSystem::default();
260                        let d = mem_fs::FileSystem::default();
261                        let e = mem_fs::FileSystem::default();
262                        let f = mem_fs::FileSystem::default();
263
264                        let union = union_fs::UnionFileSystem::new();
265
266                        union.mount(
267                            "mem_fs".to_string(),
268                            PathBuf::from("/test_fs").as_ref(),
269                            Box::new(a),
270                        )?;
271                        union.mount(
272                            "mem_fs_2".to_string(),
273                            PathBuf::from("/snapshot1").as_ref(),
274                            Box::new(b),
275                        )?;
276                        union.mount(
277                            "mem_fs_3".to_string(),
278                            PathBuf::from("/tests").as_ref(),
279                            Box::new(c),
280                        )?;
281                        union.mount(
282                            "mem_fs_4".to_string(),
283                            PathBuf::from("/nightly_2022_10_18").as_ref(),
284                            Box::new(d),
285                        )?;
286                        union.mount(
287                            "mem_fs_5".to_string(),
288                            PathBuf::from("/unstable").as_ref(),
289                            Box::new(e),
290                        )?;
291                        union.mount(
292                            "mem_fs_6".to_string(),
293                            PathBuf::from("/.tmp_wasmer_wast_0").as_ref(),
294                            Box::new(f),
295                        )?;
296
297                        Arc::new(union)
298                    }
299                    _ => {
300                        panic!("unexpected filesystem type {other:?}");
301                    }
302                };
303
304                let mut temp_dir_index: usize = 0;
305
306                let root = PathBuf::from("/");
307
308                map_host_fs_to_mem_fs(&*fs, read_dir(BASE_TEST_DIR)?, &root).await?;
309
310                for (alias, real_dir) in &self.mapped_dirs {
311                    let mut path = root.clone();
312                    path.push(real_dir);
313                    builder.add_map_dir(alias, path)?;
314                }
315
316                for dir in &self.dirs {
317                    let mut new_dir = PathBuf::from("/");
318                    new_dir.push(dir);
319
320                    builder.add_map_dir(dir, new_dir)?;
321                }
322
323                for alias in &self.temp_dirs {
324                    let temp_dir_name =
325                        PathBuf::from(format!("/.tmp_wasmer_wast_{temp_dir_index}"));
326                    fs.create_dir(temp_dir_name.as_path())?;
327                    builder.add_map_dir(alias, temp_dir_name)?;
328                    temp_dir_index += 1;
329                }
330
331                builder.set_fs(fs);
332            }
333        }
334
335        let (stdout, stdout_rx) = OutputCapturerer::new();
336        let (stderr, stderr_rx) = OutputCapturerer::new();
337        let builder = builder
338            .args(&self.args)
339            // adding this causes some tests to fail. TODO: investigate this
340            //.env("RUST_BACKTRACE", "1")
341            .stdout(Box::new(stdout))
342            .stderr(Box::new(stderr));
343
344        Ok((
345            builder,
346            host_temp_dirs_to_not_drop,
347            stdin_tx,
348            stdout_rx,
349            stderr_rx,
350        ))
351    }
352
353    /// Get the correct [`WasiVersion`] from the Wasm [`Module`].
354    fn get_version(&self, module: &Module) -> anyhow::Result<WasiVersion> {
355        use anyhow::Context;
356        let version = get_wasi_version(module, true)
357            .with_context(|| "failed to detect a version of WASI from the module")?;
358        Ok(version)
359    }
360
361    /// Get the correct WASI import object for the given module and set it up with the
362    /// [`WasiEnv`].
363    fn get_imports(
364        &self,
365        store: &mut Store,
366        ctx: &FunctionEnv<WasiEnv>,
367        module: &Module,
368    ) -> anyhow::Result<Imports> {
369        let version = self.get_version(module)?;
370        Ok(generate_import_object_from_env(store, ctx, version))
371    }
372}
373
374mod wasi_kw {
375    wast::custom_keyword!(wasi_test);
376    wast::custom_keyword!(envs);
377    wast::custom_keyword!(args);
378    wast::custom_keyword!(preopens);
379    wast::custom_keyword!(map_dirs);
380    wast::custom_keyword!(temp_dirs);
381    wast::custom_keyword!(assert_return);
382    wast::custom_keyword!(stdin);
383    wast::custom_keyword!(assert_stdout);
384    wast::custom_keyword!(assert_stderr);
385    wast::custom_keyword!(fake_i64_const = "i64.const");
386}
387
388impl<'a> Parse<'a> for WasiTest<'a> {
389    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
390        parser.parens(|parser| {
391            parser.parse::<wasi_kw::wasi_test>()?;
392            // TODO: improve error message here
393            let wasm_path = parser.parse::<&'a str>()?;
394
395            // TODO: allow these to come in any order
396            let envs = if parser.peek2::<wasi_kw::envs>()? {
397                parser.parens(|p| p.parse::<Envs>())?.envs
398            } else {
399                vec![]
400            };
401
402            let args = if parser.peek2::<wasi_kw::args>()? {
403                parser.parens(|p| p.parse::<Args>())?.args
404            } else {
405                vec![]
406            };
407
408            let dirs = if parser.peek2::<wasi_kw::preopens>()? {
409                parser.parens(|p| p.parse::<Preopens>())?.preopens
410            } else {
411                vec![]
412            };
413
414            let mapped_dirs = if parser.peek2::<wasi_kw::map_dirs>()? {
415                parser.parens(|p| p.parse::<MapDirs>())?.map_dirs
416            } else {
417                vec![]
418            };
419
420            let temp_dirs = if parser.peek2::<wasi_kw::temp_dirs>()? {
421                parser.parens(|p| p.parse::<TempDirs>())?.temp_dirs
422            } else {
423                vec![]
424            };
425
426            let assert_return = if parser.peek2::<wasi_kw::assert_return>()? {
427                Some(parser.parens(|p| p.parse::<AssertReturn>())?)
428            } else {
429                None
430            };
431
432            let stdin = if parser.peek2::<wasi_kw::stdin>()? {
433                Some(parser.parens(|p| p.parse::<Stdin>())?)
434            } else {
435                None
436            };
437
438            let assert_stdout = if parser.peek2::<wasi_kw::assert_stdout>()? {
439                Some(parser.parens(|p| p.parse::<AssertStdout>())?)
440            } else {
441                None
442            };
443
444            let assert_stderr = if parser.peek2::<wasi_kw::assert_stderr>()? {
445                Some(parser.parens(|p| p.parse::<AssertStderr>())?)
446            } else {
447                None
448            };
449
450            Ok(Self {
451                wasm_path,
452                args,
453                envs,
454                dirs,
455                mapped_dirs,
456                temp_dirs,
457                assert_return,
458                stdin,
459                assert_stdout,
460                assert_stderr,
461            })
462        })
463    }
464}
465
466#[derive(Debug, Clone, Hash)]
467struct Envs<'a> {
468    envs: Vec<(&'a str, &'a str)>,
469}
470
471impl<'a> Parse<'a> for Envs<'a> {
472    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
473        let mut envs = vec![];
474        parser.parse::<wasi_kw::envs>()?;
475
476        while parser.peek::<&'a str>()? {
477            let res = parser.parse::<&'a str>()?;
478            let mut strs = res.split('=');
479            let first = strs.next().unwrap();
480            let second = strs.next().unwrap();
481            //debug_assert!(strs.next().is_none());
482            envs.push((first, second));
483        }
484        Ok(Self { envs })
485    }
486}
487
488#[derive(Debug, Clone, Hash)]
489struct Args<'a> {
490    args: Vec<&'a str>,
491}
492
493impl<'a> Parse<'a> for Args<'a> {
494    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
495        let mut args = vec![];
496        parser.parse::<wasi_kw::args>()?;
497
498        while parser.peek::<&'a str>()? {
499            let res = parser.parse::<&'a str>()?;
500            args.push(res);
501        }
502        Ok(Self { args })
503    }
504}
505
506#[derive(Debug, Clone, Hash)]
507struct Preopens<'a> {
508    preopens: Vec<&'a str>,
509}
510
511impl<'a> Parse<'a> for Preopens<'a> {
512    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
513        let mut preopens = vec![];
514        parser.parse::<wasi_kw::preopens>()?;
515
516        while parser.peek::<&'a str>()? {
517            let res = parser.parse::<&'a str>()?;
518            preopens.push(res);
519        }
520        Ok(Self { preopens })
521    }
522}
523
524#[derive(Debug, Clone, Hash)]
525struct MapDirs<'a> {
526    map_dirs: Vec<(&'a str, &'a str)>,
527}
528
529impl<'a> Parse<'a> for MapDirs<'a> {
530    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
531        let mut map_dirs = vec![];
532        parser.parse::<wasi_kw::map_dirs>()?;
533
534        while parser.peek::<&'a str>()? {
535            let res = parser.parse::<&'a str>()?;
536            let mut iter = res.split(':');
537            let dir = iter.next().unwrap();
538            let alias = iter.next().unwrap();
539            map_dirs.push((dir, alias));
540        }
541        Ok(Self { map_dirs })
542    }
543}
544
545#[derive(Debug, Clone, Hash)]
546struct TempDirs<'a> {
547    temp_dirs: Vec<&'a str>,
548}
549
550impl<'a> Parse<'a> for TempDirs<'a> {
551    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
552        let mut temp_dirs = vec![];
553        parser.parse::<wasi_kw::temp_dirs>()?;
554
555        while parser.peek::<&'a str>()? {
556            let alias = parser.parse::<&'a str>()?;
557            temp_dirs.push(alias);
558        }
559        Ok(Self { temp_dirs })
560    }
561}
562
563#[derive(Debug, Clone, PartialEq, Eq, Hash)]
564struct AssertReturn {
565    return_value: i64,
566}
567
568impl<'a> Parse<'a> for AssertReturn {
569    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
570        parser.parse::<wasi_kw::assert_return>()?;
571        let return_value = parser.parens(|p| {
572            p.parse::<wasi_kw::fake_i64_const>()?;
573            p.parse::<i64>()
574        })?;
575        Ok(Self { return_value })
576    }
577}
578
579#[derive(Debug, Clone, PartialEq, Eq, Hash)]
580struct Stdin<'a> {
581    stream: &'a str,
582}
583
584impl<'a> Parse<'a> for Stdin<'a> {
585    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
586        parser.parse::<wasi_kw::stdin>()?;
587        Ok(Self {
588            stream: parser.parse()?,
589        })
590    }
591}
592
593#[derive(Debug, Clone, PartialEq, Eq, Hash)]
594struct AssertStdout<'a> {
595    expected: &'a str,
596}
597
598impl<'a> Parse<'a> for AssertStdout<'a> {
599    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
600        parser.parse::<wasi_kw::assert_stdout>()?;
601        Ok(Self {
602            expected: parser.parse()?,
603        })
604    }
605}
606
607#[derive(Debug, Clone, PartialEq, Eq, Hash)]
608struct AssertStderr<'a> {
609    expected: &'a str,
610}
611
612impl<'a> Parse<'a> for AssertStderr<'a> {
613    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
614        parser.parse::<wasi_kw::assert_stderr>()?;
615        Ok(Self {
616            expected: parser.parse()?,
617        })
618    }
619}
620
621#[cfg(test)]
622mod test {
623    use super::*;
624
625    #[tokio::test]
626    async fn test_parse() {
627        let pb = wast::parser::ParseBuffer::new(
628            r#"(wasi_test "my_wasm.wasm"
629                    (envs "HELLO=WORLD" "RUST_BACKTRACE=1")
630                    (args "hello" "world" "--help")
631                    (preopens "." "src/io")
632                    (assert_return (i64.const 0))
633                    (stdin "This is another \"string\" inside a string!")
634                    (assert_stdout "This is a \"string\" inside a string!")
635                    (assert_stderr "")
636)"#,
637        )
638        .unwrap();
639        let result = wast::parser::parse::<WasiTest>(&pb).unwrap();
640
641        assert_eq!(result.args, vec!["hello", "world", "--help"]);
642        assert_eq!(
643            result.envs,
644            vec![("HELLO", "WORLD"), ("RUST_BACKTRACE", "1")]
645        );
646        assert_eq!(result.dirs, vec![".", "src/io"]);
647        assert_eq!(result.assert_return.unwrap().return_value, 0);
648        assert_eq!(
649            result.assert_stdout.unwrap().expected,
650            "This is a \"string\" inside a string!"
651        );
652        assert_eq!(
653            result.stdin.unwrap().stream,
654            "This is another \"string\" inside a string!"
655        );
656        assert_eq!(result.assert_stderr.unwrap().expected, "");
657    }
658}
659
660#[derive(Debug, Clone)]
661struct OutputCapturerer {
662    output: Arc<Mutex<mpsc::Sender<Vec<u8>>>>,
663}
664
665impl OutputCapturerer {
666    fn new() -> (Self, mpsc::Receiver<Vec<u8>>) {
667        let (tx, rx) = mpsc::channel();
668        (
669            Self {
670                output: Arc::new(Mutex::new(tx)),
671            },
672            rx,
673        )
674    }
675}
676
677impl VirtualFile for OutputCapturerer {
678    fn last_accessed(&self) -> Timestamp {
679        0
680    }
681    fn last_modified(&self) -> Timestamp {
682        0
683    }
684    fn created_time(&self) -> Timestamp {
685        0
686    }
687    fn size(&self) -> u64 {
688        0
689    }
690    fn set_len(&mut self, _new_size: Filesize) -> Result<(), FsError> {
691        Ok(())
692    }
693    fn unlink(&mut self) -> Result<(), FsError> {
694        Ok(())
695    }
696    fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
697        Poll::Ready(Ok(0))
698    }
699    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
700        Poll::Ready(Ok(8192))
701    }
702}
703
704impl AsyncSeek for OutputCapturerer {
705    fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> {
706        Err(io::Error::other("can not seek logging wrapper"))
707    }
708    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
709        Poll::Ready(Err(io::Error::other("can not seek logging wrapper")))
710    }
711}
712
713impl AsyncWrite for OutputCapturerer {
714    fn poll_write(
715        self: Pin<&mut Self>,
716        _cx: &mut Context<'_>,
717        buf: &[u8],
718    ) -> Poll<io::Result<usize>> {
719        self.output
720            .lock()
721            .unwrap()
722            .send(buf.to_vec())
723            .map_err(|err| io::Error::new(io::ErrorKind::BrokenPipe, err.to_string()))?;
724        Poll::Ready(Ok(buf.len()))
725    }
726    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
727        Poll::Ready(Ok(()))
728    }
729    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
730        Poll::Ready(Ok(()))
731    }
732}
733
734impl AsyncRead for OutputCapturerer {
735    fn poll_read(
736        self: Pin<&mut Self>,
737        _cx: &mut Context<'_>,
738        _buf: &mut ReadBuf<'_>,
739    ) -> Poll<io::Result<()>> {
740        Poll::Ready(Err(io::Error::other("can not read from logging wrapper")))
741    }
742}
743
744/// When using `virtual_fs::mem_fs`, we cannot rely on `BASE_TEST_DIR`
745/// because the host filesystem cannot be used. Instead, we are
746/// copying `BASE_TEST_DIR` to the `mem_fs`.
747fn map_host_fs_to_mem_fs<'a>(
748    fs: &'a dyn FileSystem,
749    directory_reader: ReadDir,
750    path_prefix: &'a Path,
751) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + 'a>> {
752    Box::pin(async move {
753        for entry in directory_reader {
754            let entry = entry?;
755            let entry_type = entry.file_type()?;
756
757            let path = path_prefix.join(entry.path().file_name().unwrap());
758
759            if entry_type.is_dir() {
760                fs.create_dir(&path)?;
761
762                map_host_fs_to_mem_fs(fs, read_dir(entry.path())?, &path).await?
763            } else if entry_type.is_file() {
764                let mut host_file = OpenOptions::new().read(true).open(entry.path())?;
765                let mut mem_file = fs
766                    .new_open_options()
767                    .create_new(true)
768                    .write(true)
769                    .open(path)?;
770                let mut buffer = Vec::new();
771                Read::read_to_end(&mut host_file, &mut buffer)?;
772                mem_file.write_all(&buffer).await?;
773            } else if entry_type.is_symlink() {
774                //unimplemented!("`mem_fs` does not support symlink for the moment");
775            }
776        }
777
778        Ok(())
779    })
780}