xref: /wasmtime-44.0.1/tests/all/cli_tests.rs (revision b860c2c6)
1 #![cfg(not(miri))]
2 
3 use std::fs::File;
4 use std::io::Write;
5 use std::path::Path;
6 use std::process::{Command, ExitStatus, Output, Stdio};
7 use tempfile::{NamedTempFile, TempDir};
8 use wasmtime::{Result, bail};
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.
run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result<Output>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].
get_wasmtime_command() -> Result<Command>22 pub fn get_wasmtime_command() -> Result<Command> {
23     let mut cmd = wasmtime_test_util::command(get_wasmtime_path());
24 
25     // Ignore this if it's specified in the environment to allow tests to run in
26     // "default mode" by default.
27     cmd.env_remove("WASMTIME_NEW_CLI");
28 
29     Ok(cmd)
30 }
31 
get_wasmtime_path() -> &'static str32 fn get_wasmtime_path() -> &'static str {
33     env!("CARGO_BIN_EXE_wasmtime")
34 }
35 
36 // Run the wasmtime CLI with the provided args and, if it succeeds, return
37 // the standard output in a `String`.
run_wasmtime(args: &[&str]) -> Result<String>38 pub fn run_wasmtime(args: &[&str]) -> Result<String> {
39     let output = run_wasmtime_for_output(args, None)?;
40     if !output.status.success() {
41         bail!(
42             "Failed to execute wasmtime with: {:?}\nstatus: {}\n{}",
43             args,
44             output.status,
45             String::from_utf8_lossy(&output.stderr)
46         );
47     }
48     Ok(String::from_utf8(output.stdout).unwrap())
49 }
50 
build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile>51 fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> {
52     let mut wasm_file = NamedTempFile::new()?;
53     let wasm = wat::parse_file(wat_path)?;
54     wasm_file.write(&wasm)?;
55     Ok(wasm_file)
56 }
57 
58 // Very basic use case: compile binary wasm file and run specific function with arguments.
59 #[test]
run_wasmtime_simple() -> Result<()>60 fn run_wasmtime_simple() -> Result<()> {
61     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
62     run_wasmtime(&[
63         "run",
64         "--invoke",
65         "simple",
66         "-Ccache=n",
67         wasm.path().to_str().unwrap(),
68         "4",
69     ])?;
70     Ok(())
71 }
72 
73 // Wasmtime shall fail when not enough arguments were provided.
74 #[test]
run_wasmtime_simple_fail_no_args() -> Result<()>75 fn run_wasmtime_simple_fail_no_args() -> Result<()> {
76     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
77     assert!(
78         run_wasmtime(&[
79             "run",
80             "-Ccache=n",
81             "--invoke",
82             "simple",
83             wasm.path().to_str().unwrap(),
84         ])
85         .is_err(),
86         "shall fail"
87     );
88     Ok(())
89 }
90 
91 #[test]
run_coredump_smoketest() -> Result<()>92 fn run_coredump_smoketest() -> Result<()> {
93     let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?;
94     let coredump_file = NamedTempFile::new()?;
95     let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display());
96     let err = run_wasmtime(&[
97         "run",
98         "--invoke",
99         "a",
100         "-Ccache=n",
101         &coredump_arg,
102         wasm.path().to_str().unwrap(),
103     ])
104     .unwrap_err();
105     assert!(err.to_string().contains(&format!(
106         "core dumped at {}",
107         coredump_file.path().display()
108     )));
109     Ok(())
110 }
111 
112 // Running simple wat
113 #[test]
run_wasmtime_simple_wat() -> Result<()>114 fn run_wasmtime_simple_wat() -> Result<()> {
115     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
116     run_wasmtime(&[
117         "run",
118         "--invoke",
119         "simple",
120         "-Ccache=n",
121         wasm.path().to_str().unwrap(),
122         "4",
123     ])?;
124     assert_eq!(
125         run_wasmtime(&[
126             "run",
127             "--invoke",
128             "get_f32",
129             "-Ccache=n",
130             wasm.path().to_str().unwrap(),
131         ])?,
132         "100\n"
133     );
134     assert_eq!(
135         run_wasmtime(&[
136             "run",
137             "--invoke",
138             "get_f64",
139             "-Ccache=n",
140             wasm.path().to_str().unwrap(),
141         ])?,
142         "100\n"
143     );
144     Ok(())
145 }
146 
147 // Running a wat that traps.
148 #[test]
run_wasmtime_unreachable_wat() -> Result<()>149 fn run_wasmtime_unreachable_wat() -> Result<()> {
150     let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?;
151     let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
152 
153     assert_ne!(output.stderr, b"");
154     assert_eq!(output.stdout, b"");
155 
156     assert_trap_code(&output.status);
157     Ok(())
158 }
159 
assert_trap_code(status: &ExitStatus)160 fn assert_trap_code(status: &ExitStatus) {
161     let code = status
162         .code()
163         .expect("wasmtime process should exit normally");
164 
165     // Test for the specific error code Wasmtime uses to indicate a trap return.
166     #[cfg(unix)]
167     assert_eq!(code, 128 + libc::SIGABRT);
168     #[cfg(windows)]
169     assert_eq!(code, 3);
170 }
171 
172 // Run a simple WASI hello world, snapshot0 edition.
173 #[test]
hello_wasi_snapshot0() -> Result<()>174 fn hello_wasi_snapshot0() -> Result<()> {
175     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
176         let stdout = run_wasmtime(&[
177             "-Ccache=n",
178             preview2,
179             "tests/all/cli_tests/hello_wasi_snapshot0.wat",
180         ])?;
181         assert_eq!(stdout, "Hello, world!\n");
182     }
183     Ok(())
184 }
185 
186 // Run a simple WASI hello world, snapshot1 edition.
187 #[test]
hello_wasi_snapshot1() -> Result<()>188 fn hello_wasi_snapshot1() -> Result<()> {
189     let stdout = run_wasmtime(&["-Ccache=n", "tests/all/cli_tests/hello_wasi_snapshot1.wat"])?;
190     assert_eq!(stdout, "Hello, world!\n");
191     Ok(())
192 }
193 
194 #[test]
timeout_in_start() -> Result<()>195 fn timeout_in_start() -> Result<()> {
196     let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?;
197     let output = run_wasmtime_for_output(
198         &[
199             "run",
200             "-Wtimeout=1ms",
201             "-Ccache=n",
202             wasm.path().to_str().unwrap(),
203         ],
204         None,
205     )?;
206     assert!(!output.status.success());
207     assert_eq!(output.stdout, b"");
208     let stderr = String::from_utf8_lossy(&output.stderr);
209     assert!(
210         stderr.contains("wasm trap: interrupt"),
211         "bad stderr: {stderr}"
212     );
213     Ok(())
214 }
215 
216 #[test]
timeout_in_invoke() -> Result<()>217 fn timeout_in_invoke() -> Result<()> {
218     let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?;
219     let output = run_wasmtime_for_output(
220         &[
221             "run",
222             "-Wtimeout=1ms",
223             "-Ccache=n",
224             wasm.path().to_str().unwrap(),
225         ],
226         None,
227     )?;
228     assert!(!output.status.success());
229     assert_eq!(output.stdout, b"");
230     let stderr = String::from_utf8_lossy(&output.stderr);
231     assert!(
232         stderr.contains("wasm trap: interrupt"),
233         "bad stderr: {stderr}"
234     );
235     Ok(())
236 }
237 
238 // Exit with a valid non-zero exit code, snapshot0 edition.
239 #[test]
exit2_wasi_snapshot0() -> Result<()>240 fn exit2_wasi_snapshot0() -> Result<()> {
241     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?;
242 
243     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
244         let output = run_wasmtime_for_output(
245             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
246             None,
247         )?;
248         assert_eq!(output.status.code().unwrap(), 2);
249     }
250     Ok(())
251 }
252 
253 // Exit with a valid non-zero exit code, snapshot1 edition.
254 #[test]
exit2_wasi_snapshot1() -> Result<()>255 fn exit2_wasi_snapshot1() -> Result<()> {
256     let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?;
257     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
258     assert_eq!(output.status.code().unwrap(), 2);
259     Ok(())
260 }
261 
262 // Exit with a valid non-zero exit code, snapshot0 edition.
263 #[test]
exit125_wasi_snapshot0() -> Result<()>264 fn exit125_wasi_snapshot0() -> Result<()> {
265     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?;
266     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
267         let output = run_wasmtime_for_output(
268             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
269             None,
270         )?;
271         dbg!(&output);
272         assert_eq!(output.status.code().unwrap(), 125);
273     }
274     Ok(())
275 }
276 
277 // Exit with a valid non-zero exit code, snapshot1 edition.
278 #[test]
exit125_wasi_snapshot1() -> Result<()>279 fn exit125_wasi_snapshot1() -> Result<()> {
280     let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?;
281     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
282     assert_eq!(output.status.code().unwrap(), 125);
283     Ok(())
284 }
285 
286 // Exit with an invalid non-zero exit code, snapshot0 edition.
287 #[test]
exit126_wasi_snapshot0() -> Result<()>288 fn exit126_wasi_snapshot0() -> Result<()> {
289     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?;
290 
291     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
292         let output = run_wasmtime_for_output(
293             &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
294             None,
295         )?;
296         assert_eq!(output.status.code().unwrap(), 1);
297         assert!(output.stdout.is_empty());
298         assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
299     }
300     Ok(())
301 }
302 
303 // Exit with an invalid non-zero exit code, snapshot1 edition.
304 #[test]
exit126_wasi_snapshot1() -> Result<()>305 fn exit126_wasi_snapshot1() -> Result<()> {
306     let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?;
307     let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
308     assert_eq!(output.status.code().unwrap(), 1);
309     assert!(output.stdout.is_empty());
310     assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
311     Ok(())
312 }
313 
314 // Run a minimal command program.
315 #[test]
minimal_command() -> Result<()>316 fn minimal_command() -> Result<()> {
317     let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
318     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
319     assert_eq!(stdout, "");
320     Ok(())
321 }
322 
323 // Run a minimal reactor program.
324 #[test]
minimal_reactor() -> Result<()>325 fn minimal_reactor() -> Result<()> {
326     let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
327     let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
328     assert_eq!(stdout, "");
329     Ok(())
330 }
331 
332 // Attempt to call invoke on a command.
333 #[test]
command_invoke() -> Result<()>334 fn command_invoke() -> Result<()> {
335     let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
336     run_wasmtime(&[
337         "run",
338         "--invoke",
339         "_start",
340         "-Ccache=n",
341         wasm.path().to_str().unwrap(),
342     ])?;
343     Ok(())
344 }
345 
346 // Attempt to call invoke on a command.
347 #[test]
reactor_invoke() -> Result<()>348 fn reactor_invoke() -> Result<()> {
349     let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
350     run_wasmtime(&[
351         "run",
352         "--invoke",
353         "_initialize",
354         "-Ccache=n",
355         wasm.path().to_str().unwrap(),
356     ])?;
357     Ok(())
358 }
359 
360 // Run the greeter test, which runs a preloaded reactor and a command.
361 #[test]
greeter() -> Result<()>362 fn greeter() -> Result<()> {
363     let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
364     let stdout = run_wasmtime(&[
365         "run",
366         "-Ccache=n",
367         "--preload",
368         "reactor=tests/all/cli_tests/greeter_reactor.wat",
369         wasm.path().to_str().unwrap(),
370     ])?;
371     assert_eq!(
372         stdout,
373         "Hello _initialize\nHello _start\nHello greet\nHello done\n"
374     );
375     Ok(())
376 }
377 
378 // Run the greeter test, but this time preload a command.
379 #[test]
greeter_preload_command() -> Result<()>380 fn greeter_preload_command() -> Result<()> {
381     let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?;
382     let stdout = run_wasmtime(&[
383         "run",
384         "-Ccache=n",
385         "--preload",
386         "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat",
387         wasm.path().to_str().unwrap(),
388     ])?;
389     assert_eq!(stdout, "Hello _initialize\n");
390     Ok(())
391 }
392 
393 // Run the greeter test, which runs a preloaded reactor and a command.
394 #[test]
greeter_preload_callable_command() -> Result<()>395 fn greeter_preload_callable_command() -> Result<()> {
396     let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
397     let stdout = run_wasmtime(&[
398         "run",
399         "-Ccache=n",
400         "--preload",
401         "reactor=tests/all/cli_tests/greeter_callable_command.wat",
402         wasm.path().to_str().unwrap(),
403     ])?;
404     assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n");
405     Ok(())
406 }
407 
408 // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64
409 // See https://github.com/bytecodealliance/wasmtime/issues/1967
410 #[test]
exit_with_saved_fprs() -> Result<()>411 fn exit_with_saved_fprs() -> Result<()> {
412     let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?;
413     let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
414     assert_eq!(output.status.code().unwrap(), 0);
415     assert!(output.stdout.is_empty());
416     Ok(())
417 }
418 
419 #[test]
run_cwasm() -> Result<()>420 fn run_cwasm() -> Result<()> {
421     let td = TempDir::new()?;
422     let cwasm = td.path().join("foo.cwasm");
423     let stdout = run_wasmtime(&[
424         "compile",
425         "tests/all/cli_tests/simple.wat",
426         "-o",
427         cwasm.to_str().unwrap(),
428     ])?;
429     assert_eq!(stdout, "");
430     let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?;
431     assert_eq!(stdout, "");
432     Ok(())
433 }
434 
435 #[cfg(unix)]
436 #[test]
hello_wasi_snapshot0_from_stdin() -> Result<()>437 fn hello_wasi_snapshot0_from_stdin() -> Result<()> {
438     // Run a simple WASI hello world, snapshot0 edition.
439     // The module is piped from standard input.
440     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
441     for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
442         let stdout = {
443             let path = wasm.path();
444             let args: &[&str] = &["-Ccache=n", preview2, "-"];
445             let output = run_wasmtime_for_output(args, Some(path))?;
446             if !output.status.success() {
447                 bail!(
448                     "Failed to execute wasmtime with: {:?}\n{}",
449                     args,
450                     String::from_utf8_lossy(&output.stderr)
451                 );
452             }
453             Ok::<_, wasmtime::Error>(String::from_utf8(output.stdout).unwrap())
454         }?;
455         assert_eq!(stdout, "Hello, world!\n");
456     }
457     Ok(())
458 }
459 
460 #[test]
specify_env() -> Result<()>461 fn specify_env() -> Result<()> {
462     // By default no env is inherited
463     let output = get_wasmtime_command()?
464         .args(&["run", "tests/all/cli_tests/print_env.wat"])
465         .env("THIS_WILL_NOT", "show up in the output")
466         .output()?;
467     assert!(output.status.success());
468     assert_eq!(String::from_utf8_lossy(&output.stdout), "");
469 
470     // Specify a single env var
471     let output = get_wasmtime_command()?
472         .args(&[
473             "run",
474             "--env",
475             "FOO=bar",
476             "tests/all/cli_tests/print_env.wat",
477         ])
478         .output()?;
479     assert!(output.status.success());
480     assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
481 
482     // Inherit a single env var
483     let output = get_wasmtime_command()?
484         .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"])
485         .env("FOO", "bar")
486         .output()?;
487     assert!(output.status.success());
488     assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
489 
490     // Inherit a nonexistent env var
491     let output = get_wasmtime_command()?
492         .args(&[
493             "run",
494             "--env",
495             "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT",
496             "tests/all/cli_tests/print_env.wat",
497         ])
498         .output()?;
499     assert!(output.status.success());
500 
501     // Inherit all env vars
502     let output = get_wasmtime_command()?
503         .args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"])
504         .env("FOO", "bar")
505         .output()?;
506     assert!(output.status.success());
507     let stdout = String::from_utf8_lossy(&output.stdout);
508     assert!(stdout.contains("FOO=bar"), "bad output: {stdout}");
509 
510     Ok(())
511 }
512 
513 #[cfg(unix)]
514 #[test]
run_cwasm_from_stdin() -> Result<()>515 fn run_cwasm_from_stdin() -> Result<()> {
516     use std::process::Stdio;
517 
518     let td = TempDir::new()?;
519     let cwasm = td.path().join("foo.cwasm");
520     let stdout = run_wasmtime(&[
521         "compile",
522         "tests/all/cli_tests/simple.wat",
523         "-o",
524         cwasm.to_str().unwrap(),
525     ])?;
526     assert_eq!(stdout, "");
527 
528     // If stdin is literally the file itself then that should work
529     let args: &[&str] = &["run", "--allow-precompiled", "-"];
530     let output = get_wasmtime_command()?
531         .args(args)
532         .stdin(File::open(&cwasm)?)
533         .output()?;
534     assert!(output.status.success(), "a file as stdin should work");
535 
536     // If stdin is a pipe, that should also work
537     let input = std::fs::read(&cwasm)?;
538     let mut child = get_wasmtime_command()?
539         .args(args)
540         .stdin(Stdio::piped())
541         .stdout(Stdio::piped())
542         .stderr(Stdio::piped())
543         .spawn()?;
544     let mut stdin = child.stdin.take().unwrap();
545     let t = std::thread::spawn(move || {
546         let _ = stdin.write_all(&input);
547     });
548     let output = child.wait_with_output()?;
549     assert!(output.status.success());
550     t.join().unwrap();
551     Ok(())
552 }
553 
554 #[cfg(feature = "wasi-threads")]
555 #[test]
run_threads() -> Result<()>556 fn run_threads() -> Result<()> {
557     // Only run threaded tests on platforms that support threads. Also skip
558     // these tests with ASAN as it, rightfully, complains about a memory leak.
559     // The memory leak at this time is that child threads aren't joined with the
560     // main thread, meaning that allocations done on child threads are indeed
561     // leaked.
562     if crate::threads::engine().is_none() || cfg!(asan) {
563         return Ok(());
564     }
565     let wasm = build_wasm("tests/all/cli_tests/threads.wat")?;
566     let stdout = run_wasmtime(&[
567         "run",
568         "-Wthreads,shared-memory",
569         "-Sthreads",
570         "-Ccache=n",
571         wasm.path().to_str().unwrap(),
572     ])?;
573 
574     assert!(
575         stdout
576             == "Called _start\n\
577     Running wasi_thread_start\n\
578     Running wasi_thread_start\n\
579     Running wasi_thread_start\n\
580     Done\n"
581     );
582     Ok(())
583 }
584 
585 #[cfg(feature = "wasi-threads")]
586 #[test]
run_simple_with_wasi_threads() -> Result<()>587 fn run_simple_with_wasi_threads() -> Result<()> {
588     // Skip this test on platforms that don't support threads.
589     if crate::threads::engine().is_none() {
590         return Ok(());
591     }
592     // We expect to be able to run Wasm modules that do not have correct
593     // wasi-thread entry points or imported shared memory as long as no threads
594     // are spawned.
595     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
596     let stdout = run_wasmtime(&[
597         "run",
598         "-Wthreads",
599         "-Sthreads",
600         "-Ccache=n",
601         "--invoke",
602         "simple",
603         wasm.path().to_str().unwrap(),
604         "4",
605     ])?;
606     assert_eq!(stdout, "4\n");
607     Ok(())
608 }
609 
610 #[test]
wasm_flags() -> Result<()>611 fn wasm_flags() -> Result<()> {
612     // Any argument after the wasm module should be interpreted as for the
613     // command itself
614     let stdout = run_wasmtime(&[
615         "run",
616         "--",
617         "tests/all/cli_tests/print-arguments.wat",
618         "--argument",
619         "-for",
620         "the",
621         "command",
622     ])?;
623     assert_eq!(
624         stdout,
625         "\
626             print-arguments.wat\n\
627             --argument\n\
628             -for\n\
629             the\n\
630             command\n\
631         "
632     );
633     let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?;
634     assert_eq!(
635         stdout,
636         "\
637             print-arguments.wat\n\
638             -\n\
639         "
640     );
641     let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?;
642     assert_eq!(
643         stdout,
644         "\
645             print-arguments.wat\n\
646             --\n\
647         "
648     );
649     let stdout = run_wasmtime(&[
650         "run",
651         "--",
652         "tests/all/cli_tests/print-arguments.wat",
653         "--",
654         "--",
655         "-a",
656         "b",
657     ])?;
658     assert_eq!(
659         stdout,
660         "\
661             print-arguments.wat\n\
662             --\n\
663             --\n\
664             -a\n\
665             b\n\
666         "
667     );
668     Ok(())
669 }
670 
671 #[test]
name_same_as_builtin_command() -> Result<()>672 fn name_same_as_builtin_command() -> Result<()> {
673     // a bare subcommand shouldn't run successfully
674     let output = get_wasmtime_command()?
675         .current_dir("tests/all/cli_tests")
676         .arg("run")
677         .output()?;
678     assert!(!output.status.success());
679 
680     // a `--` prefix should let everything else get interpreted as a wasm
681     // module and arguments, even if the module has a name like `run`
682     let output = get_wasmtime_command()?
683         .current_dir("tests/all/cli_tests")
684         .arg("--")
685         .arg("run")
686         .output()?;
687     assert!(output.status.success(), "expected success got {output:#?}");
688 
689     // Passing options before the subcommand should work and doesn't require
690     // `--` to disambiguate
691     let output = get_wasmtime_command()?
692         .current_dir("tests/all/cli_tests")
693         .arg("-Ccache=n")
694         .arg("run")
695         .output()?;
696     assert!(output.status.success(), "expected success got {output:#?}");
697     Ok(())
698 }
699 
700 #[test]
701 #[cfg(unix)]
run_just_stdin_argument() -> Result<()>702 fn run_just_stdin_argument() -> Result<()> {
703     let output = get_wasmtime_command()?
704         .arg("-")
705         .stdin(File::open("tests/all/cli_tests/simple.wat")?)
706         .output()?;
707     assert!(output.status.success());
708     Ok(())
709 }
710 
711 #[test]
wasm_flags_without_subcommand() -> Result<()>712 fn wasm_flags_without_subcommand() -> Result<()> {
713     let output = get_wasmtime_command()?
714         .current_dir("tests/all/cli_tests/")
715         .arg("print-arguments.wat")
716         .arg("-foo")
717         .arg("bar")
718         .output()?;
719     assert!(output.status.success());
720     assert_eq!(
721         String::from_utf8_lossy(&output.stdout),
722         "\
723             print-arguments.wat\n\
724             -foo\n\
725             bar\n\
726         "
727     );
728     Ok(())
729 }
730 
731 #[test]
wasi_misaligned_pointer() -> Result<()>732 fn wasi_misaligned_pointer() -> Result<()> {
733     let output = get_wasmtime_command()?
734         .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat")
735         .output()?;
736     assert!(!output.status.success());
737     let stderr = String::from_utf8_lossy(&output.stderr);
738     assert!(
739         stderr.contains("Pointer not aligned"),
740         "bad stderr: {stderr}",
741     );
742     Ok(())
743 }
744 
745 #[test]
746 #[cfg_attr(not(feature = "component-model"), ignore)]
hello_with_preview2() -> Result<()>747 fn hello_with_preview2() -> Result<()> {
748     let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
749     let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?;
750     assert_eq!(stdout, "Hello, world!\n");
751     Ok(())
752 }
753 
754 #[test]
755 #[cfg_attr(not(feature = "component-model"), ignore)]
component_missing_feature() -> Result<()>756 fn component_missing_feature() -> Result<()> {
757     let path = "tests/all/cli_tests/empty-component.wat";
758     let wasm = build_wasm(path)?;
759     let output = get_wasmtime_command()?
760         .arg("-Ccache=n")
761         .arg("-Wcomponent-model=n")
762         .arg(wasm.path())
763         .output()?;
764     assert!(!output.status.success());
765     let stderr = String::from_utf8_lossy(&output.stderr);
766     assert!(
767         stderr.contains("cannot execute a component without `--wasm component-model`"),
768         "bad stderr: {stderr}"
769     );
770 
771     // also tests with raw *.wat input
772     let output = get_wasmtime_command()?
773         .arg("-Ccache=n")
774         .arg("-Wcomponent-model=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 #[test]
788 #[cfg_attr(not(feature = "component-model"), ignore)]
component_enabled_by_default() -> Result<()>789 fn component_enabled_by_default() -> Result<()> {
790     let path = "tests/all/cli_tests/component-basic.wat";
791     let wasm = build_wasm(path)?;
792     let output = get_wasmtime_command()?
793         .arg("-Ccache=n")
794         .arg(wasm.path())
795         .output()?;
796     assert!(output.status.success());
797 
798     // also tests with raw *.wat input
799     let output = get_wasmtime_command()?
800         .arg("-Ccache=n")
801         .arg(path)
802         .output()?;
803     assert!(output.status.success());
804 
805     Ok(())
806 }
807 
808 // If the text format is invalid then the filename should be mentioned in the
809 // error message.
810 #[test]
bad_text_syntax() -> Result<()>811 fn bad_text_syntax() -> Result<()> {
812     let output = get_wasmtime_command()?
813         .arg("-Ccache=n")
814         .arg("tests/all/cli_tests/bad-syntax.wat")
815         .output()?;
816     assert!(!output.status.success());
817     let stderr = String::from_utf8_lossy(&output.stderr);
818     assert!(
819         stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"),
820         "bad stderr: {stderr}"
821     );
822     Ok(())
823 }
824 
825 #[test]
826 #[cfg_attr(not(feature = "component-model"), ignore)]
run_basic_component() -> Result<()>827 fn run_basic_component() -> Result<()> {
828     let path = "tests/all/cli_tests/component-basic.wat";
829     let wasm = build_wasm(path)?;
830 
831     // Run both the `*.wasm` binary and the text format
832     run_wasmtime(&[
833         "-Ccache=n",
834         "-Wcomponent-model",
835         wasm.path().to_str().unwrap(),
836     ])?;
837     run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?;
838 
839     Ok(())
840 }
841 
842 #[test]
843 #[cfg_attr(not(feature = "component-model"), ignore)]
run_precompiled_component() -> Result<()>844 fn run_precompiled_component() -> Result<()> {
845     let td = TempDir::new()?;
846     let cwasm = td.path().join("component-basic.cwasm");
847     let stdout = run_wasmtime(&[
848         "compile",
849         "tests/all/cli_tests/component-basic.wat",
850         "-o",
851         cwasm.to_str().unwrap(),
852         "-Wcomponent-model",
853     ])?;
854     assert_eq!(stdout, "");
855     let stdout = run_wasmtime(&[
856         "run",
857         "-Wcomponent-model",
858         "--allow-precompiled",
859         cwasm.to_str().unwrap(),
860     ])?;
861     assert_eq!(stdout, "");
862 
863     Ok(())
864 }
865 
866 // Disable test on s390x because the large allocation may actually succeed;
867 // the whole 64-bit address space is available on this platform.
868 #[test]
869 #[cfg(not(target_arch = "s390x"))]
memory_growth_failure() -> Result<()>870 fn memory_growth_failure() -> Result<()> {
871     let output = get_wasmtime_command()?
872         .args(&[
873             "run",
874             "-Wmemory64",
875             "-Wtrap-on-grow-failure",
876             "tests/all/cli_tests/memory-grow-failure.wat",
877         ])
878         .output()?;
879     let stderr = String::from_utf8_lossy(&output.stderr);
880     assert!(
881         stderr.contains("forcing a memory growth failure to be a trap"),
882         "bad stderr: {stderr}"
883     );
884     assert!(!output.status.success());
885     Ok(())
886 }
887 
888 #[test]
table_growth_failure() -> Result<()>889 fn table_growth_failure() -> Result<()> {
890     let output = get_wasmtime_command()?
891         .args(&[
892             "run",
893             "-Wtrap-on-grow-failure",
894             "tests/all/cli_tests/table-grow-failure.wat",
895         ])
896         .output()?;
897     assert!(!output.status.success());
898     let stderr = String::from_utf8_lossy(&output.stderr);
899     assert!(
900         stderr.contains("forcing trap when growing table"),
901         "bad stderr: {stderr}"
902     );
903     Ok(())
904 }
905 
906 #[test]
table_growth_failure2() -> Result<()>907 fn table_growth_failure2() -> Result<()> {
908     let output = get_wasmtime_command()?
909         .args(&[
910             "run",
911             "-Wtrap-on-grow-failure",
912             "tests/all/cli_tests/table-grow-failure2.wat",
913         ])
914         .output()?;
915     assert!(!output.status.success());
916     let stderr = String::from_utf8_lossy(&output.stderr);
917     let expected = if cfg!(target_pointer_width = "32") {
918         "overflow calculating new table size"
919     } else {
920         "forcing trap when growing table to 4294967296 elements"
921     };
922     assert!(stderr.contains(expected), "bad stderr: {stderr}");
923     Ok(())
924 }
925 
926 #[test]
option_group_help() -> Result<()>927 fn option_group_help() -> Result<()> {
928     run_wasmtime(&["run", "-Whelp"])?;
929     run_wasmtime(&["run", "-O", "help"])?;
930     run_wasmtime(&["run", "--codegen", "help"])?;
931     run_wasmtime(&["run", "--debug=help"])?;
932     run_wasmtime(&["run", "-Shelp"])?;
933     run_wasmtime(&["run", "-Whelp-long"])?;
934     Ok(())
935 }
936 
937 #[test]
option_group_comma_separated() -> Result<()>938 fn option_group_comma_separated() -> Result<()> {
939     run_wasmtime(&[
940         "run",
941         "-Wrelaxed-simd,simd",
942         "tests/all/cli_tests/simple.wat",
943     ])?;
944     Ok(())
945 }
946 
947 #[test]
option_group_boolean_parsing() -> Result<()>948 fn option_group_boolean_parsing() -> Result<()> {
949     run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?;
950     run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?;
951     run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?;
952     run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?;
953     run_wasmtime(&[
954         "run",
955         "-Wrelaxed-simd=yes",
956         "tests/all/cli_tests/simple.wat",
957     ])?;
958     run_wasmtime(&[
959         "run",
960         "-Wrelaxed-simd=true",
961         "tests/all/cli_tests/simple.wat",
962     ])?;
963     run_wasmtime(&[
964         "run",
965         "-Wrelaxed-simd=false",
966         "tests/all/cli_tests/simple.wat",
967     ])?;
968     Ok(())
969 }
970 
971 #[test]
preview2_stdin() -> Result<()>972 fn preview2_stdin() -> Result<()> {
973     let test = "tests/all/cli_tests/count-stdin.wat";
974     let cmd = || -> Result<_> {
975         let mut cmd = get_wasmtime_command()?;
976         cmd.arg("--invoke=count").arg("-Spreview2").arg(test);
977         Ok(cmd)
978     };
979 
980     // read empty pipe is ok
981     let output = cmd()?.output()?;
982     assert!(output.status.success());
983     assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");
984 
985     // read itself is ok
986     let file = File::open(test)?;
987     let size = file.metadata()?.len();
988     let output = cmd()?.stdin(File::open(test)?).output()?;
989     assert!(output.status.success());
990     assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n"));
991 
992     // read piped input ok is ok
993     let mut child = cmd()?
994         .stdin(Stdio::piped())
995         .stdout(Stdio::piped())
996         .stderr(Stdio::piped())
997         .spawn()?;
998     let mut stdin = child.stdin.take().unwrap();
999     std::thread::spawn(move || {
1000         stdin.write_all(b"hello").unwrap();
1001     });
1002     let output = child.wait_with_output()?;
1003     assert!(output.status.success());
1004     assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n");
1005 
1006     let count_up_to = |n: usize| -> Result<_> {
1007         let mut child = get_wasmtime_command()?
1008             .arg("--invoke=count-up-to")
1009             .arg("-Spreview2")
1010             .arg(test)
1011             .arg(n.to_string())
1012             .stdin(Stdio::piped())
1013             .stdout(Stdio::piped())
1014             .stderr(Stdio::piped())
1015             .spawn()?;
1016         let mut stdin = child.stdin.take().unwrap();
1017         let t = std::thread::spawn(move || {
1018             let mut written = 0;
1019             let bytes = [0; 64 * 1024];
1020             loop {
1021                 written += match stdin.write(&bytes) {
1022                     Ok(n) => n,
1023                     Err(_) => break written,
1024                 };
1025             }
1026         });
1027         let output = child.wait_with_output()?;
1028         assert!(output.status.success());
1029         let written = t.join().unwrap();
1030         let read = String::from_utf8_lossy(&output.stdout)
1031             .trim()
1032             .parse::<usize>()
1033             .unwrap();
1034         // The test reads in 1000 byte chunks so make sure that it doesn't read
1035         // more than 1000 bytes than requested.
1036         assert!(read < n + 1000, "test read too much {read}");
1037         Ok(written)
1038     };
1039 
1040     // wasmtime shouldn't eat information that the guest never actually tried to
1041     // read.
1042     //
1043     // NB: this may be a bit flaky. Exactly how much we wrote in the above
1044     // helper thread depends on how much the OS buffers for us. For now give
1045     // some some slop and assume that OSes are unlikely to buffer more than
1046     // that.
1047     let slop = 256 * 1024;
1048     for amt in [0, 100, 100_000] {
1049         let written = count_up_to(amt)?;
1050         assert!(written < slop + amt, "wrote too much {written}");
1051     }
1052     Ok(())
1053 }
1054 
1055 #[test]
float_args() -> Result<()>1056 fn float_args() -> Result<()> {
1057     let result = run_wasmtime(&[
1058         "--invoke",
1059         "echo_f32",
1060         "tests/all/cli_tests/simple.wat",
1061         "1.0",
1062     ])?;
1063     assert_eq!(result, "1\n");
1064     let result = run_wasmtime(&[
1065         "--invoke",
1066         "echo_f64",
1067         "tests/all/cli_tests/simple.wat",
1068         "1.1",
1069     ])?;
1070     assert_eq!(result, "1.1\n");
1071     Ok(())
1072 }
1073 
1074 #[test]
mpk_without_pooling() -> Result<()>1075 fn mpk_without_pooling() -> Result<()> {
1076     let output = get_wasmtime_command()?
1077         .args(&[
1078             "run",
1079             "-O",
1080             "memory-protection-keys=y",
1081             "--invoke",
1082             "echo_f32",
1083             "tests/all/cli_tests/simple.wat",
1084             "1.0",
1085         ])
1086         .env("WASMTIME_NEW_CLI", "1")
1087         .output()?;
1088     assert!(!output.status.success());
1089     Ok(())
1090 }
1091 
1092 // Very basic use case: compile binary wasm file and run specific function with arguments.
1093 #[test]
increase_stack_size() -> Result<()>1094 fn increase_stack_size() -> Result<()> {
1095     run_wasmtime(&[
1096         "run",
1097         "--invoke",
1098         "simple",
1099         &format!("-Wmax-wasm-stack={}", 5 << 20),
1100         "-Ccache=n",
1101         "tests/all/cli_tests/simple.wat",
1102         "4",
1103     ])?;
1104     Ok(())
1105 }
1106 
1107 mod test_programs {
1108     use super::{get_wasmtime_command, run_wasmtime};
1109     use http_body_util::BodyExt;
1110     use hyper::header::HeaderValue;
1111     use std::io::{self, BufRead, BufReader, Read, Write};
1112     use std::iter;
1113     use std::net::SocketAddr;
1114     use std::process::{Child, Command, Stdio};
1115     use std::thread::{self, JoinHandle};
1116     use test_programs_artifacts::*;
1117     use tokio::net::TcpStream;
1118     use wasmtime::{Result, bail, error::Context as _, format_err};
1119 
1120     macro_rules! assert_test_exists {
1121         ($name:ident) => {
1122             #[expect(unused_imports, reason = "just here to assert the test is here")]
1123             use self::$name as _;
1124         };
1125     }
1126     foreach_cli!(assert_test_exists);
1127 
1128     #[test]
p2_cli_hello_stdout() -> Result<()>1129     fn p2_cli_hello_stdout() -> Result<()> {
1130         run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_HELLO_STDOUT_COMPONENT])?;
1131         Ok(())
1132     }
1133 
1134     #[test]
p2_cli_args() -> Result<()>1135     fn p2_cli_args() -> Result<()> {
1136         run_wasmtime(&[
1137             "run",
1138             "-Wcomponent-model",
1139             P2_CLI_ARGS_COMPONENT,
1140             "hello",
1141             "this",
1142             "",
1143             "is an argument",
1144             "with �� emoji",
1145         ])?;
1146         Ok(())
1147     }
1148 
1149     #[test]
p2_cli_stdin_empty() -> Result<()>1150     fn p2_cli_stdin_empty() -> Result<()> {
1151         let mut child = get_wasmtime_command()?
1152             .args(&["run", "-Wcomponent-model", P2_CLI_STDIN_EMPTY_COMPONENT])
1153             .stdout(Stdio::piped())
1154             .stderr(Stdio::piped())
1155             .stdin(Stdio::piped())
1156             .spawn()?;
1157         child
1158             .stdin
1159             .take()
1160             .unwrap()
1161             .write_all(b"not to be read")
1162             .unwrap();
1163         let output = child.wait_with_output()?;
1164         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1165         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1166         assert!(output.status.success());
1167         Ok(())
1168     }
1169 
1170     #[test]
p2_cli_stdin() -> Result<()>1171     fn p2_cli_stdin() -> Result<()> {
1172         let mut child = get_wasmtime_command()?
1173             .args(&["run", "-Wcomponent-model", P2_CLI_STDIN_COMPONENT])
1174             .stdout(Stdio::piped())
1175             .stderr(Stdio::piped())
1176             .stdin(Stdio::piped())
1177             .spawn()?;
1178         child
1179             .stdin
1180             .take()
1181             .unwrap()
1182             .write_all(b"So rested he by the Tumtum tree")
1183             .unwrap();
1184         let output = child.wait_with_output()?;
1185         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1186         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1187         assert!(output.status.success());
1188         Ok(())
1189     }
1190 
1191     #[test]
p2_cli_splice_stdin() -> Result<()>1192     fn p2_cli_splice_stdin() -> Result<()> {
1193         let mut child = get_wasmtime_command()?
1194             .args(&["run", "-Wcomponent-model", P2_CLI_SPLICE_STDIN_COMPONENT])
1195             .stdout(Stdio::piped())
1196             .stderr(Stdio::piped())
1197             .stdin(Stdio::piped())
1198             .spawn()?;
1199         let msg = "So rested he by the Tumtum tree";
1200         child
1201             .stdin
1202             .take()
1203             .unwrap()
1204             .write_all(msg.as_bytes())
1205             .unwrap();
1206         let output = child.wait_with_output()?;
1207         assert!(output.status.success());
1208         let stdout = String::from_utf8_lossy(&output.stdout);
1209         let stderr = String::from_utf8_lossy(&output.stderr);
1210         if !stderr.is_empty() {
1211             eprintln!("{stderr}");
1212         }
1213 
1214         assert_eq!(
1215             format!(
1216                 "before splice\n{msg}\ncompleted splicing {} bytes\n",
1217                 msg.as_bytes().len()
1218             ),
1219             stdout
1220         );
1221         Ok(())
1222     }
1223 
1224     #[test]
p2_cli_env() -> Result<()>1225     fn p2_cli_env() -> Result<()> {
1226         run_wasmtime(&[
1227             "run",
1228             "-Wcomponent-model",
1229             "--env=frabjous=day",
1230             "--env=callooh=callay",
1231             P2_CLI_ENV_COMPONENT,
1232         ])?;
1233         Ok(())
1234     }
1235 
1236     #[test]
p2_cli_file_read() -> Result<()>1237     fn p2_cli_file_read() -> Result<()> {
1238         let dir = tempfile::tempdir()?;
1239 
1240         std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1241 
1242         run_wasmtime(&[
1243             "run",
1244             "-Wcomponent-model",
1245             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1246             P2_CLI_FILE_READ_COMPONENT,
1247         ])?;
1248         Ok(())
1249     }
1250 
1251     #[test]
p2_cli_file_append() -> Result<()>1252     fn p2_cli_file_append() -> Result<()> {
1253         let dir = tempfile::tempdir()?;
1254 
1255         std::fs::File::create(dir.path().join("bar.txt"))?
1256             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1257 
1258         run_wasmtime(&[
1259             "run",
1260             "-Wcomponent-model",
1261             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1262             P2_CLI_FILE_APPEND_COMPONENT,
1263         ])?;
1264 
1265         let contents = std::fs::read(dir.path().join("bar.txt"))?;
1266         assert_eq!(
1267             std::str::from_utf8(&contents).unwrap(),
1268             "'Twas brillig, and the slithy toves.\n\
1269                    Did gyre and gimble in the wabe;\n\
1270                    All mimsy were the borogoves,\n\
1271                    And the mome raths outgrabe.\n"
1272         );
1273         Ok(())
1274     }
1275 
1276     #[test]
p2_cli_file_dir_sync() -> Result<()>1277     fn p2_cli_file_dir_sync() -> Result<()> {
1278         let dir = tempfile::tempdir()?;
1279 
1280         std::fs::File::create(dir.path().join("bar.txt"))?
1281             .write_all(b"'Twas brillig, and the slithy toves.\n")?;
1282 
1283         run_wasmtime(&[
1284             "run",
1285             "-Wcomponent-model",
1286             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1287             P2_CLI_FILE_DIR_SYNC_COMPONENT,
1288         ])?;
1289 
1290         Ok(())
1291     }
1292 
1293     #[test]
p2_cli_exit_success() -> Result<()>1294     fn p2_cli_exit_success() -> Result<()> {
1295         run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_EXIT_SUCCESS_COMPONENT])?;
1296         Ok(())
1297     }
1298 
1299     #[test]
p2_cli_exit_default() -> Result<()>1300     fn p2_cli_exit_default() -> Result<()> {
1301         run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_EXIT_DEFAULT_COMPONENT])?;
1302         Ok(())
1303     }
1304 
1305     #[test]
p2_cli_exit_failure() -> Result<()>1306     fn p2_cli_exit_failure() -> Result<()> {
1307         let output = get_wasmtime_command()?
1308             .args(&["run", "-Wcomponent-model", P2_CLI_EXIT_FAILURE_COMPONENT])
1309             .output()?;
1310         assert!(!output.status.success());
1311         assert_eq!(output.status.code(), Some(1));
1312         Ok(())
1313     }
1314 
1315     #[test]
p2_cli_exit_with_code() -> Result<()>1316     fn p2_cli_exit_with_code() -> Result<()> {
1317         let output = get_wasmtime_command()?
1318             .args(&[
1319                 "run",
1320                 "-Wcomponent-model",
1321                 "-Scli-exit-with-code",
1322                 P2_CLI_EXIT_WITH_CODE_COMPONENT,
1323             ])
1324             .output()?;
1325         assert!(!output.status.success());
1326         assert_eq!(output.status.code(), Some(42));
1327         Ok(())
1328     }
1329 
1330     #[test]
p2_cli_exit_panic() -> Result<()>1331     fn p2_cli_exit_panic() -> Result<()> {
1332         let output = get_wasmtime_command()?
1333             .args(&["run", "-Wcomponent-model", P2_CLI_EXIT_PANIC_COMPONENT])
1334             .output()?;
1335         assert!(!output.status.success());
1336         let stderr = String::from_utf8_lossy(&output.stderr);
1337         assert!(stderr.contains("Curiouser and curiouser!"));
1338         Ok(())
1339     }
1340 
1341     #[test]
p2_cli_directory_list() -> Result<()>1342     fn p2_cli_directory_list() -> Result<()> {
1343         let dir = tempfile::tempdir()?;
1344 
1345         std::fs::File::create(dir.path().join("foo.txt"))?;
1346         std::fs::File::create(dir.path().join("bar.txt"))?;
1347         std::fs::File::create(dir.path().join("baz.txt"))?;
1348         std::fs::create_dir(dir.path().join("sub"))?;
1349         std::fs::File::create(dir.path().join("sub").join("wow.txt"))?;
1350         std::fs::File::create(dir.path().join("sub").join("yay.txt"))?;
1351 
1352         run_wasmtime(&[
1353             "run",
1354             "-Wcomponent-model",
1355             &format!("--dir={}::/", dir.path().to_str().unwrap()),
1356             P2_CLI_DIRECTORY_LIST_COMPONENT,
1357         ])?;
1358         Ok(())
1359     }
1360 
1361     #[test]
p2_cli_default_clocks() -> Result<()>1362     fn p2_cli_default_clocks() -> Result<()> {
1363         run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_DEFAULT_CLOCKS_COMPONENT])?;
1364         Ok(())
1365     }
1366 
1367     #[test]
p2_cli_export_cabi_realloc() -> Result<()>1368     fn p2_cli_export_cabi_realloc() -> Result<()> {
1369         run_wasmtime(&[
1370             "run",
1371             "-Wcomponent-model",
1372             P2_CLI_EXPORT_CABI_REALLOC_COMPONENT,
1373         ])?;
1374         Ok(())
1375     }
1376 
1377     #[test]
run_wasi_http_component() -> Result<()>1378     fn run_wasi_http_component() -> Result<()> {
1379         let output = super::run_wasmtime_for_output(
1380             &[
1381                 "-Ccache=no",
1382                 "-Wcomponent-model",
1383                 "-Scli,http,preview2",
1384                 P2_HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
1385             ],
1386             None,
1387         )?;
1388         println!("{}", String::from_utf8_lossy(&output.stderr));
1389         let stdout = String::from_utf8_lossy(&output.stdout);
1390         println!("{stdout}");
1391         assert!(stdout.starts_with("Called _start\n"));
1392         assert!(stdout.ends_with("Done\n"));
1393         assert!(output.status.success());
1394         Ok(())
1395     }
1396 
1397     // Test to ensure that prints in the guest aren't buffered on the host by
1398     // accident. The test here will print something without a newline and then
1399     // wait for input on stdin, and the test here is to ensure that the
1400     // character shows up here even as the guest is waiting on input via stdin.
1401     #[test]
p2_cli_stdio_write_flushes() -> Result<()>1402     fn p2_cli_stdio_write_flushes() -> Result<()> {
1403         fn run(args: &[&str]) -> Result<()> {
1404             println!("running {args:?}");
1405             let mut child = get_wasmtime_command()?
1406                 .args(args)
1407                 .stdin(Stdio::piped())
1408                 .stdout(Stdio::piped())
1409                 .spawn()?;
1410             let mut stdout = child.stdout.take().unwrap();
1411             let mut buf = [0; 10];
1412             match stdout.read(&mut buf) {
1413                 Ok(2) => assert_eq!(&buf[..2], b"> "),
1414                 e => panic!("unexpected read result {e:?}"),
1415             }
1416             drop(stdout);
1417             drop(child.stdin.take().unwrap());
1418             let status = child.wait()?;
1419             assert!(status.success());
1420             Ok(())
1421         }
1422 
1423         run(&["run", "-Spreview2=n", P2_CLI_STDIO_WRITE_FLUSHES])?;
1424         run(&["run", "-Spreview2=y", P2_CLI_STDIO_WRITE_FLUSHES])?;
1425         run(&[
1426             "run",
1427             "-Wcomponent-model",
1428             P2_CLI_STDIO_WRITE_FLUSHES_COMPONENT,
1429         ])?;
1430         Ok(())
1431     }
1432 
1433     #[test]
p2_cli_no_tcp() -> Result<()>1434     fn p2_cli_no_tcp() -> Result<()> {
1435         let output = super::run_wasmtime_for_output(
1436             &[
1437                 "-Wcomponent-model",
1438                 // Turn on network but turn off TCP
1439                 "-Sinherit-network,tcp=no",
1440                 P2_CLI_NO_TCP_COMPONENT,
1441             ],
1442             None,
1443         )?;
1444         println!("{}", String::from_utf8_lossy(&output.stderr));
1445         assert!(output.status.success());
1446         Ok(())
1447     }
1448 
1449     #[test]
p2_cli_no_udp() -> Result<()>1450     fn p2_cli_no_udp() -> Result<()> {
1451         let output = super::run_wasmtime_for_output(
1452             &[
1453                 "-Wcomponent-model",
1454                 // Turn on network but turn off UDP
1455                 "-Sinherit-network,udp=no",
1456                 P2_CLI_NO_UDP_COMPONENT,
1457             ],
1458             None,
1459         )?;
1460         println!("{}", String::from_utf8_lossy(&output.stderr));
1461         assert!(output.status.success());
1462         Ok(())
1463     }
1464 
1465     #[test]
p2_cli_no_ip_name_lookup() -> Result<()>1466     fn p2_cli_no_ip_name_lookup() -> Result<()> {
1467         let output = super::run_wasmtime_for_output(
1468             &[
1469                 "-Wcomponent-model",
1470                 // Turn on network but ensure name lookup is disabled
1471                 "-Sinherit-network,allow-ip-name-lookup=no",
1472                 P2_CLI_NO_IP_NAME_LOOKUP_COMPONENT,
1473             ],
1474             None,
1475         )?;
1476         println!("{}", String::from_utf8_lossy(&output.stderr));
1477         assert!(output.status.success());
1478         Ok(())
1479     }
1480 
1481     #[test]
p2_cli_sleep() -> Result<()>1482     fn p2_cli_sleep() -> Result<()> {
1483         run_wasmtime(&["run", P2_CLI_SLEEP])?;
1484         run_wasmtime(&["run", P2_CLI_SLEEP_COMPONENT])?;
1485         Ok(())
1486     }
1487 
1488     #[test]
p2_cli_sleep_forever() -> Result<()>1489     fn p2_cli_sleep_forever() -> Result<()> {
1490         for timeout in [
1491             // Tests still pass when we race with going to sleep.
1492             "-Wtimeout=1ns",
1493             // Tests pass when we wait till the Wasm has (likely) gone to sleep.
1494             "-Wtimeout=250ms",
1495         ] {
1496             let e = run_wasmtime(&["run", timeout, P2_CLI_SLEEP_FOREVER]).unwrap_err();
1497             let e = e.to_string();
1498             println!("Got error: {e}");
1499             assert!(e.contains("interrupt"));
1500 
1501             let e = run_wasmtime(&["run", timeout, P2_CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err();
1502             let e = e.to_string();
1503             println!("Got error: {e}");
1504             assert!(e.contains("interrupt"));
1505         }
1506 
1507         Ok(())
1508     }
1509 
1510     /// Helper structure to manage an invocation of `wasmtime serve`
1511     struct WasmtimeServe {
1512         child: Option<Child>,
1513         stdout: Option<JoinHandle<io::Result<Vec<u8>>>>,
1514         stderr: Option<JoinHandle<io::Result<Vec<u8>>>>,
1515         addr: SocketAddr,
1516         shutdown_addr: SocketAddr,
1517     }
1518 
1519     impl WasmtimeServe {
1520         /// Creates a new server which will serve the wasm component pointed to
1521         /// by `wasm`.
1522         ///
1523         /// A `configure` callback is provided to specify how `wasmtime serve`
1524         /// will be invoked and configure arguments such as headers.
new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe>1525         fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
1526             // Spawn `wasmtime serve` on port 0 which will randomly assign it a
1527             // port.
1528             let mut cmd = super::get_wasmtime_command()?;
1529             cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm);
1530             configure(&mut cmd);
1531             Self::spawn(&mut cmd)
1532         }
1533 
spawn(cmd: &mut Command) -> Result<WasmtimeServe>1534         fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> {
1535             cmd.arg("--shutdown-addr=127.0.0.1:0");
1536             cmd.stdin(Stdio::null());
1537             cmd.stdout(Stdio::piped());
1538             cmd.stderr(Stdio::piped());
1539             let mut child = cmd.spawn()?;
1540 
1541             // Read the first few lines of stderr which will say which address
1542             // it's listening on. The first line is the shutdown line (with
1543             // `--shutdown-addr`) and the second is what `--addr` was bound to.
1544             // This is done to figure out what `:0` was bound to in the child
1545             // process.
1546             let mut line = String::new();
1547             let mut stderr = BufReader::new(child.stderr.take().unwrap());
1548             let mut read_addr_from_line = |prefix: &str| -> Result<SocketAddr> {
1549                 stderr.read_line(&mut line)?;
1550 
1551                 if !line.starts_with(prefix) {
1552                     bail!("input line `{line}` didn't start with `{prefix}`");
1553                 }
1554                 match line.find("127.0.0.1").and_then(|addr_start| {
1555                     let addr = &line[addr_start..];
1556                     let addr_end = addr.find("/")?;
1557                     addr[..addr_end].parse().ok()
1558                 }) {
1559                     Some(addr) => {
1560                         line.truncate(0);
1561                         Ok(addr)
1562                     }
1563                     None => bail!("failed to address from: {line}"),
1564                 }
1565             };
1566             let shutdown_addr = read_addr_from_line("Listening for shutdown");
1567             let addr = read_addr_from_line("Serving HTTP on");
1568             let (shutdown_addr, addr) = match (shutdown_addr, addr) {
1569                 (Ok(a), Ok(b)) => (a, b),
1570                 // If either failed kill the child and otherwise try to shepherd
1571                 // along any contextual information we have.
1572                 (Err(a), _) | (_, Err(a)) => {
1573                     child.kill()?;
1574                     child.wait()?;
1575                     stderr.read_to_string(&mut line)?;
1576                     return Err(a.context(line));
1577                 }
1578             };
1579             let mut stdout = child.stdout.take().unwrap();
1580             Ok(WasmtimeServe {
1581                 stdout: Some(thread::spawn(move || {
1582                     let mut dst = Vec::new();
1583                     stdout.read_to_end(&mut dst).map(|_| dst)
1584                 })),
1585 
1586                 stderr: Some(thread::spawn(move || {
1587                     let mut dst = Vec::new();
1588                     stderr.read_to_end(&mut dst).map(|_| dst)
1589                 })),
1590 
1591                 child: Some(child),
1592                 addr,
1593                 shutdown_addr,
1594             })
1595         }
1596 
1597         /// Completes this server gracefully by printing the output on failure.
finish(mut self) -> Result<(String, String)>1598         fn finish(mut self) -> Result<(String, String)> {
1599             self._finish()
1600         }
1601 
_finish(&mut self) -> Result<(String, String)>1602         fn _finish(&mut self) -> Result<(String, String)> {
1603             let mut child = self.child.take().unwrap();
1604 
1605             // If the child process has already exited, then great! Otherwise
1606             // the server is still running and it shouldn't be possible to exit
1607             // until a shutdown signal is sent, so do that here. Make a TCP
1608             // connection to the shutdown port which is used as a shutdown
1609             // signal.
1610             if child.try_wait()?.is_none() {
1611                 std::net::TcpStream::connect(&self.shutdown_addr)
1612                     .context("failed to initiate graceful shutdown")?;
1613             }
1614 
1615             // Regardless of whether we just shut the server down or whether it
1616             // was already shut down (e.g. panicked or similar), wait for the
1617             // result here. The result should succeed (e.g. 0 exit status), and
1618             // if it did then the stdout/stderr are the caller's problem.
1619             let mut output = child.wait_with_output()?;
1620             output.stdout = self.stdout.take().unwrap().join().unwrap()?;
1621             output.stderr = self.stderr.take().unwrap().join().unwrap()?;
1622             if !output.status.success() {
1623                 bail!("child failed {output:?}");
1624             }
1625 
1626             Ok((
1627                 String::from_utf8_lossy(&output.stdout).into_owned(),
1628                 String::from_utf8_lossy(&output.stderr).into_owned(),
1629             ))
1630         }
1631 
1632         /// Send a request to this server and wait for the response.
send_request(&self, req: http::Request<String>) -> Result<http::Response<String>>1633         async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> {
1634             let (mut send, conn_task) = self.start_requests().await?;
1635 
1636             let response = Self::send_request_with(&mut send, req).await?;
1637             drop(send);
1638 
1639             conn_task.await??;
1640 
1641             Ok(response)
1642         }
1643 
send_request_with( send: &mut hyper::client::conn::http1::SendRequest<String>, req: http::Request<String>, ) -> Result<http::Response<String>>1644         async fn send_request_with(
1645             send: &mut hyper::client::conn::http1::SendRequest<String>,
1646             req: http::Request<String>,
1647         ) -> Result<http::Response<String>> {
1648             let response = send
1649                 .send_request(req)
1650                 .await
1651                 .context("error sending request")?;
1652 
1653             let (parts, body) = response.into_parts();
1654 
1655             let body = body.collect().await.context("failed to read body")?;
1656             assert!(body.trailers().is_none());
1657             let body = std::str::from_utf8(&body.to_bytes())?.to_string();
1658 
1659             Ok(http::Response::from_parts(parts, body))
1660         }
1661 
start_requests( &self, ) -> Result<( hyper::client::conn::http1::SendRequest<String>, tokio::task::JoinHandle<hyper::Result<()>>, )>1662         async fn start_requests(
1663             &self,
1664         ) -> Result<(
1665             hyper::client::conn::http1::SendRequest<String>,
1666             tokio::task::JoinHandle<hyper::Result<()>>,
1667         )> {
1668             let tcp = TcpStream::connect(&self.addr)
1669                 .await
1670                 .context("failed to connect")?;
1671             let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp);
1672             let (send, conn) = hyper::client::conn::http1::handshake(tcp)
1673                 .await
1674                 .context("failed http handshake")?;
1675             Ok((send, tokio::task::spawn(conn)))
1676         }
1677     }
1678 
1679     // Don't leave child processes running by accident so kill the child process
1680     // if our server goes away.
1681     impl Drop for WasmtimeServe {
drop(&mut self)1682         fn drop(&mut self) {
1683             match &mut self.child {
1684                 Some(child) => match child.kill() {
1685                     Ok(()) => {}
1686                     Err(e) => {
1687                         eprintln!("failed to kill child process {e}");
1688                         return;
1689                     }
1690                 },
1691                 None => return,
1692             }
1693             match self._finish() {
1694                 Ok((stdout, stderr)) => {
1695                     if !stdout.is_empty() {
1696                         println!("server stdout:\n{stdout}");
1697                     }
1698                     if !stderr.is_empty() {
1699                         println!("server stderr:\n{stderr}");
1700                     }
1701                 }
1702                 Err(e) => println!("failed to wait for child or read stdio: {e}"),
1703             }
1704         }
1705     }
1706 
1707     #[tokio::test]
p2_cli_serve_echo_env() -> Result<()>1708     async fn p2_cli_serve_echo_env() -> Result<()> {
1709         let server = WasmtimeServe::new(P2_CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1710             cmd.arg("--env=FOO=bar");
1711             cmd.arg("--env=BAR");
1712             cmd.arg("-Scli");
1713             cmd.env_remove("BAR");
1714         })?;
1715 
1716         let foo_env = server
1717             .send_request(
1718                 hyper::Request::builder()
1719                     .uri("http://localhost/")
1720                     .header("env", "FOO")
1721                     .body(String::new())
1722                     .context("failed to make request")?,
1723             )
1724             .await?;
1725 
1726         assert!(foo_env.status().is_success());
1727         assert!(foo_env.body().is_empty());
1728         let headers = foo_env.headers();
1729         assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar")));
1730 
1731         let bar_env = server
1732             .send_request(
1733                 hyper::Request::builder()
1734                     .uri("http://localhost/")
1735                     .header("env", "BAR")
1736                     .body(String::new())
1737                     .context("failed to make request")?,
1738             )
1739             .await?;
1740 
1741         assert!(bar_env.status().is_success());
1742         assert!(bar_env.body().is_empty());
1743         let headers = bar_env.headers();
1744         assert_eq!(headers.get("env"), None);
1745 
1746         server.finish()?;
1747         Ok(())
1748     }
1749 
1750     #[tokio::test]
p2_cli_serve_outgoing_body_config() -> Result<()>1751     async fn p2_cli_serve_outgoing_body_config() -> Result<()> {
1752         let server = WasmtimeServe::new(P2_CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1753             cmd.arg("-Scli");
1754             cmd.arg("-Shttp-outgoing-body-buffer-chunks=2");
1755             cmd.arg("-Shttp-outgoing-body-chunk-size=1024");
1756         })?;
1757 
1758         let resp = server
1759             .send_request(
1760                 hyper::Request::builder()
1761                     .uri("http://localhost/")
1762                     .header("env", "FOO")
1763                     .body(String::new())
1764                     .context("failed to make request")?,
1765             )
1766             .await?;
1767 
1768         assert!(resp.status().is_success());
1769 
1770         server.finish()?;
1771         Ok(())
1772     }
1773 
1774     #[tokio::test]
1775     #[ignore] // TODO: printing stderr in the child and killing the child at the
1776     // end of this test race so the stderr may be present or not. Need
1777     // to implement a more graceful shutdown routine for `wasmtime
1778     // serve`.
p2_cli_serve_respect_pooling_options() -> Result<()>1779     async fn p2_cli_serve_respect_pooling_options() -> Result<()> {
1780         let server = WasmtimeServe::new(P2_CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1781             cmd.arg("-Opooling-total-memories=0").arg("-Scli");
1782         })?;
1783 
1784         let result = server
1785             .send_request(
1786                 hyper::Request::builder()
1787                     .uri("http://localhost/")
1788                     .header("env", "FOO")
1789                     .body(String::new())
1790                     .context("failed to make request")?,
1791             )
1792             .await;
1793         assert!(result.is_err());
1794         let (_, stderr) = server.finish()?;
1795         assert!(
1796             stderr.contains("maximum concurrent memory limit of 0 reached"),
1797             "bad stderr: {stderr}",
1798         );
1799         Ok(())
1800     }
1801 
1802     #[test]
p2_cli_large_env() -> Result<()>1803     fn p2_cli_large_env() -> Result<()> {
1804         for wasm in [P2_CLI_LARGE_ENV, P2_CLI_LARGE_ENV_COMPONENT] {
1805             println!("run {wasm:?}");
1806             let mut cmd = get_wasmtime_command()?;
1807             cmd.arg("run").arg("-Sinherit-env").arg(wasm);
1808 
1809             let debug_cmd = format!("{cmd:?}");
1810             for i in 0..512 {
1811                 let var = format!("KEY{i}");
1812                 let val = (0..1024).map(|_| 'x').collect::<String>();
1813                 cmd.env(&var, &val);
1814             }
1815             let output = cmd.output()?;
1816             if !output.status.success() {
1817                 bail!(
1818                     "Failed to execute wasmtime with: {debug_cmd}\n{}",
1819                     String::from_utf8_lossy(&output.stderr)
1820                 );
1821             }
1822         }
1823         Ok(())
1824     }
1825 
1826     #[tokio::test]
p2_cli_serve_only_one_process_allowed() -> Result<()>1827     async fn p2_cli_serve_only_one_process_allowed() -> Result<()> {
1828         let wasm = P2_CLI_SERVE_ECHO_ENV_COMPONENT;
1829         let server = WasmtimeServe::new(wasm, |cmd| {
1830             cmd.arg("-Scli");
1831         })?;
1832 
1833         let err = WasmtimeServe::spawn(
1834             super::get_wasmtime_command()?
1835                 .arg("serve")
1836                 .arg("-Scli")
1837                 .arg(format!("--addr={}", server.addr))
1838                 .arg(wasm),
1839         )
1840         .err()
1841         .expect("server spawn should have failed but it succeeded");
1842         drop(server);
1843 
1844         let err = format!("{err:?}");
1845         println!("{err}");
1846         assert!(err.contains("os error"));
1847         Ok(())
1848     }
1849 
1850     // Technically this test is a little racy. This binds port 0 to acquire a
1851     // random port, issues a single request to this port, but then kills this
1852     // server while the request is still processing. The port is then rebound
1853     // in the next process while it technically could be stolen by another
1854     // process.
1855     #[tokio::test]
p2_cli_serve_quick_rebind_allowed() -> Result<()>1856     async fn p2_cli_serve_quick_rebind_allowed() -> Result<()> {
1857         let wasm = P2_CLI_SERVE_ECHO_ENV_COMPONENT;
1858         let server = WasmtimeServe::new(wasm, |cmd| {
1859             cmd.arg("-Scli");
1860         })?;
1861         let addr = server.addr;
1862 
1863         // Start up a `send` and `conn_task` which represents a connection to
1864         // this server.
1865         let (mut send, conn_task) = server.start_requests().await?;
1866         let _ = send
1867             .send_request(
1868                 hyper::Request::builder()
1869                     .uri("http://localhost/")
1870                     .header("env", "FOO")
1871                     .body(String::new())
1872                     .context("failed to make request")?,
1873             )
1874             .await;
1875 
1876         // ... once a response has been received (or at least the status
1877         // code/headers) then kill the server. THis is done while `conn_task`
1878         // and `send` are still alive so we're guaranteed that the other side
1879         // got a request (we got a response) and our connection is still open.
1880         //
1881         // This forces the address/port into the `TIME_WAIT` state. The rebind
1882         // below in the next process will fail if `SO_REUSEADDR` isn't set.
1883         drop(server);
1884         drop(send);
1885         let _ = conn_task.await;
1886 
1887         // If this is successfully bound then we'll create `WasmtimeServe`
1888         // which reads off the first line of output to know which address was
1889         // bound.
1890         let _server2 = WasmtimeServe::spawn(
1891             super::get_wasmtime_command()?
1892                 .arg("serve")
1893                 .arg("-Scli")
1894                 .arg(format!("--addr={addr}"))
1895                 .arg(wasm),
1896         )?;
1897 
1898         Ok(())
1899     }
1900 
1901     #[tokio::test]
p2_cli_serve_with_print() -> Result<()>1902     async fn p2_cli_serve_with_print() -> Result<()> {
1903         let server = WasmtimeServe::new(P2_CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1904             cmd.arg("-Scli");
1905         })?;
1906 
1907         for _ in 0..2 {
1908             let resp = server
1909                 .send_request(
1910                     hyper::Request::builder()
1911                         .uri("http://localhost/")
1912                         .body(String::new())
1913                         .context("failed to make request")?,
1914                 )
1915                 .await?;
1916             assert!(resp.status().is_success());
1917         }
1918 
1919         let (out, err) = server.finish()?;
1920         assert_eq!(
1921             out,
1922             "\
1923 stdout [0] :: this is half a print to stdout
1924 stdout [0] :: \n\
1925 stdout [0] :: after empty
1926 stdout [1] :: this is half a print to stdout
1927 stdout [1] :: \n\
1928 stdout [1] :: after empty
1929 "
1930         );
1931         assert!(
1932             err.contains(
1933                 "\
1934 stderr [0] :: this is half a print to stderr
1935 stderr [0] :: \n\
1936 stderr [0] :: after empty
1937 stderr [0] :: start a print 1234
1938 stderr [1] :: this is half a print to stderr
1939 stderr [1] :: \n\
1940 stderr [1] :: after empty
1941 stderr [1] :: start a print 1234
1942 "
1943             ),
1944             "bad stderr: {err}"
1945         );
1946 
1947         Ok(())
1948     }
1949 
1950     #[tokio::test]
p2_cli_serve_with_print_no_prefix() -> Result<()>1951     async fn p2_cli_serve_with_print_no_prefix() -> Result<()> {
1952         let server = WasmtimeServe::new(P2_CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1953             cmd.arg("-Scli");
1954             cmd.arg("--no-logging-prefix");
1955         })?;
1956 
1957         for _ in 0..2 {
1958             let resp = server
1959                 .send_request(
1960                     hyper::Request::builder()
1961                         .uri("http://localhost/")
1962                         .body(String::new())
1963                         .context("failed to make request")?,
1964                 )
1965                 .await?;
1966             assert!(resp.status().is_success());
1967         }
1968 
1969         let (out, err) = server.finish()?;
1970         assert_eq!(
1971             out,
1972             "\
1973 this is half a print to stdout
1974 \n\
1975 after empty
1976 this is half a print to stdout
1977 \n\
1978 after empty
1979 "
1980         );
1981         assert!(
1982             err.contains(
1983                 "\
1984 this is half a print to stderr
1985 \n\
1986 after empty
1987 start a print 1234
1988 this is half a print to stderr
1989 \n\
1990 after empty
1991 start a print 1234
1992 "
1993             ),
1994             "bad stderr {err}",
1995         );
1996 
1997         Ok(())
1998     }
1999 
2000     #[tokio::test]
p2_cli_serve_authority_and_scheme() -> Result<()>2001     async fn p2_cli_serve_authority_and_scheme() -> Result<()> {
2002         let server = WasmtimeServe::new(P2_CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| {
2003             cmd.arg("-Scli");
2004         })?;
2005 
2006         let resp = server
2007             .send_request(
2008                 hyper::Request::builder()
2009                     .uri("/")
2010                     .header("Host", "localhost")
2011                     .body(String::new())
2012                     .context("failed to make request")?,
2013             )
2014             .await?;
2015         assert!(resp.status().is_success());
2016 
2017         let resp = server
2018             .send_request(
2019                 hyper::Request::builder()
2020                     .method("CONNECT")
2021                     .uri("http://localhost/")
2022                     .body(String::new())
2023                     .context("failed to make request")?,
2024             )
2025             .await?;
2026         assert!(resp.status().is_success());
2027 
2028         Ok(())
2029     }
2030 
2031     #[test]
p2_cli_argv0() -> Result<()>2032     fn p2_cli_argv0() -> Result<()> {
2033         run_wasmtime(&["run", "--argv0=a", P2_CLI_ARGV0, "a"])?;
2034         run_wasmtime(&["run", "--argv0=b", P2_CLI_ARGV0_COMPONENT, "b"])?;
2035         run_wasmtime(&["run", "--argv0=foo.wasm", P2_CLI_ARGV0, "foo.wasm"])?;
2036         Ok(())
2037     }
2038 
2039     #[tokio::test]
p2_cli_serve_config() -> Result<()>2040     async fn p2_cli_serve_config() -> Result<()> {
2041         let server = WasmtimeServe::new(P2_CLI_SERVE_CONFIG_COMPONENT, |cmd| {
2042             cmd.arg("-Scli");
2043             cmd.arg("-Sconfig");
2044             cmd.arg("-Sconfig-var=hello=world");
2045         })?;
2046 
2047         let resp = server
2048             .send_request(
2049                 hyper::Request::builder()
2050                     .uri("http://localhost/")
2051                     .body(String::new())
2052                     .context("failed to make request")?,
2053             )
2054             .await?;
2055 
2056         assert!(resp.status().is_success());
2057         assert_eq!(resp.body(), "world");
2058         Ok(())
2059     }
2060 
2061     #[test]
p2_cli_config() -> Result<()>2062     fn p2_cli_config() -> Result<()> {
2063         run_wasmtime(&[
2064             "run",
2065             "-Sconfig",
2066             "-Sconfig-var=hello=world",
2067             CONFIG_GET_COMPONENT,
2068         ])?;
2069         Ok(())
2070     }
2071 
2072     #[tokio::test]
p2_cli_serve_keyvalue() -> Result<()>2073     async fn p2_cli_serve_keyvalue() -> Result<()> {
2074         let server = WasmtimeServe::new(P2_CLI_SERVE_KEYVALUE_COMPONENT, |cmd| {
2075             cmd.arg("-Scli");
2076             cmd.arg("-Skeyvalue");
2077             cmd.arg("-Skeyvalue-in-memory-data=hello=world");
2078         })?;
2079 
2080         let resp = server
2081             .send_request(
2082                 hyper::Request::builder()
2083                     .uri("http://localhost/")
2084                     .body(String::new())
2085                     .context("failed to make request")?,
2086             )
2087             .await?;
2088 
2089         assert!(resp.status().is_success());
2090         assert_eq!(resp.body(), "world");
2091         Ok(())
2092     }
2093 
2094     #[test]
p2_cli_keyvalue() -> Result<()>2095     fn p2_cli_keyvalue() -> Result<()> {
2096         run_wasmtime(&[
2097             "run",
2098             "-Skeyvalue",
2099             "-Skeyvalue-in-memory-data=atomics_key=5",
2100             KEYVALUE_MAIN_COMPONENT,
2101         ])?;
2102         Ok(())
2103     }
2104 
2105     #[test]
p2_cli_multiple_preopens() -> Result<()>2106     fn p2_cli_multiple_preopens() -> Result<()> {
2107         run_wasmtime(&[
2108             "run",
2109             "--dir=/::/a",
2110             "--dir=/::/b",
2111             "--dir=/::/c",
2112             P2_CLI_MULTIPLE_PREOPENS_COMPONENT,
2113         ])?;
2114         Ok(())
2115     }
2116 
p2_cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()>2117     async fn p2_cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()> {
2118         let server = WasmtimeServe::new(wasm, |cmd| {
2119             cmd.arg("-Scli");
2120         })?;
2121 
2122         for _ in 0..2 {
2123             let res = server
2124                 .send_request(
2125                     hyper::Request::builder()
2126                         .uri("http://localhost/")
2127                         .body(String::new())
2128                         .context("failed to make request")?,
2129                 )
2130                 .await
2131                 .expect("got response from wasmtime");
2132             assert_eq!(res.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
2133         }
2134 
2135         let (stdout, stderr) = server.finish()?;
2136         println!("stdout: {stdout}");
2137         println!("stderr: {stderr}");
2138         assert!(stderr.contains("guest never invoked `response-outparam::set` method"));
2139         assert!(!stderr.contains("panicked"));
2140         Ok(())
2141     }
2142 
2143     #[tokio::test]
p2_cli_serve_return_before_set() -> Result<()>2144     async fn p2_cli_serve_return_before_set() -> Result<()> {
2145         p2_cli_serve_guest_never_invoked_set(P2_CLI_SERVE_RETURN_BEFORE_SET_COMPONENT).await
2146     }
2147 
2148     #[tokio::test]
p2_cli_serve_trap_before_set() -> Result<()>2149     async fn p2_cli_serve_trap_before_set() -> Result<()> {
2150         p2_cli_serve_guest_never_invoked_set(P2_CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await
2151     }
2152 
2153     #[test]
p3_cli_hello_stdout() -> Result<()>2154     fn p3_cli_hello_stdout() -> Result<()> {
2155         let output = run_wasmtime(&[
2156             "run",
2157             "-Wcomponent-model-async",
2158             "-Sp3",
2159             P3_CLI_HELLO_STDOUT_COMPONENT,
2160         ]);
2161         if cfg!(feature = "component-model-async") {
2162             let output = output?;
2163             assert_eq!(output, "hello, world\n");
2164         } else {
2165             assert!(output.is_err());
2166         }
2167         Ok(())
2168     }
2169 
2170     #[test]
p2_cli_hello_stdout_invoke() -> Result<()>2171     fn p2_cli_hello_stdout_invoke() -> Result<()> {
2172         println!("{P2_CLI_HELLO_STDOUT_COMPONENT}");
2173         let output = run_wasmtime(&[
2174             "run",
2175             "-Wcomponent-model",
2176             "--invoke",
2177             "run()",
2178             P2_CLI_HELLO_STDOUT_COMPONENT,
2179         ])?;
2180         // First this component prints "hello, world", then the invoke
2181         // result is printed as "ok".
2182         assert_eq!(output, "hello, world\nok\n");
2183         Ok(())
2184     }
2185 
2186     #[test]
p3_cli_hello_stdout_post_return() -> Result<()>2187     fn p3_cli_hello_stdout_post_return() -> Result<()> {
2188         let output = run_wasmtime(&[
2189             "run",
2190             "-Wcomponent-model-async",
2191             "-Sp3",
2192             P3_CLI_HELLO_STDOUT_POST_RETURN_COMPONENT,
2193         ]);
2194         if cfg!(feature = "component-model-async") {
2195             let output = output?;
2196             assert_eq!(output, "hello, world\n");
2197         } else {
2198             assert!(output.is_err());
2199         }
2200         Ok(())
2201     }
2202 
2203     #[test]
p3_cli_hello_stdout_post_return_invoke() -> Result<()>2204     fn p3_cli_hello_stdout_post_return_invoke() -> Result<()> {
2205         let output = run_wasmtime(&[
2206             "run",
2207             "-Wcomponent-model-async",
2208             "-Sp3",
2209             "--invoke",
2210             "run()",
2211             P3_CLI_HELLO_STDOUT_POST_RETURN_COMPONENT,
2212         ]);
2213         if cfg!(feature = "component-model-async") {
2214             let output = output?;
2215             assert_eq!(output, "hello, world\nok\n");
2216         } else {
2217             assert!(output.is_err());
2218         }
2219         Ok(())
2220     }
2221 
2222     #[test]
p2_random_limits() -> Result<()>2223     fn p2_random_limits() -> Result<()> {
2224         println!("{P2_RANDOM_COMPONENT}");
2225 
2226         // By default, p2_random.rs fits within the random limits - always
2227         // asks for 256 bytes.
2228         let output = run_wasmtime(&["run", P2_RANDOM_COMPONENT])?;
2229         assert_eq!(output, "");
2230 
2231         // Lowering limit to 255 bytes should produce a trap in the first
2232         // call, which goes by way of the wasip1 adapter:
2233         let output = run_wasmtime(&["run", "-Smax-random-size=255", P2_RANDOM_COMPONENT])
2234             .err()
2235             .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?;
2236         let output = format!("{output:?}");
2237         assert!(
2238             output.contains("lib_generated::random_get"),
2239             "expected error stack frames to contain 'wasip1::lib_generated::random_get'. Got:\n{output}"
2240         );
2241         assert!(
2242             output.contains("requested len 256 exceeds limit 255"),
2243             "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}"
2244         );
2245 
2246         // Lowering limit to 255 bytes and setting environment variable for
2247         // the first call to only request 255 bytes should be OK, and then the
2248         // program produce a trap in the second call, which calls the wasip2
2249         // random import directly:
2250         let output = run_wasmtime(&[
2251             "run",
2252             "-Smax-random-size=255",
2253             "--env",
2254             "TEST_P1_RANDOM_LEN=255",
2255             P2_RANDOM_COMPONENT,
2256         ])
2257         .err()
2258         .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?;
2259         let output = format!("{output:?}");
2260         assert!(
2261             output.contains("random::random::get_random_bytes"),
2262             "expected error stack frames to contain 'wasi::random::random::get_random_bytes'. Got:\n{output}"
2263         );
2264         assert!(
2265             output.contains("requested len 256 exceeds limit 255"),
2266             "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}"
2267         );
2268 
2269         // Lowering limit to 255 bytes and setting environment variable for
2270         // the first and second calls to be at the limit should produce a trap
2271         // in the third call, which calls the wasip2 insecure random import:
2272         let output = run_wasmtime(&[
2273             "run",
2274             "-Smax-random-size=255",
2275             "--env",
2276             "TEST_P1_RANDOM_LEN=255",
2277             "--env",
2278             "TEST_P2_RANDOM_LEN=255",
2279             P2_RANDOM_COMPONENT,
2280         ])
2281         .err()
2282         .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?;
2283         let output = format!("{output:?}");
2284         assert!(
2285             output.contains("random::insecure::get_insecure_random_bytes"),
2286             "expected error stack frames to contain 'wasi::random::insecure::get_insecure_random_bytes'. Got:\n{output}"
2287         );
2288         assert!(
2289             output.contains("requested len 256 exceeds limit 255"),
2290             "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}"
2291         );
2292 
2293         // Lowering limit to 255 bytes and setting environment variable for
2294         // the first and second calls to be under the limit should trap in the
2295         // third call, which calls the wasip2 insecure random import:
2296         let output = run_wasmtime(&[
2297             "run",
2298             "-Smax-random-size=255",
2299             "--env",
2300             "TEST_P1_RANDOM_LEN=255",
2301             "--env",
2302             "TEST_P2_RANDOM_LEN=255",
2303             "--env",
2304             "TEST_P2_INSECURE_RANDOM_LEN=255",
2305             P2_RANDOM_COMPONENT,
2306         ])
2307         .context("setting all calls to be equal to the limit should pass")?;
2308         assert_eq!(output, "");
2309 
2310         Ok(())
2311     }
2312 
2313     #[test]
p2_cli_http_headers() -> Result<()>2314     fn p2_cli_http_headers() -> Result<()> {
2315         let td = tempfile::TempDir::new()?;
2316         let cwasm = td.path().join("http_headers.cwasm");
2317         let cwasm = cwasm.to_str().unwrap();
2318         run_wasmtime(&["compile", P2_CLI_HTTP_HEADERS_COMPONENT, "-o", cwasm])?;
2319         // p2 traps on too-many-fields/size-too-big, so expect error messages.
2320         for test in ["append", "append-empty", "append-same", "append-same-empty"] {
2321             let err = run_wasmtime(&[
2322                 "run",
2323                 "-Shttp,p3",
2324                 "-Smax-http-fields-size=1048576",
2325                 "--allow-precompiled",
2326                 cwasm,
2327                 &format!("p2-{test}"),
2328             ])
2329             .unwrap_err();
2330             assert!(
2331                 err.to_string()
2332                     .contains("total size of fields exceeds limit")
2333                     || err.to_string().contains("too many fields in the field map"),
2334                 "bad error message: {err:?}"
2335             );
2336 
2337             // gated by default too
2338             let err = run_wasmtime(&[
2339                 "run",
2340                 "-Shttp,p3",
2341                 "--allow-precompiled",
2342                 cwasm,
2343                 &format!("p2-{test}"),
2344             ])
2345             .unwrap_err();
2346             assert!(
2347                 err.to_string()
2348                     .contains("total size of fields exceeds limit"),
2349                 "bad error message: {err:?}"
2350             );
2351         }
2352 
2353         // With an extremely large limit Wasmtime still shouldn't panic (p2).
2354         let err = run_wasmtime(&[
2355             "run",
2356             "-Shttp,p3",
2357             &format!("-Smax-http-fields-size={}", 1 << 30),
2358             "--allow-precompiled",
2359             cwasm,
2360             "p2-append",
2361         ])
2362         .unwrap_err();
2363         assert!(
2364             err.to_string().contains("too many fields in the field map"),
2365             "bad error message: {err:?}"
2366         );
2367 
2368         // p3 returns an error code instead of trapping, so the guest
2369         // program exits successfully after receiving the error.
2370         for test in ["append", "append-empty", "append-same", "append-same-empty"] {
2371             let output = run_wasmtime(&[
2372                 "run",
2373                 "-Shttp,p3",
2374                 "-Smax-http-fields-size=1048576",
2375                 "--allow-precompiled",
2376                 cwasm,
2377                 &format!("p3-{test}"),
2378             ])?;
2379             assert!(
2380                 output.contains("error received"),
2381                 "p3-{test}: guest should have received an error"
2382             );
2383 
2384             // gated by default too
2385             let output = run_wasmtime(&[
2386                 "run",
2387                 "-Shttp,p3",
2388                 "--allow-precompiled",
2389                 cwasm,
2390                 &format!("p3-{test}"),
2391             ])?;
2392             assert!(
2393                 output.contains("error received"),
2394                 "p3-{test} (default limit): guest should have received an error"
2395             );
2396         }
2397 
2398         // With an extremely large limit Wasmtime still shouldn't panic (p3).
2399         let output = run_wasmtime(&[
2400             "run",
2401             "-Shttp,p3",
2402             &format!("-Smax-http-fields-size={}", 1 << 30),
2403             "--allow-precompiled",
2404             cwasm,
2405             "p3-append",
2406         ])?;
2407         assert!(
2408             output.contains("error received"),
2409             "p3-append (large limit): guest should have received an error"
2410         );
2411         Ok(())
2412     }
2413 
2414     #[test]
2415     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p2_cli_invoke_async() -> Result<()>2416     fn p2_cli_invoke_async() -> Result<()> {
2417         let output = run_wasmtime(&[
2418             "run",
2419             "-Wcomponent-model-async",
2420             "--invoke",
2421             "echo(\"hello?\")",
2422             P2_CLI_INVOKE_ASYNC_COMPONENT,
2423         ])?;
2424         assert_eq!(output, "\"hello?\"\n");
2425         Ok(())
2426     }
2427 
run_much_stdout(component: &str, extra_flags: &[&str]) -> Result<()>2428     fn run_much_stdout(component: &str, extra_flags: &[&str]) -> Result<()> {
2429         let total_write_size = 1 << 18;
2430         let expected = iter::repeat('a').take(total_write_size).collect::<String>();
2431 
2432         for i in 10..15 {
2433             let string = iter::repeat('a').take(1 << i).collect::<String>();
2434             let times = (total_write_size >> i).to_string();
2435             println!("writing {} bytes {times} times", string.len());
2436 
2437             let mut args = Vec::new();
2438             args.push("run");
2439             args.extend_from_slice(extra_flags);
2440             args.push(component);
2441             args.push(&string);
2442             args.push(&times);
2443             let output = run_wasmtime(&args)?;
2444             println!(
2445                 "expected {} bytes, got {} bytes",
2446                 expected.len(),
2447                 output.len()
2448             );
2449             assert!(output == expected);
2450         }
2451 
2452         Ok(())
2453     }
2454 
2455     #[test]
p1_cli_much_stdout() -> Result<()>2456     fn p1_cli_much_stdout() -> Result<()> {
2457         run_much_stdout(P1_CLI_MUCH_STDOUT_COMPONENT, &[])
2458     }
2459 
2460     #[test]
p2_cli_much_stdout() -> Result<()>2461     fn p2_cli_much_stdout() -> Result<()> {
2462         run_much_stdout(P2_CLI_MUCH_STDOUT_COMPONENT, &[])
2463     }
2464 
2465     #[test]
2466     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_much_stdout() -> Result<()>2467     fn p3_cli_much_stdout() -> Result<()> {
2468         run_much_stdout(
2469             P3_CLI_MUCH_STDOUT_COMPONENT,
2470             &["-Wcomponent-model-async", "-Sp3"],
2471         )
2472     }
2473 
2474     #[test]
p2_cli_max_resources() -> Result<()>2475     fn p2_cli_max_resources() -> Result<()> {
2476         let err = run_wasmtime(&["run", "-Smax-resources=50", P2_CLI_MAX_RESOURCES_COMPONENT])
2477             .unwrap_err();
2478         assert!(
2479             err.to_string().contains("resource table has no free keys"),
2480             "bad error message: {err}"
2481         );
2482         run_wasmtime(&["run", "-Smax-resources=200", P2_CLI_MAX_RESOURCES_COMPONENT])?;
2483         Ok(())
2484     }
2485 
2486     #[tokio::test]
2487     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_serve_hello_world() -> Result<()>2488     async fn p3_cli_serve_hello_world() -> Result<()> {
2489         cli_serve_hello_world(P3_CLI_SERVE_HELLO_WORLD_COMPONENT, 1, 1, |cmd| {
2490             cmd.arg("-Wcomponent-model-async");
2491             cmd.arg("-Sp3,cli");
2492         })
2493         .await
2494     }
2495 
2496     #[tokio::test]
p2_cli_serve_hello_world() -> Result<()>2497     async fn p2_cli_serve_hello_world() -> Result<()> {
2498         cli_serve_hello_world(P2_CLI_SERVE_HELLO_WORLD_COMPONENT, 1, 1, |cmd| {
2499             cmd.arg("-Scli");
2500         })
2501         .await
2502     }
2503 
2504     const CONNECTION_COUNT_MANY: usize = 20;
2505     const REQUESTS_PER_CONNECTION_MANY: usize = 5;
2506 
2507     #[tokio::test]
2508     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_serve_hello_world_many() -> Result<()>2509     async fn p3_cli_serve_hello_world_many() -> Result<()> {
2510         cli_serve_hello_world(
2511             P3_CLI_SERVE_HELLO_WORLD_COMPONENT,
2512             CONNECTION_COUNT_MANY,
2513             REQUESTS_PER_CONNECTION_MANY,
2514             |cmd| {
2515                 cmd.arg("-Wcomponent-model-async");
2516                 cmd.arg("-Sp3,cli");
2517             },
2518         )
2519         .await
2520     }
2521 
2522     #[tokio::test]
2523     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_serve_hello_world_many_no_reuse() -> Result<()>2524     async fn p3_cli_serve_hello_world_many_no_reuse() -> Result<()> {
2525         cli_serve_hello_world(
2526             P3_CLI_SERVE_HELLO_WORLD_COMPONENT,
2527             CONNECTION_COUNT_MANY,
2528             REQUESTS_PER_CONNECTION_MANY,
2529             |cmd| {
2530                 cmd.arg("-Wcomponent-model-async");
2531                 cmd.arg("-Sp3,cli");
2532                 cmd.arg("--max-instance-reuse-count=1");
2533             },
2534         )
2535         .await
2536     }
2537 
2538     #[tokio::test]
2539     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_serve_hello_world_many_no_concurrent_reuse() -> Result<()>2540     async fn p3_cli_serve_hello_world_many_no_concurrent_reuse() -> Result<()> {
2541         cli_serve_hello_world(
2542             P3_CLI_SERVE_HELLO_WORLD_COMPONENT,
2543             CONNECTION_COUNT_MANY,
2544             REQUESTS_PER_CONNECTION_MANY,
2545             |cmd| {
2546                 cmd.arg("-Wcomponent-model-async");
2547                 cmd.arg("-Sp3,cli");
2548                 cmd.arg("--max-instance-concurrent-reuse-count=1");
2549             },
2550         )
2551         .await
2552     }
2553 
2554     #[tokio::test]
p2_cli_serve_hello_world_many() -> Result<()>2555     async fn p2_cli_serve_hello_world_many() -> Result<()> {
2556         cli_serve_hello_world(
2557             P2_CLI_SERVE_HELLO_WORLD_COMPONENT,
2558             CONNECTION_COUNT_MANY,
2559             REQUESTS_PER_CONNECTION_MANY,
2560             |cmd| {
2561                 cmd.arg("-Scli");
2562             },
2563         )
2564         .await
2565     }
2566 
2567     #[tokio::test]
p2_cli_serve_hello_world_many_with_reuse() -> Result<()>2568     async fn p2_cli_serve_hello_world_many_with_reuse() -> Result<()> {
2569         cli_serve_hello_world(
2570             P2_CLI_SERVE_HELLO_WORLD_COMPONENT,
2571             CONNECTION_COUNT_MANY,
2572             REQUESTS_PER_CONNECTION_MANY,
2573             |cmd| {
2574                 cmd.arg("-Scli");
2575                 cmd.arg("--max-instance-reuse-count=128");
2576             },
2577         )
2578         .await
2579     }
2580 
cli_serve_hello_world( component: &str, connection_count: usize, requests_per_connection: usize, configure: impl FnOnce(&mut Command), ) -> Result<()>2581     async fn cli_serve_hello_world(
2582         component: &str,
2583         connection_count: usize,
2584         requests_per_connection: usize,
2585         configure: impl FnOnce(&mut Command),
2586     ) -> Result<()> {
2587         let server = std::sync::Arc::new(WasmtimeServe::new(component, configure)?);
2588 
2589         let tasks = (0..connection_count).map({
2590             let server = server.clone();
2591             move |_| {
2592                 tokio::task::spawn({
2593                     let server = server.clone();
2594                     async move {
2595                         let (mut send, conn_task) = server.start_requests().await?;
2596 
2597                         for _ in 0..requests_per_connection {
2598                             let result = WasmtimeServe::send_request_with(
2599                                 &mut send,
2600                                 hyper::Request::builder()
2601                                     .uri("http://localhost/")
2602                                     .body(String::new())
2603                                     .context("failed to make request")?,
2604                             )
2605                             .await?;
2606 
2607                             assert!(result.status().is_success());
2608                             assert_eq!(result.body(), "Hello, WASI!");
2609                         }
2610 
2611                         drop(send);
2612 
2613                         conn_task.await??;
2614 
2615                         wasmtime::error::Ok(())
2616                     }
2617                 })
2618             }
2619         });
2620 
2621         for task in tasks {
2622             task.await??;
2623         }
2624 
2625         std::sync::Arc::into_inner(server).unwrap().finish()?;
2626         Ok(())
2627     }
2628 
2629     #[tokio::test]
p2_cli_serve_sleep() -> Result<()>2630     async fn p2_cli_serve_sleep() -> Result<()> {
2631         cli_serve_sleep(P2_CLI_SERVE_SLEEP_COMPONENT, 1, 1, |cmd| {
2632             cmd.arg("-Scli");
2633         })
2634         .await
2635     }
2636 
2637     #[tokio::test]
2638     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_serve_sleep() -> Result<()>2639     async fn p3_cli_serve_sleep() -> Result<()> {
2640         cli_serve_sleep(P3_CLI_SERVE_SLEEP_COMPONENT, 1, 1, |cmd| {
2641             cmd.arg("-Wcomponent-model-async");
2642             cmd.arg("-Sp3,cli");
2643         })
2644         .await
2645     }
2646 
2647     #[tokio::test]
p2_cli_serve_sleep_many() -> Result<()>2648     async fn p2_cli_serve_sleep_many() -> Result<()> {
2649         cli_serve_sleep(
2650             P2_CLI_SERVE_SLEEP_COMPONENT,
2651             CONNECTION_COUNT_MANY,
2652             REQUESTS_PER_CONNECTION_MANY,
2653             |cmd| {
2654                 cmd.arg("-Scli");
2655             },
2656         )
2657         .await
2658     }
2659 
2660     #[tokio::test]
2661     #[cfg_attr(not(feature = "component-model-async"), ignore)]
p3_cli_serve_sleep_many() -> Result<()>2662     async fn p3_cli_serve_sleep_many() -> Result<()> {
2663         cli_serve_sleep(
2664             P3_CLI_SERVE_SLEEP_COMPONENT,
2665             CONNECTION_COUNT_MANY,
2666             REQUESTS_PER_CONNECTION_MANY,
2667             |cmd| {
2668                 cmd.arg("-Wcomponent-model-async");
2669                 cmd.arg("-Sp3,cli");
2670             },
2671         )
2672         .await
2673     }
2674 
cli_serve_sleep( component: &str, connection_count: usize, requests_per_connection: usize, configure: impl FnOnce(&mut Command), ) -> Result<()>2675     async fn cli_serve_sleep(
2676         component: &str,
2677         connection_count: usize,
2678         requests_per_connection: usize,
2679         configure: impl FnOnce(&mut Command),
2680     ) -> Result<()> {
2681         let server = std::sync::Arc::new(WasmtimeServe::new(component, move |cmd| {
2682             configure(cmd);
2683             cmd.arg("-Wtimeout=100us");
2684         })?);
2685 
2686         let tasks = (0..connection_count).map({
2687             let server = server.clone();
2688             move |_| {
2689                 tokio::task::spawn({
2690                     let server = server.clone();
2691                     async move {
2692                         let (mut send, conn_task) = server.start_requests().await?;
2693 
2694                         for _ in 0..requests_per_connection {
2695                             let result = WasmtimeServe::send_request_with(
2696                                 &mut send,
2697                                 hyper::Request::builder()
2698                                     .uri("http://localhost/")
2699                                     .body(String::new())
2700                                     .context("failed to make request")?,
2701                             )
2702                             .await?;
2703 
2704                             assert!(result.status().is_server_error());
2705                         }
2706 
2707                         drop(send);
2708 
2709                         conn_task.await??;
2710 
2711                         wasmtime::error::Ok(())
2712                     }
2713                 })
2714             }
2715         });
2716 
2717         for task in tasks {
2718             task.await??;
2719         }
2720 
2721         let (stdout, stderr) = std::sync::Arc::into_inner(server).unwrap().finish()?;
2722         assert_eq!(stdout, "");
2723         assert!(stderr.contains("guest timed out"), "bad stderr: {stderr}");
2724         Ok(())
2725     }
2726 
2727     #[test]
p2_cli_many_resources() -> Result<()>2728     fn p2_cli_many_resources() -> Result<()> {
2729         let err = run_wasmtime(&[
2730             "run",
2731             "-Smax-resources=100",
2732             P2_CLI_MANY_RESOURCES_COMPONENT,
2733         ])
2734         .unwrap_err();
2735         assert!(
2736             err.to_string().contains("resource table has no free keys"),
2737             "bad error message: {err}"
2738         );
2739         Ok(())
2740     }
2741 
2742     #[test]
p3_cli_many_tasks() -> Result<()>2743     fn p3_cli_many_tasks() -> Result<()> {
2744         let err = run_wasmtime(&[
2745             "run",
2746             "-Smax-resources=100",
2747             "-Sp3",
2748             "-Wcomponent-model-async",
2749             dbg!(P3_CLI_MANY_TASKS_COMPONENT),
2750         ])
2751         .unwrap_err();
2752         assert!(
2753             err.to_string().contains("resource table has no free keys"),
2754             "bad error message: {err}"
2755         );
2756         Ok(())
2757     }
2758 
2759     #[test]
p1_cli_hostcall_fuel() -> Result<()>2760     fn p1_cli_hostcall_fuel() -> Result<()> {
2761         let dir = tempfile::tempdir()?;
2762         run_wasmtime(&[
2763             "run",
2764             &format!("--dir={}::.", dir.path().to_str().unwrap()),
2765             "-Shostcall-fuel=1000",
2766             P1_CLI_HOSTCALL_FUEL,
2767         ])?;
2768         Ok(())
2769     }
2770 
2771     #[test]
p2_cli_hostcall_fuel() -> Result<()>2772     fn p2_cli_hostcall_fuel() -> Result<()> {
2773         enum Exit {
2774             Ok,
2775             NoFuel,
2776             TooManyZeroes,
2777             BufferTooLarge,
2778         }
2779 
2780         let dir = tempfile::tempdir()?;
2781         let file = dir.path().join("1mb");
2782         std::fs::write(&file, vec![0; 1024 * 1024])?;
2783         for (arg, exit) in [
2784             ("poll", Exit::NoFuel),
2785             ("read", Exit::Ok),
2786             ("write", Exit::NoFuel),
2787             ("mkdir", Exit::NoFuel),
2788             ("write-stream", Exit::NoFuel),
2789             ("write-stream-blocking", Exit::NoFuel),
2790             ("resolve", Exit::NoFuel),
2791             ("udp-send-many", Exit::NoFuel),
2792             ("udp-send-big", Exit::NoFuel),
2793             ("write-zeroes", Exit::TooManyZeroes),
2794             ("write-stream-buffer-too-large", Exit::BufferTooLarge),
2795             ("write-zeroes-buffer-too-large", Exit::BufferTooLarge),
2796             ("read-file-big", Exit::Ok),
2797             ("read-tcp-big", Exit::Ok),
2798         ] {
2799             println!("test: {arg}");
2800             let result = run_wasmtime(&[
2801                 "run",
2802                 "-Shostcall-fuel=5000",
2803                 "-Sinherit-network",
2804                 &format!("--dir={}::.", dir.path().to_str().unwrap()),
2805                 P2_CLI_HOSTCALL_FUEL_COMPONENT,
2806                 arg,
2807             ]);
2808 
2809             match exit {
2810                 Exit::Ok => {
2811                     result.unwrap();
2812                 }
2813                 Exit::NoFuel => {
2814                     let err = result.unwrap_err();
2815                     assert!(
2816                         err.to_string()
2817                             .contains("fuel allocated for hostcalls has been exhausted"),
2818                         "bad error message: {err}"
2819                     );
2820                 }
2821                 Exit::TooManyZeroes => {
2822                     let err = result.unwrap_err();
2823                     assert!(
2824                         err.to_string()
2825                             .contains("cannot write more zeroes than `check_write` allows"),
2826                         "bad error message: {err}"
2827                     );
2828                 }
2829                 Exit::BufferTooLarge => {
2830                     let err = result.unwrap_err();
2831                     assert!(
2832                         err.to_string().contains("Buffer too large"),
2833                         "bad error message: {err}"
2834                     );
2835                 }
2836             }
2837         }
2838         Ok(())
2839     }
2840 
2841     #[test]
p3_cli_random_limits() -> Result<()>2842     fn p3_cli_random_limits() -> Result<()> {
2843         let c = P3_CLI_RANDOM_LIMITS_COMPONENT;
2844 
2845         for rand in ["random", "insecure"] {
2846             run_wasmtime(&["run", "-Sp3", "-Wcomponent-model-async", c, rand, "256"])?;
2847             run_wasmtime(&[
2848                 "run",
2849                 "-Sp3",
2850                 "-Wcomponent-model-async",
2851                 "-Smax-random-size=255",
2852                 c,
2853                 rand,
2854                 "256",
2855             ])?;
2856         }
2857 
2858         Ok(())
2859     }
2860 
2861     #[test]
p3_cli_read_stdin() -> Result<()>2862     fn p3_cli_read_stdin() -> Result<()> {
2863         let mut cmd = get_wasmtime_command()?;
2864         let mut child = cmd
2865             .arg("-Sp3")
2866             .arg("-Wcomponent-model-async")
2867             .arg(P3_CLI_READ_STDIN_COMPONENT)
2868             .stdin(std::process::Stdio::piped())
2869             .stdout(std::process::Stdio::piped())
2870             .stderr(std::process::Stdio::piped())
2871             .spawn()
2872             .unwrap();
2873         child.stdin.take().unwrap().write_all(b"hello!").unwrap();
2874         let output = child.wait_with_output()?;
2875         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
2876         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
2877         assert!(output.status.success());
2878 
2879         Ok(())
2880     }
2881 }
2882 
2883 #[test]
settings_command() -> Result<()>2884 fn settings_command() -> Result<()> {
2885     // Skip this test on platforms that Cranelift doesn't support.
2886     if cranelift_native::builder().is_err() {
2887         return Ok(());
2888     }
2889     let output = run_wasmtime(&["settings"])?;
2890     assert!(output.contains("Cranelift settings for target"));
2891     Ok(())
2892 }
2893 
2894 #[cfg(target_arch = "x86_64")]
2895 #[test]
profile_with_vtune() -> Result<()>2896 fn profile_with_vtune() -> Result<()> {
2897     if !is_vtune_available() {
2898         println!("> `vtune` is not available on the system path; skipping test");
2899         return Ok(());
2900     }
2901 
2902     let mut bin = Command::new("vtune");
2903     bin.args(&[
2904         // Configure VTune...
2905         "-verbose",
2906         "-collect",
2907         "hotspots",
2908         "-user-data-dir",
2909         &std::env::temp_dir().to_string_lossy(),
2910         // ...then run Wasmtime with profiling enabled:
2911         get_wasmtime_path(),
2912         "--profile=vtune",
2913         "tests/all/cli_tests/simple.wat",
2914     ]);
2915 
2916     println!("> executing: {bin:?}");
2917     let output = bin.output()?;
2918 
2919     let stdout = String::from_utf8_lossy(&output.stdout);
2920     let stderr = String::from_utf8_lossy(&output.stderr);
2921     println!("> stdout:\n{stdout}");
2922     println!("> stderr:\n{stderr}");
2923 
2924     assert!(output.status.success());
2925     assert!(!stderr.contains("Error"));
2926     assert!(stdout.contains("CPU Time"));
2927     Ok(())
2928 }
2929 
2930 #[cfg(target_arch = "x86_64")]
is_vtune_available() -> bool2931 fn is_vtune_available() -> bool {
2932     Command::new("vtune").arg("-version").output().is_ok()
2933 }
2934 
2935 #[test]
profile_guest() -> Result<()>2936 fn profile_guest() -> Result<()> {
2937     let tmpdir = std::env::temp_dir();
2938     let dir = tmpdir.to_string_lossy();
2939 
2940     let output = run_wasmtime_for_output(
2941         &[
2942             &format!("--profile=guest,{dir}/out.json"),
2943             "--env",
2944             "FOO=bar",
2945             "tests/all/cli_tests/print_env.wat",
2946         ],
2947         None,
2948     )?;
2949 
2950     assert!(output.status.success());
2951     let stdout = String::from_utf8_lossy(&output.stdout);
2952     let stderr = String::from_utf8_lossy(&output.stderr);
2953     println!("> stdout:\n{stdout}");
2954     println!("> stderr:\n{stderr}");
2955     assert!(!stderr.contains("Error"));
2956     let out_json = std::fs::read_to_string(format!("{dir}/out.json")).unwrap();
2957     println!("> out.json:\n{out_json}");
2958     Ok(())
2959 }
2960 
2961 #[test]
unreachable_without_wasi() -> Result<()>2962 fn unreachable_without_wasi() -> Result<()> {
2963     let output = run_wasmtime_for_output(
2964         &[
2965             "-Scli=n",
2966             "-Ccache=n",
2967             "tests/all/cli_tests/unreachable.wat",
2968         ],
2969         None,
2970     )?;
2971 
2972     assert_ne!(output.stderr, b"");
2973     assert_eq!(output.stdout, b"");
2974     assert_trap_code(&output.status);
2975     Ok(())
2976 }
2977 
2978 #[test]
config_cli_flag() -> Result<()>2979 fn config_cli_flag() -> Result<()> {
2980     let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
2981 
2982     // Test some valid TOML values
2983     let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2984     cfg.write_all(
2985         br#"
2986         [optimize]
2987         opt-level = 2
2988         signals-based-traps = false
2989 
2990         [codegen]
2991         collector = "null"
2992 
2993         [debug]
2994         address-map = true
2995 
2996         [wasm]
2997         max-wasm-stack = 65536
2998 
2999         [wasi]
3000         cli = true
3001         "#,
3002     )?;
3003     let output = run_wasmtime(&[
3004         "run",
3005         "--config",
3006         cfg_path.to_str().unwrap(),
3007         "--invoke",
3008         "get_f64",
3009         wasm.path().to_str().unwrap(),
3010     ])?;
3011     assert_eq!(output, "100\n");
3012 
3013     // Make sure CLI flags overrides TOML values
3014     let output = run_wasmtime(&[
3015         "run",
3016         "--config",
3017         cfg_path.to_str().unwrap(),
3018         "--invoke",
3019         "get_f64",
3020         "-W",
3021         "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail
3022         wasm.path().to_str().unwrap(),
3023     ]);
3024     assert!(
3025         output
3026             .as_ref()
3027             .unwrap_err()
3028             .to_string()
3029             .contains("max_wasm_stack size cannot be zero"),
3030         "'{output:?}' did not contain expected error message",
3031     );
3032 
3033     // Test invalid TOML key
3034     let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
3035     cfg.write_all(
3036         br#"
3037         [optimize]
3038         this-key-does-not-exist = true
3039         "#,
3040     )?;
3041     let output = run_wasmtime(&[
3042         "run",
3043         "--config",
3044         cfg_path.to_str().unwrap(),
3045         wasm.path().to_str().unwrap(),
3046     ]);
3047     assert!(
3048         output
3049             .as_ref()
3050             .unwrap_err()
3051             .to_string()
3052             .contains("unknown field `this-key-does-not-exist`"),
3053         "'{output:?}' did not contain expected error message"
3054     );
3055 
3056     // Test invalid TOML table
3057     let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
3058     cfg.write_all(
3059         br#"
3060         [invalid_table]
3061         "#,
3062     )?;
3063     let output = run_wasmtime(&[
3064         "run",
3065         "--config",
3066         cfg_path.to_str().unwrap(),
3067         wasm.path().to_str().unwrap(),
3068     ]);
3069     assert!(
3070         output
3071             .as_ref()
3072             .unwrap_err()
3073             .to_string()
3074             .contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"),
3075         "'{output:?}' did not contain expected error message",
3076     );
3077 
3078     Ok(())
3079 }
3080 
3081 #[test]
invalid_subcommand() -> Result<()>3082 fn invalid_subcommand() -> Result<()> {
3083     let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?;
3084     dbg!(&output);
3085     assert!(!output.status.success());
3086     assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand"));
3087     Ok(())
3088 }
3089 
3090 #[test]
numeric_args() -> Result<()>3091 fn numeric_args() -> Result<()> {
3092     let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?;
3093     // Test decimal i32
3094     let output = run_wasmtime_for_output(
3095         &[
3096             "run",
3097             "--invoke",
3098             "i32_test",
3099             wasm.path().to_str().unwrap(),
3100             "42",
3101         ],
3102         None,
3103     )?;
3104     assert_eq!(output.status.success(), true);
3105     assert_eq!(output.stdout, b"42\n");
3106     // Test hexadecimal i32 with lowercase prefix
3107     let output = run_wasmtime_for_output(
3108         &[
3109             "run",
3110             "--invoke",
3111             "i32_test",
3112             wasm.path().to_str().unwrap(),
3113             "0x2A",
3114         ],
3115         None,
3116     )?;
3117     assert_eq!(output.status.success(), true);
3118     assert_eq!(output.stdout, b"42\n");
3119     // Test hexadecimal i32 with uppercase prefix
3120     let output = run_wasmtime_for_output(
3121         &[
3122             "run",
3123             "--invoke",
3124             "i32_test",
3125             wasm.path().to_str().unwrap(),
3126             "0X2a",
3127         ],
3128         None,
3129     )?;
3130     assert_eq!(output.status.success(), true);
3131     assert_eq!(output.stdout, b"42\n");
3132     // Test that non-prefixed hex strings are not interpreted as hex
3133     let output = run_wasmtime_for_output(
3134         &[
3135             "run",
3136             "--invoke",
3137             "i32_test",
3138             wasm.path().to_str().unwrap(),
3139             "ff",
3140         ],
3141         None,
3142     )?;
3143     assert!(!output.status.success()); // Should fail as "ff" is not a valid decimal number
3144 
3145     // Test decimal i64
3146     let output = run_wasmtime_for_output(
3147         &[
3148             "run",
3149             "--invoke",
3150             "i64_test",
3151             wasm.path().to_str().unwrap(),
3152             "42",
3153         ],
3154         None,
3155     )?;
3156     assert_eq!(output.status.success(), true);
3157     assert_eq!(output.stdout, b"42\n");
3158     // Test hexadecimal i64
3159     let output = run_wasmtime_for_output(
3160         &[
3161             "run",
3162             "--invoke",
3163             "i64_test",
3164             wasm.path().to_str().unwrap(),
3165             "0x2A",
3166         ],
3167         None,
3168     )?;
3169     assert_eq!(output.status.success(), true);
3170     assert_eq!(output.stdout, b"42\n");
3171     Ok(())
3172 }
3173 
3174 #[test]
compilation_logs() -> Result<()>3175 fn compilation_logs() -> Result<()> {
3176     let temp = tempfile::NamedTempFile::new()?;
3177     let output = get_wasmtime_command()?
3178         .args(&[
3179             "compile",
3180             "-Wgc",
3181             "tests/all/cli_tests/issue-10353.wat",
3182             "--output",
3183             &temp.path().display().to_string(),
3184         ])
3185         .env("WASMTIME_LOG", "trace")
3186         .env("RUST_BACKTRACE", "1")
3187         .output()?;
3188     if !output.status.success() {
3189         println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
3190         println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
3191         panic!("wasmtime compilation failed when logs requested");
3192     }
3193     Ok(())
3194 }
3195 
3196 #[test]
big_table_in_pooling_allocator() -> Result<()>3197 fn big_table_in_pooling_allocator() -> Result<()> {
3198     // Works by default
3199     run_wasmtime(&["tests/all/cli_tests/big_table.wat"])?;
3200 
3201     // Does not work by default in the pooling allocator, and the error message
3202     // should mention something about the pooling allocator.
3203     let output = run_wasmtime_for_output(
3204         &["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"],
3205         None,
3206     )?;
3207     assert!(!output.status.success());
3208     println!("{}", String::from_utf8_lossy(&output.stderr));
3209     assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator"));
3210 
3211     // Does work with `-Wmax-table-elements`
3212     run_wasmtime(&[
3213         "-Opooling-allocator",
3214         "-Wmax-table-elements=25000",
3215         "tests/all/cli_tests/big_table.wat",
3216     ])?;
3217     // Also works with `-Opooling-table-elements`
3218     run_wasmtime(&[
3219         "-Opooling-allocator",
3220         "-Opooling-table-elements=25000",
3221         "tests/all/cli_tests/big_table.wat",
3222     ])?;
3223     Ok(())
3224 }
3225 
wizen(args: &[&str], wat: &str) -> Result<Output>3226 fn wizen(args: &[&str], wat: &str) -> Result<Output> {
3227     let mut cmd = get_wasmtime_command()?;
3228     cmd.arg("wizer").args(args).arg("-");
3229     cmd.stdin(Stdio::piped())
3230         .stdout(Stdio::piped())
3231         .stderr(Stdio::piped());
3232     let mut child = cmd.spawn()?;
3233     let mut stdin = child.stdin.take().unwrap();
3234     stdin.write_all(wat.as_bytes())?;
3235     drop(stdin);
3236 
3237     let output = child.wait_with_output()?;
3238     if !output.status.success() {
3239         println!(
3240             "Failed to execute wasmtime wizer with: {cmd:?}\n{}",
3241             String::from_utf8_lossy(&output.stderr)
3242         );
3243     }
3244     Ok(output)
3245 }
3246 
3247 #[test]
wizer_no_imports_by_default() -> Result<()>3248 fn wizer_no_imports_by_default() -> Result<()> {
3249     let result = wizen(
3250         &[],
3251         r#"(module
3252             (func (export "wizer-initialize"))
3253         )"#,
3254     )?;
3255     assert!(result.status.success());
3256 
3257     let result = wizen(
3258         &[],
3259         r#"(module
3260             (import "foo" "bar" (func))
3261             (func (export "wizer-initialize"))
3262         )"#,
3263     )?;
3264     assert!(!result.status.success());
3265 
3266     let result = wizen(
3267         &[],
3268         r#"(module
3269             (import "wasi_snapshot_preview1" "fd_write" (func (param i32 i32 i32 i32) (result i32)))
3270             (func (export "wizer-initialize"))
3271         )"#,
3272     )?;
3273     assert!(!result.status.success());
3274 
3275     let result = wizen(
3276         &["-Scli"],
3277         r#"(module
3278             (import "wasi_snapshot_preview1" "fd_write" (func (param i32 i32 i32 i32) (result i32)))
3279             (func (export "wizer-initialize"))
3280         )"#,
3281     )?;
3282     assert!(result.status.success());
3283 
3284     Ok(())
3285 }
3286 
3287 #[test]
wizer_components() -> Result<()>3288 fn wizer_components() -> Result<()> {
3289     let result = wizen(
3290         &[],
3291         r#"
3292 (component
3293   (core module $a
3294     (global (mut i32) (i32.const 0))
3295     (func (export "init")
3296         i32.const 100
3297         global.set 0)
3298   )
3299   (core instance $a (instantiate $a))
3300   (func (export "wizer-initialize") (canon lift (core func $a "init")))
3301 )
3302         "#,
3303     )?;
3304     assert!(result.status.success());
3305 
3306     let component_with_wasi = r#"
3307 (component
3308   (import "wasi:cli/[email protected]" (instance
3309     (export "get-arguments" (func (result (list string))))
3310   ))
3311   (core module $a
3312     (global (mut i32) (i32.const 0))
3313     (func (export "init")
3314         i32.const 100
3315         global.set 0)
3316   )
3317   (core instance $a (instantiate $a))
3318   (func (export "wizer-initialize") (canon lift (core func $a "init")))
3319 )
3320         "#;
3321 
3322     let result = wizen(&[], component_with_wasi)?;
3323     assert!(!result.status.success());
3324     let result = wizen(&["-Scli"], component_with_wasi)?;
3325     assert!(result.status.success());
3326 
3327     Ok(())
3328 }
3329