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