wasmer_wast/
wasi_wast.rs

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