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