xref: /wasmtime-44.0.1/tests/all/cli_tests.rs (revision efb4e4c0)
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 #[test]
861 fn memory_growth_failure() -> Result<()> {
862     let output = get_wasmtime_command()?
863         .args(&[
864             "run",
865             "-Wmemory64",
866             "-Wtrap-on-grow-failure",
867             "tests/all/cli_tests/memory-grow-failure.wat",
868         ])
869         .output()?;
870     assert!(!output.status.success());
871     let stderr = String::from_utf8_lossy(&output.stderr);
872     assert!(
873         stderr.contains("forcing a memory growth failure to be a trap"),
874         "bad stderr: {stderr}"
875     );
876     Ok(())
877 }
878 
879 #[test]
880 fn table_growth_failure() -> Result<()> {
881     let output = get_wasmtime_command()?
882         .args(&[
883             "run",
884             "-Wtrap-on-grow-failure",
885             "tests/all/cli_tests/table-grow-failure.wat",
886         ])
887         .output()?;
888     assert!(!output.status.success());
889     let stderr = String::from_utf8_lossy(&output.stderr);
890     assert!(
891         stderr.contains("forcing trap when growing table"),
892         "bad stderr: {stderr}"
893     );
894     Ok(())
895 }
896 
897 #[test]
898 fn table_growth_failure2() -> Result<()> {
899     let output = get_wasmtime_command()?
900         .args(&[
901             "run",
902             "-Wtrap-on-grow-failure",
903             "tests/all/cli_tests/table-grow-failure2.wat",
904         ])
905         .output()?;
906     assert!(!output.status.success());
907     let stderr = String::from_utf8_lossy(&output.stderr);
908     let expected = if cfg!(target_pointer_width = "32") {
909         "overflow calculating new table size"
910     } else {
911         "forcing trap when growing table to 4294967296 elements"
912     };
913     assert!(stderr.contains(expected), "bad stderr: {stderr}");
914     Ok(())
915 }
916 
917 #[test]
918 fn option_group_help() -> Result<()> {
919     run_wasmtime(&["run", "-Whelp"])?;
920     run_wasmtime(&["run", "-O", "help"])?;
921     run_wasmtime(&["run", "--codegen", "help"])?;
922     run_wasmtime(&["run", "--debug=help"])?;
923     run_wasmtime(&["run", "-Shelp"])?;
924     run_wasmtime(&["run", "-Whelp-long"])?;
925     Ok(())
926 }
927 
928 #[test]
929 fn option_group_comma_separated() -> Result<()> {
930     run_wasmtime(&[
931         "run",
932         "-Wrelaxed-simd,simd",
933         "tests/all/cli_tests/simple.wat",
934     ])?;
935     Ok(())
936 }
937 
938 #[test]
939 fn option_group_boolean_parsing() -> Result<()> {
940     run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?;
941     run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?;
942     run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?;
943     run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?;
944     run_wasmtime(&[
945         "run",
946         "-Wrelaxed-simd=yes",
947         "tests/all/cli_tests/simple.wat",
948     ])?;
949     run_wasmtime(&[
950         "run",
951         "-Wrelaxed-simd=true",
952         "tests/all/cli_tests/simple.wat",
953     ])?;
954     run_wasmtime(&[
955         "run",
956         "-Wrelaxed-simd=false",
957         "tests/all/cli_tests/simple.wat",
958     ])?;
959     Ok(())
960 }
961 
962 #[test]
963 fn preview2_stdin() -> Result<()> {
964     let test = "tests/all/cli_tests/count-stdin.wat";
965     let cmd = || -> Result<_> {
966         let mut cmd = get_wasmtime_command()?;
967         cmd.arg("--invoke=count").arg("-Spreview2").arg(test);
968         Ok(cmd)
969     };
970 
971     // read empty pipe is ok
972     let output = cmd()?.output()?;
973     assert!(output.status.success());
974     assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");
975 
976     // read itself is ok
977     let file = File::open(test)?;
978     let size = file.metadata()?.len();
979     let output = cmd()?.stdin(File::open(test)?).output()?;
980     assert!(output.status.success());
981     assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n"));
982 
983     // read piped input ok is ok
984     let mut child = cmd()?
985         .stdin(Stdio::piped())
986         .stdout(Stdio::piped())
987         .stderr(Stdio::piped())
988         .spawn()?;
989     let mut stdin = child.stdin.take().unwrap();
990     std::thread::spawn(move || {
991         stdin.write_all(b"hello").unwrap();
992     });
993     let output = child.wait_with_output()?;
994     assert!(output.status.success());
995     assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n");
996 
997     let count_up_to = |n: usize| -> Result<_> {
998         let mut child = get_wasmtime_command()?
999             .arg("--invoke=count-up-to")
1000             .arg("-Spreview2")
1001             .arg(test)
1002             .arg(n.to_string())
1003             .stdin(Stdio::piped())
1004             .stdout(Stdio::piped())
1005             .stderr(Stdio::piped())
1006             .spawn()?;
1007         let mut stdin = child.stdin.take().unwrap();
1008         let t = std::thread::spawn(move || {
1009             let mut written = 0;
1010             let bytes = [0; 64 * 1024];
1011             loop {
1012                 written += match stdin.write(&bytes) {
1013                     Ok(n) => n,
1014                     Err(_) => break written,
1015                 };
1016             }
1017         });
1018         let output = child.wait_with_output()?;
1019         assert!(output.status.success());
1020         let written = t.join().unwrap();
1021         let read = String::from_utf8_lossy(&output.stdout)
1022             .trim()
1023             .parse::<usize>()
1024             .unwrap();
1025         // The test reads in 1000 byte chunks so make sure that it doesn't read
1026         // more than 1000 bytes than requested.
1027         assert!(read < n + 1000, "test read too much {read}");
1028         Ok(written)
1029     };
1030 
1031     // wasmtime shouldn't eat information that the guest never actually tried to
1032     // read.
1033     //
1034     // NB: this may be a bit flaky. Exactly how much we wrote in the above
1035     // helper thread depends on how much the OS buffers for us. For now give
1036     // some some slop and assume that OSes are unlikely to buffer more than
1037     // that.
1038     let slop = 256 * 1024;
1039     for amt in [0, 100, 100_000] {
1040         let written = count_up_to(amt)?;
1041         assert!(written < slop + amt, "wrote too much {written}");
1042     }
1043     Ok(())
1044 }
1045 
1046 #[test]
1047 fn float_args() -> Result<()> {
1048     let result = run_wasmtime(&[
1049         "--invoke",
1050         "echo_f32",
1051         "tests/all/cli_tests/simple.wat",
1052         "1.0",
1053     ])?;
1054     assert_eq!(result, "1\n");
1055     let result = run_wasmtime(&[
1056         "--invoke",
1057         "echo_f64",
1058         "tests/all/cli_tests/simple.wat",
1059         "1.1",
1060     ])?;
1061     assert_eq!(result, "1.1\n");
1062     Ok(())
1063 }
1064 
1065 #[test]
1066 fn mpk_without_pooling() -> Result<()> {
1067     let output = get_wasmtime_command()?
1068         .args(&[
1069             "run",
1070             "-O",
1071             "memory-protection-keys=y",
1072             "--invoke",
1073             "echo_f32",
1074             "tests/all/cli_tests/simple.wat",
1075             "1.0",
1076         ])
1077         .env("WASMTIME_NEW_CLI", "1")
1078         .output()?;
1079     assert!(!output.status.success());
1080     Ok(())
1081 }
1082 
1083 // Very basic use case: compile binary wasm file and run specific function with arguments.
1084 #[test]
1085 fn increase_stack_size() -> Result<()> {
1086     run_wasmtime(&[
1087         "run",
1088         "--invoke",
1089         "simple",
1090         &format!("-Wmax-wasm-stack={}", 5 << 20),
1091         "-Ccache=n",
1092         "tests/all/cli_tests/simple.wat",
1093         "4",
1094     ])?;
1095     Ok(())
1096 }
1097 
1098 mod test_programs {
1099     use super::{get_wasmtime_command, run_wasmtime};
1100     use anyhow::{bail, Context, Result};
1101     use http_body_util::BodyExt;
1102     use hyper::header::HeaderValue;
1103     use std::io::{BufRead, BufReader, Read, Write};
1104     use std::net::SocketAddr;
1105     use std::process::{Child, Command, Stdio};
1106     use test_programs_artifacts::*;
1107     use tokio::net::TcpStream;
1108 
1109     macro_rules! assert_test_exists {
1110         ($name:ident) => {
1111             #[allow(unused_imports)]
1112             use self::$name as _;
1113         };
1114     }
1115     foreach_cli!(assert_test_exists);
1116 
1117     #[test]
1118     fn cli_hello_stdout() -> Result<()> {
1119         run_wasmtime(&[
1120             "run",
1121             "-Wcomponent-model",
1122             CLI_HELLO_STDOUT_COMPONENT,
1123             "gussie",
1124             "sparky",
1125             "willa",
1126         ])?;
1127         Ok(())
1128     }
1129 
1130     #[test]
1131     fn cli_args() -> Result<()> {
1132         run_wasmtime(&[
1133             "run",
1134             "-Wcomponent-model",
1135             CLI_ARGS_COMPONENT,
1136             "hello",
1137             "this",
1138             "",
1139             "is an argument",
1140             "with �� emoji",
1141         ])?;
1142         Ok(())
1143     }
1144 
1145     #[test]
1146     fn cli_stdin_empty() -> Result<()> {
1147         let mut child = get_wasmtime_command()?
1148             .args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT])
1149             .stdout(Stdio::piped())
1150             .stderr(Stdio::piped())
1151             .stdin(Stdio::piped())
1152             .spawn()?;
1153         child
1154             .stdin
1155             .take()
1156             .unwrap()
1157             .write_all(b"not to be read")
1158             .unwrap();
1159         let output = child.wait_with_output()?;
1160         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1161         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1162         assert!(output.status.success());
1163         Ok(())
1164     }
1165 
1166     #[test]
1167     fn cli_stdin() -> Result<()> {
1168         let mut child = get_wasmtime_command()?
1169             .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT])
1170             .stdout(Stdio::piped())
1171             .stderr(Stdio::piped())
1172             .stdin(Stdio::piped())
1173             .spawn()?;
1174         child
1175             .stdin
1176             .take()
1177             .unwrap()
1178             .write_all(b"So rested he by the Tumtum tree")
1179             .unwrap();
1180         let output = child.wait_with_output()?;
1181         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1182         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1183         assert!(output.status.success());
1184         Ok(())
1185     }
1186 
1187     #[test]
1188     fn cli_splice_stdin() -> Result<()> {
1189         let mut child = get_wasmtime_command()?
1190             .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT])
1191             .stdout(Stdio::piped())
1192             .stderr(Stdio::piped())
1193             .stdin(Stdio::piped())
1194             .spawn()?;
1195         let msg = "So rested he by the Tumtum tree";
1196         child
1197             .stdin
1198             .take()
1199             .unwrap()
1200             .write_all(msg.as_bytes())
1201             .unwrap();
1202         let output = child.wait_with_output()?;
1203         assert!(output.status.success());
1204         let stdout = String::from_utf8_lossy(&output.stdout);
1205         let stderr = String::from_utf8_lossy(&output.stderr);
1206         if !stderr.is_empty() {
1207             eprintln!("{stderr}");
1208         }
1209 
1210         assert_eq!(
1211             format!(
1212                 "before splice\n{msg}\ncompleted splicing {} bytes\n",
1213                 msg.as_bytes().len()
1214             ),
1215             stdout
1216         );
1217         Ok(())
1218     }
1219 
1220     #[test]
1221     fn cli_env() -> Result<()> {
1222         run_wasmtime(&[
1223             "run",
1224             "-Wcomponent-model",
1225             "--env=frabjous=day",
1226             "--env=callooh=callay",
1227             CLI_ENV_COMPONENT,
1228         ])?;
1229         Ok(())
1230     }
1231 
1232     #[test]
1233     fn cli_file_read() -> Result<()> {
1234         let dir = tempfile::tempdir()?;
1235 
1236         std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1237 
1238         run_wasmtime(&[
1239             "run",
1240             "-Wcomponent-model",
1241             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1242             CLI_FILE_READ_COMPONENT,
1243         ])?;
1244         Ok(())
1245     }
1246 
1247     #[test]
1248     fn cli_file_append() -> Result<()> {
1249         let dir = tempfile::tempdir()?;
1250 
1251         std::fs::File::create(dir.path().join("bar.txt"))?
1252             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1253 
1254         run_wasmtime(&[
1255             "run",
1256             "-Wcomponent-model",
1257             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1258             CLI_FILE_APPEND_COMPONENT,
1259         ])?;
1260 
1261         let contents = std::fs::read(dir.path().join("bar.txt"))?;
1262         assert_eq!(
1263             std::str::from_utf8(&contents).unwrap(),
1264             "'Twas brillig, and the slithy toves.\n\
1265                    Did gyre and gimble in the wabe;\n\
1266                    All mimsy were the borogoves,\n\
1267                    And the mome raths outgrabe.\n"
1268         );
1269         Ok(())
1270     }
1271 
1272     #[test]
1273     fn cli_file_dir_sync() -> Result<()> {
1274         let dir = tempfile::tempdir()?;
1275 
1276         std::fs::File::create(dir.path().join("bar.txt"))?
1277             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1278 
1279         run_wasmtime(&[
1280             "run",
1281             "-Wcomponent-model",
1282             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1283             CLI_FILE_DIR_SYNC_COMPONENT,
1284         ])?;
1285 
1286         Ok(())
1287     }
1288 
1289     #[test]
1290     fn cli_exit_success() -> Result<()> {
1291         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?;
1292         Ok(())
1293     }
1294 
1295     #[test]
1296     fn cli_exit_default() -> Result<()> {
1297         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?;
1298         Ok(())
1299     }
1300 
1301     #[test]
1302     fn cli_exit_failure() -> Result<()> {
1303         let output = get_wasmtime_command()?
1304             .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT])
1305             .output()?;
1306         assert!(!output.status.success());
1307         assert_eq!(output.status.code(), Some(1));
1308         Ok(())
1309     }
1310 
1311     #[test]
1312     fn cli_exit_with_code() -> Result<()> {
1313         let output = get_wasmtime_command()?
1314             .args(&[
1315                 "run",
1316                 "-Wcomponent-model",
1317                 "-Scli-exit-with-code",
1318                 CLI_EXIT_WITH_CODE_COMPONENT,
1319             ])
1320             .output()?;
1321         assert!(!output.status.success());
1322         assert_eq!(output.status.code(), Some(42));
1323         Ok(())
1324     }
1325 
1326     #[test]
1327     fn cli_exit_panic() -> Result<()> {
1328         let output = get_wasmtime_command()?
1329             .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT])
1330             .output()?;
1331         assert!(!output.status.success());
1332         let stderr = String::from_utf8_lossy(&output.stderr);
1333         assert!(stderr.contains("Curiouser and curiouser!"));
1334         Ok(())
1335     }
1336 
1337     #[test]
1338     fn cli_directory_list() -> Result<()> {
1339         let dir = tempfile::tempdir()?;
1340 
1341         std::fs::File::create(dir.path().join("foo.txt"))?;
1342         std::fs::File::create(dir.path().join("bar.txt"))?;
1343         std::fs::File::create(dir.path().join("baz.txt"))?;
1344         std::fs::create_dir(dir.path().join("sub"))?;
1345         std::fs::File::create(dir.path().join("sub").join("wow.txt"))?;
1346         std::fs::File::create(dir.path().join("sub").join("yay.txt"))?;
1347 
1348         run_wasmtime(&[
1349             "run",
1350             "-Wcomponent-model",
1351             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1352             CLI_DIRECTORY_LIST_COMPONENT,
1353         ])?;
1354         Ok(())
1355     }
1356 
1357     #[test]
1358     fn cli_default_clocks() -> Result<()> {
1359         run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?;
1360         Ok(())
1361     }
1362 
1363     #[test]
1364     fn cli_export_cabi_realloc() -> Result<()> {
1365         run_wasmtime(&[
1366             "run",
1367             "-Wcomponent-model",
1368             CLI_EXPORT_CABI_REALLOC_COMPONENT,
1369         ])?;
1370         Ok(())
1371     }
1372 
1373     #[test]
1374     fn run_wasi_http_component() -> Result<()> {
1375         let output = super::run_wasmtime_for_output(
1376             &[
1377                 "-Ccache=no",
1378                 "-Wcomponent-model",
1379                 "-Scli,http,preview2",
1380                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
1381             ],
1382             None,
1383         )?;
1384         println!("{}", String::from_utf8_lossy(&output.stderr));
1385         let stdout = String::from_utf8_lossy(&output.stdout);
1386         println!("{stdout}");
1387         assert!(stdout.starts_with("Called _start\n"));
1388         assert!(stdout.ends_with("Done\n"));
1389         assert!(output.status.success());
1390         Ok(())
1391     }
1392 
1393     // Test to ensure that prints in the guest aren't buffered on the host by
1394     // accident. The test here will print something without a newline and then
1395     // wait for input on stdin, and the test here is to ensure that the
1396     // character shows up here even as the guest is waiting on input via stdin.
1397     #[test]
1398     fn cli_stdio_write_flushes() -> Result<()> {
1399         fn run(args: &[&str]) -> Result<()> {
1400             println!("running {args:?}");
1401             let mut child = get_wasmtime_command()?
1402                 .args(args)
1403                 .stdin(Stdio::piped())
1404                 .stdout(Stdio::piped())
1405                 .spawn()?;
1406             let mut stdout = child.stdout.take().unwrap();
1407             let mut buf = [0; 10];
1408             match stdout.read(&mut buf) {
1409                 Ok(2) => assert_eq!(&buf[..2], b"> "),
1410                 e => panic!("unexpected read result {e:?}"),
1411             }
1412             drop(stdout);
1413             drop(child.stdin.take().unwrap());
1414             let status = child.wait()?;
1415             assert!(status.success());
1416             Ok(())
1417         }
1418 
1419         run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?;
1420         run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?;
1421         run(&[
1422             "run",
1423             "-Wcomponent-model",
1424             CLI_STDIO_WRITE_FLUSHES_COMPONENT,
1425         ])?;
1426         Ok(())
1427     }
1428 
1429     #[test]
1430     fn cli_no_tcp() -> Result<()> {
1431         let output = super::run_wasmtime_for_output(
1432             &[
1433                 "-Wcomponent-model",
1434                 // Turn on network but turn off TCP
1435                 "-Sinherit-network,tcp=no",
1436                 CLI_NO_TCP_COMPONENT,
1437             ],
1438             None,
1439         )?;
1440         println!("{}", String::from_utf8_lossy(&output.stderr));
1441         assert!(output.status.success());
1442         Ok(())
1443     }
1444 
1445     #[test]
1446     fn cli_no_udp() -> Result<()> {
1447         let output = super::run_wasmtime_for_output(
1448             &[
1449                 "-Wcomponent-model",
1450                 // Turn on network but turn off UDP
1451                 "-Sinherit-network,udp=no",
1452                 CLI_NO_UDP_COMPONENT,
1453             ],
1454             None,
1455         )?;
1456         println!("{}", String::from_utf8_lossy(&output.stderr));
1457         assert!(output.status.success());
1458         Ok(())
1459     }
1460 
1461     #[test]
1462     fn cli_no_ip_name_lookup() -> Result<()> {
1463         let output = super::run_wasmtime_for_output(
1464             &[
1465                 "-Wcomponent-model",
1466                 // Turn on network but ensure name lookup is disabled
1467                 "-Sinherit-network,allow-ip-name-lookup=no",
1468                 CLI_NO_IP_NAME_LOOKUP_COMPONENT,
1469             ],
1470             None,
1471         )?;
1472         println!("{}", String::from_utf8_lossy(&output.stderr));
1473         assert!(output.status.success());
1474         Ok(())
1475     }
1476 
1477     #[test]
1478     fn cli_sleep() -> Result<()> {
1479         run_wasmtime(&["run", CLI_SLEEP])?;
1480         run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?;
1481         Ok(())
1482     }
1483 
1484     #[test]
1485     fn cli_sleep_forever() -> Result<()> {
1486         for timeout in [
1487             // Tests still pass when we race with going to sleep.
1488             "-Wtimeout=1ns",
1489             // Tests pass when we wait till the Wasm has (likely) gone to sleep.
1490             "-Wtimeout=250ms",
1491         ] {
1492             let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER]).unwrap_err();
1493             let e = e.to_string();
1494             println!("Got error: {e}");
1495             assert!(e.contains("interrupt"));
1496 
1497             let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err();
1498             let e = e.to_string();
1499             println!("Got error: {e}");
1500             assert!(e.contains("interrupt"));
1501         }
1502 
1503         Ok(())
1504     }
1505 
1506     /// Helper structure to manage an invocation of `wasmtime serve`
1507     struct WasmtimeServe {
1508         child: Option<Child>,
1509         addr: SocketAddr,
1510         shutdown_addr: SocketAddr,
1511     }
1512 
1513     impl WasmtimeServe {
1514         /// Creates a new server which will serve the wasm component pointed to
1515         /// by `wasm`.
1516         ///
1517         /// A `configure` callback is provided to specify how `wasmtime serve`
1518         /// will be invoked and configure arguments such as headers.
1519         fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
1520             // Spawn `wasmtime serve` on port 0 which will randomly assign it a
1521             // port.
1522             let mut cmd = super::get_wasmtime_command()?;
1523             cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm);
1524             configure(&mut cmd);
1525             Self::spawn(&mut cmd)
1526         }
1527 
1528         fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> {
1529             cmd.arg("--shutdown-addr=127.0.0.1:0");
1530             cmd.stdin(Stdio::null());
1531             cmd.stdout(Stdio::piped());
1532             cmd.stderr(Stdio::piped());
1533             let mut child = cmd.spawn()?;
1534 
1535             // Read the first few lines of stderr which will say which address
1536             // it's listening on. The first line is the shutdown line (with
1537             // `--shutdown-addr`) and the second is what `--addr` was bound to.
1538             // This is done to figure out what `:0` was bound to in the child
1539             // process.
1540             let mut line = String::new();
1541             let mut reader = BufReader::new(child.stderr.take().unwrap());
1542             let mut read_addr_from_line = |prefix: &str| -> Result<SocketAddr> {
1543                 reader.read_line(&mut line)?;
1544 
1545                 if !line.starts_with(prefix) {
1546                     bail!("input line `{line}` didn't start with `{prefix}`");
1547                 }
1548                 match line.find("127.0.0.1").and_then(|addr_start| {
1549                     let addr = &line[addr_start..];
1550                     let addr_end = addr.find("/")?;
1551                     addr[..addr_end].parse().ok()
1552                 }) {
1553                     Some(addr) => {
1554                         line.truncate(0);
1555                         Ok(addr)
1556                     }
1557                     None => bail!("failed to address from: {line}"),
1558                 }
1559             };
1560             let shutdown_addr = read_addr_from_line("Listening for shutdown");
1561             let addr = read_addr_from_line("Serving HTTP on");
1562             let (shutdown_addr, addr) = match (shutdown_addr, addr) {
1563                 (Ok(a), Ok(b)) => (a, b),
1564                 // If either failed kill the child and otherwise try to shepherd
1565                 // along any contextual information we have.
1566                 (Err(a), _) | (_, Err(a)) => {
1567                     child.kill()?;
1568                     child.wait()?;
1569                     reader.read_to_string(&mut line)?;
1570                     return Err(a.context(line));
1571                 }
1572             };
1573             assert!(reader.buffer().is_empty());
1574             child.stderr = Some(reader.into_inner());
1575             Ok(WasmtimeServe {
1576                 child: Some(child),
1577                 addr,
1578                 shutdown_addr,
1579             })
1580         }
1581 
1582         /// Completes this server gracefully by printing the output on failure.
1583         fn finish(mut self) -> Result<(String, String)> {
1584             let mut child = self.child.take().unwrap();
1585 
1586             // If the child process has already exited, then great! Otherwise
1587             // the server is still running and it shouldn't be possible to exit
1588             // until a shutdown signal is sent, so do that here. Make a TCP
1589             // connection to the shutdown port which is used as a shutdown
1590             // signal.
1591             if child.try_wait()?.is_none() {
1592                 std::net::TcpStream::connect(&self.shutdown_addr)
1593                     .context("failed to initiate graceful shutdown")?;
1594             }
1595 
1596             // Regardless of whether we just shut the server down or whether it
1597             // was already shut down (e.g. panicked or similar), wait for the
1598             // result here. The result should succeed (e.g. 0 exit status), and
1599             // if it did then the stdout/stderr are the caller's problem.
1600             let output = child.wait_with_output()?;
1601             if !output.status.success() {
1602                 bail!("child failed {output:?}");
1603             }
1604 
1605             Ok((
1606                 String::from_utf8_lossy(&output.stdout).into_owned(),
1607                 String::from_utf8_lossy(&output.stderr).into_owned(),
1608             ))
1609         }
1610 
1611         /// Send a request to this server and wait for the response.
1612         async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> {
1613             let (mut send, conn_task) = self.start_requests().await?;
1614 
1615             let response = send
1616                 .send_request(req)
1617                 .await
1618                 .context("error sending request")?;
1619             drop(send);
1620             let (parts, body) = response.into_parts();
1621 
1622             let body = body.collect().await.context("failed to read body")?;
1623             assert!(body.trailers().is_none());
1624             let body = std::str::from_utf8(&body.to_bytes())?.to_string();
1625 
1626             conn_task.await??;
1627 
1628             Ok(http::Response::from_parts(parts, body))
1629         }
1630 
1631         async fn start_requests(
1632             &self,
1633         ) -> Result<(
1634             hyper::client::conn::http1::SendRequest<String>,
1635             tokio::task::JoinHandle<hyper::Result<()>>,
1636         )> {
1637             let tcp = TcpStream::connect(&self.addr)
1638                 .await
1639                 .context("failed to connect")?;
1640             let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp);
1641             let (send, conn) = hyper::client::conn::http1::handshake(tcp)
1642                 .await
1643                 .context("failed http handshake")?;
1644             Ok((send, tokio::task::spawn(conn)))
1645         }
1646     }
1647 
1648     // Don't leave child processes running by accident so kill the child process
1649     // if our server goes away.
1650     impl Drop for WasmtimeServe {
1651         fn drop(&mut self) {
1652             let mut child = match self.child.take() {
1653                 Some(child) => child,
1654                 None => return,
1655             };
1656             if child.kill().is_err() {
1657                 return;
1658             }
1659             let output = match child.wait_with_output() {
1660                 Ok(output) => output,
1661                 Err(_) => return,
1662             };
1663 
1664             println!("server status: {}", output.status);
1665             if !output.stdout.is_empty() {
1666                 println!(
1667                     "server stdout:\n{}",
1668                     String::from_utf8_lossy(&output.stdout)
1669                 );
1670             }
1671             if !output.stderr.is_empty() {
1672                 println!(
1673                     "server stderr:\n{}",
1674                     String::from_utf8_lossy(&output.stderr)
1675                 );
1676             }
1677         }
1678     }
1679 
1680     #[tokio::test]
1681     async fn cli_serve_echo_env() -> Result<()> {
1682         let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1683             cmd.arg("--env=FOO=bar");
1684             cmd.arg("--env=BAR");
1685             cmd.arg("-Scli");
1686             cmd.env_remove("BAR");
1687         })?;
1688 
1689         let foo_env = server
1690             .send_request(
1691                 hyper::Request::builder()
1692                     .uri("http://localhost/")
1693                     .header("env", "FOO")
1694                     .body(String::new())
1695                     .context("failed to make request")?,
1696             )
1697             .await?;
1698 
1699         assert!(foo_env.status().is_success());
1700         assert!(foo_env.body().is_empty());
1701         let headers = foo_env.headers();
1702         assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar")));
1703 
1704         let bar_env = server
1705             .send_request(
1706                 hyper::Request::builder()
1707                     .uri("http://localhost/")
1708                     .header("env", "BAR")
1709                     .body(String::new())
1710                     .context("failed to make request")?,
1711             )
1712             .await?;
1713 
1714         assert!(bar_env.status().is_success());
1715         assert!(bar_env.body().is_empty());
1716         let headers = bar_env.headers();
1717         assert_eq!(headers.get("env"), None);
1718 
1719         server.finish()?;
1720         Ok(())
1721     }
1722 
1723     #[tokio::test]
1724     async fn cli_serve_outgoing_body_config() -> Result<()> {
1725         let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1726             cmd.arg("-Scli");
1727             cmd.arg("-Shttp-outgoing-body-buffer-chunks=2");
1728             cmd.arg("-Shttp-outgoing-body-chunk-size=1024");
1729         })?;
1730 
1731         let resp = server
1732             .send_request(
1733                 hyper::Request::builder()
1734                     .uri("http://localhost/")
1735                     .header("env", "FOO")
1736                     .body(String::new())
1737                     .context("failed to make request")?,
1738             )
1739             .await?;
1740 
1741         assert!(resp.status().is_success());
1742 
1743         server.finish()?;
1744         Ok(())
1745     }
1746 
1747     #[tokio::test]
1748     #[ignore] // TODO: printing stderr in the child and killing the child at the
1749               // end of this test race so the stderr may be present or not. Need
1750               // to implement a more graceful shutdown routine for `wasmtime
1751               // serve`.
1752     async fn cli_serve_respect_pooling_options() -> Result<()> {
1753         let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1754             cmd.arg("-Opooling-total-memories=0").arg("-Scli");
1755         })?;
1756 
1757         let result = server
1758             .send_request(
1759                 hyper::Request::builder()
1760                     .uri("http://localhost/")
1761                     .header("env", "FOO")
1762                     .body(String::new())
1763                     .context("failed to make request")?,
1764             )
1765             .await;
1766         assert!(result.is_err());
1767         let (_, stderr) = server.finish()?;
1768         assert!(
1769             stderr.contains("maximum concurrent memory limit of 0 reached"),
1770             "bad stderr: {stderr}",
1771         );
1772         Ok(())
1773     }
1774 
1775     #[test]
1776     fn cli_large_env() -> Result<()> {
1777         for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] {
1778             println!("run {wasm:?}");
1779             let mut cmd = get_wasmtime_command()?;
1780             cmd.arg("run").arg("-Sinherit-env").arg(wasm);
1781 
1782             let debug_cmd = format!("{cmd:?}");
1783             for i in 0..512 {
1784                 let var = format!("KEY{i}");
1785                 let val = (0..1024).map(|_| 'x').collect::<String>();
1786                 cmd.env(&var, &val);
1787             }
1788             let output = cmd.output()?;
1789             if !output.status.success() {
1790                 bail!(
1791                     "Failed to execute wasmtime with: {debug_cmd}\n{}",
1792                     String::from_utf8_lossy(&output.stderr)
1793                 );
1794             }
1795         }
1796         Ok(())
1797     }
1798 
1799     #[tokio::test]
1800     async fn cli_serve_only_one_process_allowed() -> Result<()> {
1801         let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
1802         let server = WasmtimeServe::new(wasm, |cmd| {
1803             cmd.arg("-Scli");
1804         })?;
1805 
1806         let err = WasmtimeServe::spawn(
1807             super::get_wasmtime_command()?
1808                 .arg("serve")
1809                 .arg("-Scli")
1810                 .arg(format!("--addr={}", server.addr))
1811                 .arg(wasm),
1812         )
1813         .err()
1814         .expect("server spawn should have failed but it succeeded");
1815         drop(server);
1816 
1817         let err = format!("{err:?}");
1818         println!("{err}");
1819         assert!(err.contains("os error"));
1820         Ok(())
1821     }
1822 
1823     // Technically this test is a little racy. This binds port 0 to acquire a
1824     // random port, issues a single request to this port, but then kills this
1825     // server while the request is still processing. The port is then rebound
1826     // in the next process while it technically could be stolen by another
1827     // process.
1828     #[tokio::test]
1829     async fn cli_serve_quick_rebind_allowed() -> Result<()> {
1830         let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
1831         let server = WasmtimeServe::new(wasm, |cmd| {
1832             cmd.arg("-Scli");
1833         })?;
1834         let addr = server.addr;
1835 
1836         // Start up a `send` and `conn_task` which represents a connection to
1837         // this server.
1838         let (mut send, conn_task) = server.start_requests().await?;
1839         let _ = send
1840             .send_request(
1841                 hyper::Request::builder()
1842                     .uri("http://localhost/")
1843                     .header("env", "FOO")
1844                     .body(String::new())
1845                     .context("failed to make request")?,
1846             )
1847             .await;
1848 
1849         // ... once a response has been received (or at least the status
1850         // code/headers) then kill the server. THis is done while `conn_task`
1851         // and `send` are still alive so we're guaranteed that the other side
1852         // got a request (we got a response) and our connection is still open.
1853         //
1854         // This forces the address/port into the `TIME_WAIT` state. The rebind
1855         // below in the next process will fail if `SO_REUSEADDR` isn't set.
1856         drop(server);
1857         drop(send);
1858         let _ = conn_task.await;
1859 
1860         // If this is successfully bound then we'll create `WasmtimeServe`
1861         // which reads off the first line of output to know which address was
1862         // bound.
1863         let _server2 = WasmtimeServe::spawn(
1864             super::get_wasmtime_command()?
1865                 .arg("serve")
1866                 .arg("-Scli")
1867                 .arg(format!("--addr={addr}"))
1868                 .arg(wasm),
1869         )?;
1870 
1871         Ok(())
1872     }
1873 
1874     #[tokio::test]
1875     async fn cli_serve_with_print() -> Result<()> {
1876         let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1877             cmd.arg("-Scli");
1878         })?;
1879 
1880         for _ in 0..2 {
1881             let resp = server
1882                 .send_request(
1883                     hyper::Request::builder()
1884                         .uri("http://localhost/")
1885                         .body(String::new())
1886                         .context("failed to make request")?,
1887                 )
1888                 .await?;
1889             assert!(resp.status().is_success());
1890         }
1891 
1892         let (out, err) = server.finish()?;
1893         assert_eq!(
1894             out,
1895             "\
1896 stdout [0] :: this is half a print to stdout
1897 stdout [0] :: \n\
1898 stdout [0] :: after empty
1899 stdout [1] :: this is half a print to stdout
1900 stdout [1] :: \n\
1901 stdout [1] :: after empty
1902 "
1903         );
1904         assert!(
1905             err.contains(
1906                 "\
1907 stderr [0] :: this is half a print to stderr
1908 stderr [0] :: \n\
1909 stderr [0] :: after empty
1910 stderr [1] :: this is half a print to stderr
1911 stderr [1] :: \n\
1912 stderr [1] :: after empty
1913 "
1914             ),
1915             "bad stderr: {err}"
1916         );
1917 
1918         Ok(())
1919     }
1920 
1921     #[tokio::test]
1922     async fn cli_serve_with_print_no_prefix() -> Result<()> {
1923         let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1924             cmd.arg("-Scli");
1925             cmd.arg("--no-logging-prefix");
1926         })?;
1927 
1928         for _ in 0..2 {
1929             let resp = server
1930                 .send_request(
1931                     hyper::Request::builder()
1932                         .uri("http://localhost/")
1933                         .body(String::new())
1934                         .context("failed to make request")?,
1935                 )
1936                 .await?;
1937             assert!(resp.status().is_success());
1938         }
1939 
1940         let (out, err) = server.finish()?;
1941         assert_eq!(
1942             out,
1943             "\
1944 this is half a print to stdout
1945 \n\
1946 after empty
1947 this is half a print to stdout
1948 \n\
1949 after empty
1950 "
1951         );
1952         assert!(
1953             err.contains(
1954                 "\
1955 this is half a print to stderr
1956 \n\
1957 after empty
1958 this is half a print to stderr
1959 \n\
1960 after empty
1961 "
1962             ),
1963             "bad stderr {err}",
1964         );
1965 
1966         Ok(())
1967     }
1968 
1969     #[tokio::test]
1970     async fn cli_serve_authority_and_scheme() -> Result<()> {
1971         let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| {
1972             cmd.arg("-Scli");
1973         })?;
1974 
1975         let resp = server
1976             .send_request(
1977                 hyper::Request::builder()
1978                     .uri("/")
1979                     .header("Host", "localhost")
1980                     .body(String::new())
1981                     .context("failed to make request")?,
1982             )
1983             .await?;
1984         assert!(resp.status().is_success());
1985 
1986         let resp = server
1987             .send_request(
1988                 hyper::Request::builder()
1989                     .method("CONNECT")
1990                     .uri("http://localhost/")
1991                     .body(String::new())
1992                     .context("failed to make request")?,
1993             )
1994             .await?;
1995         assert!(resp.status().is_success());
1996 
1997         Ok(())
1998     }
1999 
2000     #[test]
2001     fn cli_argv0() -> Result<()> {
2002         run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?;
2003         run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?;
2004         run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?;
2005         Ok(())
2006     }
2007 
2008     #[tokio::test]
2009     async fn cli_serve_config() -> Result<()> {
2010         let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| {
2011             cmd.arg("-Scli");
2012             cmd.arg("-Sconfig");
2013             cmd.arg("-Sconfig-var=hello=world");
2014         })?;
2015 
2016         let resp = server
2017             .send_request(
2018                 hyper::Request::builder()
2019                     .uri("http://localhost/")
2020                     .body(String::new())
2021                     .context("failed to make request")?,
2022             )
2023             .await?;
2024 
2025         assert!(resp.status().is_success());
2026         assert_eq!(resp.body(), "world");
2027         Ok(())
2028     }
2029 
2030     #[test]
2031     fn cli_config() -> Result<()> {
2032         run_wasmtime(&[
2033             "run",
2034             "-Sconfig",
2035             "-Sconfig-var=hello=world",
2036             CONFIG_GET_COMPONENT,
2037         ])?;
2038         Ok(())
2039     }
2040 
2041     #[tokio::test]
2042     async fn cli_serve_keyvalue() -> Result<()> {
2043         let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| {
2044             cmd.arg("-Scli");
2045             cmd.arg("-Skeyvalue");
2046             cmd.arg("-Skeyvalue-in-memory-data=hello=world");
2047         })?;
2048 
2049         let resp = server
2050             .send_request(
2051                 hyper::Request::builder()
2052                     .uri("http://localhost/")
2053                     .body(String::new())
2054                     .context("failed to make request")?,
2055             )
2056             .await?;
2057 
2058         assert!(resp.status().is_success());
2059         assert_eq!(resp.body(), "world");
2060         Ok(())
2061     }
2062 
2063     #[test]
2064     fn cli_keyvalue() -> Result<()> {
2065         run_wasmtime(&[
2066             "run",
2067             "-Skeyvalue",
2068             "-Skeyvalue-in-memory-data=atomics_key=5",
2069             KEYVALUE_MAIN_COMPONENT,
2070         ])?;
2071         Ok(())
2072     }
2073 
2074     #[test]
2075     fn cli_multiple_preopens() -> Result<()> {
2076         run_wasmtime(&[
2077             "run",
2078             "--dir=/::/a",
2079             "--dir=/::/b",
2080             "--dir=/::/c",
2081             CLI_MULTIPLE_PREOPENS_COMPONENT,
2082         ])?;
2083         Ok(())
2084     }
2085 
2086     async fn cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()> {
2087         let server = WasmtimeServe::new(wasm, |cmd| {
2088             cmd.arg("-Scli");
2089         })?;
2090 
2091         for _ in 0..2 {
2092             let res = server
2093                 .send_request(
2094                     hyper::Request::builder()
2095                         .uri("http://localhost/")
2096                         .body(String::new())
2097                         .context("failed to make request")?,
2098                 )
2099                 .await;
2100             assert!(res.is_err());
2101         }
2102 
2103         let (stdout, stderr) = server.finish()?;
2104         println!("stdout: {stdout}");
2105         println!("stderr: {stderr}");
2106         assert!(stderr.contains("guest never invoked `response-outparam::set` method"));
2107         assert!(!stderr.contains("panicked"));
2108         Ok(())
2109     }
2110 
2111     #[tokio::test]
2112     async fn cli_serve_return_before_set() -> Result<()> {
2113         cli_serve_guest_never_invoked_set(CLI_SERVE_RETURN_BEFORE_SET_COMPONENT).await
2114     }
2115 
2116     #[tokio::test]
2117     async fn cli_serve_trap_before_set() -> Result<()> {
2118         cli_serve_guest_never_invoked_set(CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await
2119     }
2120 }
2121 
2122 #[test]
2123 fn settings_command() -> Result<()> {
2124     // Skip this test on platforms that Cranelift doesn't support.
2125     if cranelift_native::builder().is_err() {
2126         return Ok(());
2127     }
2128     let output = run_wasmtime(&["settings"])?;
2129     assert!(output.contains("Cranelift settings for target"));
2130     Ok(())
2131 }
2132 
2133 #[cfg(target_arch = "x86_64")]
2134 #[test]
2135 fn profile_with_vtune() -> Result<()> {
2136     if !is_vtune_available() {
2137         println!("> `vtune` is not available on the system path; skipping test");
2138         return Ok(());
2139     }
2140 
2141     let mut bin = Command::new("vtune");
2142     bin.args(&[
2143         // Configure VTune...
2144         "-verbose",
2145         "-collect",
2146         "hotspots",
2147         "-user-data-dir",
2148         &std::env::temp_dir().to_string_lossy(),
2149         // ...then run Wasmtime with profiling enabled:
2150         get_wasmtime_path(),
2151         "--profile=vtune",
2152         "tests/all/cli_tests/simple.wat",
2153     ]);
2154 
2155     println!("> executing: {bin:?}");
2156     let output = bin.output()?;
2157 
2158     assert!(output.status.success());
2159     let stdout = String::from_utf8_lossy(&output.stdout);
2160     let stderr = String::from_utf8_lossy(&output.stderr);
2161     println!("> stdout:\n{stdout}");
2162     assert!(stdout.contains("CPU Time"));
2163     println!("> stderr:\n{stderr}");
2164     assert!(!stderr.contains("Error"));
2165     Ok(())
2166 }
2167 
2168 #[cfg(target_arch = "x86_64")]
2169 fn is_vtune_available() -> bool {
2170     Command::new("vtune").arg("-version").output().is_ok()
2171 }
2172 
2173 #[test]
2174 fn unreachable_without_wasi() -> Result<()> {
2175     let output = run_wasmtime_for_output(
2176         &[
2177             "-Scli=n",
2178             "-Ccache=n",
2179             "tests/all/cli_tests/unreachable.wat",
2180         ],
2181         None,
2182     )?;
2183 
2184     assert_ne!(output.stderr, b"");
2185     assert_eq!(output.stdout, b"");
2186     assert_trap_code(&output.status);
2187     Ok(())
2188 }
2189 
2190 #[test]
2191 fn config_cli_flag() -> Result<()> {
2192     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
2193 
2194     // Test some valid TOML values
2195     let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2196     cfg.write_all(
2197         br#"
2198         [optimize]
2199         opt-level = 2
2200         regalloc-algorithm = "single-pass"
2201         signals-based-traps = false
2202 
2203         [codegen]
2204         collector = "null"
2205 
2206         [debug]
2207         debug-info = true
2208 
2209         [wasm]
2210         max-wasm-stack = 65536
2211 
2212         [wasi]
2213         cli = true
2214         "#,
2215     )?;
2216     let output = run_wasmtime(&[
2217         "run",
2218         "--config",
2219         cfg_path.to_str().unwrap(),
2220         "--invoke",
2221         "get_f64",
2222         wasm.path().to_str().unwrap(),
2223     ])?;
2224     assert_eq!(output, "100\n");
2225 
2226     // Make sure CLI flags overrides TOML values
2227     let output = run_wasmtime(&[
2228         "run",
2229         "--config",
2230         cfg_path.to_str().unwrap(),
2231         "--invoke",
2232         "get_f64",
2233         "-W",
2234         "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail
2235         wasm.path().to_str().unwrap(),
2236     ]);
2237     assert!(
2238         output
2239             .as_ref()
2240             .unwrap_err()
2241             .to_string()
2242             .contains("max_wasm_stack size cannot be zero"),
2243         "'{output:?}' did not contain expected error message",
2244     );
2245 
2246     // Test invalid TOML key
2247     let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2248     cfg.write_all(
2249         br#"
2250         [optimize]
2251         this-key-does-not-exist = true
2252         "#,
2253     )?;
2254     let output = run_wasmtime(&[
2255         "run",
2256         "--config",
2257         cfg_path.to_str().unwrap(),
2258         wasm.path().to_str().unwrap(),
2259     ]);
2260     assert!(
2261         output
2262             .as_ref()
2263             .unwrap_err()
2264             .to_string()
2265             .contains("unknown field `this-key-does-not-exist`"),
2266         "'{output:?}' did not contain expected error message"
2267     );
2268 
2269     // Test invalid TOML table
2270     let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2271     cfg.write_all(
2272         br#"
2273         [invalid_table]
2274         "#,
2275     )?;
2276     let output = run_wasmtime(&[
2277         "run",
2278         "--config",
2279         cfg_path.to_str().unwrap(),
2280         wasm.path().to_str().unwrap(),
2281     ]);
2282     assert!(
2283         output
2284             .as_ref()
2285             .unwrap_err()
2286             .to_string()
2287             .contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"),
2288         "'{output:?}' did not contain expected error message",
2289     );
2290 
2291     Ok(())
2292 }
2293 
2294 #[test]
2295 fn invalid_subcommand() -> Result<()> {
2296     let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?;
2297     dbg!(&output);
2298     assert!(!output.status.success());
2299     assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand"));
2300     Ok(())
2301 }
2302 
2303 #[test]
2304 fn numeric_args() -> Result<()> {
2305     let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?;
2306     // Test decimal i32
2307     let output = run_wasmtime_for_output(
2308         &[
2309             "run",
2310             "--invoke",
2311             "i32_test",
2312             wasm.path().to_str().unwrap(),
2313             "42",
2314         ],
2315         None,
2316     )?;
2317     assert_eq!(output.status.success(), true);
2318     assert_eq!(output.stdout, b"42\n");
2319     // Test hexadecimal i32 with lowercase prefix
2320     let output = run_wasmtime_for_output(
2321         &[
2322             "run",
2323             "--invoke",
2324             "i32_test",
2325             wasm.path().to_str().unwrap(),
2326             "0x2A",
2327         ],
2328         None,
2329     )?;
2330     assert_eq!(output.status.success(), true);
2331     assert_eq!(output.stdout, b"42\n");
2332     // Test hexadecimal i32 with uppercase prefix
2333     let output = run_wasmtime_for_output(
2334         &[
2335             "run",
2336             "--invoke",
2337             "i32_test",
2338             wasm.path().to_str().unwrap(),
2339             "0X2a",
2340         ],
2341         None,
2342     )?;
2343     assert_eq!(output.status.success(), true);
2344     assert_eq!(output.stdout, b"42\n");
2345     // Test that non-prefixed hex strings are not interpreted as hex
2346     let output = run_wasmtime_for_output(
2347         &[
2348             "run",
2349             "--invoke",
2350             "i32_test",
2351             wasm.path().to_str().unwrap(),
2352             "ff",
2353         ],
2354         None,
2355     )?;
2356     assert!(!output.status.success()); // Should fail as "ff" is not a valid decimal number
2357 
2358     // Test decimal i64
2359     let output = run_wasmtime_for_output(
2360         &[
2361             "run",
2362             "--invoke",
2363             "i64_test",
2364             wasm.path().to_str().unwrap(),
2365             "42",
2366         ],
2367         None,
2368     )?;
2369     assert_eq!(output.status.success(), true);
2370     assert_eq!(output.stdout, b"42\n");
2371     // Test hexadecimal i64
2372     let output = run_wasmtime_for_output(
2373         &[
2374             "run",
2375             "--invoke",
2376             "i64_test",
2377             wasm.path().to_str().unwrap(),
2378             "0x2A",
2379         ],
2380         None,
2381     )?;
2382     assert_eq!(output.status.success(), true);
2383     assert_eq!(output.stdout, b"42\n");
2384     Ok(())
2385 }
2386