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