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#[derive(Debug)]
31pub enum WasiFileSystemKind {
32 Host,
34
35 InMemory,
37
38 Tmp,
40
41 PassthruMemory,
43
44 UnionHostMemory,
46
47 RootFileSystemBuilder,
49}
50
51#[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
66const 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 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 pub fn lex_string(wast: &'a str) -> parser::Result<ParseBuffer<'a>> {
88 ParseBuffer::new(wast)
89 }
90
91 pub fn parse_tokens(tokens: &'a ParseBuffer<'a>) -> parser::Result<Self> {
93 parser::parse(tokens)
94 }
95
96 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 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 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 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 #[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 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 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 .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 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 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 let wasm_path = parser.parse::<&'a str>()?;
394
395 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 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
744fn 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 }
776 }
777
778 Ok(())
779 })
780}