xref: /wasmtime-44.0.1/tests/all/cli_tests.rs (revision 72004aad)
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 old_cli_warn_if_ambiguous_flags() -> Result<()> {
1063     // This is accepted in the old CLI parser and the new but it's interpreted
1064     // differently so a warning should be printed.
1065     let output = get_wasmtime_command()?
1066         .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"])
1067         .output()?;
1068     assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n");
1069     assert_eq!(
1070         String::from_utf8_lossy(&output.stderr),
1071         "\
1072 warning: this CLI invocation of Wasmtime will be parsed differently in future
1073          Wasmtime versions -- see this online issue for more information:
1074          https://github.com/bytecodealliance/wasmtime/issues/7384
1075 
1076          Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing,
1077          however this behavior can also be temporarily configured with an
1078          environment variable:
1079 
1080          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or
1081          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior
1082 warning: using `--invoke` with a function that returns values is experimental and may break in the future
1083 "
1084     );
1085 
1086     // Test disabling the warning
1087     let output = get_wasmtime_command()?
1088         .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"])
1089         .env("WASMTIME_NEW_CLI", "0")
1090         .output()?;
1091     assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n");
1092     assert_eq!(
1093         String::from_utf8_lossy(&output.stderr),
1094         "\
1095 warning: using `--invoke` with a function that returns values is experimental and may break in the future
1096 "
1097     );
1098 
1099     // Test forcing the new behavior where nothing happens because the file is
1100     // invoked with `--invoke` as its own argument.
1101     let output = get_wasmtime_command()?
1102         .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"])
1103         .env("WASMTIME_NEW_CLI", "1")
1104         .output()?;
1105     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
1106     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1107 
1108     // This is unambiguous
1109     let output = get_wasmtime_command()?
1110         .args(&["--invoke", "get_f32", "tests/all/cli_tests/simple.wat"])
1111         .output()?;
1112     assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n");
1113     assert_eq!(
1114         String::from_utf8_lossy(&output.stderr),
1115         "\
1116 warning: using `--invoke` with a function that returns values is experimental and may break in the future
1117 "
1118     );
1119 
1120     // This fails to parse in the old but succeeds in the new, so it should run
1121     // under the new semantics with no warning.
1122     let output = get_wasmtime_command()?
1123         .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"])
1124         .output()?;
1125     assert_eq!(
1126         String::from_utf8_lossy(&output.stdout),
1127         "print-arguments.wat\n--arg\n"
1128     );
1129     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1130 
1131     // Old behavior can be forced however
1132     let output = get_wasmtime_command()?
1133         .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"])
1134         .env("WASMTIME_NEW_CLI", "0")
1135         .output()?;
1136     assert!(!output.status.success());
1137 
1138     // This works in both the old and the new, so no warnings
1139     let output = get_wasmtime_command()?
1140         .args(&["run", "tests/all/cli_tests/print-arguments.wat", "arg"])
1141         .output()?;
1142     assert_eq!(
1143         String::from_utf8_lossy(&output.stdout),
1144         "print-arguments.wat\narg\n"
1145     );
1146     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1147 
1148     // This works in both the old and the new, so no warnings
1149     let output = get_wasmtime_command()?
1150         .args(&[
1151             "run",
1152             "--",
1153             "tests/all/cli_tests/print-arguments.wat",
1154             "--arg",
1155         ])
1156         .output()?;
1157     assert_eq!(
1158         String::from_utf8_lossy(&output.stdout),
1159         "print-arguments.wat\n--arg\n"
1160     );
1161     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1162 
1163     // Old flags still work, but with a warning
1164     let output = get_wasmtime_command()?
1165         .args(&[
1166             "run",
1167             "--max-wasm-stack",
1168             "1000000",
1169             "tests/all/cli_tests/print-arguments.wat",
1170         ])
1171         .output()?;
1172     assert_eq!(
1173         String::from_utf8_lossy(&output.stdout),
1174         "print-arguments.wat\n"
1175     );
1176     assert_eq!(
1177         String::from_utf8_lossy(&output.stderr),
1178         "\
1179 warning: this CLI invocation of Wasmtime is going to break in the future -- for
1180          more information see this issue online:
1181          https://github.com/bytecodealliance/wasmtime/issues/7384
1182 
1183          Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing,
1184          however this behavior can also be temporarily configured with an
1185          environment variable:
1186 
1187          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or
1188          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error
1189 "
1190     );
1191 
1192     // Old flags warning is suppressible.
1193     let output = get_wasmtime_command()?
1194         .args(&[
1195             "run",
1196             "--max-wasm-stack",
1197             "1000000",
1198             "tests/all/cli_tests/print-arguments.wat",
1199         ])
1200         .env("WASMTIME_NEW_CLI", "0")
1201         .output()?;
1202     assert_eq!(
1203         String::from_utf8_lossy(&output.stdout),
1204         "print-arguments.wat\n"
1205     );
1206     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1207 
1208     // the `--dir` flag prints no warning when used with `::`
1209     let dir = tempfile::tempdir()?;
1210     std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1211     let output = get_wasmtime_command()?
1212         .args(&[
1213             "run",
1214             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1215             test_programs_artifacts::CLI_FILE_READ,
1216         ])
1217         .output()?;
1218     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
1219     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1220 
1221     Ok(())
1222 }
1223 
1224 #[test]
1225 fn float_args() -> Result<()> {
1226     let result = run_wasmtime(&[
1227         "--invoke",
1228         "echo_f32",
1229         "tests/all/cli_tests/simple.wat",
1230         "1.0",
1231     ])?;
1232     assert_eq!(result, "1\n");
1233     let result = run_wasmtime(&[
1234         "--invoke",
1235         "echo_f64",
1236         "tests/all/cli_tests/simple.wat",
1237         "1.1",
1238     ])?;
1239     assert_eq!(result, "1.1\n");
1240     Ok(())
1241 }
1242 
1243 #[test]
1244 fn mpk_without_pooling() -> Result<()> {
1245     let output = get_wasmtime_command()?
1246         .args(&[
1247             "run",
1248             "-O",
1249             "memory-protection-keys=y",
1250             "--invoke",
1251             "echo_f32",
1252             "tests/all/cli_tests/simple.wat",
1253             "1.0",
1254         ])
1255         .env("WASMTIME_NEW_CLI", "1")
1256         .output()?;
1257     assert!(!output.status.success());
1258     Ok(())
1259 }
1260 
1261 mod test_programs {
1262     use super::{get_wasmtime_command, run_wasmtime};
1263     use anyhow::{bail, Context, Result};
1264     use http_body_util::BodyExt;
1265     use hyper::header::HeaderValue;
1266     use std::io::{BufRead, BufReader, Read, Write};
1267     use std::net::SocketAddr;
1268     use std::process::{Child, Command, Stdio};
1269     use test_programs_artifacts::*;
1270     use tokio::net::TcpStream;
1271 
1272     macro_rules! assert_test_exists {
1273         ($name:ident) => {
1274             #[allow(unused_imports)]
1275             use self::$name as _;
1276         };
1277     }
1278     foreach_cli!(assert_test_exists);
1279 
1280     #[test]
1281     fn cli_hello_stdout() -> Result<()> {
1282         run_wasmtime(&[
1283             "run",
1284             "-Wcomponent-model",
1285             CLI_HELLO_STDOUT_COMPONENT,
1286             "gussie",
1287             "sparky",
1288             "willa",
1289         ])?;
1290         Ok(())
1291     }
1292 
1293     #[test]
1294     fn cli_args() -> Result<()> {
1295         run_wasmtime(&[
1296             "run",
1297             "-Wcomponent-model",
1298             CLI_ARGS_COMPONENT,
1299             "hello",
1300             "this",
1301             "",
1302             "is an argument",
1303             "with �� emoji",
1304         ])?;
1305         Ok(())
1306     }
1307 
1308     #[test]
1309     fn cli_stdin() -> Result<()> {
1310         let mut child = get_wasmtime_command()?
1311             .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT])
1312             .stdout(Stdio::piped())
1313             .stderr(Stdio::piped())
1314             .stdin(Stdio::piped())
1315             .spawn()?;
1316         child
1317             .stdin
1318             .take()
1319             .unwrap()
1320             .write_all(b"So rested he by the Tumtum tree")
1321             .unwrap();
1322         let output = child.wait_with_output()?;
1323         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1324         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1325         assert!(output.status.success());
1326         Ok(())
1327     }
1328 
1329     #[test]
1330     fn cli_splice_stdin() -> Result<()> {
1331         let mut child = get_wasmtime_command()?
1332             .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT])
1333             .stdout(Stdio::piped())
1334             .stderr(Stdio::piped())
1335             .stdin(Stdio::piped())
1336             .spawn()?;
1337         let msg = "So rested he by the Tumtum tree";
1338         child
1339             .stdin
1340             .take()
1341             .unwrap()
1342             .write_all(msg.as_bytes())
1343             .unwrap();
1344         let output = child.wait_with_output()?;
1345         assert!(output.status.success());
1346         let stdout = String::from_utf8_lossy(&output.stdout);
1347         let stderr = String::from_utf8_lossy(&output.stderr);
1348         if !stderr.is_empty() {
1349             eprintln!("{stderr}");
1350         }
1351 
1352         assert_eq!(
1353             format!(
1354                 "before splice\n{msg}\ncompleted splicing {} bytes\n",
1355                 msg.as_bytes().len()
1356             ),
1357             stdout
1358         );
1359         Ok(())
1360     }
1361 
1362     #[test]
1363     fn cli_env() -> Result<()> {
1364         run_wasmtime(&[
1365             "run",
1366             "-Wcomponent-model",
1367             "--env=frabjous=day",
1368             "--env=callooh=callay",
1369             CLI_ENV_COMPONENT,
1370         ])?;
1371         Ok(())
1372     }
1373 
1374     #[test]
1375     fn cli_file_read() -> Result<()> {
1376         let dir = tempfile::tempdir()?;
1377 
1378         std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1379 
1380         run_wasmtime(&[
1381             "run",
1382             "-Wcomponent-model",
1383             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1384             CLI_FILE_READ_COMPONENT,
1385         ])?;
1386         Ok(())
1387     }
1388 
1389     #[test]
1390     fn cli_file_append() -> Result<()> {
1391         let dir = tempfile::tempdir()?;
1392 
1393         std::fs::File::create(dir.path().join("bar.txt"))?
1394             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1395 
1396         run_wasmtime(&[
1397             "run",
1398             "-Wcomponent-model",
1399             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1400             CLI_FILE_APPEND_COMPONENT,
1401         ])?;
1402 
1403         let contents = std::fs::read(dir.path().join("bar.txt"))?;
1404         assert_eq!(
1405             std::str::from_utf8(&contents).unwrap(),
1406             "'Twas brillig, and the slithy toves.\n\
1407                    Did gyre and gimble in the wabe;\n\
1408                    All mimsy were the borogoves,\n\
1409                    And the mome raths outgrabe.\n"
1410         );
1411         Ok(())
1412     }
1413 
1414     #[test]
1415     fn cli_file_dir_sync() -> Result<()> {
1416         let dir = tempfile::tempdir()?;
1417 
1418         std::fs::File::create(dir.path().join("bar.txt"))?
1419             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1420 
1421         run_wasmtime(&[
1422             "run",
1423             "-Wcomponent-model",
1424             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1425             CLI_FILE_DIR_SYNC_COMPONENT,
1426         ])?;
1427 
1428         Ok(())
1429     }
1430 
1431     #[test]
1432     fn cli_exit_success() -> Result<()> {
1433         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?;
1434         Ok(())
1435     }
1436 
1437     #[test]
1438     fn cli_exit_default() -> Result<()> {
1439         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?;
1440         Ok(())
1441     }
1442 
1443     #[test]
1444     fn cli_exit_failure() -> Result<()> {
1445         let output = get_wasmtime_command()?
1446             .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT])
1447             .output()?;
1448         assert!(!output.status.success());
1449         assert_eq!(output.status.code(), Some(1));
1450         Ok(())
1451     }
1452 
1453     #[test]
1454     fn cli_exit_panic() -> Result<()> {
1455         let output = get_wasmtime_command()?
1456             .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT])
1457             .output()?;
1458         assert!(!output.status.success());
1459         let stderr = String::from_utf8_lossy(&output.stderr);
1460         assert!(stderr.contains("Curiouser and curiouser!"));
1461         Ok(())
1462     }
1463 
1464     #[test]
1465     fn cli_directory_list() -> Result<()> {
1466         let dir = tempfile::tempdir()?;
1467 
1468         std::fs::File::create(dir.path().join("foo.txt"))?;
1469         std::fs::File::create(dir.path().join("bar.txt"))?;
1470         std::fs::File::create(dir.path().join("baz.txt"))?;
1471         std::fs::create_dir(dir.path().join("sub"))?;
1472         std::fs::File::create(dir.path().join("sub").join("wow.txt"))?;
1473         std::fs::File::create(dir.path().join("sub").join("yay.txt"))?;
1474 
1475         run_wasmtime(&[
1476             "run",
1477             "-Wcomponent-model",
1478             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1479             CLI_DIRECTORY_LIST_COMPONENT,
1480         ])?;
1481         Ok(())
1482     }
1483 
1484     #[test]
1485     fn cli_default_clocks() -> Result<()> {
1486         run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?;
1487         Ok(())
1488     }
1489 
1490     #[test]
1491     fn cli_export_cabi_realloc() -> Result<()> {
1492         run_wasmtime(&[
1493             "run",
1494             "-Wcomponent-model",
1495             CLI_EXPORT_CABI_REALLOC_COMPONENT,
1496         ])?;
1497         Ok(())
1498     }
1499 
1500     #[test]
1501     fn run_wasi_http_component() -> Result<()> {
1502         let output = super::run_wasmtime_for_output(
1503             &[
1504                 "-Ccache=no",
1505                 "-Wcomponent-model",
1506                 "-Scli,http,preview2",
1507                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
1508             ],
1509             None,
1510         )?;
1511         println!("{}", String::from_utf8_lossy(&output.stderr));
1512         let stdout = String::from_utf8_lossy(&output.stdout);
1513         println!("{}", stdout);
1514         assert!(stdout.starts_with("Called _start\n"));
1515         assert!(stdout.ends_with("Done\n"));
1516         assert!(output.status.success());
1517         Ok(())
1518     }
1519 
1520     // Test to ensure that prints in the guest aren't buffered on the host by
1521     // accident. The test here will print something without a newline and then
1522     // wait for input on stdin, and the test here is to ensure that the
1523     // character shows up here even as the guest is waiting on input via stdin.
1524     #[test]
1525     fn cli_stdio_write_flushes() -> Result<()> {
1526         fn run(args: &[&str]) -> Result<()> {
1527             println!("running {args:?}");
1528             let mut child = get_wasmtime_command()?
1529                 .args(args)
1530                 .stdin(Stdio::piped())
1531                 .stdout(Stdio::piped())
1532                 .spawn()?;
1533             let mut stdout = child.stdout.take().unwrap();
1534             let mut buf = [0; 10];
1535             match stdout.read(&mut buf) {
1536                 Ok(2) => assert_eq!(&buf[..2], b"> "),
1537                 e => panic!("unexpected read result {e:?}"),
1538             }
1539             drop(stdout);
1540             drop(child.stdin.take().unwrap());
1541             let status = child.wait()?;
1542             assert!(status.success());
1543             Ok(())
1544         }
1545 
1546         run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?;
1547         run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?;
1548         run(&[
1549             "run",
1550             "-Wcomponent-model",
1551             CLI_STDIO_WRITE_FLUSHES_COMPONENT,
1552         ])?;
1553         Ok(())
1554     }
1555 
1556     #[test]
1557     fn cli_no_tcp() -> Result<()> {
1558         let output = super::run_wasmtime_for_output(
1559             &[
1560                 "-Wcomponent-model",
1561                 // Turn on network but turn off TCP
1562                 "-Sinherit-network,tcp=no",
1563                 CLI_NO_TCP_COMPONENT,
1564             ],
1565             None,
1566         )?;
1567         println!("{}", String::from_utf8_lossy(&output.stderr));
1568         assert!(output.status.success());
1569         Ok(())
1570     }
1571 
1572     #[test]
1573     fn cli_no_udp() -> Result<()> {
1574         let output = super::run_wasmtime_for_output(
1575             &[
1576                 "-Wcomponent-model",
1577                 // Turn on network but turn off UDP
1578                 "-Sinherit-network,udp=no",
1579                 CLI_NO_UDP_COMPONENT,
1580             ],
1581             None,
1582         )?;
1583         println!("{}", String::from_utf8_lossy(&output.stderr));
1584         assert!(output.status.success());
1585         Ok(())
1586     }
1587 
1588     #[test]
1589     fn cli_no_ip_name_lookup() -> Result<()> {
1590         let output = super::run_wasmtime_for_output(
1591             &[
1592                 "-Wcomponent-model",
1593                 // Turn on network but ensure name lookup is disabled
1594                 "-Sinherit-network,allow-ip-name-lookup=no",
1595                 CLI_NO_IP_NAME_LOOKUP_COMPONENT,
1596             ],
1597             None,
1598         )?;
1599         println!("{}", String::from_utf8_lossy(&output.stderr));
1600         assert!(output.status.success());
1601         Ok(())
1602     }
1603 
1604     #[test]
1605     fn cli_sleep() -> Result<()> {
1606         run_wasmtime(&["run", CLI_SLEEP])?;
1607         run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?;
1608         Ok(())
1609     }
1610 
1611     /// Helper structure to manage an invocation of `wasmtime serve`
1612     struct WasmtimeServe {
1613         child: Option<Child>,
1614         addr: SocketAddr,
1615     }
1616 
1617     impl WasmtimeServe {
1618         /// Creates a new server which will serve the wasm component pointed to
1619         /// by `wasm`.
1620         ///
1621         /// A `configure` callback is provided to specify how `wasmtime serve`
1622         /// will be invoked and configure arguments such as headers.
1623         fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
1624             // Spawn `wasmtime serve` on port 0 which will randomly assign it a
1625             // port.
1626             let mut cmd = super::get_wasmtime_command()?;
1627             cmd.stdin(Stdio::null());
1628             cmd.stdout(Stdio::piped());
1629             cmd.stderr(Stdio::piped());
1630             cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm);
1631             configure(&mut cmd);
1632             let mut child = cmd.spawn()?;
1633 
1634             // Read the first line of stderr which will say which address it's
1635             // listening on.
1636             //
1637             // NB: this intentionally discards any extra buffered data in the
1638             // `BufReader` once the newline is found. The server shouldn't print
1639             // anything interesting other than the address so once we get a line
1640             // all remaining output is left to be captured by future requests
1641             // send to the server.
1642             let mut line = String::new();
1643             BufReader::new(child.stderr.as_mut().unwrap()).read_line(&mut line)?;
1644             let addr_start = line.find("127.0.0.1").unwrap();
1645             let addr = &line[addr_start..];
1646             let addr_end = addr.find("/").unwrap();
1647             let addr = &addr[..addr_end];
1648             Ok(WasmtimeServe {
1649                 child: Some(child),
1650                 addr: addr.parse().unwrap(),
1651             })
1652         }
1653 
1654         /// Completes this server gracefully by printing the output on failure.
1655         fn finish(mut self) -> Result<()> {
1656             let mut child = self.child.take().unwrap();
1657 
1658             // If the child process has already exited then collect the output
1659             // and test if it succeeded. Otherwise it's still running so kill it
1660             // and then reap it. Assume that if it's still running then the test
1661             // has otherwise passed so no need to print the output.
1662             if child.try_wait()?.is_some() {
1663                 let output = child.wait_with_output()?;
1664                 if output.status.success() {
1665                     return Ok(());
1666                 }
1667                 bail!("child failed {output:?}");
1668             } else {
1669                 child.kill()?;
1670                 child.wait_with_output()?;
1671             }
1672             Ok(())
1673         }
1674 
1675         /// Send a request to this server and wait for the response.
1676         async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> {
1677             let tcp = TcpStream::connect(&self.addr)
1678                 .await
1679                 .context("failed to connect")?;
1680             let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp);
1681             let (mut send, conn) = hyper::client::conn::http1::handshake(tcp)
1682                 .await
1683                 .context("failed http handshake")?;
1684 
1685             let conn_task = tokio::task::spawn(conn);
1686 
1687             let response = send
1688                 .send_request(req)
1689                 .await
1690                 .context("error sending request")?;
1691             drop(send);
1692             let (parts, body) = response.into_parts();
1693 
1694             let body = body.collect().await.context("failed to read body")?;
1695             assert!(body.trailers().is_none());
1696             let body = std::str::from_utf8(&body.to_bytes())?.to_string();
1697 
1698             conn_task.await??;
1699 
1700             Ok(http::Response::from_parts(parts, body))
1701         }
1702     }
1703 
1704     // Don't leave child processes running by accident so kill the child process
1705     // if our server goes away.
1706     impl Drop for WasmtimeServe {
1707         fn drop(&mut self) {
1708             let mut child = match self.child.take() {
1709                 Some(child) => child,
1710                 None => return,
1711             };
1712             if child.kill().is_err() {
1713                 return;
1714             }
1715             let output = match child.wait_with_output() {
1716                 Ok(output) => output,
1717                 Err(_) => return,
1718             };
1719 
1720             println!("server status: {}", output.status);
1721             if !output.stdout.is_empty() {
1722                 println!(
1723                     "server stdout:\n{}",
1724                     String::from_utf8_lossy(&output.stdout)
1725                 );
1726             }
1727             if !output.stderr.is_empty() {
1728                 println!(
1729                     "server stderr:\n{}",
1730                     String::from_utf8_lossy(&output.stderr)
1731                 );
1732             }
1733         }
1734     }
1735 
1736     #[tokio::test]
1737     async fn cli_serve_echo_env() -> Result<()> {
1738         let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1739             cmd.arg("--env=FOO=bar");
1740             cmd.arg("--env=BAR");
1741             cmd.arg("-Scli");
1742             cmd.env_remove("BAR");
1743         })?;
1744 
1745         let foo_env = server
1746             .send_request(
1747                 hyper::Request::builder()
1748                     .uri("http://localhost/")
1749                     .header("env", "FOO")
1750                     .body(String::new())
1751                     .context("failed to make request")?,
1752             )
1753             .await?;
1754 
1755         assert!(foo_env.status().is_success());
1756         assert!(foo_env.body().is_empty());
1757         let headers = foo_env.headers();
1758         assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar")));
1759 
1760         let bar_env = server
1761             .send_request(
1762                 hyper::Request::builder()
1763                     .uri("http://localhost/")
1764                     .header("env", "BAR")
1765                     .body(String::new())
1766                     .context("failed to make request")?,
1767             )
1768             .await?;
1769 
1770         assert!(bar_env.status().is_success());
1771         assert!(bar_env.body().is_empty());
1772         let headers = bar_env.headers();
1773         assert_eq!(headers.get("env"), None);
1774 
1775         server.finish()?;
1776         Ok(())
1777     }
1778 }
1779 
1780 #[test]
1781 fn settings_command() -> Result<()> {
1782     let output = run_wasmtime(&["settings"])?;
1783     assert!(output.contains("Cranelift settings for target"));
1784     Ok(())
1785 }
1786