xref: /wasmtime-44.0.1/tests/all/cli_tests.rs (revision 9fc41bae)
1 #![cfg(not(miri))]
2 
3 use anyhow::{bail, Result};
4 use std::fs::File;
5 use std::io::Write;
6 use std::path::{Path, PathBuf};
7 use std::process::{Command, Output, Stdio};
8 use tempfile::{NamedTempFile, TempDir};
9 
10 // Run the wasmtime CLI with the provided args and return the `Output`.
11 // If the `stdin` is `Some`, opens the file and redirects to the child's stdin.
12 pub fn run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result<Output> {
13     let mut cmd = get_wasmtime_command()?;
14     cmd.args(args);
15     if let Some(file) = stdin {
16         cmd.stdin(File::open(file)?);
17     }
18     cmd.output().map_err(Into::into)
19 }
20 
21 /// Get the Wasmtime CLI as a [Command].
22 pub fn get_wasmtime_command() -> Result<Command> {
23     // Figure out the Wasmtime binary from the current executable.
24     let bin = get_wasmtime_path()?;
25     let runner = std::env::vars()
26         .filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER"))
27         .next();
28 
29     // If we're running tests with a "runner" then we might be doing something
30     // like cross-emulation, so spin up the emulator rather than the tests
31     // itself, which may not be natively executable.
32     let mut cmd = if let Some((_, runner)) = runner {
33         let mut parts = runner.split_whitespace();
34         let mut cmd = Command::new(parts.next().unwrap());
35         for arg in parts {
36             cmd.arg(arg);
37         }
38         cmd.arg(&bin);
39         cmd
40     } else {
41         Command::new(&bin)
42     };
43 
44     // Ignore this if it's specified in the environment to allow tests to run in
45     // "default mode" by default.
46     cmd.env_remove("WASMTIME_NEW_CLI");
47 
48     Ok(cmd)
49 }
50 
51 fn get_wasmtime_path() -> Result<PathBuf> {
52     let mut path = std::env::current_exe()?;
53     path.pop(); // chop off the file name
54     path.pop(); // chop off `deps`
55     path.push("wasmtime");
56     Ok(path)
57 }
58 
59 // Run the wasmtime CLI with the provided args and, if it succeeds, return
60 // the standard output in a `String`.
61 fn run_wasmtime(args: &[&str]) -> Result<String> {
62     let output = run_wasmtime_for_output(args, None)?;
63     if !output.status.success() {
64         bail!(
65             "Failed to execute wasmtime with: {:?}\n{}",
66             args,
67             String::from_utf8_lossy(&output.stderr)
68         );
69     }
70     Ok(String::from_utf8(output.stdout).unwrap())
71 }
72 
73 fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> {
74     let mut wasm_file = NamedTempFile::new()?;
75     let wasm = wat::parse_file(wat_path)?;
76     wasm_file.write(&wasm)?;
77     Ok(wasm_file)
78 }
79 
80 // Very basic use case: compile binary wasm file and run specific function with arguments.
81 #[test]
82 fn run_wasmtime_simple() -> Result<()> {
83     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
84     run_wasmtime(&[
85         "run",
86         "--invoke",
87         "simple",
88         "-Ccache=n",
89         wasm.path().to_str().unwrap(),
90         "4",
91     ])?;
92     Ok(())
93 }
94 
95 // Wasmtime shall fail when not enough arguments were provided.
96 #[test]
97 fn run_wasmtime_simple_fail_no_args() -> Result<()> {
98     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
99     assert!(
100         run_wasmtime(&[
101             "run",
102             "-Ccache=n",
103             "--invoke",
104             "simple",
105             wasm.path().to_str().unwrap(),
106         ])
107         .is_err(),
108         "shall fail"
109     );
110     Ok(())
111 }
112 
113 #[test]
114 fn run_coredump_smoketest() -> Result<()> {
115     let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?;
116     let coredump_file = NamedTempFile::new()?;
117     let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display());
118     let err = run_wasmtime(&[
119         "run",
120         "--invoke",
121         "a",
122         "-Ccache=n",
123         &coredump_arg,
124         wasm.path().to_str().unwrap(),
125     ])
126     .unwrap_err();
127     assert!(err.to_string().contains(&format!(
128         "core dumped at {}",
129         coredump_file.path().display()
130     )));
131     Ok(())
132 }
133 
134 // Running simple wat
135 #[test]
136 fn run_wasmtime_simple_wat() -> Result<()> {
137     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
138     run_wasmtime(&[
139         "run",
140         "--invoke",
141         "simple",
142         "-Ccache=n",
143         wasm.path().to_str().unwrap(),
144         "4",
145     ])?;
146     assert_eq!(
147         run_wasmtime(&[
148             "run",
149             "--invoke",
150             "get_f32",
151             "-Ccache=n",
152             wasm.path().to_str().unwrap(),
153         ])?,
154         "100\n"
155     );
156     assert_eq!(
157         run_wasmtime(&[
158             "run",
159             "--invoke",
160             "get_f64",
161             "-Ccache=n",
162             wasm.path().to_str().unwrap(),
163         ])?,
164         "100\n"
165     );
166     Ok(())
167 }
168 
169 // Running a wat that traps.
170 #[test]
171 fn run_wasmtime_unreachable_wat() -> Result<()> {
172     let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?;
173     let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
174 
175     assert_ne!(output.stderr, b"");
176     assert_eq!(output.stdout, b"");
177     assert!(!output.status.success());
178 
179     let code = output
180         .status
181         .code()
182         .expect("wasmtime process should exit normally");
183 
184     // Test for the specific error code Wasmtime uses to indicate a trap return.
185     #[cfg(unix)]
186     assert_eq!(code, 128 + libc::SIGABRT);
187     #[cfg(windows)]
188     assert_eq!(code, 3);
189     Ok(())
190 }
191 
192 // Run a simple WASI hello world, snapshot0 edition.
193 #[test]
194 fn hello_wasi_snapshot0() -> Result<()> {
195     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
196     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
197         let stdout = run_wasmtime(&["-Ccache=n", preview2, wasm.path().to_str().unwrap()])?;
198         assert_eq!(stdout, "Hello, world!\n");
199     }
200     Ok(())
201 }
202 
203 // Run a simple WASI hello world, snapshot1 edition.
204 #[test]
205 fn hello_wasi_snapshot1() -> Result<()> {
206     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
207     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
208     assert_eq!(stdout, "Hello, world!\n");
209     Ok(())
210 }
211 
212 #[test]
213 fn timeout_in_start() -> Result<()> {
214     let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?;
215     let output = run_wasmtime_for_output(
216         &[
217             "run",
218             "-Wtimeout=1ms",
219             "-Ccache=n",
220             wasm.path().to_str().unwrap(),
221         ],
222         None,
223     )?;
224     assert!(!output.status.success());
225     assert_eq!(output.stdout, b"");
226     let stderr = String::from_utf8_lossy(&output.stderr);
227     assert!(
228         stderr.contains("wasm trap: interrupt"),
229         "bad stderr: {stderr}"
230     );
231     Ok(())
232 }
233 
234 #[test]
235 fn timeout_in_invoke() -> Result<()> {
236     let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?;
237     let output = run_wasmtime_for_output(
238         &[
239             "run",
240             "-Wtimeout=1ms",
241             "-Ccache=n",
242             wasm.path().to_str().unwrap(),
243         ],
244         None,
245     )?;
246     assert!(!output.status.success());
247     assert_eq!(output.stdout, b"");
248     let stderr = String::from_utf8_lossy(&output.stderr);
249     assert!(
250         stderr.contains("wasm trap: interrupt"),
251         "bad stderr: {stderr}"
252     );
253     Ok(())
254 }
255 
256 // Exit with a valid non-zero exit code, snapshot0 edition.
257 #[test]
258 fn exit2_wasi_snapshot0() -> Result<()> {
259     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?;
260 
261     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
262         let output = run_wasmtime_for_output(
263             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
264             None,
265         )?;
266         assert_eq!(output.status.code().unwrap(), 2);
267     }
268     Ok(())
269 }
270 
271 // Exit with a valid non-zero exit code, snapshot1 edition.
272 #[test]
273 fn exit2_wasi_snapshot1() -> Result<()> {
274     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?;
275     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
276     assert_eq!(output.status.code().unwrap(), 2);
277     Ok(())
278 }
279 
280 // Exit with a valid non-zero exit code, snapshot0 edition.
281 #[test]
282 fn exit125_wasi_snapshot0() -> Result<()> {
283     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?;
284     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
285         let output = run_wasmtime_for_output(
286             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
287             None,
288         )?;
289         dbg!(&output);
290         if cfg!(windows) {
291             assert_eq!(output.status.code().unwrap(), 1);
292         } else {
293             assert_eq!(output.status.code().unwrap(), 125);
294         }
295     }
296     Ok(())
297 }
298 
299 // Exit with a valid non-zero exit code, snapshot1 edition.
300 #[test]
301 fn exit125_wasi_snapshot1() -> Result<()> {
302     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?;
303     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
304     if cfg!(windows) {
305         assert_eq!(output.status.code().unwrap(), 1);
306     } else {
307         assert_eq!(output.status.code().unwrap(), 125);
308     }
309     Ok(())
310 }
311 
312 // Exit with an invalid non-zero exit code, snapshot0 edition.
313 #[test]
314 fn exit126_wasi_snapshot0() -> Result<()> {
315     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?;
316 
317     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
318         let output = run_wasmtime_for_output(
319             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
320             None,
321         )?;
322         assert_eq!(output.status.code().unwrap(), 1);
323         assert!(output.stdout.is_empty());
324         assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
325     }
326     Ok(())
327 }
328 
329 // Exit with an invalid non-zero exit code, snapshot1 edition.
330 #[test]
331 fn exit126_wasi_snapshot1() -> Result<()> {
332     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?;
333     let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
334     assert_eq!(output.status.code().unwrap(), 1);
335     assert!(output.stdout.is_empty());
336     assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
337     Ok(())
338 }
339 
340 // Run a minimal command program.
341 #[test]
342 fn minimal_command() -> Result<()> {
343     let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
344     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
345     assert_eq!(stdout, "");
346     Ok(())
347 }
348 
349 // Run a minimal reactor program.
350 #[test]
351 fn minimal_reactor() -> Result<()> {
352     let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
353     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
354     assert_eq!(stdout, "");
355     Ok(())
356 }
357 
358 // Attempt to call invoke on a command.
359 #[test]
360 fn command_invoke() -> Result<()> {
361     let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
362     run_wasmtime(&[
363         "run",
364         "--invoke",
365         "_start",
366         "-Ccache=n",
367         wasm.path().to_str().unwrap(),
368     ])?;
369     Ok(())
370 }
371 
372 // Attempt to call invoke on a command.
373 #[test]
374 fn reactor_invoke() -> Result<()> {
375     let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
376     run_wasmtime(&[
377         "run",
378         "--invoke",
379         "_initialize",
380         "-Ccache=n",
381         wasm.path().to_str().unwrap(),
382     ])?;
383     Ok(())
384 }
385 
386 // Run the greeter test, which runs a preloaded reactor and a command.
387 #[test]
388 fn greeter() -> Result<()> {
389     let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
390     let stdout = run_wasmtime(&[
391         "run",
392         "-Ccache=n",
393         "--preload",
394         "reactor=tests/all/cli_tests/greeter_reactor.wat",
395         wasm.path().to_str().unwrap(),
396     ])?;
397     assert_eq!(
398         stdout,
399         "Hello _initialize\nHello _start\nHello greet\nHello done\n"
400     );
401     Ok(())
402 }
403 
404 // Run the greeter test, but this time preload a command.
405 #[test]
406 fn greeter_preload_command() -> Result<()> {
407     let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?;
408     let stdout = run_wasmtime(&[
409         "run",
410         "-Ccache=n",
411         "--preload",
412         "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat",
413         wasm.path().to_str().unwrap(),
414     ])?;
415     assert_eq!(stdout, "Hello _initialize\n");
416     Ok(())
417 }
418 
419 // Run the greeter test, which runs a preloaded reactor and a command.
420 #[test]
421 fn greeter_preload_callable_command() -> Result<()> {
422     let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
423     let stdout = run_wasmtime(&[
424         "run",
425         "-Ccache=n",
426         "--preload",
427         "reactor=tests/all/cli_tests/greeter_callable_command.wat",
428         wasm.path().to_str().unwrap(),
429     ])?;
430     assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n");
431     Ok(())
432 }
433 
434 // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64
435 // See https://github.com/bytecodealliance/wasmtime/issues/1967
436 #[test]
437 fn exit_with_saved_fprs() -> Result<()> {
438     let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?;
439     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
440     assert_eq!(output.status.code().unwrap(), 0);
441     assert!(output.stdout.is_empty());
442     Ok(())
443 }
444 
445 #[test]
446 fn run_cwasm() -> Result<()> {
447     let td = TempDir::new()?;
448     let cwasm = td.path().join("foo.cwasm");
449     let stdout = run_wasmtime(&[
450         "compile",
451         "tests/all/cli_tests/simple.wat",
452         "-o",
453         cwasm.to_str().unwrap(),
454     ])?;
455     assert_eq!(stdout, "");
456     let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?;
457     assert_eq!(stdout, "");
458     Ok(())
459 }
460 
461 #[cfg(unix)]
462 #[test]
463 fn hello_wasi_snapshot0_from_stdin() -> Result<()> {
464     // Run a simple WASI hello world, snapshot0 edition.
465     // The module is piped from standard input.
466     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
467     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
468         let stdout = {
469             let path = wasm.path();
470             let args: &[&str] = &["-Ccache=n", preview2, "-"];
471             let output = run_wasmtime_for_output(args, Some(path))?;
472             if !output.status.success() {
473                 bail!(
474                     "Failed to execute wasmtime with: {:?}\n{}",
475                     args,
476                     String::from_utf8_lossy(&output.stderr)
477                 );
478             }
479             Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap())
480         }?;
481         assert_eq!(stdout, "Hello, world!\n");
482     }
483     Ok(())
484 }
485 
486 #[test]
487 fn specify_env() -> Result<()> {
488     // By default no env is inherited
489     let output = get_wasmtime_command()?
490         .args(&["run", "tests/all/cli_tests/print_env.wat"])
491         .env("THIS_WILL_NOT", "show up in the output")
492         .output()?;
493     assert!(output.status.success());
494     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
495 
496     // Specify a single env var
497     let output = get_wasmtime_command()?
498         .args(&[
499             "run",
500             "--env",
501             "FOO=bar",
502             "tests/all/cli_tests/print_env.wat",
503         ])
504         .output()?;
505     assert!(output.status.success());
506     assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
507 
508     // Inherit a single env var
509     let output = get_wasmtime_command()?
510         .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"])
511         .env("FOO", "bar")
512         .output()?;
513     assert!(output.status.success());
514     assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
515 
516     // Inherit a nonexistent env var
517     let output = get_wasmtime_command()?
518         .args(&[
519             "run",
520             "--env",
521             "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT",
522             "tests/all/cli_tests/print_env.wat",
523         ])
524         .output()?;
525     assert!(output.status.success());
526 
527     // Inherit all env vars
528     let output = get_wasmtime_command()?
529         .args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"])
530         .env("FOO", "bar")
531         .output()?;
532     assert!(output.status.success());
533     let stdout = String::from_utf8_lossy(&output.stdout);
534     assert!(stdout.contains("FOO=bar"), "bad output: {stdout}");
535 
536     Ok(())
537 }
538 
539 #[cfg(unix)]
540 #[test]
541 fn run_cwasm_from_stdin() -> Result<()> {
542     use std::process::Stdio;
543 
544     let td = TempDir::new()?;
545     let cwasm = td.path().join("foo.cwasm");
546     let stdout = run_wasmtime(&[
547         "compile",
548         "tests/all/cli_tests/simple.wat",
549         "-o",
550         cwasm.to_str().unwrap(),
551     ])?;
552     assert_eq!(stdout, "");
553 
554     // If stdin is literally the file itself then that should work
555     let args: &[&str] = &["run", "--allow-precompiled", "-"];
556     let output = get_wasmtime_command()?
557         .args(args)
558         .stdin(File::open(&cwasm)?)
559         .output()?;
560     assert!(output.status.success(), "a file as stdin should work");
561 
562     // If stdin is a pipe, that should also work
563     let input = std::fs::read(&cwasm)?;
564     let mut child = get_wasmtime_command()?
565         .args(args)
566         .stdin(Stdio::piped())
567         .stdout(Stdio::piped())
568         .stderr(Stdio::piped())
569         .spawn()?;
570     let mut stdin = child.stdin.take().unwrap();
571     let t = std::thread::spawn(move || {
572         let _ = stdin.write_all(&input);
573     });
574     let output = child.wait_with_output()?;
575     assert!(output.status.success());
576     t.join().unwrap();
577     Ok(())
578 }
579 
580 #[cfg(feature = "wasi-threads")]
581 #[test]
582 fn run_threads() -> Result<()> {
583     let wasm = build_wasm("tests/all/cli_tests/threads.wat")?;
584     let stdout = run_wasmtime(&[
585         "run",
586         "-Wthreads",
587         "-Sthreads",
588         "-Ccache=n",
589         wasm.path().to_str().unwrap(),
590     ])?;
591 
592     assert!(
593         stdout
594             == "Called _start\n\
595     Running wasi_thread_start\n\
596     Running wasi_thread_start\n\
597     Running wasi_thread_start\n\
598     Done\n"
599     );
600     Ok(())
601 }
602 
603 #[cfg(feature = "wasi-threads")]
604 #[test]
605 fn run_simple_with_wasi_threads() -> Result<()> {
606     // We expect to be able to run Wasm modules that do not have correct
607     // wasi-thread entry points or imported shared memory as long as no threads
608     // are spawned.
609     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
610     let stdout = run_wasmtime(&[
611         "run",
612         "-Wthreads",
613         "-Sthreads",
614         "-Ccache=n",
615         "--invoke",
616         "simple",
617         wasm.path().to_str().unwrap(),
618         "4",
619     ])?;
620     assert_eq!(stdout, "4\n");
621     Ok(())
622 }
623 
624 #[test]
625 fn wasm_flags() -> Result<()> {
626     // Any argument after the wasm module should be interpreted as for the
627     // command itself
628     let stdout = run_wasmtime(&[
629         "run",
630         "--",
631         "tests/all/cli_tests/print-arguments.wat",
632         "--argument",
633         "-for",
634         "the",
635         "command",
636     ])?;
637     assert_eq!(
638         stdout,
639         "\
640             print-arguments.wat\n\
641             --argument\n\
642             -for\n\
643             the\n\
644             command\n\
645         "
646     );
647     let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?;
648     assert_eq!(
649         stdout,
650         "\
651             print-arguments.wat\n\
652             -\n\
653         "
654     );
655     let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?;
656     assert_eq!(
657         stdout,
658         "\
659             print-arguments.wat\n\
660             --\n\
661         "
662     );
663     let stdout = run_wasmtime(&[
664         "run",
665         "--",
666         "tests/all/cli_tests/print-arguments.wat",
667         "--",
668         "--",
669         "-a",
670         "b",
671     ])?;
672     assert_eq!(
673         stdout,
674         "\
675             print-arguments.wat\n\
676             --\n\
677             --\n\
678             -a\n\
679             b\n\
680         "
681     );
682     Ok(())
683 }
684 
685 #[test]
686 fn name_same_as_builtin_command() -> Result<()> {
687     // a bare subcommand shouldn't run successfully
688     let output = get_wasmtime_command()?
689         .current_dir("tests/all/cli_tests")
690         .arg("run")
691         .output()?;
692     assert!(!output.status.success());
693 
694     // a `--` prefix should let everything else get interpreted as a wasm
695     // module and arguments, even if the module has a name like `run`
696     let output = get_wasmtime_command()?
697         .current_dir("tests/all/cli_tests")
698         .arg("--")
699         .arg("run")
700         .output()?;
701     assert!(output.status.success(), "expected success got {output:#?}");
702 
703     // Passing options before the subcommand should work and doesn't require
704     // `--` to disambiguate
705     let output = get_wasmtime_command()?
706         .current_dir("tests/all/cli_tests")
707         .arg("-Ccache=n")
708         .arg("run")
709         .output()?;
710     assert!(output.status.success(), "expected success got {output:#?}");
711     Ok(())
712 }
713 
714 #[test]
715 #[cfg(unix)]
716 fn run_just_stdin_argument() -> Result<()> {
717     let output = get_wasmtime_command()?
718         .arg("-")
719         .stdin(File::open("tests/all/cli_tests/simple.wat")?)
720         .output()?;
721     assert!(output.status.success());
722     Ok(())
723 }
724 
725 #[test]
726 fn wasm_flags_without_subcommand() -> Result<()> {
727     let output = get_wasmtime_command()?
728         .current_dir("tests/all/cli_tests/")
729         .arg("print-arguments.wat")
730         .arg("-foo")
731         .arg("bar")
732         .output()?;
733     assert!(output.status.success());
734     assert_eq!(
735         String::from_utf8_lossy(&output.stdout),
736         "\
737             print-arguments.wat\n\
738             -foo\n\
739             bar\n\
740         "
741     );
742     Ok(())
743 }
744 
745 #[test]
746 fn wasi_misaligned_pointer() -> Result<()> {
747     let output = get_wasmtime_command()?
748         .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat")
749         .output()?;
750     assert!(!output.status.success());
751     let stderr = String::from_utf8_lossy(&output.stderr);
752     assert!(
753         stderr.contains("Pointer not aligned"),
754         "bad stderr: {stderr}",
755     );
756     Ok(())
757 }
758 
759 #[test]
760 #[cfg_attr(not(feature = "component-model"), ignore)]
761 fn hello_with_preview2() -> Result<()> {
762     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
763     let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?;
764     assert_eq!(stdout, "Hello, world!\n");
765     Ok(())
766 }
767 
768 #[test]
769 #[cfg_attr(not(feature = "component-model"), ignore)]
770 fn component_missing_feature() -> Result<()> {
771     let path = "tests/all/cli_tests/empty-component.wat";
772     let wasm = build_wasm(path)?;
773     let output = get_wasmtime_command()?
774         .arg("-Ccache=n")
775         .arg("-Wcomponent-model=n")
776         .arg(wasm.path())
777         .output()?;
778     assert!(!output.status.success());
779     let stderr = String::from_utf8_lossy(&output.stderr);
780     assert!(
781         stderr.contains("cannot execute a component without `--wasm component-model`"),
782         "bad stderr: {stderr}"
783     );
784 
785     // also tests with raw *.wat input
786     let output = get_wasmtime_command()?
787         .arg("-Ccache=n")
788         .arg("-Wcomponent-model=n")
789         .arg(path)
790         .output()?;
791     assert!(!output.status.success());
792     let stderr = String::from_utf8_lossy(&output.stderr);
793     assert!(
794         stderr.contains("cannot execute a component without `--wasm component-model`"),
795         "bad stderr: {stderr}"
796     );
797 
798     Ok(())
799 }
800 
801 #[test]
802 #[cfg_attr(not(feature = "component-model"), ignore)]
803 fn component_enabled_by_default() -> Result<()> {
804     let path = "tests/all/cli_tests/component-basic.wat";
805     let wasm = build_wasm(path)?;
806     let output = get_wasmtime_command()?
807         .arg("-Ccache=n")
808         .arg(wasm.path())
809         .output()?;
810     assert!(output.status.success());
811 
812     // also tests with raw *.wat input
813     let output = get_wasmtime_command()?
814         .arg("-Ccache=n")
815         .arg(path)
816         .output()?;
817     assert!(output.status.success());
818 
819     Ok(())
820 }
821 
822 // If the text format is invalid then the filename should be mentioned in the
823 // error message.
824 #[test]
825 fn bad_text_syntax() -> Result<()> {
826     let output = get_wasmtime_command()?
827         .arg("-Ccache=n")
828         .arg("tests/all/cli_tests/bad-syntax.wat")
829         .output()?;
830     assert!(!output.status.success());
831     let stderr = String::from_utf8_lossy(&output.stderr);
832     assert!(
833         stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"),
834         "bad stderr: {stderr}"
835     );
836     Ok(())
837 }
838 
839 #[test]
840 #[cfg_attr(not(feature = "component-model"), ignore)]
841 fn run_basic_component() -> Result<()> {
842     let path = "tests/all/cli_tests/component-basic.wat";
843     let wasm = build_wasm(path)?;
844 
845     // Run both the `*.wasm` binary and the text format
846     run_wasmtime(&[
847         "-Ccache=n",
848         "-Wcomponent-model",
849         wasm.path().to_str().unwrap(),
850     ])?;
851     run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?;
852 
853     Ok(())
854 }
855 
856 #[test]
857 #[cfg_attr(not(feature = "component-model"), ignore)]
858 fn run_precompiled_component() -> Result<()> {
859     let td = TempDir::new()?;
860     let cwasm = td.path().join("component-basic.cwasm");
861     let stdout = run_wasmtime(&[
862         "compile",
863         "tests/all/cli_tests/component-basic.wat",
864         "-o",
865         cwasm.to_str().unwrap(),
866         "-Wcomponent-model",
867     ])?;
868     assert_eq!(stdout, "");
869     let stdout = run_wasmtime(&[
870         "run",
871         "-Wcomponent-model",
872         "--allow-precompiled",
873         cwasm.to_str().unwrap(),
874     ])?;
875     assert_eq!(stdout, "");
876 
877     Ok(())
878 }
879 
880 #[test]
881 fn memory_growth_failure() -> Result<()> {
882     let output = get_wasmtime_command()?
883         .args(&[
884             "run",
885             "-Wmemory64",
886             "-Wtrap-on-grow-failure",
887             "tests/all/cli_tests/memory-grow-failure.wat",
888         ])
889         .output()?;
890     assert!(!output.status.success());
891     let stderr = String::from_utf8_lossy(&output.stderr);
892     assert!(
893         stderr.contains("forcing a memory growth failure to be a trap"),
894         "bad stderr: {stderr}"
895     );
896     Ok(())
897 }
898 
899 #[test]
900 fn table_growth_failure() -> Result<()> {
901     let output = get_wasmtime_command()?
902         .args(&[
903             "run",
904             "-Wtrap-on-grow-failure",
905             "tests/all/cli_tests/table-grow-failure.wat",
906         ])
907         .output()?;
908     assert!(!output.status.success());
909     let stderr = String::from_utf8_lossy(&output.stderr);
910     assert!(
911         stderr.contains("forcing trap when growing table"),
912         "bad stderr: {stderr}"
913     );
914     Ok(())
915 }
916 
917 #[test]
918 fn table_growth_failure2() -> Result<()> {
919     let output = get_wasmtime_command()?
920         .args(&[
921             "run",
922             "-Wtrap-on-grow-failure",
923             "tests/all/cli_tests/table-grow-failure2.wat",
924         ])
925         .output()?;
926     assert!(!output.status.success());
927     let stderr = String::from_utf8_lossy(&output.stderr);
928     assert!(
929         stderr.contains("forcing trap when growing table to 4294967296 elements"),
930         "bad stderr: {stderr}"
931     );
932     Ok(())
933 }
934 
935 #[test]
936 fn option_group_help() -> Result<()> {
937     run_wasmtime(&["run", "-Whelp"])?;
938     run_wasmtime(&["run", "-O", "help"])?;
939     run_wasmtime(&["run", "--codegen", "help"])?;
940     run_wasmtime(&["run", "--debug=help"])?;
941     run_wasmtime(&["run", "-Shelp"])?;
942     run_wasmtime(&["run", "-Whelp-long"])?;
943     Ok(())
944 }
945 
946 #[test]
947 fn option_group_comma_separated() -> Result<()> {
948     run_wasmtime(&[
949         "run",
950         "-Wrelaxed-simd,simd",
951         "tests/all/cli_tests/simple.wat",
952     ])?;
953     Ok(())
954 }
955 
956 #[test]
957 fn option_group_boolean_parsing() -> Result<()> {
958     run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?;
959     run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?;
960     run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?;
961     run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?;
962     run_wasmtime(&[
963         "run",
964         "-Wrelaxed-simd=yes",
965         "tests/all/cli_tests/simple.wat",
966     ])?;
967     run_wasmtime(&[
968         "run",
969         "-Wrelaxed-simd=true",
970         "tests/all/cli_tests/simple.wat",
971     ])?;
972     run_wasmtime(&[
973         "run",
974         "-Wrelaxed-simd=false",
975         "tests/all/cli_tests/simple.wat",
976     ])?;
977     Ok(())
978 }
979 
980 #[test]
981 fn preview2_stdin() -> Result<()> {
982     let test = "tests/all/cli_tests/count-stdin.wat";
983     let cmd = || -> Result<_> {
984         let mut cmd = get_wasmtime_command()?;
985         cmd.arg("--invoke=count").arg("-Spreview2").arg(test);
986         Ok(cmd)
987     };
988 
989     // read empty pipe is ok
990     let output = cmd()?.output()?;
991     assert!(output.status.success());
992     assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");
993 
994     // read itself is ok
995     let file = File::open(test)?;
996     let size = file.metadata()?.len();
997     let output = cmd()?.stdin(File::open(test)?).output()?;
998     assert!(output.status.success());
999     assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n"));
1000 
1001     // read piped input ok is ok
1002     let mut child = cmd()?
1003         .stdin(Stdio::piped())
1004         .stdout(Stdio::piped())
1005         .stderr(Stdio::piped())
1006         .spawn()?;
1007     let mut stdin = child.stdin.take().unwrap();
1008     std::thread::spawn(move || {
1009         stdin.write_all(b"hello").unwrap();
1010     });
1011     let output = child.wait_with_output()?;
1012     assert!(output.status.success());
1013     assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n");
1014 
1015     let count_up_to = |n: usize| -> Result<_> {
1016         let mut child = get_wasmtime_command()?
1017             .arg("--invoke=count-up-to")
1018             .arg("-Spreview2")
1019             .arg(test)
1020             .arg(n.to_string())
1021             .stdin(Stdio::piped())
1022             .stdout(Stdio::piped())
1023             .stderr(Stdio::piped())
1024             .spawn()?;
1025         let mut stdin = child.stdin.take().unwrap();
1026         let t = std::thread::spawn(move || {
1027             let mut written = 0;
1028             let bytes = [0; 64 * 1024];
1029             loop {
1030                 written += match stdin.write(&bytes) {
1031                     Ok(n) => n,
1032                     Err(_) => break written,
1033                 };
1034             }
1035         });
1036         let output = child.wait_with_output()?;
1037         assert!(output.status.success());
1038         let written = t.join().unwrap();
1039         let read = String::from_utf8_lossy(&output.stdout)
1040             .trim()
1041             .parse::<usize>()
1042             .unwrap();
1043         // The test reads in 1000 byte chunks so make sure that it doesn't read
1044         // more than 1000 bytes than requested.
1045         assert!(read < n + 1000, "test read too much {read}");
1046         Ok(written)
1047     };
1048 
1049     // wasmtime shouldn't eat information that the guest never actually tried to
1050     // read.
1051     //
1052     // NB: this may be a bit flaky. Exactly how much we wrote in the above
1053     // helper thread depends on how much the OS buffers for us. For now give
1054     // some some slop and assume that OSes are unlikely to buffer more than
1055     // that.
1056     let slop = 256 * 1024;
1057     for amt in [0, 100, 100_000] {
1058         let written = count_up_to(amt)?;
1059         assert!(written < slop + amt, "wrote too much {written}");
1060     }
1061     Ok(())
1062 }
1063 
1064 #[test]
1065 fn float_args() -> Result<()> {
1066     let result = run_wasmtime(&[
1067         "--invoke",
1068         "echo_f32",
1069         "tests/all/cli_tests/simple.wat",
1070         "1.0",
1071     ])?;
1072     assert_eq!(result, "1\n");
1073     let result = run_wasmtime(&[
1074         "--invoke",
1075         "echo_f64",
1076         "tests/all/cli_tests/simple.wat",
1077         "1.1",
1078     ])?;
1079     assert_eq!(result, "1.1\n");
1080     Ok(())
1081 }
1082 
1083 #[test]
1084 fn mpk_without_pooling() -> Result<()> {
1085     let output = get_wasmtime_command()?
1086         .args(&[
1087             "run",
1088             "-O",
1089             "memory-protection-keys=y",
1090             "--invoke",
1091             "echo_f32",
1092             "tests/all/cli_tests/simple.wat",
1093             "1.0",
1094         ])
1095         .env("WASMTIME_NEW_CLI", "1")
1096         .output()?;
1097     assert!(!output.status.success());
1098     Ok(())
1099 }
1100 
1101 // Very basic use case: compile binary wasm file and run specific function with arguments.
1102 #[test]
1103 fn increase_stack_size() -> Result<()> {
1104     run_wasmtime(&[
1105         "run",
1106         "--invoke",
1107         "simple",
1108         &format!("-Wmax-wasm-stack={}", 5 << 20),
1109         "-Ccache=n",
1110         "tests/all/cli_tests/simple.wat",
1111         "4",
1112     ])?;
1113     Ok(())
1114 }
1115 
1116 mod test_programs {
1117     use super::{get_wasmtime_command, run_wasmtime};
1118     use anyhow::{bail, Context, Result};
1119     use http_body_util::BodyExt;
1120     use hyper::header::HeaderValue;
1121     use std::io::{BufRead, BufReader, Read, Write};
1122     use std::net::SocketAddr;
1123     use std::process::{Child, Command, Stdio};
1124     use test_programs_artifacts::*;
1125     use tokio::net::TcpStream;
1126 
1127     macro_rules! assert_test_exists {
1128         ($name:ident) => {
1129             #[allow(unused_imports)]
1130             use self::$name as _;
1131         };
1132     }
1133     foreach_cli!(assert_test_exists);
1134 
1135     #[test]
1136     fn cli_hello_stdout() -> Result<()> {
1137         run_wasmtime(&[
1138             "run",
1139             "-Wcomponent-model",
1140             CLI_HELLO_STDOUT_COMPONENT,
1141             "gussie",
1142             "sparky",
1143             "willa",
1144         ])?;
1145         Ok(())
1146     }
1147 
1148     #[test]
1149     fn cli_args() -> Result<()> {
1150         run_wasmtime(&[
1151             "run",
1152             "-Wcomponent-model",
1153             CLI_ARGS_COMPONENT,
1154             "hello",
1155             "this",
1156             "",
1157             "is an argument",
1158             "with �� emoji",
1159         ])?;
1160         Ok(())
1161     }
1162 
1163     #[test]
1164     fn cli_stdin() -> Result<()> {
1165         let mut child = get_wasmtime_command()?
1166             .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT])
1167             .stdout(Stdio::piped())
1168             .stderr(Stdio::piped())
1169             .stdin(Stdio::piped())
1170             .spawn()?;
1171         child
1172             .stdin
1173             .take()
1174             .unwrap()
1175             .write_all(b"So rested he by the Tumtum tree")
1176             .unwrap();
1177         let output = child.wait_with_output()?;
1178         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1179         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1180         assert!(output.status.success());
1181         Ok(())
1182     }
1183 
1184     #[test]
1185     fn cli_splice_stdin() -> Result<()> {
1186         let mut child = get_wasmtime_command()?
1187             .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT])
1188             .stdout(Stdio::piped())
1189             .stderr(Stdio::piped())
1190             .stdin(Stdio::piped())
1191             .spawn()?;
1192         let msg = "So rested he by the Tumtum tree";
1193         child
1194             .stdin
1195             .take()
1196             .unwrap()
1197             .write_all(msg.as_bytes())
1198             .unwrap();
1199         let output = child.wait_with_output()?;
1200         assert!(output.status.success());
1201         let stdout = String::from_utf8_lossy(&output.stdout);
1202         let stderr = String::from_utf8_lossy(&output.stderr);
1203         if !stderr.is_empty() {
1204             eprintln!("{stderr}");
1205         }
1206 
1207         assert_eq!(
1208             format!(
1209                 "before splice\n{msg}\ncompleted splicing {} bytes\n",
1210                 msg.as_bytes().len()
1211             ),
1212             stdout
1213         );
1214         Ok(())
1215     }
1216 
1217     #[test]
1218     fn cli_env() -> Result<()> {
1219         run_wasmtime(&[
1220             "run",
1221             "-Wcomponent-model",
1222             "--env=frabjous=day",
1223             "--env=callooh=callay",
1224             CLI_ENV_COMPONENT,
1225         ])?;
1226         Ok(())
1227     }
1228 
1229     #[test]
1230     fn cli_file_read() -> Result<()> {
1231         let dir = tempfile::tempdir()?;
1232 
1233         std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1234 
1235         run_wasmtime(&[
1236             "run",
1237             "-Wcomponent-model",
1238             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1239             CLI_FILE_READ_COMPONENT,
1240         ])?;
1241         Ok(())
1242     }
1243 
1244     #[test]
1245     fn cli_file_append() -> Result<()> {
1246         let dir = tempfile::tempdir()?;
1247 
1248         std::fs::File::create(dir.path().join("bar.txt"))?
1249             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1250 
1251         run_wasmtime(&[
1252             "run",
1253             "-Wcomponent-model",
1254             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1255             CLI_FILE_APPEND_COMPONENT,
1256         ])?;
1257 
1258         let contents = std::fs::read(dir.path().join("bar.txt"))?;
1259         assert_eq!(
1260             std::str::from_utf8(&contents).unwrap(),
1261             "'Twas brillig, and the slithy toves.\n\
1262                    Did gyre and gimble in the wabe;\n\
1263                    All mimsy were the borogoves,\n\
1264                    And the mome raths outgrabe.\n"
1265         );
1266         Ok(())
1267     }
1268 
1269     #[test]
1270     fn cli_file_dir_sync() -> Result<()> {
1271         let dir = tempfile::tempdir()?;
1272 
1273         std::fs::File::create(dir.path().join("bar.txt"))?
1274             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1275 
1276         run_wasmtime(&[
1277             "run",
1278             "-Wcomponent-model",
1279             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1280             CLI_FILE_DIR_SYNC_COMPONENT,
1281         ])?;
1282 
1283         Ok(())
1284     }
1285 
1286     #[test]
1287     fn cli_exit_success() -> Result<()> {
1288         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?;
1289         Ok(())
1290     }
1291 
1292     #[test]
1293     fn cli_exit_default() -> Result<()> {
1294         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?;
1295         Ok(())
1296     }
1297 
1298     #[test]
1299     fn cli_exit_failure() -> Result<()> {
1300         let output = get_wasmtime_command()?
1301             .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT])
1302             .output()?;
1303         assert!(!output.status.success());
1304         assert_eq!(output.status.code(), Some(1));
1305         Ok(())
1306     }
1307 
1308     #[test]
1309     fn cli_exit_panic() -> Result<()> {
1310         let output = get_wasmtime_command()?
1311             .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT])
1312             .output()?;
1313         assert!(!output.status.success());
1314         let stderr = String::from_utf8_lossy(&output.stderr);
1315         assert!(stderr.contains("Curiouser and curiouser!"));
1316         Ok(())
1317     }
1318 
1319     #[test]
1320     fn cli_directory_list() -> Result<()> {
1321         let dir = tempfile::tempdir()?;
1322 
1323         std::fs::File::create(dir.path().join("foo.txt"))?;
1324         std::fs::File::create(dir.path().join("bar.txt"))?;
1325         std::fs::File::create(dir.path().join("baz.txt"))?;
1326         std::fs::create_dir(dir.path().join("sub"))?;
1327         std::fs::File::create(dir.path().join("sub").join("wow.txt"))?;
1328         std::fs::File::create(dir.path().join("sub").join("yay.txt"))?;
1329 
1330         run_wasmtime(&[
1331             "run",
1332             "-Wcomponent-model",
1333             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1334             CLI_DIRECTORY_LIST_COMPONENT,
1335         ])?;
1336         Ok(())
1337     }
1338 
1339     #[test]
1340     fn cli_default_clocks() -> Result<()> {
1341         run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?;
1342         Ok(())
1343     }
1344 
1345     #[test]
1346     fn cli_export_cabi_realloc() -> Result<()> {
1347         run_wasmtime(&[
1348             "run",
1349             "-Wcomponent-model",
1350             CLI_EXPORT_CABI_REALLOC_COMPONENT,
1351         ])?;
1352         Ok(())
1353     }
1354 
1355     #[test]
1356     fn run_wasi_http_component() -> Result<()> {
1357         let output = super::run_wasmtime_for_output(
1358             &[
1359                 "-Ccache=no",
1360                 "-Wcomponent-model",
1361                 "-Scli,http,preview2",
1362                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
1363             ],
1364             None,
1365         )?;
1366         println!("{}", String::from_utf8_lossy(&output.stderr));
1367         let stdout = String::from_utf8_lossy(&output.stdout);
1368         println!("{stdout}");
1369         assert!(stdout.starts_with("Called _start\n"));
1370         assert!(stdout.ends_with("Done\n"));
1371         assert!(output.status.success());
1372         Ok(())
1373     }
1374 
1375     // Test to ensure that prints in the guest aren't buffered on the host by
1376     // accident. The test here will print something without a newline and then
1377     // wait for input on stdin, and the test here is to ensure that the
1378     // character shows up here even as the guest is waiting on input via stdin.
1379     #[test]
1380     fn cli_stdio_write_flushes() -> Result<()> {
1381         fn run(args: &[&str]) -> Result<()> {
1382             println!("running {args:?}");
1383             let mut child = get_wasmtime_command()?
1384                 .args(args)
1385                 .stdin(Stdio::piped())
1386                 .stdout(Stdio::piped())
1387                 .spawn()?;
1388             let mut stdout = child.stdout.take().unwrap();
1389             let mut buf = [0; 10];
1390             match stdout.read(&mut buf) {
1391                 Ok(2) => assert_eq!(&buf[..2], b"> "),
1392                 e => panic!("unexpected read result {e:?}"),
1393             }
1394             drop(stdout);
1395             drop(child.stdin.take().unwrap());
1396             let status = child.wait()?;
1397             assert!(status.success());
1398             Ok(())
1399         }
1400 
1401         run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?;
1402         run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?;
1403         run(&[
1404             "run",
1405             "-Wcomponent-model",
1406             CLI_STDIO_WRITE_FLUSHES_COMPONENT,
1407         ])?;
1408         Ok(())
1409     }
1410 
1411     #[test]
1412     fn cli_no_tcp() -> Result<()> {
1413         let output = super::run_wasmtime_for_output(
1414             &[
1415                 "-Wcomponent-model",
1416                 // Turn on network but turn off TCP
1417                 "-Sinherit-network,tcp=no",
1418                 CLI_NO_TCP_COMPONENT,
1419             ],
1420             None,
1421         )?;
1422         println!("{}", String::from_utf8_lossy(&output.stderr));
1423         assert!(output.status.success());
1424         Ok(())
1425     }
1426 
1427     #[test]
1428     fn cli_no_udp() -> Result<()> {
1429         let output = super::run_wasmtime_for_output(
1430             &[
1431                 "-Wcomponent-model",
1432                 // Turn on network but turn off UDP
1433                 "-Sinherit-network,udp=no",
1434                 CLI_NO_UDP_COMPONENT,
1435             ],
1436             None,
1437         )?;
1438         println!("{}", String::from_utf8_lossy(&output.stderr));
1439         assert!(output.status.success());
1440         Ok(())
1441     }
1442 
1443     #[test]
1444     fn cli_no_ip_name_lookup() -> Result<()> {
1445         let output = super::run_wasmtime_for_output(
1446             &[
1447                 "-Wcomponent-model",
1448                 // Turn on network but ensure name lookup is disabled
1449                 "-Sinherit-network,allow-ip-name-lookup=no",
1450                 CLI_NO_IP_NAME_LOOKUP_COMPONENT,
1451             ],
1452             None,
1453         )?;
1454         println!("{}", String::from_utf8_lossy(&output.stderr));
1455         assert!(output.status.success());
1456         Ok(())
1457     }
1458 
1459     #[test]
1460     fn cli_sleep() -> Result<()> {
1461         run_wasmtime(&["run", CLI_SLEEP])?;
1462         run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?;
1463         Ok(())
1464     }
1465 
1466     #[test]
1467     fn cli_sleep_forever() -> Result<()> {
1468         for timeout in [
1469             // Tests still pass when we race with going to sleep.
1470             "-Wtimeout=1ns",
1471             // Tests pass when we wait till the Wasm has (likely) gone to sleep.
1472             "-Wtimeout=250ms",
1473         ] {
1474             let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER]).unwrap_err();
1475             let e = e.to_string();
1476             println!("Got error: {e}");
1477             assert!(e.contains("interrupt"));
1478 
1479             let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err();
1480             let e = e.to_string();
1481             println!("Got error: {e}");
1482             assert!(e.contains("interrupt"));
1483         }
1484 
1485         Ok(())
1486     }
1487 
1488     /// Helper structure to manage an invocation of `wasmtime serve`
1489     struct WasmtimeServe {
1490         child: Option<Child>,
1491         addr: SocketAddr,
1492     }
1493 
1494     impl WasmtimeServe {
1495         /// Creates a new server which will serve the wasm component pointed to
1496         /// by `wasm`.
1497         ///
1498         /// A `configure` callback is provided to specify how `wasmtime serve`
1499         /// will be invoked and configure arguments such as headers.
1500         fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
1501             // Spawn `wasmtime serve` on port 0 which will randomly assign it a
1502             // port.
1503             let mut cmd = super::get_wasmtime_command()?;
1504             cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm);
1505             configure(&mut cmd);
1506             Self::spawn(&mut cmd)
1507         }
1508 
1509         fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> {
1510             cmd.stdin(Stdio::null());
1511             cmd.stdout(Stdio::piped());
1512             cmd.stderr(Stdio::piped());
1513             let mut child = cmd.spawn()?;
1514 
1515             // Read the first line of stderr which will say which address it's
1516             // listening on.
1517             //
1518             // NB: this intentionally discards any extra buffered data in the
1519             // `BufReader` once the newline is found. The server shouldn't print
1520             // anything interesting other than the address so once we get a line
1521             // all remaining output is left to be captured by future requests
1522             // send to the server.
1523             let mut line = String::new();
1524             let mut reader = BufReader::new(child.stderr.take().unwrap());
1525             reader.read_line(&mut line)?;
1526 
1527             match line.find("127.0.0.1").and_then(|addr_start| {
1528                 let addr = &line[addr_start..];
1529                 let addr_end = addr.find("/")?;
1530                 addr[..addr_end].parse().ok()
1531             }) {
1532                 Some(addr) => {
1533                     assert!(reader.buffer().is_empty());
1534                     child.stderr = Some(reader.into_inner());
1535                     Ok(WasmtimeServe {
1536                         child: Some(child),
1537                         addr,
1538                     })
1539                 }
1540                 None => {
1541                     child.kill()?;
1542                     child.wait()?;
1543                     reader.read_to_string(&mut line)?;
1544                     bail!("failed to start child: {line}")
1545                 }
1546             }
1547         }
1548 
1549         /// Completes this server gracefully by printing the output on failure.
1550         fn finish(mut self) -> Result<(String, String)> {
1551             let mut child = self.child.take().unwrap();
1552 
1553             // If the child process has already exited then collect the output
1554             // and test if it succeeded. Otherwise it's still running so kill it
1555             // and then reap it. Assume that if it's still running then the test
1556             // has otherwise passed so no need to print the output.
1557             let known_failure = if child.try_wait()?.is_some() {
1558                 false
1559             } else {
1560                 child.kill()?;
1561                 true
1562             };
1563             let output = child.wait_with_output()?;
1564             if !known_failure && !output.status.success() {
1565                 bail!("child failed {output:?}");
1566             }
1567 
1568             Ok((
1569                 String::from_utf8_lossy(&output.stdout).into_owned(),
1570                 String::from_utf8_lossy(&output.stderr).into_owned(),
1571             ))
1572         }
1573 
1574         /// Send a request to this server and wait for the response.
1575         async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> {
1576             let (mut send, conn_task) = self.start_requests().await?;
1577 
1578             let response = send
1579                 .send_request(req)
1580                 .await
1581                 .context("error sending request")?;
1582             drop(send);
1583             let (parts, body) = response.into_parts();
1584 
1585             let body = body.collect().await.context("failed to read body")?;
1586             assert!(body.trailers().is_none());
1587             let body = std::str::from_utf8(&body.to_bytes())?.to_string();
1588 
1589             conn_task.await??;
1590 
1591             Ok(http::Response::from_parts(parts, body))
1592         }
1593 
1594         async fn start_requests(
1595             &self,
1596         ) -> Result<(
1597             hyper::client::conn::http1::SendRequest<String>,
1598             tokio::task::JoinHandle<hyper::Result<()>>,
1599         )> {
1600             let tcp = TcpStream::connect(&self.addr)
1601                 .await
1602                 .context("failed to connect")?;
1603             let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp);
1604             let (send, conn) = hyper::client::conn::http1::handshake(tcp)
1605                 .await
1606                 .context("failed http handshake")?;
1607             Ok((send, tokio::task::spawn(conn)))
1608         }
1609     }
1610 
1611     // Don't leave child processes running by accident so kill the child process
1612     // if our server goes away.
1613     impl Drop for WasmtimeServe {
1614         fn drop(&mut self) {
1615             let mut child = match self.child.take() {
1616                 Some(child) => child,
1617                 None => return,
1618             };
1619             if child.kill().is_err() {
1620                 return;
1621             }
1622             let output = match child.wait_with_output() {
1623                 Ok(output) => output,
1624                 Err(_) => return,
1625             };
1626 
1627             println!("server status: {}", output.status);
1628             if !output.stdout.is_empty() {
1629                 println!(
1630                     "server stdout:\n{}",
1631                     String::from_utf8_lossy(&output.stdout)
1632                 );
1633             }
1634             if !output.stderr.is_empty() {
1635                 println!(
1636                     "server stderr:\n{}",
1637                     String::from_utf8_lossy(&output.stderr)
1638                 );
1639             }
1640         }
1641     }
1642 
1643     #[tokio::test]
1644     async fn cli_serve_echo_env() -> Result<()> {
1645         let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1646             cmd.arg("--env=FOO=bar");
1647             cmd.arg("--env=BAR");
1648             cmd.arg("-Scli");
1649             cmd.env_remove("BAR");
1650         })?;
1651 
1652         let foo_env = server
1653             .send_request(
1654                 hyper::Request::builder()
1655                     .uri("http://localhost/")
1656                     .header("env", "FOO")
1657                     .body(String::new())
1658                     .context("failed to make request")?,
1659             )
1660             .await?;
1661 
1662         assert!(foo_env.status().is_success());
1663         assert!(foo_env.body().is_empty());
1664         let headers = foo_env.headers();
1665         assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar")));
1666 
1667         let bar_env = server
1668             .send_request(
1669                 hyper::Request::builder()
1670                     .uri("http://localhost/")
1671                     .header("env", "BAR")
1672                     .body(String::new())
1673                     .context("failed to make request")?,
1674             )
1675             .await?;
1676 
1677         assert!(bar_env.status().is_success());
1678         assert!(bar_env.body().is_empty());
1679         let headers = bar_env.headers();
1680         assert_eq!(headers.get("env"), None);
1681 
1682         server.finish()?;
1683         Ok(())
1684     }
1685 
1686     #[tokio::test]
1687     #[ignore] // TODO: printing stderr in the child and killing the child at the
1688               // end of this test race so the stderr may be present or not. Need
1689               // to implement a more graceful shutdown routine for `wasmtime
1690               // serve`.
1691     async fn cli_serve_respect_pooling_options() -> Result<()> {
1692         let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1693             cmd.arg("-Opooling-total-memories=0").arg("-Scli");
1694         })?;
1695 
1696         let result = server
1697             .send_request(
1698                 hyper::Request::builder()
1699                     .uri("http://localhost/")
1700                     .header("env", "FOO")
1701                     .body(String::new())
1702                     .context("failed to make request")?,
1703             )
1704             .await;
1705         assert!(result.is_err());
1706         let (_, stderr) = server.finish()?;
1707         assert!(
1708             stderr.contains("maximum concurrent memory limit of 0 reached"),
1709             "bad stderr: {stderr}",
1710         );
1711         Ok(())
1712     }
1713 
1714     #[test]
1715     fn cli_large_env() -> Result<()> {
1716         for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] {
1717             println!("run {wasm:?}");
1718             let mut cmd = get_wasmtime_command()?;
1719             cmd.arg("run").arg("-Sinherit-env").arg(wasm);
1720 
1721             let debug_cmd = format!("{cmd:?}");
1722             for i in 0..512 {
1723                 let var = format!("KEY{i}");
1724                 let val = (0..1024).map(|_| 'x').collect::<String>();
1725                 cmd.env(&var, &val);
1726             }
1727             let output = cmd.output()?;
1728             if !output.status.success() {
1729                 bail!(
1730                     "Failed to execute wasmtime with: {debug_cmd}\n{}",
1731                     String::from_utf8_lossy(&output.stderr)
1732                 );
1733             }
1734         }
1735         Ok(())
1736     }
1737 
1738     #[tokio::test]
1739     async fn cli_serve_only_one_process_allowed() -> Result<()> {
1740         let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
1741         let server = WasmtimeServe::new(wasm, |cmd| {
1742             cmd.arg("-Scli");
1743         })?;
1744 
1745         let err = WasmtimeServe::spawn(
1746             super::get_wasmtime_command()?
1747                 .arg("serve")
1748                 .arg("-Scli")
1749                 .arg(format!("--addr={}", server.addr))
1750                 .arg(wasm),
1751         )
1752         .err()
1753         .expect("server spawn should have failed but it succeeded");
1754         drop(server);
1755 
1756         let err = format!("{err:?}");
1757         println!("{err}");
1758         assert!(err.contains("os error"));
1759         Ok(())
1760     }
1761 
1762     // Technically this test is a little racy. This binds port 0 to acquire a
1763     // random port, issues a single request to this port, but then kills this
1764     // server while the request is still processing. The port is then rebound
1765     // in the next process while it technically could be stolen by another
1766     // process.
1767     #[tokio::test]
1768     async fn cli_serve_quick_rebind_allowed() -> Result<()> {
1769         let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
1770         let server = WasmtimeServe::new(wasm, |cmd| {
1771             cmd.arg("-Scli");
1772         })?;
1773         let addr = server.addr;
1774 
1775         // Start up a `send` and `conn_task` which represents a connection to
1776         // this server.
1777         let (mut send, conn_task) = server.start_requests().await?;
1778         let _ = send
1779             .send_request(
1780                 hyper::Request::builder()
1781                     .uri("http://localhost/")
1782                     .header("env", "FOO")
1783                     .body(String::new())
1784                     .context("failed to make request")?,
1785             )
1786             .await;
1787 
1788         // ... once a response has been received (or at least the status
1789         // code/headers) then kill the server. THis is done while `conn_task`
1790         // and `send` are still alive so we're guaranteed that the other side
1791         // got a request (we got a response) and our connection is still open.
1792         //
1793         // This forces the address/port into the `TIME_WAIT` state. The rebind
1794         // below in the next process will fail if `SO_REUSEADDR` isn't set.
1795         drop(server);
1796         drop(send);
1797         let _ = conn_task.await;
1798 
1799         // If this is successfully bound then we'll create `WasmtimeServe`
1800         // which reads off the first line of output to know which address was
1801         // bound.
1802         let _server2 = WasmtimeServe::spawn(
1803             super::get_wasmtime_command()?
1804                 .arg("serve")
1805                 .arg("-Scli")
1806                 .arg(format!("--addr={addr}"))
1807                 .arg(wasm),
1808         )?;
1809 
1810         Ok(())
1811     }
1812 
1813     #[tokio::test]
1814     async fn cli_serve_with_print() -> Result<()> {
1815         let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1816             cmd.arg("-Scli");
1817         })?;
1818 
1819         for _ in 0..2 {
1820             let resp = server
1821                 .send_request(
1822                     hyper::Request::builder()
1823                         .uri("http://localhost/")
1824                         .body(String::new())
1825                         .context("failed to make request")?,
1826                 )
1827                 .await?;
1828             assert!(resp.status().is_success());
1829         }
1830 
1831         let (out, err) = server.finish()?;
1832         assert_eq!(
1833             out,
1834             "\
1835 stdout [0] :: this is half a print to stdout
1836 stdout [0] :: \n\
1837 stdout [0] :: after empty
1838 stdout [1] :: this is half a print to stdout
1839 stdout [1] :: \n\
1840 stdout [1] :: after empty
1841 "
1842         );
1843         assert_eq!(
1844             err,
1845             "\
1846 stderr [0] :: this is half a print to stderr
1847 stderr [0] :: \n\
1848 stderr [0] :: after empty
1849 stderr [1] :: this is half a print to stderr
1850 stderr [1] :: \n\
1851 stderr [1] :: after empty
1852 "
1853         );
1854 
1855         Ok(())
1856     }
1857 
1858     #[tokio::test]
1859     async fn cli_serve_authority_and_scheme() -> Result<()> {
1860         let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| {
1861             cmd.arg("-Scli");
1862         })?;
1863 
1864         let resp = server
1865             .send_request(
1866                 hyper::Request::builder()
1867                     .uri("/")
1868                     .header("Host", "localhost")
1869                     .body(String::new())
1870                     .context("failed to make request")?,
1871             )
1872             .await?;
1873         assert!(resp.status().is_success());
1874 
1875         let resp = server
1876             .send_request(
1877                 hyper::Request::builder()
1878                     .method("CONNECT")
1879                     .uri("http://localhost/")
1880                     .body(String::new())
1881                     .context("failed to make request")?,
1882             )
1883             .await?;
1884         assert!(resp.status().is_success());
1885 
1886         Ok(())
1887     }
1888 
1889     #[test]
1890     fn cli_argv0() -> Result<()> {
1891         run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?;
1892         run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?;
1893         run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?;
1894         Ok(())
1895     }
1896 
1897     #[tokio::test]
1898     async fn cli_serve_runtime_config() -> Result<()> {
1899         let server = WasmtimeServe::new(CLI_SERVE_RUNTIME_CONFIG_COMPONENT, |cmd| {
1900             cmd.arg("-Scli");
1901             cmd.arg("-Sruntime-config");
1902             cmd.arg("-Sruntime-config-var=hello=world");
1903         })?;
1904 
1905         let resp = server
1906             .send_request(
1907                 hyper::Request::builder()
1908                     .uri("http://localhost/")
1909                     .body(String::new())
1910                     .context("failed to make request")?,
1911             )
1912             .await?;
1913 
1914         assert!(resp.status().is_success());
1915         assert_eq!(resp.body(), "world");
1916         Ok(())
1917     }
1918 
1919     #[test]
1920     fn cli_runtime_config() -> Result<()> {
1921         run_wasmtime(&[
1922             "run",
1923             "-Sruntime-config",
1924             "-Sruntime-config-var=hello=world",
1925             RUNTIME_CONFIG_GET_COMPONENT,
1926         ])?;
1927         Ok(())
1928     }
1929 
1930     #[tokio::test]
1931     async fn cli_serve_keyvalue() -> Result<()> {
1932         let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| {
1933             cmd.arg("-Scli");
1934             cmd.arg("-Skeyvalue");
1935             cmd.arg("-Skeyvalue-in-memory-data=hello=world");
1936         })?;
1937 
1938         let resp = server
1939             .send_request(
1940                 hyper::Request::builder()
1941                     .uri("http://localhost/")
1942                     .body(String::new())
1943                     .context("failed to make request")?,
1944             )
1945             .await?;
1946 
1947         assert!(resp.status().is_success());
1948         assert_eq!(resp.body(), "world");
1949         Ok(())
1950     }
1951 
1952     #[test]
1953     fn cli_keyvalue() -> Result<()> {
1954         run_wasmtime(&[
1955             "run",
1956             "-Skeyvalue",
1957             "-Skeyvalue-in-memory-data=atomics_key=5",
1958             KEYVALUE_MAIN_COMPONENT,
1959         ])?;
1960         Ok(())
1961     }
1962 }
1963 
1964 #[test]
1965 fn settings_command() -> Result<()> {
1966     let output = run_wasmtime(&["settings"])?;
1967     assert!(output.contains("Cranelift settings for target"));
1968     Ok(())
1969 }
1970 
1971 #[cfg(target_arch = "x86_64")]
1972 #[test]
1973 fn profile_with_vtune() -> Result<()> {
1974     if !is_vtune_available() {
1975         println!("> `vtune` is not available on the system path; skipping test");
1976         return Ok(());
1977     }
1978 
1979     let mut bin = Command::new("vtune");
1980     bin.args(&[
1981         // Configure VTune...
1982         "-verbose",
1983         "-collect",
1984         "hotspots",
1985         "-user-data-dir",
1986         &std::env::temp_dir().to_string_lossy(),
1987         // ...then run Wasmtime with profiling enabled:
1988         &get_wasmtime_path()?.to_string_lossy(),
1989         "--profile=vtune",
1990         "tests/all/cli_tests/simple.wat",
1991     ]);
1992 
1993     println!("> executing: {bin:?}");
1994     let output = bin.output()?;
1995 
1996     assert!(output.status.success());
1997     let stdout = String::from_utf8_lossy(&output.stdout);
1998     let stderr = String::from_utf8_lossy(&output.stderr);
1999     println!("> stdout:\n{stdout}");
2000     assert!(stdout.contains("CPU Time"));
2001     println!("> stderr:\n{stderr}");
2002     assert!(!stderr.contains("Error"));
2003     Ok(())
2004 }
2005 
2006 #[cfg(target_arch = "x86_64")]
2007 fn is_vtune_available() -> bool {
2008     Command::new("vtune").arg("-version").output().is_ok()
2009 }
2010