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(×);
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