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