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, mount_fs, passthru_fs, tmp_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, WasiError, 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::mount_fs::MountFileSystem`
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::new(&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        let exit_code = match start.call(&mut store, &[]) {
155            Ok(_) => 0,
156            Err(e) => {
157                if let Some(WasiError::Exit(code)) = e.downcast_ref::<WasiError>() {
158                    code.raw() as i64
159                } else {
160                    let stdout_str = get_stdio_output(&stdout_rx)?;
161                    let stderr_str = get_stdio_output(&stderr_rx)?;
162                    return Err(e).with_context(|| {
163                        format!(
164                            "failed to run WASI `_start` function: failed with stdout: \"{stdout_str}\"\nstderr: \"{stderr_str}\"",
165                        )
166                    });
167                }
168            }
169        };
170
171        if let Some(expected) = &self.assert_return {
172            assert_eq!(exit_code, expected.return_value);
173        }
174
175        if let Some(expected_stdout) = &self.assert_stdout {
176            let stdout_str = get_stdio_output(&stdout_rx)?;
177            //dbg!(&expected_stdout, &stdout_str);
178            assert_eq!(stdout_str, expected_stdout.expected);
179        }
180
181        if let Some(expected_stderr) = &self.assert_stderr {
182            let stderr_str = get_stdio_output(&stderr_rx)?;
183            assert_eq!(stderr_str, expected_stderr.expected);
184        }
185
186        Ok(true)
187    }
188
189    /// Create the wasi env with the given metadata.
190    #[allow(clippy::type_complexity)]
191    async fn create_wasi_env(
192        &self,
193        filesystem_kind: WasiFileSystemKind,
194    ) -> anyhow::Result<(
195        WasiEnvBuilder,
196        Vec<tempfile::TempDir>,
197        Pipe,
198        mpsc::Receiver<Vec<u8>>,
199        mpsc::Receiver<Vec<u8>>,
200    )> {
201        let mut builder = WasiEnv::builder(self.wasm_path);
202
203        let (stdin_tx, stdin_rx) = Pipe::channel();
204        builder.set_stdin(Box::new(stdin_rx));
205
206        for (name, value) in &self.envs {
207            builder.add_env(name, value);
208        }
209
210        let mut host_temp_dirs_to_not_drop = vec![];
211
212        match filesystem_kind {
213            WasiFileSystemKind::Host => {
214                // Use a temporary folder, otherwise other file systems will spot the artifacts of this FS.
215                let mut source = PathBuf::from(BASE_TEST_DIR);
216                source.push("test_fs");
217
218                fs::create_dir_all(TEMP_ROOT)
219                    .with_context(|| anyhow!("cannot create root tmp folder for WASI tests"))?;
220
221                let root_dir = TempDir::with_prefix_in("host_fs_copy-", TEMP_ROOT)
222                    .with_context(|| anyhow!("cannot create temporary directory"))?;
223                copy(source.as_path(), root_dir.path(), &dir::CopyOptions::new())
224                    .with_context(|| anyhow!("cannot copy to the temporary directory"))?;
225                let base_dir = root_dir.path().to_path_buf();
226                host_temp_dirs_to_not_drop.push(root_dir);
227
228                let fs = host_fs::FileSystem::new(Handle::current(), base_dir.clone()).unwrap();
229
230                for (alias, real_dir) in &self.mapped_dirs {
231                    let mut guest_dir = PathBuf::from("/");
232                    guest_dir.push(real_dir);
233                    builder.add_map_dir(alias, guest_dir)?;
234                }
235
236                // due to the structure of our code, all preopen dirs must be mapped now
237                for dir in &self.dirs {
238                    let mut new_dir = PathBuf::from("/");
239                    new_dir.push(dir);
240                    builder.add_map_dir(dir, new_dir)?;
241                }
242
243                for alias in &self.temp_dirs {
244                    let temp_dir = tempfile::tempdir_in(&base_dir)?;
245                    let guest_temp_dir =
246                        Path::new("/").join(temp_dir.path().strip_prefix(&base_dir).unwrap());
247                    builder.add_map_dir(alias, guest_temp_dir)?;
248                    host_temp_dirs_to_not_drop.push(temp_dir);
249                }
250
251                builder.set_fs(Arc::new(fs) as Arc<dyn FileSystem + Send + Sync>);
252            }
253
254            other => {
255                let fs: Arc<dyn FileSystem + Send + Sync> = match other {
256                    WasiFileSystemKind::InMemory => Arc::<mem_fs::FileSystem>::default(),
257                    WasiFileSystemKind::Tmp => Arc::<tmp_fs::TmpFileSystem>::default(),
258                    WasiFileSystemKind::PassthruMemory => {
259                        let fs = Arc::<mem_fs::FileSystem>::default();
260                        Arc::new(passthru_fs::PassthruFileSystem::new_arc(fs))
261                    }
262                    WasiFileSystemKind::RootFileSystemBuilder => {
263                        Arc::new(RootFileSystemBuilder::new().build())
264                    }
265                    WasiFileSystemKind::UnionHostMemory => {
266                        let a = mem_fs::FileSystem::default();
267                        let b = mem_fs::FileSystem::default();
268                        let c = mem_fs::FileSystem::default();
269                        let d = mem_fs::FileSystem::default();
270                        let e = mem_fs::FileSystem::default();
271                        let f = mem_fs::FileSystem::default();
272
273                        let mount_fs = mount_fs::MountFileSystem::new();
274
275                        mount_fs.mount("/test_fs", Arc::new(a))?;
276                        mount_fs.mount("/snapshot1", Arc::new(b))?;
277                        mount_fs.mount("/tests", Arc::new(c))?;
278                        mount_fs.mount("/nightly_2022_10_18", Arc::new(d))?;
279                        mount_fs.mount("/unstable", Arc::new(e))?;
280                        mount_fs.mount("/.tmp_wasmer_wast_0", Arc::new(f))?;
281
282                        Arc::new(mount_fs)
283                    }
284                    _ => {
285                        panic!("unexpected filesystem type {other:?}");
286                    }
287                };
288
289                let mut temp_dir_index: usize = 0;
290
291                let root = PathBuf::from("/");
292
293                map_host_fs_to_mem_fs(&*fs, read_dir(BASE_TEST_DIR)?, &root).await?;
294
295                for (alias, real_dir) in &self.mapped_dirs {
296                    let mut path = root.clone();
297                    path.push(real_dir);
298                    builder.add_map_dir(alias, path)?;
299                }
300
301                for dir in &self.dirs {
302                    let mut new_dir = PathBuf::from("/");
303                    new_dir.push(dir);
304
305                    builder.add_map_dir(dir, new_dir)?;
306                }
307
308                for alias in &self.temp_dirs {
309                    let temp_dir_name =
310                        PathBuf::from(format!("/.tmp_wasmer_wast_{temp_dir_index}"));
311                    fs.create_dir(temp_dir_name.as_path())?;
312                    builder.add_map_dir(alias, temp_dir_name)?;
313                    temp_dir_index += 1;
314                }
315
316                builder.set_fs(fs);
317            }
318        }
319
320        let (stdout, stdout_rx) = OutputCapturerer::new();
321        let (stderr, stderr_rx) = OutputCapturerer::new();
322        let builder = builder
323            .args(&self.args)
324            // adding this causes some tests to fail. TODO: investigate this
325            //.env("RUST_BACKTRACE", "1")
326            .stdout(Box::new(stdout))
327            .stderr(Box::new(stderr));
328
329        Ok((
330            builder,
331            host_temp_dirs_to_not_drop,
332            stdin_tx,
333            stdout_rx,
334            stderr_rx,
335        ))
336    }
337
338    /// Get the correct [`WasiVersion`] from the Wasm [`Module`].
339    fn get_version(&self, module: &Module) -> anyhow::Result<WasiVersion> {
340        use anyhow::Context;
341        let version = get_wasi_version(module, true)
342            .with_context(|| "failed to detect a version of WASI from the module")?;
343        Ok(version)
344    }
345
346    /// Get the correct WASI import object for the given module and set it up with the
347    /// [`WasiEnv`].
348    fn get_imports(
349        &self,
350        store: &mut Store,
351        ctx: &FunctionEnv<WasiEnv>,
352        module: &Module,
353    ) -> anyhow::Result<Imports> {
354        let version = self.get_version(module)?;
355        Ok(generate_import_object_from_env(store, ctx, version))
356    }
357}
358
359mod wasi_kw {
360    wast::custom_keyword!(wasi_test);
361    wast::custom_keyword!(envs);
362    wast::custom_keyword!(args);
363    wast::custom_keyword!(preopens);
364    wast::custom_keyword!(map_dirs);
365    wast::custom_keyword!(temp_dirs);
366    wast::custom_keyword!(assert_return);
367    wast::custom_keyword!(stdin);
368    wast::custom_keyword!(assert_stdout);
369    wast::custom_keyword!(assert_stderr);
370    wast::custom_keyword!(fake_i64_const = "i64.const");
371}
372
373impl<'a> Parse<'a> for WasiTest<'a> {
374    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
375        parser.parens(|parser| {
376            parser.parse::<wasi_kw::wasi_test>()?;
377            // TODO: improve error message here
378            let wasm_path = parser.parse::<&'a str>()?;
379
380            // TODO: allow these to come in any order
381            let envs = if parser.peek2::<wasi_kw::envs>()? {
382                parser.parens(|p| p.parse::<Envs>())?.envs
383            } else {
384                vec![]
385            };
386
387            let args = if parser.peek2::<wasi_kw::args>()? {
388                parser.parens(|p| p.parse::<Args>())?.args
389            } else {
390                vec![]
391            };
392
393            let dirs = if parser.peek2::<wasi_kw::preopens>()? {
394                parser.parens(|p| p.parse::<Preopens>())?.preopens
395            } else {
396                vec![]
397            };
398
399            let mapped_dirs = if parser.peek2::<wasi_kw::map_dirs>()? {
400                parser.parens(|p| p.parse::<MapDirs>())?.map_dirs
401            } else {
402                vec![]
403            };
404
405            let temp_dirs = if parser.peek2::<wasi_kw::temp_dirs>()? {
406                parser.parens(|p| p.parse::<TempDirs>())?.temp_dirs
407            } else {
408                vec![]
409            };
410
411            let assert_return = if parser.peek2::<wasi_kw::assert_return>()? {
412                Some(parser.parens(|p| p.parse::<AssertReturn>())?)
413            } else {
414                None
415            };
416
417            let stdin = if parser.peek2::<wasi_kw::stdin>()? {
418                Some(parser.parens(|p| p.parse::<Stdin>())?)
419            } else {
420                None
421            };
422
423            let assert_stdout = if parser.peek2::<wasi_kw::assert_stdout>()? {
424                Some(parser.parens(|p| p.parse::<AssertStdout>())?)
425            } else {
426                None
427            };
428
429            let assert_stderr = if parser.peek2::<wasi_kw::assert_stderr>()? {
430                Some(parser.parens(|p| p.parse::<AssertStderr>())?)
431            } else {
432                None
433            };
434
435            Ok(Self {
436                wasm_path,
437                args,
438                envs,
439                dirs,
440                mapped_dirs,
441                temp_dirs,
442                assert_return,
443                stdin,
444                assert_stdout,
445                assert_stderr,
446            })
447        })
448    }
449}
450
451#[derive(Debug, Clone, Hash)]
452struct Envs<'a> {
453    envs: Vec<(&'a str, &'a str)>,
454}
455
456impl<'a> Parse<'a> for Envs<'a> {
457    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
458        let mut envs = vec![];
459        parser.parse::<wasi_kw::envs>()?;
460
461        while parser.peek::<&'a str>()? {
462            let res = parser.parse::<&'a str>()?;
463            let mut strs = res.split('=');
464            let first = strs.next().unwrap();
465            let second = strs.next().unwrap();
466            //debug_assert!(strs.next().is_none());
467            envs.push((first, second));
468        }
469        Ok(Self { envs })
470    }
471}
472
473#[derive(Debug, Clone, Hash)]
474struct Args<'a> {
475    args: Vec<&'a str>,
476}
477
478impl<'a> Parse<'a> for Args<'a> {
479    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
480        let mut args = vec![];
481        parser.parse::<wasi_kw::args>()?;
482
483        while parser.peek::<&'a str>()? {
484            let res = parser.parse::<&'a str>()?;
485            args.push(res);
486        }
487        Ok(Self { args })
488    }
489}
490
491#[derive(Debug, Clone, Hash)]
492struct Preopens<'a> {
493    preopens: Vec<&'a str>,
494}
495
496impl<'a> Parse<'a> for Preopens<'a> {
497    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
498        let mut preopens = vec![];
499        parser.parse::<wasi_kw::preopens>()?;
500
501        while parser.peek::<&'a str>()? {
502            let res = parser.parse::<&'a str>()?;
503            preopens.push(res);
504        }
505        Ok(Self { preopens })
506    }
507}
508
509#[derive(Debug, Clone, Hash)]
510struct MapDirs<'a> {
511    map_dirs: Vec<(&'a str, &'a str)>,
512}
513
514impl<'a> Parse<'a> for MapDirs<'a> {
515    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
516        let mut map_dirs = vec![];
517        parser.parse::<wasi_kw::map_dirs>()?;
518
519        while parser.peek::<&'a str>()? {
520            let res = parser.parse::<&'a str>()?;
521            let mut iter = res.split(':');
522            let dir = iter.next().unwrap();
523            let alias = iter.next().unwrap();
524            map_dirs.push((dir, alias));
525        }
526        Ok(Self { map_dirs })
527    }
528}
529
530#[derive(Debug, Clone, Hash)]
531struct TempDirs<'a> {
532    temp_dirs: Vec<&'a str>,
533}
534
535impl<'a> Parse<'a> for TempDirs<'a> {
536    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
537        let mut temp_dirs = vec![];
538        parser.parse::<wasi_kw::temp_dirs>()?;
539
540        while parser.peek::<&'a str>()? {
541            let alias = parser.parse::<&'a str>()?;
542            temp_dirs.push(alias);
543        }
544        Ok(Self { temp_dirs })
545    }
546}
547
548#[derive(Debug, Clone, PartialEq, Eq, Hash)]
549struct AssertReturn {
550    return_value: i64,
551}
552
553impl<'a> Parse<'a> for AssertReturn {
554    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
555        parser.parse::<wasi_kw::assert_return>()?;
556        let return_value = parser.parens(|p| {
557            p.parse::<wasi_kw::fake_i64_const>()?;
558            p.parse::<i64>()
559        })?;
560        Ok(Self { return_value })
561    }
562}
563
564#[derive(Debug, Clone, PartialEq, Eq, Hash)]
565struct Stdin<'a> {
566    stream: &'a str,
567}
568
569impl<'a> Parse<'a> for Stdin<'a> {
570    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
571        parser.parse::<wasi_kw::stdin>()?;
572        Ok(Self {
573            stream: parser.parse()?,
574        })
575    }
576}
577
578#[derive(Debug, Clone, PartialEq, Eq, Hash)]
579struct AssertStdout<'a> {
580    expected: &'a str,
581}
582
583impl<'a> Parse<'a> for AssertStdout<'a> {
584    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
585        parser.parse::<wasi_kw::assert_stdout>()?;
586        Ok(Self {
587            expected: parser.parse()?,
588        })
589    }
590}
591
592#[derive(Debug, Clone, PartialEq, Eq, Hash)]
593struct AssertStderr<'a> {
594    expected: &'a str,
595}
596
597impl<'a> Parse<'a> for AssertStderr<'a> {
598    fn parse(parser: Parser<'a>) -> parser::Result<Self> {
599        parser.parse::<wasi_kw::assert_stderr>()?;
600        Ok(Self {
601            expected: parser.parse()?,
602        })
603    }
604}
605
606#[cfg(test)]
607mod test {
608    use super::*;
609
610    #[tokio::test]
611    async fn test_parse() {
612        let pb = wast::parser::ParseBuffer::new(
613            r#"(wasi_test "my_wasm.wasm"
614                    (envs "HELLO=WORLD" "RUST_BACKTRACE=1")
615                    (args "hello" "world" "--help")
616                    (preopens "." "src/io")
617                    (assert_return (i64.const 0))
618                    (stdin "This is another \"string\" inside a string!")
619                    (assert_stdout "This is a \"string\" inside a string!")
620                    (assert_stderr "")
621)"#,
622        )
623        .unwrap();
624        let result = wast::parser::parse::<WasiTest>(&pb).unwrap();
625
626        assert_eq!(result.args, vec!["hello", "world", "--help"]);
627        assert_eq!(
628            result.envs,
629            vec![("HELLO", "WORLD"), ("RUST_BACKTRACE", "1")]
630        );
631        assert_eq!(result.dirs, vec![".", "src/io"]);
632        assert_eq!(result.assert_return.unwrap().return_value, 0);
633        assert_eq!(
634            result.assert_stdout.unwrap().expected,
635            "This is a \"string\" inside a string!"
636        );
637        assert_eq!(
638            result.stdin.unwrap().stream,
639            "This is another \"string\" inside a string!"
640        );
641        assert_eq!(result.assert_stderr.unwrap().expected, "");
642    }
643}
644
645#[derive(Debug, Clone)]
646struct OutputCapturerer {
647    output: Arc<Mutex<mpsc::Sender<Vec<u8>>>>,
648}
649
650impl OutputCapturerer {
651    fn new() -> (Self, mpsc::Receiver<Vec<u8>>) {
652        let (tx, rx) = mpsc::channel();
653        (
654            Self {
655                output: Arc::new(Mutex::new(tx)),
656            },
657            rx,
658        )
659    }
660}
661
662impl VirtualFile for OutputCapturerer {
663    fn last_accessed(&self) -> Timestamp {
664        0
665    }
666    fn last_modified(&self) -> Timestamp {
667        0
668    }
669    fn created_time(&self) -> Timestamp {
670        0
671    }
672    fn size(&self) -> u64 {
673        0
674    }
675    fn set_len(&mut self, _new_size: Filesize) -> Result<(), FsError> {
676        Ok(())
677    }
678    fn unlink(&mut self) -> Result<(), FsError> {
679        Ok(())
680    }
681    fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
682        Poll::Ready(Ok(0))
683    }
684    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
685        Poll::Ready(Ok(8192))
686    }
687}
688
689impl AsyncSeek for OutputCapturerer {
690    fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> {
691        Err(io::Error::other("can not seek logging wrapper"))
692    }
693    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
694        Poll::Ready(Err(io::Error::other("can not seek logging wrapper")))
695    }
696}
697
698impl AsyncWrite for OutputCapturerer {
699    fn poll_write(
700        self: Pin<&mut Self>,
701        _cx: &mut Context<'_>,
702        buf: &[u8],
703    ) -> Poll<io::Result<usize>> {
704        self.output
705            .lock()
706            .unwrap()
707            .send(buf.to_vec())
708            .map_err(|err| io::Error::new(io::ErrorKind::BrokenPipe, err.to_string()))?;
709        Poll::Ready(Ok(buf.len()))
710    }
711    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
712        Poll::Ready(Ok(()))
713    }
714    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
715        Poll::Ready(Ok(()))
716    }
717}
718
719impl AsyncRead for OutputCapturerer {
720    fn poll_read(
721        self: Pin<&mut Self>,
722        _cx: &mut Context<'_>,
723        _buf: &mut ReadBuf<'_>,
724    ) -> Poll<io::Result<()>> {
725        Poll::Ready(Err(io::Error::other("can not read from logging wrapper")))
726    }
727}
728
729/// When using `virtual_fs::mem_fs`, we cannot rely on `BASE_TEST_DIR`
730/// because the host filesystem cannot be used. Instead, we are
731/// copying `BASE_TEST_DIR` to the `mem_fs`.
732fn map_host_fs_to_mem_fs<'a>(
733    fs: &'a dyn FileSystem,
734    directory_reader: ReadDir,
735    path_prefix: &'a Path,
736) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + 'a>> {
737    Box::pin(async move {
738        for entry in directory_reader {
739            let entry = entry?;
740            let entry_type = entry.file_type()?;
741
742            let path = path_prefix.join(entry.path().file_name().unwrap());
743
744            if entry_type.is_dir() {
745                fs.create_dir(&path)?;
746
747                map_host_fs_to_mem_fs(fs, read_dir(entry.path())?, &path).await?
748            } else if entry_type.is_file() {
749                let mut host_file = OpenOptions::new().read(true).open(entry.path())?;
750                let mut mem_file = fs
751                    .new_open_options()
752                    .create_new(true)
753                    .write(true)
754                    .open(path)?;
755                let mut buffer = Vec::new();
756                Read::read_to_end(&mut host_file, &mut buffer)?;
757                mem_file.write_all(&buffer).await?;
758            } else if entry_type.is_symlink() {
759                //unimplemented!("`mem_fs` does not support symlink for the moment");
760            }
761        }
762
763        Ok(())
764    })
765}