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