xref: /wasmtime-44.0.1/tests/all/cli_tests.rs (revision f9f8a4df)
1 #![cfg(not(miri))]
2 
3 use anyhow::{bail, Result};
4 use std::fs::File;
5 use std::io::Write;
6 use std::path::Path;
7 use std::process::{Command, Output, Stdio};
8 use tempfile::{NamedTempFile, TempDir};
9 
10 // Run the wasmtime CLI with the provided args and return the `Output`.
11 // If the `stdin` is `Some`, opens the file and redirects to the child's stdin.
12 pub fn run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result<Output> {
13     let mut cmd = get_wasmtime_command()?;
14     cmd.args(args);
15     if let Some(file) = stdin {
16         cmd.stdin(File::open(file)?);
17     }
18     cmd.output().map_err(Into::into)
19 }
20 
21 /// Get the Wasmtime CLI as a [Command].
22 pub fn get_wasmtime_command() -> Result<Command> {
23     // Figure out the Wasmtime binary from the current executable.
24     let runner = std::env::vars()
25         .filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER"))
26         .next();
27     let mut me = std::env::current_exe()?;
28     me.pop(); // chop off the file name
29     me.pop(); // chop off `deps`
30     me.push("wasmtime");
31 
32     // If we're running tests with a "runner" then we might be doing something
33     // like cross-emulation, so spin up the emulator rather than the tests
34     // itself, which may not be natively executable.
35     let mut cmd = if let Some((_, runner)) = runner {
36         let mut parts = runner.split_whitespace();
37         let mut cmd = Command::new(parts.next().unwrap());
38         for arg in parts {
39             cmd.arg(arg);
40         }
41         cmd.arg(&me);
42         cmd
43     } else {
44         Command::new(&me)
45     };
46 
47     // Ignore this if it's specified in the environment to allow tests to run in
48     // "default mode" by default.
49     cmd.env_remove("WASMTIME_NEW_CLI");
50 
51     Ok(cmd)
52 }
53 
54 // Run the wasmtime CLI with the provided args and, if it succeeds, return
55 // the standard output in a `String`.
56 fn run_wasmtime(args: &[&str]) -> Result<String> {
57     let output = run_wasmtime_for_output(args, None)?;
58     if !output.status.success() {
59         bail!(
60             "Failed to execute wasmtime with: {:?}\n{}",
61             args,
62             String::from_utf8_lossy(&output.stderr)
63         );
64     }
65     Ok(String::from_utf8(output.stdout).unwrap())
66 }
67 
68 fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> {
69     let mut wasm_file = NamedTempFile::new()?;
70     let wasm = wat::parse_file(wat_path)?;
71     wasm_file.write(&wasm)?;
72     Ok(wasm_file)
73 }
74 
75 // Very basic use case: compile binary wasm file and run specific function with arguments.
76 #[test]
77 fn run_wasmtime_simple() -> Result<()> {
78     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
79     run_wasmtime(&[
80         "run",
81         "--invoke",
82         "simple",
83         "-Ccache=n",
84         wasm.path().to_str().unwrap(),
85         "4",
86     ])?;
87     Ok(())
88 }
89 
90 // Wasmtime shall fail when not enough arguments were provided.
91 #[test]
92 fn run_wasmtime_simple_fail_no_args() -> Result<()> {
93     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
94     assert!(
95         run_wasmtime(&[
96             "run",
97             "-Ccache=n",
98             "--invoke",
99             "simple",
100             wasm.path().to_str().unwrap(),
101         ])
102         .is_err(),
103         "shall fail"
104     );
105     Ok(())
106 }
107 
108 #[test]
109 fn run_coredump_smoketest() -> Result<()> {
110     let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?;
111     let coredump_file = NamedTempFile::new()?;
112     let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display());
113     let err = run_wasmtime(&[
114         "run",
115         "--invoke",
116         "a",
117         "-Ccache=n",
118         &coredump_arg,
119         wasm.path().to_str().unwrap(),
120     ])
121     .unwrap_err();
122     assert!(err.to_string().contains(&format!(
123         "core dumped at {}",
124         coredump_file.path().display()
125     )));
126     Ok(())
127 }
128 
129 // Running simple wat
130 #[test]
131 fn run_wasmtime_simple_wat() -> Result<()> {
132     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
133     run_wasmtime(&[
134         "run",
135         "--invoke",
136         "simple",
137         "-Ccache=n",
138         wasm.path().to_str().unwrap(),
139         "4",
140     ])?;
141     assert_eq!(
142         run_wasmtime(&[
143             "run",
144             "--invoke",
145             "get_f32",
146             "-Ccache=n",
147             wasm.path().to_str().unwrap(),
148         ])?,
149         "100\n"
150     );
151     assert_eq!(
152         run_wasmtime(&[
153             "run",
154             "--invoke",
155             "get_f64",
156             "-Ccache=n",
157             wasm.path().to_str().unwrap(),
158         ])?,
159         "100\n"
160     );
161     Ok(())
162 }
163 
164 // Running a wat that traps.
165 #[test]
166 fn run_wasmtime_unreachable_wat() -> Result<()> {
167     let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?;
168     let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
169 
170     assert_ne!(output.stderr, b"");
171     assert_eq!(output.stdout, b"");
172     assert!(!output.status.success());
173 
174     let code = output
175         .status
176         .code()
177         .expect("wasmtime process should exit normally");
178 
179     // Test for the specific error code Wasmtime uses to indicate a trap return.
180     #[cfg(unix)]
181     assert_eq!(code, 128 + libc::SIGABRT);
182     #[cfg(windows)]
183     assert_eq!(code, 3);
184     Ok(())
185 }
186 
187 // Run a simple WASI hello world, snapshot0 edition.
188 #[test]
189 fn hello_wasi_snapshot0() -> Result<()> {
190     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
191     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
192         let stdout = run_wasmtime(&["-Ccache=n", preview2, wasm.path().to_str().unwrap()])?;
193         assert_eq!(stdout, "Hello, world!\n");
194     }
195     Ok(())
196 }
197 
198 // Run a simple WASI hello world, snapshot1 edition.
199 #[test]
200 fn hello_wasi_snapshot1() -> Result<()> {
201     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
202     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
203     assert_eq!(stdout, "Hello, world!\n");
204     Ok(())
205 }
206 
207 #[test]
208 fn timeout_in_start() -> Result<()> {
209     let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?;
210     let output = run_wasmtime_for_output(
211         &[
212             "run",
213             "-Wtimeout=1ms",
214             "-Ccache=n",
215             wasm.path().to_str().unwrap(),
216         ],
217         None,
218     )?;
219     assert!(!output.status.success());
220     assert_eq!(output.stdout, b"");
221     let stderr = String::from_utf8_lossy(&output.stderr);
222     assert!(
223         stderr.contains("wasm trap: interrupt"),
224         "bad stderr: {}",
225         stderr
226     );
227     Ok(())
228 }
229 
230 #[test]
231 fn timeout_in_invoke() -> Result<()> {
232     let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?;
233     let output = run_wasmtime_for_output(
234         &[
235             "run",
236             "-Wtimeout=1ms",
237             "-Ccache=n",
238             wasm.path().to_str().unwrap(),
239         ],
240         None,
241     )?;
242     assert!(!output.status.success());
243     assert_eq!(output.stdout, b"");
244     let stderr = String::from_utf8_lossy(&output.stderr);
245     assert!(
246         stderr.contains("wasm trap: interrupt"),
247         "bad stderr: {}",
248         stderr
249     );
250     Ok(())
251 }
252 
253 // Exit with a valid non-zero exit code, snapshot0 edition.
254 #[test]
255 fn exit2_wasi_snapshot0() -> Result<()> {
256     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?;
257 
258     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
259         let output = run_wasmtime_for_output(
260             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
261             None,
262         )?;
263         assert_eq!(output.status.code().unwrap(), 2);
264     }
265     Ok(())
266 }
267 
268 // Exit with a valid non-zero exit code, snapshot1 edition.
269 #[test]
270 fn exit2_wasi_snapshot1() -> Result<()> {
271     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?;
272     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
273     assert_eq!(output.status.code().unwrap(), 2);
274     Ok(())
275 }
276 
277 // Exit with a valid non-zero exit code, snapshot0 edition.
278 #[test]
279 fn exit125_wasi_snapshot0() -> Result<()> {
280     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?;
281     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
282         let output = run_wasmtime_for_output(
283             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
284             None,
285         )?;
286         dbg!(&output);
287         if cfg!(windows) {
288             assert_eq!(output.status.code().unwrap(), 1);
289         } else {
290             assert_eq!(output.status.code().unwrap(), 125);
291         }
292     }
293     Ok(())
294 }
295 
296 // Exit with a valid non-zero exit code, snapshot1 edition.
297 #[test]
298 fn exit125_wasi_snapshot1() -> Result<()> {
299     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?;
300     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
301     if cfg!(windows) {
302         assert_eq!(output.status.code().unwrap(), 1);
303     } else {
304         assert_eq!(output.status.code().unwrap(), 125);
305     }
306     Ok(())
307 }
308 
309 // Exit with an invalid non-zero exit code, snapshot0 edition.
310 #[test]
311 fn exit126_wasi_snapshot0() -> Result<()> {
312     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?;
313 
314     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
315         let output = run_wasmtime_for_output(
316             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
317             None,
318         )?;
319         assert_eq!(output.status.code().unwrap(), 1);
320         assert!(output.stdout.is_empty());
321         assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
322     }
323     Ok(())
324 }
325 
326 // Exit with an invalid non-zero exit code, snapshot1 edition.
327 #[test]
328 fn exit126_wasi_snapshot1() -> Result<()> {
329     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?;
330     let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
331     assert_eq!(output.status.code().unwrap(), 1);
332     assert!(output.stdout.is_empty());
333     assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
334     Ok(())
335 }
336 
337 // Run a minimal command program.
338 #[test]
339 fn minimal_command() -> Result<()> {
340     let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
341     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
342     assert_eq!(stdout, "");
343     Ok(())
344 }
345 
346 // Run a minimal reactor program.
347 #[test]
348 fn minimal_reactor() -> Result<()> {
349     let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
350     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
351     assert_eq!(stdout, "");
352     Ok(())
353 }
354 
355 // Attempt to call invoke on a command.
356 #[test]
357 fn command_invoke() -> Result<()> {
358     let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
359     run_wasmtime(&[
360         "run",
361         "--invoke",
362         "_start",
363         "-Ccache=n",
364         wasm.path().to_str().unwrap(),
365     ])?;
366     Ok(())
367 }
368 
369 // Attempt to call invoke on a command.
370 #[test]
371 fn reactor_invoke() -> Result<()> {
372     let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
373     run_wasmtime(&[
374         "run",
375         "--invoke",
376         "_initialize",
377         "-Ccache=n",
378         wasm.path().to_str().unwrap(),
379     ])?;
380     Ok(())
381 }
382 
383 // Run the greeter test, which runs a preloaded reactor and a command.
384 #[test]
385 fn greeter() -> Result<()> {
386     let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
387     let stdout = run_wasmtime(&[
388         "run",
389         "-Ccache=n",
390         "--preload",
391         "reactor=tests/all/cli_tests/greeter_reactor.wat",
392         wasm.path().to_str().unwrap(),
393     ])?;
394     assert_eq!(
395         stdout,
396         "Hello _initialize\nHello _start\nHello greet\nHello done\n"
397     );
398     Ok(())
399 }
400 
401 // Run the greeter test, but this time preload a command.
402 #[test]
403 fn greeter_preload_command() -> Result<()> {
404     let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?;
405     let stdout = run_wasmtime(&[
406         "run",
407         "-Ccache=n",
408         "--preload",
409         "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat",
410         wasm.path().to_str().unwrap(),
411     ])?;
412     assert_eq!(stdout, "Hello _initialize\n");
413     Ok(())
414 }
415 
416 // Run the greeter test, which runs a preloaded reactor and a command.
417 #[test]
418 fn greeter_preload_callable_command() -> Result<()> {
419     let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
420     let stdout = run_wasmtime(&[
421         "run",
422         "-Ccache=n",
423         "--preload",
424         "reactor=tests/all/cli_tests/greeter_callable_command.wat",
425         wasm.path().to_str().unwrap(),
426     ])?;
427     assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n");
428     Ok(())
429 }
430 
431 // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64
432 // See https://github.com/bytecodealliance/wasmtime/issues/1967
433 #[test]
434 fn exit_with_saved_fprs() -> Result<()> {
435     let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?;
436     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
437     assert_eq!(output.status.code().unwrap(), 0);
438     assert!(output.stdout.is_empty());
439     Ok(())
440 }
441 
442 #[test]
443 fn run_cwasm() -> Result<()> {
444     let td = TempDir::new()?;
445     let cwasm = td.path().join("foo.cwasm");
446     let stdout = run_wasmtime(&[
447         "compile",
448         "tests/all/cli_tests/simple.wat",
449         "-o",
450         cwasm.to_str().unwrap(),
451     ])?;
452     assert_eq!(stdout, "");
453     let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?;
454     assert_eq!(stdout, "");
455     Ok(())
456 }
457 
458 #[cfg(unix)]
459 #[test]
460 fn hello_wasi_snapshot0_from_stdin() -> Result<()> {
461     // Run a simple WASI hello world, snapshot0 edition.
462     // The module is piped from standard input.
463     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
464     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
465         let stdout = {
466             let path = wasm.path();
467             let args: &[&str] = &["-Ccache=n", preview2, "-"];
468             let output = run_wasmtime_for_output(args, Some(path))?;
469             if !output.status.success() {
470                 bail!(
471                     "Failed to execute wasmtime with: {:?}\n{}",
472                     args,
473                     String::from_utf8_lossy(&output.stderr)
474                 );
475             }
476             Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap())
477         }?;
478         assert_eq!(stdout, "Hello, world!\n");
479     }
480     Ok(())
481 }
482 
483 #[test]
484 fn specify_env() -> Result<()> {
485     // By default no env is inherited
486     let output = get_wasmtime_command()?
487         .args(&["run", "tests/all/cli_tests/print_env.wat"])
488         .env("THIS_WILL_NOT", "show up in the output")
489         .output()?;
490     assert!(output.status.success());
491     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
492 
493     // Specify a single env var
494     let output = get_wasmtime_command()?
495         .args(&[
496             "run",
497             "--env",
498             "FOO=bar",
499             "tests/all/cli_tests/print_env.wat",
500         ])
501         .output()?;
502     assert!(output.status.success());
503     assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
504 
505     // Inherit a single env var
506     let output = get_wasmtime_command()?
507         .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"])
508         .env("FOO", "bar")
509         .output()?;
510     assert!(output.status.success());
511     assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
512 
513     // Inherit a nonexistent env var
514     let output = get_wasmtime_command()?
515         .args(&[
516             "run",
517             "--env",
518             "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT",
519             "tests/all/cli_tests/print_env.wat",
520         ])
521         .output()?;
522     assert!(!output.status.success());
523 
524     Ok(())
525 }
526 
527 #[cfg(unix)]
528 #[test]
529 fn run_cwasm_from_stdin() -> Result<()> {
530     use std::process::Stdio;
531 
532     let td = TempDir::new()?;
533     let cwasm = td.path().join("foo.cwasm");
534     let stdout = run_wasmtime(&[
535         "compile",
536         "tests/all/cli_tests/simple.wat",
537         "-o",
538         cwasm.to_str().unwrap(),
539     ])?;
540     assert_eq!(stdout, "");
541 
542     // If stdin is literally the file itself then that should work
543     let args: &[&str] = &["run", "--allow-precompiled", "-"];
544     let output = get_wasmtime_command()?
545         .args(args)
546         .stdin(File::open(&cwasm)?)
547         .output()?;
548     assert!(output.status.success(), "a file as stdin should work");
549 
550     // If stdin is a pipe, that should also work
551     let input = std::fs::read(&cwasm)?;
552     let mut child = get_wasmtime_command()?
553         .args(args)
554         .stdin(Stdio::piped())
555         .stdout(Stdio::piped())
556         .stderr(Stdio::piped())
557         .spawn()?;
558     let mut stdin = child.stdin.take().unwrap();
559     let t = std::thread::spawn(move || {
560         let _ = stdin.write_all(&input);
561     });
562     let output = child.wait_with_output()?;
563     assert!(output.status.success());
564     t.join().unwrap();
565     Ok(())
566 }
567 
568 #[cfg(feature = "wasi-threads")]
569 #[test]
570 fn run_threads() -> Result<()> {
571     let wasm = build_wasm("tests/all/cli_tests/threads.wat")?;
572     let stdout = run_wasmtime(&[
573         "run",
574         "-Wthreads",
575         "-Sthreads",
576         "-Ccache=n",
577         wasm.path().to_str().unwrap(),
578     ])?;
579 
580     assert!(
581         stdout
582             == "Called _start\n\
583     Running wasi_thread_start\n\
584     Running wasi_thread_start\n\
585     Running wasi_thread_start\n\
586     Done\n"
587     );
588     Ok(())
589 }
590 
591 #[cfg(feature = "wasi-threads")]
592 #[test]
593 fn run_simple_with_wasi_threads() -> Result<()> {
594     // We expect to be able to run Wasm modules that do not have correct
595     // wasi-thread entry points or imported shared memory as long as no threads
596     // are spawned.
597     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
598     let stdout = run_wasmtime(&[
599         "run",
600         "-Wthreads",
601         "-Sthreads",
602         "-Ccache=n",
603         "--invoke",
604         "simple",
605         wasm.path().to_str().unwrap(),
606         "4",
607     ])?;
608     assert_eq!(stdout, "4\n");
609     Ok(())
610 }
611 
612 #[test]
613 fn wasm_flags() -> Result<()> {
614     // Any argument after the wasm module should be interpreted as for the
615     // command itself
616     let stdout = run_wasmtime(&[
617         "run",
618         "--",
619         "tests/all/cli_tests/print-arguments.wat",
620         "--argument",
621         "-for",
622         "the",
623         "command",
624     ])?;
625     assert_eq!(
626         stdout,
627         "\
628             print-arguments.wat\n\
629             --argument\n\
630             -for\n\
631             the\n\
632             command\n\
633         "
634     );
635     let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?;
636     assert_eq!(
637         stdout,
638         "\
639             print-arguments.wat\n\
640             -\n\
641         "
642     );
643     let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?;
644     assert_eq!(
645         stdout,
646         "\
647             print-arguments.wat\n\
648             --\n\
649         "
650     );
651     let stdout = run_wasmtime(&[
652         "run",
653         "--",
654         "tests/all/cli_tests/print-arguments.wat",
655         "--",
656         "--",
657         "-a",
658         "b",
659     ])?;
660     assert_eq!(
661         stdout,
662         "\
663             print-arguments.wat\n\
664             --\n\
665             --\n\
666             -a\n\
667             b\n\
668         "
669     );
670     Ok(())
671 }
672 
673 #[test]
674 fn name_same_as_builtin_command() -> Result<()> {
675     // a bare subcommand shouldn't run successfully
676     let output = get_wasmtime_command()?
677         .current_dir("tests/all/cli_tests")
678         .arg("run")
679         .output()?;
680     assert!(!output.status.success());
681 
682     // a `--` prefix should let everything else get interpreted as a wasm
683     // module and arguments, even if the module has a name like `run`
684     let output = get_wasmtime_command()?
685         .current_dir("tests/all/cli_tests")
686         .arg("--")
687         .arg("run")
688         .output()?;
689     assert!(output.status.success(), "expected success got {output:#?}");
690 
691     // Passing options before the subcommand should work and doesn't require
692     // `--` to disambiguate
693     let output = get_wasmtime_command()?
694         .current_dir("tests/all/cli_tests")
695         .arg("-Ccache=n")
696         .arg("run")
697         .output()?;
698     assert!(output.status.success(), "expected success got {output:#?}");
699     Ok(())
700 }
701 
702 #[test]
703 #[cfg(unix)]
704 fn run_just_stdin_argument() -> Result<()> {
705     let output = get_wasmtime_command()?
706         .arg("-")
707         .stdin(File::open("tests/all/cli_tests/simple.wat")?)
708         .output()?;
709     assert!(output.status.success());
710     Ok(())
711 }
712 
713 #[test]
714 fn wasm_flags_without_subcommand() -> Result<()> {
715     let output = get_wasmtime_command()?
716         .current_dir("tests/all/cli_tests/")
717         .arg("print-arguments.wat")
718         .arg("-foo")
719         .arg("bar")
720         .output()?;
721     assert!(output.status.success());
722     assert_eq!(
723         String::from_utf8_lossy(&output.stdout),
724         "\
725             print-arguments.wat\n\
726             -foo\n\
727             bar\n\
728         "
729     );
730     Ok(())
731 }
732 
733 #[test]
734 fn wasi_misaligned_pointer() -> Result<()> {
735     let output = get_wasmtime_command()?
736         .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat")
737         .output()?;
738     assert!(!output.status.success());
739     let stderr = String::from_utf8_lossy(&output.stderr);
740     assert!(
741         stderr.contains("Pointer not aligned"),
742         "bad stderr: {stderr}",
743     );
744     Ok(())
745 }
746 
747 #[test]
748 #[cfg_attr(not(feature = "component-model"), ignore)]
749 fn hello_with_preview2() -> Result<()> {
750     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
751     let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?;
752     assert_eq!(stdout, "Hello, world!\n");
753     Ok(())
754 }
755 
756 #[test]
757 #[cfg_attr(not(feature = "component-model"), ignore)]
758 fn component_missing_feature() -> Result<()> {
759     let path = "tests/all/cli_tests/empty-component.wat";
760     let wasm = build_wasm(path)?;
761     let output = get_wasmtime_command()?
762         .arg("-Ccache=n")
763         .arg(wasm.path())
764         .output()?;
765     assert!(!output.status.success());
766     let stderr = String::from_utf8_lossy(&output.stderr);
767     assert!(
768         stderr.contains("cannot execute a component without `--wasm component-model`"),
769         "bad stderr: {stderr}"
770     );
771 
772     // also tests with raw *.wat input
773     let output = get_wasmtime_command()?
774         .arg("-Ccache=n")
775         .arg(path)
776         .output()?;
777     assert!(!output.status.success());
778     let stderr = String::from_utf8_lossy(&output.stderr);
779     assert!(
780         stderr.contains("cannot execute a component without `--wasm component-model`"),
781         "bad stderr: {stderr}"
782     );
783 
784     Ok(())
785 }
786 
787 // If the text format is invalid then the filename should be mentioned in the
788 // error message.
789 #[test]
790 fn bad_text_syntax() -> Result<()> {
791     let output = get_wasmtime_command()?
792         .arg("-Ccache=n")
793         .arg("tests/all/cli_tests/bad-syntax.wat")
794         .output()?;
795     assert!(!output.status.success());
796     let stderr = String::from_utf8_lossy(&output.stderr);
797     assert!(
798         stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"),
799         "bad stderr: {stderr}"
800     );
801     Ok(())
802 }
803 
804 #[test]
805 #[cfg_attr(not(feature = "component-model"), ignore)]
806 fn run_basic_component() -> Result<()> {
807     let path = "tests/all/cli_tests/component-basic.wat";
808     let wasm = build_wasm(path)?;
809 
810     // Run both the `*.wasm` binary and the text format
811     run_wasmtime(&[
812         "-Ccache=n",
813         "-Wcomponent-model",
814         wasm.path().to_str().unwrap(),
815     ])?;
816     run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?;
817 
818     Ok(())
819 }
820 
821 #[test]
822 #[cfg_attr(not(feature = "component-model"), ignore)]
823 fn run_precompiled_component() -> Result<()> {
824     let td = TempDir::new()?;
825     let cwasm = td.path().join("component-basic.cwasm");
826     let stdout = run_wasmtime(&[
827         "compile",
828         "tests/all/cli_tests/component-basic.wat",
829         "-o",
830         cwasm.to_str().unwrap(),
831         "-Wcomponent-model",
832     ])?;
833     assert_eq!(stdout, "");
834     let stdout = run_wasmtime(&[
835         "run",
836         "-Wcomponent-model",
837         "--allow-precompiled",
838         cwasm.to_str().unwrap(),
839     ])?;
840     assert_eq!(stdout, "");
841 
842     Ok(())
843 }
844 
845 #[test]
846 fn memory_growth_failure() -> Result<()> {
847     let output = get_wasmtime_command()?
848         .args(&[
849             "run",
850             "-Wmemory64",
851             "-Wtrap-on-grow-failure",
852             "tests/all/cli_tests/memory-grow-failure.wat",
853         ])
854         .output()?;
855     assert!(!output.status.success());
856     let stderr = String::from_utf8_lossy(&output.stderr);
857     assert!(
858         stderr.contains("forcing a memory growth failure to be a trap"),
859         "bad stderr: {stderr}"
860     );
861     Ok(())
862 }
863 
864 #[test]
865 fn table_growth_failure() -> Result<()> {
866     let output = get_wasmtime_command()?
867         .args(&[
868             "run",
869             "-Wtrap-on-grow-failure",
870             "tests/all/cli_tests/table-grow-failure.wat",
871         ])
872         .output()?;
873     assert!(!output.status.success());
874     let stderr = String::from_utf8_lossy(&output.stderr);
875     assert!(
876         stderr.contains("forcing trap when growing table"),
877         "bad stderr: {stderr}"
878     );
879     Ok(())
880 }
881 
882 #[test]
883 fn table_growth_failure2() -> Result<()> {
884     let output = get_wasmtime_command()?
885         .args(&[
886             "run",
887             "-Wtrap-on-grow-failure",
888             "tests/all/cli_tests/table-grow-failure2.wat",
889         ])
890         .output()?;
891     assert!(!output.status.success());
892     let stderr = String::from_utf8_lossy(&output.stderr);
893     assert!(
894         stderr.contains("forcing a table growth failure to be a trap"),
895         "bad stderr: {stderr}"
896     );
897     Ok(())
898 }
899 
900 #[test]
901 fn option_group_help() -> Result<()> {
902     run_wasmtime(&["run", "-Whelp"])?;
903     run_wasmtime(&["run", "-O", "help"])?;
904     run_wasmtime(&["run", "--codegen", "help"])?;
905     run_wasmtime(&["run", "--debug=help"])?;
906     run_wasmtime(&["run", "-Shelp"])?;
907     run_wasmtime(&["run", "-Whelp-long"])?;
908     Ok(())
909 }
910 
911 #[test]
912 fn option_group_comma_separated() -> Result<()> {
913     run_wasmtime(&[
914         "run",
915         "-Wrelaxed-simd,simd",
916         "tests/all/cli_tests/simple.wat",
917     ])?;
918     Ok(())
919 }
920 
921 #[test]
922 fn option_group_boolean_parsing() -> Result<()> {
923     run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?;
924     run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?;
925     run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?;
926     run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?;
927     run_wasmtime(&[
928         "run",
929         "-Wrelaxed-simd=yes",
930         "tests/all/cli_tests/simple.wat",
931     ])?;
932     run_wasmtime(&[
933         "run",
934         "-Wrelaxed-simd=true",
935         "tests/all/cli_tests/simple.wat",
936     ])?;
937     run_wasmtime(&[
938         "run",
939         "-Wrelaxed-simd=false",
940         "tests/all/cli_tests/simple.wat",
941     ])?;
942     Ok(())
943 }
944 
945 #[test]
946 fn preview2_stdin() -> Result<()> {
947     let test = "tests/all/cli_tests/count-stdin.wat";
948     let cmd = || -> Result<_> {
949         let mut cmd = get_wasmtime_command()?;
950         cmd.arg("--invoke=count").arg("-Spreview2").arg(test);
951         Ok(cmd)
952     };
953 
954     // read empty pipe is ok
955     let output = cmd()?.output()?;
956     assert!(output.status.success());
957     assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");
958 
959     // read itself is ok
960     let file = File::open(test)?;
961     let size = file.metadata()?.len();
962     let output = cmd()?.stdin(File::open(test)?).output()?;
963     assert!(output.status.success());
964     assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n"));
965 
966     // read piped input ok is ok
967     let mut child = cmd()?
968         .stdin(Stdio::piped())
969         .stdout(Stdio::piped())
970         .stderr(Stdio::piped())
971         .spawn()?;
972     let mut stdin = child.stdin.take().unwrap();
973     std::thread::spawn(move || {
974         stdin.write_all(b"hello").unwrap();
975     });
976     let output = child.wait_with_output()?;
977     assert!(output.status.success());
978     assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n");
979 
980     let count_up_to = |n: usize| -> Result<_> {
981         let mut child = get_wasmtime_command()?
982             .arg("--invoke=count-up-to")
983             .arg("-Spreview2")
984             .arg(test)
985             .arg(n.to_string())
986             .stdin(Stdio::piped())
987             .stdout(Stdio::piped())
988             .stderr(Stdio::piped())
989             .spawn()?;
990         let mut stdin = child.stdin.take().unwrap();
991         let t = std::thread::spawn(move || {
992             let mut written = 0;
993             let bytes = [0; 64 * 1024];
994             loop {
995                 written += match stdin.write(&bytes) {
996                     Ok(n) => n,
997                     Err(_) => break written,
998                 };
999             }
1000         });
1001         let output = child.wait_with_output()?;
1002         assert!(output.status.success());
1003         let written = t.join().unwrap();
1004         let read = String::from_utf8_lossy(&output.stdout)
1005             .trim()
1006             .parse::<usize>()
1007             .unwrap();
1008         // The test reads in 1000 byte chunks so make sure that it doesn't read
1009         // more than 1000 bytes than requested.
1010         assert!(read < n + 1000, "test read too much {read}");
1011         Ok(written)
1012     };
1013 
1014     // wasmtime shouldn't eat information that the guest never actually tried to
1015     // read.
1016     //
1017     // NB: this may be a bit flaky. Exactly how much we wrote in the above
1018     // helper thread depends on how much the OS buffers for us. For now give
1019     // some some slop and assume that OSes are unlikely to buffer more than
1020     // that.
1021     let slop = 256 * 1024;
1022     for amt in [0, 100, 100_000] {
1023         let written = count_up_to(amt)?;
1024         assert!(written < slop + amt, "wrote too much {written}");
1025     }
1026     Ok(())
1027 }
1028 
1029 #[test]
1030 fn old_cli_warn_if_ambiguous_flags() -> Result<()> {
1031     // This is accepted in the old CLI parser and the new but it's interpreted
1032     // differently so a warning should be printed.
1033     let output = get_wasmtime_command()?
1034         .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"])
1035         .output()?;
1036     assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n");
1037     assert_eq!(
1038         String::from_utf8_lossy(&output.stderr),
1039         "\
1040 warning: this CLI invocation of Wasmtime will be parsed differently in future
1041          Wasmtime versions -- see this online issue for more information:
1042          https://github.com/bytecodealliance/wasmtime/issues/7384
1043 
1044          Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing,
1045          however this behavior can also be temporarily configured with an
1046          environment variable:
1047 
1048          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or
1049          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior
1050 warning: using `--invoke` with a function that returns values is experimental and may break in the future
1051 "
1052     );
1053 
1054     // Test disabling the warning
1055     let output = get_wasmtime_command()?
1056         .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"])
1057         .env("WASMTIME_NEW_CLI", "0")
1058         .output()?;
1059     assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n");
1060     assert_eq!(
1061         String::from_utf8_lossy(&output.stderr),
1062         "\
1063 warning: using `--invoke` with a function that returns values is experimental and may break in the future
1064 "
1065     );
1066 
1067     // Test forcing the new behavior where nothing happens because the file is
1068     // invoked with `--invoke` as its own argument.
1069     let output = get_wasmtime_command()?
1070         .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"])
1071         .env("WASMTIME_NEW_CLI", "1")
1072         .output()?;
1073     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
1074     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1075 
1076     // This is unambiguous
1077     let output = get_wasmtime_command()?
1078         .args(&["--invoke", "get_f32", "tests/all/cli_tests/simple.wat"])
1079         .output()?;
1080     assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n");
1081     assert_eq!(
1082         String::from_utf8_lossy(&output.stderr),
1083         "\
1084 warning: using `--invoke` with a function that returns values is experimental and may break in the future
1085 "
1086     );
1087 
1088     // This fails to parse in the old but succeeds in the new, so it should run
1089     // under the new semantics with no warning.
1090     let output = get_wasmtime_command()?
1091         .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"])
1092         .output()?;
1093     assert_eq!(
1094         String::from_utf8_lossy(&output.stdout),
1095         "print-arguments.wat\n--arg\n"
1096     );
1097     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1098 
1099     // Old behavior can be forced however
1100     let output = get_wasmtime_command()?
1101         .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"])
1102         .env("WASMTIME_NEW_CLI", "0")
1103         .output()?;
1104     assert!(!output.status.success());
1105 
1106     // This works in both the old and the new, so no warnings
1107     let output = get_wasmtime_command()?
1108         .args(&["run", "tests/all/cli_tests/print-arguments.wat", "arg"])
1109         .output()?;
1110     assert_eq!(
1111         String::from_utf8_lossy(&output.stdout),
1112         "print-arguments.wat\narg\n"
1113     );
1114     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1115 
1116     // This works in both the old and the new, so no warnings
1117     let output = get_wasmtime_command()?
1118         .args(&[
1119             "run",
1120             "--",
1121             "tests/all/cli_tests/print-arguments.wat",
1122             "--arg",
1123         ])
1124         .output()?;
1125     assert_eq!(
1126         String::from_utf8_lossy(&output.stdout),
1127         "print-arguments.wat\n--arg\n"
1128     );
1129     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1130 
1131     // Old flags still work, but with a warning
1132     let output = get_wasmtime_command()?
1133         .args(&[
1134             "run",
1135             "--max-wasm-stack",
1136             "1000000",
1137             "tests/all/cli_tests/print-arguments.wat",
1138         ])
1139         .output()?;
1140     assert_eq!(
1141         String::from_utf8_lossy(&output.stdout),
1142         "print-arguments.wat\n"
1143     );
1144     assert_eq!(
1145         String::from_utf8_lossy(&output.stderr),
1146         "\
1147 warning: this CLI invocation of Wasmtime is going to break in the future -- for
1148          more information see this issue online:
1149          https://github.com/bytecodealliance/wasmtime/issues/7384
1150 
1151          Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing,
1152          however this behavior can also be temporarily configured with an
1153          environment variable:
1154 
1155          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or
1156          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error
1157 "
1158     );
1159 
1160     // Old flags warning is suppressible.
1161     let output = get_wasmtime_command()?
1162         .args(&[
1163             "run",
1164             "--max-wasm-stack",
1165             "1000000",
1166             "tests/all/cli_tests/print-arguments.wat",
1167         ])
1168         .env("WASMTIME_NEW_CLI", "0")
1169         .output()?;
1170     assert_eq!(
1171         String::from_utf8_lossy(&output.stdout),
1172         "print-arguments.wat\n"
1173     );
1174     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1175 
1176     // the `--dir` flag prints no warning when used with `::`
1177     let dir = tempfile::tempdir()?;
1178     std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1179     let output = get_wasmtime_command()?
1180         .args(&[
1181             "run",
1182             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1183             test_programs_artifacts::CLI_FILE_READ,
1184         ])
1185         .output()?;
1186     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
1187     assert_eq!(String::from_utf8_lossy(&output.stderr), "");
1188 
1189     Ok(())
1190 }
1191 
1192 #[test]
1193 fn float_args() -> Result<()> {
1194     let result = run_wasmtime(&[
1195         "--invoke",
1196         "echo_f32",
1197         "tests/all/cli_tests/simple.wat",
1198         "1.0",
1199     ])?;
1200     assert_eq!(result, "1\n");
1201     let result = run_wasmtime(&[
1202         "--invoke",
1203         "echo_f64",
1204         "tests/all/cli_tests/simple.wat",
1205         "1.1",
1206     ])?;
1207     assert_eq!(result, "1.1\n");
1208     Ok(())
1209 }
1210 
1211 mod test_programs {
1212     use super::{get_wasmtime_command, run_wasmtime};
1213     use anyhow::Result;
1214     use std::io::{Read, Write};
1215     use std::process::Stdio;
1216     use test_programs_artifacts::*;
1217 
1218     macro_rules! assert_test_exists {
1219         ($name:ident) => {
1220             #[allow(unused_imports)]
1221             use self::$name as _;
1222         };
1223     }
1224     foreach_cli!(assert_test_exists);
1225 
1226     #[test]
1227     fn cli_hello_stdout() -> Result<()> {
1228         run_wasmtime(&[
1229             "run",
1230             "-Wcomponent-model",
1231             CLI_HELLO_STDOUT_COMPONENT,
1232             "gussie",
1233             "sparky",
1234             "willa",
1235         ])?;
1236         Ok(())
1237     }
1238 
1239     #[test]
1240     fn cli_args() -> Result<()> {
1241         run_wasmtime(&[
1242             "run",
1243             "-Wcomponent-model",
1244             CLI_ARGS_COMPONENT,
1245             "hello",
1246             "this",
1247             "",
1248             "is an argument",
1249             "with �� emoji",
1250         ])?;
1251         Ok(())
1252     }
1253 
1254     #[test]
1255     fn cli_stdin() -> Result<()> {
1256         let mut child = get_wasmtime_command()?
1257             .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT])
1258             .stdout(Stdio::piped())
1259             .stderr(Stdio::piped())
1260             .stdin(Stdio::piped())
1261             .spawn()?;
1262         child
1263             .stdin
1264             .take()
1265             .unwrap()
1266             .write_all(b"So rested he by the Tumtum tree")
1267             .unwrap();
1268         let output = child.wait_with_output()?;
1269         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1270         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1271         assert!(output.status.success());
1272         Ok(())
1273     }
1274 
1275     #[test]
1276     fn cli_splice_stdin() -> Result<()> {
1277         let mut child = get_wasmtime_command()?
1278             .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT])
1279             .stdout(Stdio::piped())
1280             .stderr(Stdio::piped())
1281             .stdin(Stdio::piped())
1282             .spawn()?;
1283         let msg = "So rested he by the Tumtum tree";
1284         child
1285             .stdin
1286             .take()
1287             .unwrap()
1288             .write_all(msg.as_bytes())
1289             .unwrap();
1290         let output = child.wait_with_output()?;
1291         assert!(output.status.success());
1292         let stdout = String::from_utf8_lossy(&output.stdout);
1293         let stderr = String::from_utf8_lossy(&output.stderr);
1294         if !stderr.is_empty() {
1295             eprintln!("{stderr}");
1296         }
1297 
1298         assert_eq!(
1299             format!(
1300                 "before splice\n{msg}\ncompleted splicing {} bytes\n",
1301                 msg.as_bytes().len()
1302             ),
1303             stdout
1304         );
1305         Ok(())
1306     }
1307 
1308     #[test]
1309     fn cli_env() -> Result<()> {
1310         run_wasmtime(&[
1311             "run",
1312             "-Wcomponent-model",
1313             "--env=frabjous=day",
1314             "--env=callooh=callay",
1315             CLI_ENV_COMPONENT,
1316         ])?;
1317         Ok(())
1318     }
1319 
1320     #[test]
1321     fn cli_file_read() -> Result<()> {
1322         let dir = tempfile::tempdir()?;
1323 
1324         std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1325 
1326         run_wasmtime(&[
1327             "run",
1328             "-Wcomponent-model",
1329             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1330             CLI_FILE_READ_COMPONENT,
1331         ])?;
1332         Ok(())
1333     }
1334 
1335     #[test]
1336     fn cli_file_append() -> Result<()> {
1337         let dir = tempfile::tempdir()?;
1338 
1339         std::fs::File::create(dir.path().join("bar.txt"))?
1340             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1341 
1342         run_wasmtime(&[
1343             "run",
1344             "-Wcomponent-model",
1345             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1346             CLI_FILE_APPEND_COMPONENT,
1347         ])?;
1348 
1349         let contents = std::fs::read(dir.path().join("bar.txt"))?;
1350         assert_eq!(
1351             std::str::from_utf8(&contents).unwrap(),
1352             "'Twas brillig, and the slithy toves.\n\
1353                    Did gyre and gimble in the wabe;\n\
1354                    All mimsy were the borogoves,\n\
1355                    And the mome raths outgrabe.\n"
1356         );
1357         Ok(())
1358     }
1359 
1360     #[test]
1361     fn cli_file_dir_sync() -> Result<()> {
1362         let dir = tempfile::tempdir()?;
1363 
1364         std::fs::File::create(dir.path().join("bar.txt"))?
1365             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1366 
1367         run_wasmtime(&[
1368             "run",
1369             "-Wcomponent-model",
1370             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1371             CLI_FILE_DIR_SYNC_COMPONENT,
1372         ])?;
1373 
1374         Ok(())
1375     }
1376 
1377     #[test]
1378     fn cli_exit_success() -> Result<()> {
1379         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?;
1380         Ok(())
1381     }
1382 
1383     #[test]
1384     fn cli_exit_default() -> Result<()> {
1385         run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?;
1386         Ok(())
1387     }
1388 
1389     #[test]
1390     fn cli_exit_failure() -> Result<()> {
1391         let output = get_wasmtime_command()?
1392             .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT])
1393             .output()?;
1394         assert!(!output.status.success());
1395         assert_eq!(output.status.code(), Some(1));
1396         Ok(())
1397     }
1398 
1399     #[test]
1400     fn cli_exit_panic() -> Result<()> {
1401         let output = get_wasmtime_command()?
1402             .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT])
1403             .output()?;
1404         assert!(!output.status.success());
1405         let stderr = String::from_utf8_lossy(&output.stderr);
1406         assert!(stderr.contains("Curiouser and curiouser!"));
1407         Ok(())
1408     }
1409 
1410     #[test]
1411     fn cli_directory_list() -> Result<()> {
1412         let dir = tempfile::tempdir()?;
1413 
1414         std::fs::File::create(dir.path().join("foo.txt"))?;
1415         std::fs::File::create(dir.path().join("bar.txt"))?;
1416         std::fs::File::create(dir.path().join("baz.txt"))?;
1417         std::fs::create_dir(dir.path().join("sub"))?;
1418         std::fs::File::create(dir.path().join("sub").join("wow.txt"))?;
1419         std::fs::File::create(dir.path().join("sub").join("yay.txt"))?;
1420 
1421         run_wasmtime(&[
1422             "run",
1423             "-Wcomponent-model",
1424             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1425             CLI_DIRECTORY_LIST_COMPONENT,
1426         ])?;
1427         Ok(())
1428     }
1429 
1430     #[test]
1431     fn cli_default_clocks() -> Result<()> {
1432         run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?;
1433         Ok(())
1434     }
1435 
1436     #[test]
1437     fn cli_export_cabi_realloc() -> Result<()> {
1438         run_wasmtime(&[
1439             "run",
1440             "-Wcomponent-model",
1441             CLI_EXPORT_CABI_REALLOC_COMPONENT,
1442         ])?;
1443         Ok(())
1444     }
1445 
1446     #[test]
1447     fn run_wasi_http_component() -> Result<()> {
1448         let output = super::run_wasmtime_for_output(
1449             &[
1450                 "-Ccache=no",
1451                 "-Wcomponent-model",
1452                 "-Scommon,http,preview2",
1453                 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
1454             ],
1455             None,
1456         )?;
1457         println!("{}", String::from_utf8_lossy(&output.stderr));
1458         let stdout = String::from_utf8_lossy(&output.stdout);
1459         println!("{}", stdout);
1460         assert!(stdout.starts_with("Called _start\n"));
1461         assert!(stdout.ends_with("Done\n"));
1462         assert!(output.status.success());
1463         Ok(())
1464     }
1465 
1466     // Test to ensure that prints in the guest aren't buffered on the host by
1467     // accident. The test here will print something without a newline and then
1468     // wait for input on stdin, and the test here is to ensure that the
1469     // character shows up here even as the guest is waiting on input via stdin.
1470     #[test]
1471     fn cli_stdio_write_flushes() -> Result<()> {
1472         fn run(args: &[&str]) -> Result<()> {
1473             println!("running {args:?}");
1474             let mut child = get_wasmtime_command()?
1475                 .args(args)
1476                 .stdin(Stdio::piped())
1477                 .stdout(Stdio::piped())
1478                 .spawn()?;
1479             let mut stdout = child.stdout.take().unwrap();
1480             let mut buf = [0; 10];
1481             match stdout.read(&mut buf) {
1482                 Ok(2) => assert_eq!(&buf[..2], b"> "),
1483                 e => panic!("unexpected read result {e:?}"),
1484             }
1485             drop(stdout);
1486             drop(child.stdin.take().unwrap());
1487             let status = child.wait()?;
1488             assert!(status.success());
1489             Ok(())
1490         }
1491 
1492         run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?;
1493         run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?;
1494         run(&[
1495             "run",
1496             "-Wcomponent-model",
1497             CLI_STDIO_WRITE_FLUSHES_COMPONENT,
1498         ])?;
1499         Ok(())
1500     }
1501 
1502     #[test]
1503     fn cli_no_tcp() -> Result<()> {
1504         let output = super::run_wasmtime_for_output(
1505             &[
1506                 "-Wcomponent-model",
1507                 // Turn on network but turn off TCP
1508                 "-Sinherit-network,tcp=no",
1509                 CLI_NO_TCP_COMPONENT,
1510             ],
1511             None,
1512         )?;
1513         println!("{}", String::from_utf8_lossy(&output.stderr));
1514         assert!(output.status.success());
1515         Ok(())
1516     }
1517 
1518     #[test]
1519     fn cli_no_udp() -> Result<()> {
1520         let output = super::run_wasmtime_for_output(
1521             &[
1522                 "-Wcomponent-model",
1523                 // Turn on network but turn off UDP
1524                 "-Sinherit-network,udp=no",
1525                 CLI_NO_UDP_COMPONENT,
1526             ],
1527             None,
1528         )?;
1529         println!("{}", String::from_utf8_lossy(&output.stderr));
1530         assert!(output.status.success());
1531         Ok(())
1532     }
1533 }
1534