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