1 #![cfg(not(miri))] 2 3 use anyhow::{bail, Result}; 4 use std::fs::File; 5 use std::io::Write; 6 use std::path::Path; 7 use std::process::{Command, 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 // Skip this test on platforms that don't support threads. 556 if crate::threads::engine().is_none() { 557 return Ok(()); 558 } 559 let wasm = build_wasm("tests/all/cli_tests/threads.wat")?; 560 let stdout = run_wasmtime(&[ 561 "run", 562 "-Wthreads", 563 "-Sthreads", 564 "-Ccache=n", 565 wasm.path().to_str().unwrap(), 566 ])?; 567 568 assert!( 569 stdout 570 == "Called _start\n\ 571 Running wasi_thread_start\n\ 572 Running wasi_thread_start\n\ 573 Running wasi_thread_start\n\ 574 Done\n" 575 ); 576 Ok(()) 577 } 578 579 #[cfg(feature = "wasi-threads")] 580 #[test] 581 fn run_simple_with_wasi_threads() -> Result<()> { 582 // Skip this test on platforms that don't support threads. 583 if crate::threads::engine().is_none() { 584 return Ok(()); 585 } 586 // We expect to be able to run Wasm modules that do not have correct 587 // wasi-thread entry points or imported shared memory as long as no threads 588 // are spawned. 589 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 590 let stdout = run_wasmtime(&[ 591 "run", 592 "-Wthreads", 593 "-Sthreads", 594 "-Ccache=n", 595 "--invoke", 596 "simple", 597 wasm.path().to_str().unwrap(), 598 "4", 599 ])?; 600 assert_eq!(stdout, "4\n"); 601 Ok(()) 602 } 603 604 #[test] 605 fn wasm_flags() -> Result<()> { 606 // Any argument after the wasm module should be interpreted as for the 607 // command itself 608 let stdout = run_wasmtime(&[ 609 "run", 610 "--", 611 "tests/all/cli_tests/print-arguments.wat", 612 "--argument", 613 "-for", 614 "the", 615 "command", 616 ])?; 617 assert_eq!( 618 stdout, 619 "\ 620 print-arguments.wat\n\ 621 --argument\n\ 622 -for\n\ 623 the\n\ 624 command\n\ 625 " 626 ); 627 let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?; 628 assert_eq!( 629 stdout, 630 "\ 631 print-arguments.wat\n\ 632 -\n\ 633 " 634 ); 635 let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?; 636 assert_eq!( 637 stdout, 638 "\ 639 print-arguments.wat\n\ 640 --\n\ 641 " 642 ); 643 let stdout = run_wasmtime(&[ 644 "run", 645 "--", 646 "tests/all/cli_tests/print-arguments.wat", 647 "--", 648 "--", 649 "-a", 650 "b", 651 ])?; 652 assert_eq!( 653 stdout, 654 "\ 655 print-arguments.wat\n\ 656 --\n\ 657 --\n\ 658 -a\n\ 659 b\n\ 660 " 661 ); 662 Ok(()) 663 } 664 665 #[test] 666 fn name_same_as_builtin_command() -> Result<()> { 667 // a bare subcommand shouldn't run successfully 668 let output = get_wasmtime_command()? 669 .current_dir("tests/all/cli_tests") 670 .arg("run") 671 .output()?; 672 assert!(!output.status.success()); 673 674 // a `--` prefix should let everything else get interpreted as a wasm 675 // module and arguments, even if the module has a name like `run` 676 let output = get_wasmtime_command()? 677 .current_dir("tests/all/cli_tests") 678 .arg("--") 679 .arg("run") 680 .output()?; 681 assert!(output.status.success(), "expected success got {output:#?}"); 682 683 // Passing options before the subcommand should work and doesn't require 684 // `--` to disambiguate 685 let output = get_wasmtime_command()? 686 .current_dir("tests/all/cli_tests") 687 .arg("-Ccache=n") 688 .arg("run") 689 .output()?; 690 assert!(output.status.success(), "expected success got {output:#?}"); 691 Ok(()) 692 } 693 694 #[test] 695 #[cfg(unix)] 696 fn run_just_stdin_argument() -> Result<()> { 697 let output = get_wasmtime_command()? 698 .arg("-") 699 .stdin(File::open("tests/all/cli_tests/simple.wat")?) 700 .output()?; 701 assert!(output.status.success()); 702 Ok(()) 703 } 704 705 #[test] 706 fn wasm_flags_without_subcommand() -> Result<()> { 707 let output = get_wasmtime_command()? 708 .current_dir("tests/all/cli_tests/") 709 .arg("print-arguments.wat") 710 .arg("-foo") 711 .arg("bar") 712 .output()?; 713 assert!(output.status.success()); 714 assert_eq!( 715 String::from_utf8_lossy(&output.stdout), 716 "\ 717 print-arguments.wat\n\ 718 -foo\n\ 719 bar\n\ 720 " 721 ); 722 Ok(()) 723 } 724 725 #[test] 726 fn wasi_misaligned_pointer() -> Result<()> { 727 let output = get_wasmtime_command()? 728 .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat") 729 .output()?; 730 assert!(!output.status.success()); 731 let stderr = String::from_utf8_lossy(&output.stderr); 732 assert!( 733 stderr.contains("Pointer not aligned"), 734 "bad stderr: {stderr}", 735 ); 736 Ok(()) 737 } 738 739 #[test] 740 #[cfg_attr(not(feature = "component-model"), ignore)] 741 fn hello_with_preview2() -> Result<()> { 742 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; 743 let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?; 744 assert_eq!(stdout, "Hello, world!\n"); 745 Ok(()) 746 } 747 748 #[test] 749 #[cfg_attr(not(feature = "component-model"), ignore)] 750 fn component_missing_feature() -> Result<()> { 751 let path = "tests/all/cli_tests/empty-component.wat"; 752 let wasm = build_wasm(path)?; 753 let output = get_wasmtime_command()? 754 .arg("-Ccache=n") 755 .arg("-Wcomponent-model=n") 756 .arg(wasm.path()) 757 .output()?; 758 assert!(!output.status.success()); 759 let stderr = String::from_utf8_lossy(&output.stderr); 760 assert!( 761 stderr.contains("cannot execute a component without `--wasm component-model`"), 762 "bad stderr: {stderr}" 763 ); 764 765 // also tests with raw *.wat input 766 let output = get_wasmtime_command()? 767 .arg("-Ccache=n") 768 .arg("-Wcomponent-model=n") 769 .arg(path) 770 .output()?; 771 assert!(!output.status.success()); 772 let stderr = String::from_utf8_lossy(&output.stderr); 773 assert!( 774 stderr.contains("cannot execute a component without `--wasm component-model`"), 775 "bad stderr: {stderr}" 776 ); 777 778 Ok(()) 779 } 780 781 #[test] 782 #[cfg_attr(not(feature = "component-model"), ignore)] 783 fn component_enabled_by_default() -> Result<()> { 784 let path = "tests/all/cli_tests/component-basic.wat"; 785 let wasm = build_wasm(path)?; 786 let output = get_wasmtime_command()? 787 .arg("-Ccache=n") 788 .arg(wasm.path()) 789 .output()?; 790 assert!(output.status.success()); 791 792 // also tests with raw *.wat input 793 let output = get_wasmtime_command()? 794 .arg("-Ccache=n") 795 .arg(path) 796 .output()?; 797 assert!(output.status.success()); 798 799 Ok(()) 800 } 801 802 // If the text format is invalid then the filename should be mentioned in the 803 // error message. 804 #[test] 805 fn bad_text_syntax() -> Result<()> { 806 let output = get_wasmtime_command()? 807 .arg("-Ccache=n") 808 .arg("tests/all/cli_tests/bad-syntax.wat") 809 .output()?; 810 assert!(!output.status.success()); 811 let stderr = String::from_utf8_lossy(&output.stderr); 812 assert!( 813 stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"), 814 "bad stderr: {stderr}" 815 ); 816 Ok(()) 817 } 818 819 #[test] 820 #[cfg_attr(not(feature = "component-model"), ignore)] 821 fn run_basic_component() -> Result<()> { 822 let path = "tests/all/cli_tests/component-basic.wat"; 823 let wasm = build_wasm(path)?; 824 825 // Run both the `*.wasm` binary and the text format 826 run_wasmtime(&[ 827 "-Ccache=n", 828 "-Wcomponent-model", 829 wasm.path().to_str().unwrap(), 830 ])?; 831 run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?; 832 833 Ok(()) 834 } 835 836 #[test] 837 #[cfg_attr(not(feature = "component-model"), ignore)] 838 fn run_precompiled_component() -> Result<()> { 839 let td = TempDir::new()?; 840 let cwasm = td.path().join("component-basic.cwasm"); 841 let stdout = run_wasmtime(&[ 842 "compile", 843 "tests/all/cli_tests/component-basic.wat", 844 "-o", 845 cwasm.to_str().unwrap(), 846 "-Wcomponent-model", 847 ])?; 848 assert_eq!(stdout, ""); 849 let stdout = run_wasmtime(&[ 850 "run", 851 "-Wcomponent-model", 852 "--allow-precompiled", 853 cwasm.to_str().unwrap(), 854 ])?; 855 assert_eq!(stdout, ""); 856 857 Ok(()) 858 } 859 860 // Disable test on s390x because the large allocation may actually succeed; 861 // the whole 64-bit address space is available on this platform. 862 #[test] 863 #[cfg(not(target_arch = "s390x"))] 864 fn memory_growth_failure() -> Result<()> { 865 let output = get_wasmtime_command()? 866 .args(&[ 867 "run", 868 "-Wmemory64", 869 "-Wtrap-on-grow-failure", 870 "tests/all/cli_tests/memory-grow-failure.wat", 871 ]) 872 .output()?; 873 assert!(!output.status.success()); 874 let stderr = String::from_utf8_lossy(&output.stderr); 875 assert!( 876 stderr.contains("forcing a memory growth failure to be a trap"), 877 "bad stderr: {stderr}" 878 ); 879 Ok(()) 880 } 881 882 #[test] 883 fn table_growth_failure() -> Result<()> { 884 let output = get_wasmtime_command()? 885 .args(&[ 886 "run", 887 "-Wtrap-on-grow-failure", 888 "tests/all/cli_tests/table-grow-failure.wat", 889 ]) 890 .output()?; 891 assert!(!output.status.success()); 892 let stderr = String::from_utf8_lossy(&output.stderr); 893 assert!( 894 stderr.contains("forcing trap when growing table"), 895 "bad stderr: {stderr}" 896 ); 897 Ok(()) 898 } 899 900 #[test] 901 fn table_growth_failure2() -> Result<()> { 902 let output = get_wasmtime_command()? 903 .args(&[ 904 "run", 905 "-Wtrap-on-grow-failure", 906 "tests/all/cli_tests/table-grow-failure2.wat", 907 ]) 908 .output()?; 909 assert!(!output.status.success()); 910 let stderr = String::from_utf8_lossy(&output.stderr); 911 let expected = if cfg!(target_pointer_width = "32") { 912 "overflow calculating new table size" 913 } else { 914 "forcing trap when growing table to 4294967296 elements" 915 }; 916 assert!(stderr.contains(expected), "bad stderr: {stderr}"); 917 Ok(()) 918 } 919 920 #[test] 921 fn option_group_help() -> Result<()> { 922 run_wasmtime(&["run", "-Whelp"])?; 923 run_wasmtime(&["run", "-O", "help"])?; 924 run_wasmtime(&["run", "--codegen", "help"])?; 925 run_wasmtime(&["run", "--debug=help"])?; 926 run_wasmtime(&["run", "-Shelp"])?; 927 run_wasmtime(&["run", "-Whelp-long"])?; 928 Ok(()) 929 } 930 931 #[test] 932 fn option_group_comma_separated() -> Result<()> { 933 run_wasmtime(&[ 934 "run", 935 "-Wrelaxed-simd,simd", 936 "tests/all/cli_tests/simple.wat", 937 ])?; 938 Ok(()) 939 } 940 941 #[test] 942 fn option_group_boolean_parsing() -> Result<()> { 943 run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?; 944 run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?; 945 run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?; 946 run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?; 947 run_wasmtime(&[ 948 "run", 949 "-Wrelaxed-simd=yes", 950 "tests/all/cli_tests/simple.wat", 951 ])?; 952 run_wasmtime(&[ 953 "run", 954 "-Wrelaxed-simd=true", 955 "tests/all/cli_tests/simple.wat", 956 ])?; 957 run_wasmtime(&[ 958 "run", 959 "-Wrelaxed-simd=false", 960 "tests/all/cli_tests/simple.wat", 961 ])?; 962 Ok(()) 963 } 964 965 #[test] 966 fn preview2_stdin() -> Result<()> { 967 let test = "tests/all/cli_tests/count-stdin.wat"; 968 let cmd = || -> Result<_> { 969 let mut cmd = get_wasmtime_command()?; 970 cmd.arg("--invoke=count").arg("-Spreview2").arg(test); 971 Ok(cmd) 972 }; 973 974 // read empty pipe is ok 975 let output = cmd()?.output()?; 976 assert!(output.status.success()); 977 assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n"); 978 979 // read itself is ok 980 let file = File::open(test)?; 981 let size = file.metadata()?.len(); 982 let output = cmd()?.stdin(File::open(test)?).output()?; 983 assert!(output.status.success()); 984 assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n")); 985 986 // read piped input ok is ok 987 let mut child = cmd()? 988 .stdin(Stdio::piped()) 989 .stdout(Stdio::piped()) 990 .stderr(Stdio::piped()) 991 .spawn()?; 992 let mut stdin = child.stdin.take().unwrap(); 993 std::thread::spawn(move || { 994 stdin.write_all(b"hello").unwrap(); 995 }); 996 let output = child.wait_with_output()?; 997 assert!(output.status.success()); 998 assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n"); 999 1000 let count_up_to = |n: usize| -> Result<_> { 1001 let mut child = get_wasmtime_command()? 1002 .arg("--invoke=count-up-to") 1003 .arg("-Spreview2") 1004 .arg(test) 1005 .arg(n.to_string()) 1006 .stdin(Stdio::piped()) 1007 .stdout(Stdio::piped()) 1008 .stderr(Stdio::piped()) 1009 .spawn()?; 1010 let mut stdin = child.stdin.take().unwrap(); 1011 let t = std::thread::spawn(move || { 1012 let mut written = 0; 1013 let bytes = [0; 64 * 1024]; 1014 loop { 1015 written += match stdin.write(&bytes) { 1016 Ok(n) => n, 1017 Err(_) => break written, 1018 }; 1019 } 1020 }); 1021 let output = child.wait_with_output()?; 1022 assert!(output.status.success()); 1023 let written = t.join().unwrap(); 1024 let read = String::from_utf8_lossy(&output.stdout) 1025 .trim() 1026 .parse::<usize>() 1027 .unwrap(); 1028 // The test reads in 1000 byte chunks so make sure that it doesn't read 1029 // more than 1000 bytes than requested. 1030 assert!(read < n + 1000, "test read too much {read}"); 1031 Ok(written) 1032 }; 1033 1034 // wasmtime shouldn't eat information that the guest never actually tried to 1035 // read. 1036 // 1037 // NB: this may be a bit flaky. Exactly how much we wrote in the above 1038 // helper thread depends on how much the OS buffers for us. For now give 1039 // some some slop and assume that OSes are unlikely to buffer more than 1040 // that. 1041 let slop = 256 * 1024; 1042 for amt in [0, 100, 100_000] { 1043 let written = count_up_to(amt)?; 1044 assert!(written < slop + amt, "wrote too much {written}"); 1045 } 1046 Ok(()) 1047 } 1048 1049 #[test] 1050 fn float_args() -> Result<()> { 1051 let result = run_wasmtime(&[ 1052 "--invoke", 1053 "echo_f32", 1054 "tests/all/cli_tests/simple.wat", 1055 "1.0", 1056 ])?; 1057 assert_eq!(result, "1\n"); 1058 let result = run_wasmtime(&[ 1059 "--invoke", 1060 "echo_f64", 1061 "tests/all/cli_tests/simple.wat", 1062 "1.1", 1063 ])?; 1064 assert_eq!(result, "1.1\n"); 1065 Ok(()) 1066 } 1067 1068 #[test] 1069 fn mpk_without_pooling() -> Result<()> { 1070 let output = get_wasmtime_command()? 1071 .args(&[ 1072 "run", 1073 "-O", 1074 "memory-protection-keys=y", 1075 "--invoke", 1076 "echo_f32", 1077 "tests/all/cli_tests/simple.wat", 1078 "1.0", 1079 ]) 1080 .env("WASMTIME_NEW_CLI", "1") 1081 .output()?; 1082 assert!(!output.status.success()); 1083 Ok(()) 1084 } 1085 1086 // Very basic use case: compile binary wasm file and run specific function with arguments. 1087 #[test] 1088 fn increase_stack_size() -> Result<()> { 1089 run_wasmtime(&[ 1090 "run", 1091 "--invoke", 1092 "simple", 1093 &format!("-Wmax-wasm-stack={}", 5 << 20), 1094 "-Ccache=n", 1095 "tests/all/cli_tests/simple.wat", 1096 "4", 1097 ])?; 1098 Ok(()) 1099 } 1100 1101 mod test_programs { 1102 use super::{get_wasmtime_command, run_wasmtime}; 1103 use anyhow::{bail, Context, Result}; 1104 use http_body_util::BodyExt; 1105 use hyper::header::HeaderValue; 1106 use std::io::{BufRead, BufReader, Read, Write}; 1107 use std::net::SocketAddr; 1108 use std::process::{Child, Command, Stdio}; 1109 use test_programs_artifacts::*; 1110 use tokio::net::TcpStream; 1111 1112 macro_rules! assert_test_exists { 1113 ($name:ident) => { 1114 #[allow(unused_imports)] 1115 use self::$name as _; 1116 }; 1117 } 1118 foreach_cli!(assert_test_exists); 1119 1120 #[test] 1121 fn cli_hello_stdout() -> Result<()> { 1122 run_wasmtime(&["run", "-Wcomponent-model", CLI_HELLO_STDOUT_COMPONENT])?; 1123 Ok(()) 1124 } 1125 1126 #[test] 1127 fn cli_args() -> Result<()> { 1128 run_wasmtime(&[ 1129 "run", 1130 "-Wcomponent-model", 1131 CLI_ARGS_COMPONENT, 1132 "hello", 1133 "this", 1134 "", 1135 "is an argument", 1136 "with emoji", 1137 ])?; 1138 Ok(()) 1139 } 1140 1141 #[test] 1142 fn cli_stdin_empty() -> Result<()> { 1143 let mut child = get_wasmtime_command()? 1144 .args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT]) 1145 .stdout(Stdio::piped()) 1146 .stderr(Stdio::piped()) 1147 .stdin(Stdio::piped()) 1148 .spawn()?; 1149 child 1150 .stdin 1151 .take() 1152 .unwrap() 1153 .write_all(b"not to be read") 1154 .unwrap(); 1155 let output = child.wait_with_output()?; 1156 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1157 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1158 assert!(output.status.success()); 1159 Ok(()) 1160 } 1161 1162 #[test] 1163 fn cli_stdin() -> Result<()> { 1164 let mut child = get_wasmtime_command()? 1165 .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT]) 1166 .stdout(Stdio::piped()) 1167 .stderr(Stdio::piped()) 1168 .stdin(Stdio::piped()) 1169 .spawn()?; 1170 child 1171 .stdin 1172 .take() 1173 .unwrap() 1174 .write_all(b"So rested he by the Tumtum tree") 1175 .unwrap(); 1176 let output = child.wait_with_output()?; 1177 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1178 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1179 assert!(output.status.success()); 1180 Ok(()) 1181 } 1182 1183 #[test] 1184 fn cli_splice_stdin() -> Result<()> { 1185 let mut child = get_wasmtime_command()? 1186 .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT]) 1187 .stdout(Stdio::piped()) 1188 .stderr(Stdio::piped()) 1189 .stdin(Stdio::piped()) 1190 .spawn()?; 1191 let msg = "So rested he by the Tumtum tree"; 1192 child 1193 .stdin 1194 .take() 1195 .unwrap() 1196 .write_all(msg.as_bytes()) 1197 .unwrap(); 1198 let output = child.wait_with_output()?; 1199 assert!(output.status.success()); 1200 let stdout = String::from_utf8_lossy(&output.stdout); 1201 let stderr = String::from_utf8_lossy(&output.stderr); 1202 if !stderr.is_empty() { 1203 eprintln!("{stderr}"); 1204 } 1205 1206 assert_eq!( 1207 format!( 1208 "before splice\n{msg}\ncompleted splicing {} bytes\n", 1209 msg.as_bytes().len() 1210 ), 1211 stdout 1212 ); 1213 Ok(()) 1214 } 1215 1216 #[test] 1217 fn cli_env() -> Result<()> { 1218 run_wasmtime(&[ 1219 "run", 1220 "-Wcomponent-model", 1221 "--env=frabjous=day", 1222 "--env=callooh=callay", 1223 CLI_ENV_COMPONENT, 1224 ])?; 1225 Ok(()) 1226 } 1227 1228 #[test] 1229 fn cli_file_read() -> Result<()> { 1230 let dir = tempfile::tempdir()?; 1231 1232 std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; 1233 1234 run_wasmtime(&[ 1235 "run", 1236 "-Wcomponent-model", 1237 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1238 CLI_FILE_READ_COMPONENT, 1239 ])?; 1240 Ok(()) 1241 } 1242 1243 #[test] 1244 fn cli_file_append() -> Result<()> { 1245 let dir = tempfile::tempdir()?; 1246 1247 std::fs::File::create(dir.path().join("bar.txt"))? 1248 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1249 1250 run_wasmtime(&[ 1251 "run", 1252 "-Wcomponent-model", 1253 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1254 CLI_FILE_APPEND_COMPONENT, 1255 ])?; 1256 1257 let contents = std::fs::read(dir.path().join("bar.txt"))?; 1258 assert_eq!( 1259 std::str::from_utf8(&contents).unwrap(), 1260 "'Twas brillig, and the slithy toves.\n\ 1261 Did gyre and gimble in the wabe;\n\ 1262 All mimsy were the borogoves,\n\ 1263 And the mome raths outgrabe.\n" 1264 ); 1265 Ok(()) 1266 } 1267 1268 #[test] 1269 fn cli_file_dir_sync() -> Result<()> { 1270 let dir = tempfile::tempdir()?; 1271 1272 std::fs::File::create(dir.path().join("bar.txt"))? 1273 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1274 1275 run_wasmtime(&[ 1276 "run", 1277 "-Wcomponent-model", 1278 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1279 CLI_FILE_DIR_SYNC_COMPONENT, 1280 ])?; 1281 1282 Ok(()) 1283 } 1284 1285 #[test] 1286 fn cli_exit_success() -> Result<()> { 1287 run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?; 1288 Ok(()) 1289 } 1290 1291 #[test] 1292 fn cli_exit_default() -> Result<()> { 1293 run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?; 1294 Ok(()) 1295 } 1296 1297 #[test] 1298 fn cli_exit_failure() -> Result<()> { 1299 let output = get_wasmtime_command()? 1300 .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT]) 1301 .output()?; 1302 assert!(!output.status.success()); 1303 assert_eq!(output.status.code(), Some(1)); 1304 Ok(()) 1305 } 1306 1307 #[test] 1308 fn cli_exit_with_code() -> Result<()> { 1309 let output = get_wasmtime_command()? 1310 .args(&[ 1311 "run", 1312 "-Wcomponent-model", 1313 "-Scli-exit-with-code", 1314 CLI_EXIT_WITH_CODE_COMPONENT, 1315 ]) 1316 .output()?; 1317 assert!(!output.status.success()); 1318 assert_eq!(output.status.code(), Some(42)); 1319 Ok(()) 1320 } 1321 1322 #[test] 1323 fn cli_exit_panic() -> Result<()> { 1324 let output = get_wasmtime_command()? 1325 .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT]) 1326 .output()?; 1327 assert!(!output.status.success()); 1328 let stderr = String::from_utf8_lossy(&output.stderr); 1329 assert!(stderr.contains("Curiouser and curiouser!")); 1330 Ok(()) 1331 } 1332 1333 #[test] 1334 fn cli_directory_list() -> Result<()> { 1335 let dir = tempfile::tempdir()?; 1336 1337 std::fs::File::create(dir.path().join("foo.txt"))?; 1338 std::fs::File::create(dir.path().join("bar.txt"))?; 1339 std::fs::File::create(dir.path().join("baz.txt"))?; 1340 std::fs::create_dir(dir.path().join("sub"))?; 1341 std::fs::File::create(dir.path().join("sub").join("wow.txt"))?; 1342 std::fs::File::create(dir.path().join("sub").join("yay.txt"))?; 1343 1344 run_wasmtime(&[ 1345 "run", 1346 "-Wcomponent-model", 1347 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1348 CLI_DIRECTORY_LIST_COMPONENT, 1349 ])?; 1350 Ok(()) 1351 } 1352 1353 #[test] 1354 fn cli_default_clocks() -> Result<()> { 1355 run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?; 1356 Ok(()) 1357 } 1358 1359 #[test] 1360 fn cli_export_cabi_realloc() -> Result<()> { 1361 run_wasmtime(&[ 1362 "run", 1363 "-Wcomponent-model", 1364 CLI_EXPORT_CABI_REALLOC_COMPONENT, 1365 ])?; 1366 Ok(()) 1367 } 1368 1369 #[test] 1370 fn run_wasi_http_component() -> Result<()> { 1371 let output = super::run_wasmtime_for_output( 1372 &[ 1373 "-Ccache=no", 1374 "-Wcomponent-model", 1375 "-Scli,http,preview2", 1376 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, 1377 ], 1378 None, 1379 )?; 1380 println!("{}", String::from_utf8_lossy(&output.stderr)); 1381 let stdout = String::from_utf8_lossy(&output.stdout); 1382 println!("{stdout}"); 1383 assert!(stdout.starts_with("Called _start\n")); 1384 assert!(stdout.ends_with("Done\n")); 1385 assert!(output.status.success()); 1386 Ok(()) 1387 } 1388 1389 // Test to ensure that prints in the guest aren't buffered on the host by 1390 // accident. The test here will print something without a newline and then 1391 // wait for input on stdin, and the test here is to ensure that the 1392 // character shows up here even as the guest is waiting on input via stdin. 1393 #[test] 1394 fn cli_stdio_write_flushes() -> Result<()> { 1395 fn run(args: &[&str]) -> Result<()> { 1396 println!("running {args:?}"); 1397 let mut child = get_wasmtime_command()? 1398 .args(args) 1399 .stdin(Stdio::piped()) 1400 .stdout(Stdio::piped()) 1401 .spawn()?; 1402 let mut stdout = child.stdout.take().unwrap(); 1403 let mut buf = [0; 10]; 1404 match stdout.read(&mut buf) { 1405 Ok(2) => assert_eq!(&buf[..2], b"> "), 1406 e => panic!("unexpected read result {e:?}"), 1407 } 1408 drop(stdout); 1409 drop(child.stdin.take().unwrap()); 1410 let status = child.wait()?; 1411 assert!(status.success()); 1412 Ok(()) 1413 } 1414 1415 run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?; 1416 run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?; 1417 run(&[ 1418 "run", 1419 "-Wcomponent-model", 1420 CLI_STDIO_WRITE_FLUSHES_COMPONENT, 1421 ])?; 1422 Ok(()) 1423 } 1424 1425 #[test] 1426 fn cli_no_tcp() -> Result<()> { 1427 let output = super::run_wasmtime_for_output( 1428 &[ 1429 "-Wcomponent-model", 1430 // Turn on network but turn off TCP 1431 "-Sinherit-network,tcp=no", 1432 CLI_NO_TCP_COMPONENT, 1433 ], 1434 None, 1435 )?; 1436 println!("{}", String::from_utf8_lossy(&output.stderr)); 1437 assert!(output.status.success()); 1438 Ok(()) 1439 } 1440 1441 #[test] 1442 fn cli_no_udp() -> Result<()> { 1443 let output = super::run_wasmtime_for_output( 1444 &[ 1445 "-Wcomponent-model", 1446 // Turn on network but turn off UDP 1447 "-Sinherit-network,udp=no", 1448 CLI_NO_UDP_COMPONENT, 1449 ], 1450 None, 1451 )?; 1452 println!("{}", String::from_utf8_lossy(&output.stderr)); 1453 assert!(output.status.success()); 1454 Ok(()) 1455 } 1456 1457 #[test] 1458 fn cli_no_ip_name_lookup() -> Result<()> { 1459 let output = super::run_wasmtime_for_output( 1460 &[ 1461 "-Wcomponent-model", 1462 // Turn on network but ensure name lookup is disabled 1463 "-Sinherit-network,allow-ip-name-lookup=no", 1464 CLI_NO_IP_NAME_LOOKUP_COMPONENT, 1465 ], 1466 None, 1467 )?; 1468 println!("{}", String::from_utf8_lossy(&output.stderr)); 1469 assert!(output.status.success()); 1470 Ok(()) 1471 } 1472 1473 #[test] 1474 fn cli_sleep() -> Result<()> { 1475 run_wasmtime(&["run", CLI_SLEEP])?; 1476 run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?; 1477 Ok(()) 1478 } 1479 1480 #[test] 1481 fn cli_sleep_forever() -> Result<()> { 1482 for timeout in [ 1483 // Tests still pass when we race with going to sleep. 1484 "-Wtimeout=1ns", 1485 // Tests pass when we wait till the Wasm has (likely) gone to sleep. 1486 "-Wtimeout=250ms", 1487 ] { 1488 let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER]).unwrap_err(); 1489 let e = e.to_string(); 1490 println!("Got error: {e}"); 1491 assert!(e.contains("interrupt")); 1492 1493 let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err(); 1494 let e = e.to_string(); 1495 println!("Got error: {e}"); 1496 assert!(e.contains("interrupt")); 1497 } 1498 1499 Ok(()) 1500 } 1501 1502 /// Helper structure to manage an invocation of `wasmtime serve` 1503 struct WasmtimeServe { 1504 child: Option<Child>, 1505 addr: SocketAddr, 1506 shutdown_addr: SocketAddr, 1507 } 1508 1509 impl WasmtimeServe { 1510 /// Creates a new server which will serve the wasm component pointed to 1511 /// by `wasm`. 1512 /// 1513 /// A `configure` callback is provided to specify how `wasmtime serve` 1514 /// will be invoked and configure arguments such as headers. 1515 fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> { 1516 // Spawn `wasmtime serve` on port 0 which will randomly assign it a 1517 // port. 1518 let mut cmd = super::get_wasmtime_command()?; 1519 cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm); 1520 configure(&mut cmd); 1521 Self::spawn(&mut cmd) 1522 } 1523 1524 fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> { 1525 cmd.arg("--shutdown-addr=127.0.0.1:0"); 1526 cmd.stdin(Stdio::null()); 1527 cmd.stdout(Stdio::piped()); 1528 cmd.stderr(Stdio::piped()); 1529 let mut child = cmd.spawn()?; 1530 1531 // Read the first few lines of stderr which will say which address 1532 // it's listening on. The first line is the shutdown line (with 1533 // `--shutdown-addr`) and the second is what `--addr` was bound to. 1534 // This is done to figure out what `:0` was bound to in the child 1535 // process. 1536 let mut line = String::new(); 1537 let mut reader = BufReader::new(child.stderr.take().unwrap()); 1538 let mut read_addr_from_line = |prefix: &str| -> Result<SocketAddr> { 1539 reader.read_line(&mut line)?; 1540 1541 if !line.starts_with(prefix) { 1542 bail!("input line `{line}` didn't start with `{prefix}`"); 1543 } 1544 match line.find("127.0.0.1").and_then(|addr_start| { 1545 let addr = &line[addr_start..]; 1546 let addr_end = addr.find("/")?; 1547 addr[..addr_end].parse().ok() 1548 }) { 1549 Some(addr) => { 1550 line.truncate(0); 1551 Ok(addr) 1552 } 1553 None => bail!("failed to address from: {line}"), 1554 } 1555 }; 1556 let shutdown_addr = read_addr_from_line("Listening for shutdown"); 1557 let addr = read_addr_from_line("Serving HTTP on"); 1558 let (shutdown_addr, addr) = match (shutdown_addr, addr) { 1559 (Ok(a), Ok(b)) => (a, b), 1560 // If either failed kill the child and otherwise try to shepherd 1561 // along any contextual information we have. 1562 (Err(a), _) | (_, Err(a)) => { 1563 child.kill()?; 1564 child.wait()?; 1565 reader.read_to_string(&mut line)?; 1566 return Err(a.context(line)); 1567 } 1568 }; 1569 assert!(reader.buffer().is_empty()); 1570 child.stderr = Some(reader.into_inner()); 1571 Ok(WasmtimeServe { 1572 child: Some(child), 1573 addr, 1574 shutdown_addr, 1575 }) 1576 } 1577 1578 /// Completes this server gracefully by printing the output on failure. 1579 fn finish(mut self) -> Result<(String, String)> { 1580 let mut child = self.child.take().unwrap(); 1581 1582 // If the child process has already exited, then great! Otherwise 1583 // the server is still running and it shouldn't be possible to exit 1584 // until a shutdown signal is sent, so do that here. Make a TCP 1585 // connection to the shutdown port which is used as a shutdown 1586 // signal. 1587 if child.try_wait()?.is_none() { 1588 std::net::TcpStream::connect(&self.shutdown_addr) 1589 .context("failed to initiate graceful shutdown")?; 1590 } 1591 1592 // Regardless of whether we just shut the server down or whether it 1593 // was already shut down (e.g. panicked or similar), wait for the 1594 // result here. The result should succeed (e.g. 0 exit status), and 1595 // if it did then the stdout/stderr are the caller's problem. 1596 let output = child.wait_with_output()?; 1597 if !output.status.success() { 1598 bail!("child failed {output:?}"); 1599 } 1600 1601 Ok(( 1602 String::from_utf8_lossy(&output.stdout).into_owned(), 1603 String::from_utf8_lossy(&output.stderr).into_owned(), 1604 )) 1605 } 1606 1607 /// Send a request to this server and wait for the response. 1608 async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> { 1609 let (mut send, conn_task) = self.start_requests().await?; 1610 1611 let response = send 1612 .send_request(req) 1613 .await 1614 .context("error sending request")?; 1615 drop(send); 1616 let (parts, body) = response.into_parts(); 1617 1618 let body = body.collect().await.context("failed to read body")?; 1619 assert!(body.trailers().is_none()); 1620 let body = std::str::from_utf8(&body.to_bytes())?.to_string(); 1621 1622 conn_task.await??; 1623 1624 Ok(http::Response::from_parts(parts, body)) 1625 } 1626 1627 async fn start_requests( 1628 &self, 1629 ) -> Result<( 1630 hyper::client::conn::http1::SendRequest<String>, 1631 tokio::task::JoinHandle<hyper::Result<()>>, 1632 )> { 1633 let tcp = TcpStream::connect(&self.addr) 1634 .await 1635 .context("failed to connect")?; 1636 let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp); 1637 let (send, conn) = hyper::client::conn::http1::handshake(tcp) 1638 .await 1639 .context("failed http handshake")?; 1640 Ok((send, tokio::task::spawn(conn))) 1641 } 1642 } 1643 1644 // Don't leave child processes running by accident so kill the child process 1645 // if our server goes away. 1646 impl Drop for WasmtimeServe { 1647 fn drop(&mut self) { 1648 let mut child = match self.child.take() { 1649 Some(child) => child, 1650 None => return, 1651 }; 1652 if child.kill().is_err() { 1653 return; 1654 } 1655 let output = match child.wait_with_output() { 1656 Ok(output) => output, 1657 Err(_) => return, 1658 }; 1659 1660 println!("server status: {}", output.status); 1661 if !output.stdout.is_empty() { 1662 println!( 1663 "server stdout:\n{}", 1664 String::from_utf8_lossy(&output.stdout) 1665 ); 1666 } 1667 if !output.stderr.is_empty() { 1668 println!( 1669 "server stderr:\n{}", 1670 String::from_utf8_lossy(&output.stderr) 1671 ); 1672 } 1673 } 1674 } 1675 1676 #[tokio::test] 1677 async fn cli_serve_echo_env() -> Result<()> { 1678 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1679 cmd.arg("--env=FOO=bar"); 1680 cmd.arg("--env=BAR"); 1681 cmd.arg("-Scli"); 1682 cmd.env_remove("BAR"); 1683 })?; 1684 1685 let foo_env = server 1686 .send_request( 1687 hyper::Request::builder() 1688 .uri("http://localhost/") 1689 .header("env", "FOO") 1690 .body(String::new()) 1691 .context("failed to make request")?, 1692 ) 1693 .await?; 1694 1695 assert!(foo_env.status().is_success()); 1696 assert!(foo_env.body().is_empty()); 1697 let headers = foo_env.headers(); 1698 assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar"))); 1699 1700 let bar_env = server 1701 .send_request( 1702 hyper::Request::builder() 1703 .uri("http://localhost/") 1704 .header("env", "BAR") 1705 .body(String::new()) 1706 .context("failed to make request")?, 1707 ) 1708 .await?; 1709 1710 assert!(bar_env.status().is_success()); 1711 assert!(bar_env.body().is_empty()); 1712 let headers = bar_env.headers(); 1713 assert_eq!(headers.get("env"), None); 1714 1715 server.finish()?; 1716 Ok(()) 1717 } 1718 1719 #[tokio::test] 1720 async fn cli_serve_outgoing_body_config() -> Result<()> { 1721 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1722 cmd.arg("-Scli"); 1723 cmd.arg("-Shttp-outgoing-body-buffer-chunks=2"); 1724 cmd.arg("-Shttp-outgoing-body-chunk-size=1024"); 1725 })?; 1726 1727 let resp = server 1728 .send_request( 1729 hyper::Request::builder() 1730 .uri("http://localhost/") 1731 .header("env", "FOO") 1732 .body(String::new()) 1733 .context("failed to make request")?, 1734 ) 1735 .await?; 1736 1737 assert!(resp.status().is_success()); 1738 1739 server.finish()?; 1740 Ok(()) 1741 } 1742 1743 #[tokio::test] 1744 #[ignore] // TODO: printing stderr in the child and killing the child at the 1745 // end of this test race so the stderr may be present or not. Need 1746 // to implement a more graceful shutdown routine for `wasmtime 1747 // serve`. 1748 async fn cli_serve_respect_pooling_options() -> Result<()> { 1749 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1750 cmd.arg("-Opooling-total-memories=0").arg("-Scli"); 1751 })?; 1752 1753 let result = server 1754 .send_request( 1755 hyper::Request::builder() 1756 .uri("http://localhost/") 1757 .header("env", "FOO") 1758 .body(String::new()) 1759 .context("failed to make request")?, 1760 ) 1761 .await; 1762 assert!(result.is_err()); 1763 let (_, stderr) = server.finish()?; 1764 assert!( 1765 stderr.contains("maximum concurrent memory limit of 0 reached"), 1766 "bad stderr: {stderr}", 1767 ); 1768 Ok(()) 1769 } 1770 1771 #[test] 1772 fn cli_large_env() -> Result<()> { 1773 for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] { 1774 println!("run {wasm:?}"); 1775 let mut cmd = get_wasmtime_command()?; 1776 cmd.arg("run").arg("-Sinherit-env").arg(wasm); 1777 1778 let debug_cmd = format!("{cmd:?}"); 1779 for i in 0..512 { 1780 let var = format!("KEY{i}"); 1781 let val = (0..1024).map(|_| 'x').collect::<String>(); 1782 cmd.env(&var, &val); 1783 } 1784 let output = cmd.output()?; 1785 if !output.status.success() { 1786 bail!( 1787 "Failed to execute wasmtime with: {debug_cmd}\n{}", 1788 String::from_utf8_lossy(&output.stderr) 1789 ); 1790 } 1791 } 1792 Ok(()) 1793 } 1794 1795 #[tokio::test] 1796 async fn cli_serve_only_one_process_allowed() -> Result<()> { 1797 let wasm = CLI_SERVE_ECHO_ENV_COMPONENT; 1798 let server = WasmtimeServe::new(wasm, |cmd| { 1799 cmd.arg("-Scli"); 1800 })?; 1801 1802 let err = WasmtimeServe::spawn( 1803 super::get_wasmtime_command()? 1804 .arg("serve") 1805 .arg("-Scli") 1806 .arg(format!("--addr={}", server.addr)) 1807 .arg(wasm), 1808 ) 1809 .err() 1810 .expect("server spawn should have failed but it succeeded"); 1811 drop(server); 1812 1813 let err = format!("{err:?}"); 1814 println!("{err}"); 1815 assert!(err.contains("os error")); 1816 Ok(()) 1817 } 1818 1819 // Technically this test is a little racy. This binds port 0 to acquire a 1820 // random port, issues a single request to this port, but then kills this 1821 // server while the request is still processing. The port is then rebound 1822 // in the next process while it technically could be stolen by another 1823 // process. 1824 #[tokio::test] 1825 async fn cli_serve_quick_rebind_allowed() -> Result<()> { 1826 let wasm = CLI_SERVE_ECHO_ENV_COMPONENT; 1827 let server = WasmtimeServe::new(wasm, |cmd| { 1828 cmd.arg("-Scli"); 1829 })?; 1830 let addr = server.addr; 1831 1832 // Start up a `send` and `conn_task` which represents a connection to 1833 // this server. 1834 let (mut send, conn_task) = server.start_requests().await?; 1835 let _ = send 1836 .send_request( 1837 hyper::Request::builder() 1838 .uri("http://localhost/") 1839 .header("env", "FOO") 1840 .body(String::new()) 1841 .context("failed to make request")?, 1842 ) 1843 .await; 1844 1845 // ... once a response has been received (or at least the status 1846 // code/headers) then kill the server. THis is done while `conn_task` 1847 // and `send` are still alive so we're guaranteed that the other side 1848 // got a request (we got a response) and our connection is still open. 1849 // 1850 // This forces the address/port into the `TIME_WAIT` state. The rebind 1851 // below in the next process will fail if `SO_REUSEADDR` isn't set. 1852 drop(server); 1853 drop(send); 1854 let _ = conn_task.await; 1855 1856 // If this is successfully bound then we'll create `WasmtimeServe` 1857 // which reads off the first line of output to know which address was 1858 // bound. 1859 let _server2 = WasmtimeServe::spawn( 1860 super::get_wasmtime_command()? 1861 .arg("serve") 1862 .arg("-Scli") 1863 .arg(format!("--addr={addr}")) 1864 .arg(wasm), 1865 )?; 1866 1867 Ok(()) 1868 } 1869 1870 #[tokio::test] 1871 async fn cli_serve_with_print() -> Result<()> { 1872 let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1873 cmd.arg("-Scli"); 1874 })?; 1875 1876 for _ in 0..2 { 1877 let resp = server 1878 .send_request( 1879 hyper::Request::builder() 1880 .uri("http://localhost/") 1881 .body(String::new()) 1882 .context("failed to make request")?, 1883 ) 1884 .await?; 1885 assert!(resp.status().is_success()); 1886 } 1887 1888 let (out, err) = server.finish()?; 1889 assert_eq!( 1890 out, 1891 "\ 1892 stdout [0] :: this is half a print to stdout 1893 stdout [0] :: \n\ 1894 stdout [0] :: after empty 1895 stdout [1] :: this is half a print to stdout 1896 stdout [1] :: \n\ 1897 stdout [1] :: after empty 1898 " 1899 ); 1900 assert!( 1901 err.contains( 1902 "\ 1903 stderr [0] :: this is half a print to stderr 1904 stderr [0] :: \n\ 1905 stderr [0] :: after empty 1906 stderr [1] :: this is half a print to stderr 1907 stderr [1] :: \n\ 1908 stderr [1] :: after empty 1909 " 1910 ), 1911 "bad stderr: {err}" 1912 ); 1913 1914 Ok(()) 1915 } 1916 1917 #[tokio::test] 1918 async fn cli_serve_with_print_no_prefix() -> Result<()> { 1919 let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1920 cmd.arg("-Scli"); 1921 cmd.arg("--no-logging-prefix"); 1922 })?; 1923 1924 for _ in 0..2 { 1925 let resp = server 1926 .send_request( 1927 hyper::Request::builder() 1928 .uri("http://localhost/") 1929 .body(String::new()) 1930 .context("failed to make request")?, 1931 ) 1932 .await?; 1933 assert!(resp.status().is_success()); 1934 } 1935 1936 let (out, err) = server.finish()?; 1937 assert_eq!( 1938 out, 1939 "\ 1940 this is half a print to stdout 1941 \n\ 1942 after empty 1943 this is half a print to stdout 1944 \n\ 1945 after empty 1946 " 1947 ); 1948 assert!( 1949 err.contains( 1950 "\ 1951 this is half a print to stderr 1952 \n\ 1953 after empty 1954 this is half a print to stderr 1955 \n\ 1956 after empty 1957 " 1958 ), 1959 "bad stderr {err}", 1960 ); 1961 1962 Ok(()) 1963 } 1964 1965 #[tokio::test] 1966 async fn cli_serve_authority_and_scheme() -> Result<()> { 1967 let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| { 1968 cmd.arg("-Scli"); 1969 })?; 1970 1971 let resp = server 1972 .send_request( 1973 hyper::Request::builder() 1974 .uri("/") 1975 .header("Host", "localhost") 1976 .body(String::new()) 1977 .context("failed to make request")?, 1978 ) 1979 .await?; 1980 assert!(resp.status().is_success()); 1981 1982 let resp = server 1983 .send_request( 1984 hyper::Request::builder() 1985 .method("CONNECT") 1986 .uri("http://localhost/") 1987 .body(String::new()) 1988 .context("failed to make request")?, 1989 ) 1990 .await?; 1991 assert!(resp.status().is_success()); 1992 1993 Ok(()) 1994 } 1995 1996 #[test] 1997 fn cli_argv0() -> Result<()> { 1998 run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?; 1999 run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?; 2000 run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?; 2001 Ok(()) 2002 } 2003 2004 #[tokio::test] 2005 async fn cli_serve_config() -> Result<()> { 2006 let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| { 2007 cmd.arg("-Scli"); 2008 cmd.arg("-Sconfig"); 2009 cmd.arg("-Sconfig-var=hello=world"); 2010 })?; 2011 2012 let resp = server 2013 .send_request( 2014 hyper::Request::builder() 2015 .uri("http://localhost/") 2016 .body(String::new()) 2017 .context("failed to make request")?, 2018 ) 2019 .await?; 2020 2021 assert!(resp.status().is_success()); 2022 assert_eq!(resp.body(), "world"); 2023 Ok(()) 2024 } 2025 2026 #[test] 2027 fn cli_config() -> Result<()> { 2028 run_wasmtime(&[ 2029 "run", 2030 "-Sconfig", 2031 "-Sconfig-var=hello=world", 2032 CONFIG_GET_COMPONENT, 2033 ])?; 2034 Ok(()) 2035 } 2036 2037 #[tokio::test] 2038 async fn cli_serve_keyvalue() -> Result<()> { 2039 let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| { 2040 cmd.arg("-Scli"); 2041 cmd.arg("-Skeyvalue"); 2042 cmd.arg("-Skeyvalue-in-memory-data=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 cli_keyvalue() -> Result<()> { 2061 run_wasmtime(&[ 2062 "run", 2063 "-Skeyvalue", 2064 "-Skeyvalue-in-memory-data=atomics_key=5", 2065 KEYVALUE_MAIN_COMPONENT, 2066 ])?; 2067 Ok(()) 2068 } 2069 2070 #[test] 2071 fn cli_multiple_preopens() -> Result<()> { 2072 run_wasmtime(&[ 2073 "run", 2074 "--dir=/::/a", 2075 "--dir=/::/b", 2076 "--dir=/::/c", 2077 CLI_MULTIPLE_PREOPENS_COMPONENT, 2078 ])?; 2079 Ok(()) 2080 } 2081 2082 async fn cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()> { 2083 let server = WasmtimeServe::new(wasm, |cmd| { 2084 cmd.arg("-Scli"); 2085 })?; 2086 2087 for _ in 0..2 { 2088 let res = server 2089 .send_request( 2090 hyper::Request::builder() 2091 .uri("http://localhost/") 2092 .body(String::new()) 2093 .context("failed to make request")?, 2094 ) 2095 .await; 2096 assert!(res.is_err()); 2097 } 2098 2099 let (stdout, stderr) = server.finish()?; 2100 println!("stdout: {stdout}"); 2101 println!("stderr: {stderr}"); 2102 assert!(stderr.contains("guest never invoked `response-outparam::set` method")); 2103 assert!(!stderr.contains("panicked")); 2104 Ok(()) 2105 } 2106 2107 #[tokio::test] 2108 async fn cli_serve_return_before_set() -> Result<()> { 2109 cli_serve_guest_never_invoked_set(CLI_SERVE_RETURN_BEFORE_SET_COMPONENT).await 2110 } 2111 2112 #[tokio::test] 2113 async fn cli_serve_trap_before_set() -> Result<()> { 2114 cli_serve_guest_never_invoked_set(CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await 2115 } 2116 2117 mod invoke { 2118 use super::*; 2119 2120 #[test] 2121 fn cli_hello_stdout() -> Result<()> { 2122 println!("{CLI_HELLO_STDOUT_COMPONENT}"); 2123 let output = run_wasmtime(&[ 2124 "run", 2125 "-Wcomponent-model", 2126 "--invoke", 2127 "run()", 2128 CLI_HELLO_STDOUT_COMPONENT, 2129 ])?; 2130 // First this component prints "hello, world", then the invoke 2131 // result is printed as "ok". 2132 assert_eq!(output, "hello, world\nok\n"); 2133 Ok(()) 2134 } 2135 } 2136 } 2137 2138 #[test] 2139 fn settings_command() -> Result<()> { 2140 // Skip this test on platforms that Cranelift doesn't support. 2141 if cranelift_native::builder().is_err() { 2142 return Ok(()); 2143 } 2144 let output = run_wasmtime(&["settings"])?; 2145 assert!(output.contains("Cranelift settings for target")); 2146 Ok(()) 2147 } 2148 2149 #[cfg(target_arch = "x86_64")] 2150 #[test] 2151 fn profile_with_vtune() -> Result<()> { 2152 if !is_vtune_available() { 2153 println!("> `vtune` is not available on the system path; skipping test"); 2154 return Ok(()); 2155 } 2156 2157 let mut bin = Command::new("vtune"); 2158 bin.args(&[ 2159 // Configure VTune... 2160 "-verbose", 2161 "-collect", 2162 "hotspots", 2163 "-user-data-dir", 2164 &std::env::temp_dir().to_string_lossy(), 2165 // ...then run Wasmtime with profiling enabled: 2166 get_wasmtime_path(), 2167 "--profile=vtune", 2168 "tests/all/cli_tests/simple.wat", 2169 ]); 2170 2171 println!("> executing: {bin:?}"); 2172 let output = bin.output()?; 2173 2174 assert!(output.status.success()); 2175 let stdout = String::from_utf8_lossy(&output.stdout); 2176 let stderr = String::from_utf8_lossy(&output.stderr); 2177 println!("> stdout:\n{stdout}"); 2178 assert!(stdout.contains("CPU Time")); 2179 println!("> stderr:\n{stderr}"); 2180 assert!(!stderr.contains("Error")); 2181 Ok(()) 2182 } 2183 2184 #[cfg(target_arch = "x86_64")] 2185 fn is_vtune_available() -> bool { 2186 Command::new("vtune").arg("-version").output().is_ok() 2187 } 2188 2189 #[test] 2190 fn unreachable_without_wasi() -> Result<()> { 2191 let output = run_wasmtime_for_output( 2192 &[ 2193 "-Scli=n", 2194 "-Ccache=n", 2195 "tests/all/cli_tests/unreachable.wat", 2196 ], 2197 None, 2198 )?; 2199 2200 assert_ne!(output.stderr, b""); 2201 assert_eq!(output.stdout, b""); 2202 assert_trap_code(&output.status); 2203 Ok(()) 2204 } 2205 2206 #[test] 2207 fn config_cli_flag() -> Result<()> { 2208 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 2209 2210 // Test some valid TOML values 2211 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2212 cfg.write_all( 2213 br#" 2214 [optimize] 2215 opt-level = 2 2216 signals-based-traps = false 2217 2218 [codegen] 2219 collector = "null" 2220 2221 [wasm] 2222 max-wasm-stack = 65536 2223 2224 [wasi] 2225 cli = true 2226 "#, 2227 )?; 2228 let output = run_wasmtime(&[ 2229 "run", 2230 "--config", 2231 cfg_path.to_str().unwrap(), 2232 "--invoke", 2233 "get_f64", 2234 wasm.path().to_str().unwrap(), 2235 ])?; 2236 assert_eq!(output, "100\n"); 2237 2238 // Make sure CLI flags overrides TOML values 2239 let output = run_wasmtime(&[ 2240 "run", 2241 "--config", 2242 cfg_path.to_str().unwrap(), 2243 "--invoke", 2244 "get_f64", 2245 "-W", 2246 "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail 2247 wasm.path().to_str().unwrap(), 2248 ]); 2249 assert!( 2250 output 2251 .as_ref() 2252 .unwrap_err() 2253 .to_string() 2254 .contains("max_wasm_stack size cannot be zero"), 2255 "'{output:?}' did not contain expected error message", 2256 ); 2257 2258 // Test invalid TOML key 2259 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2260 cfg.write_all( 2261 br#" 2262 [optimize] 2263 this-key-does-not-exist = true 2264 "#, 2265 )?; 2266 let output = run_wasmtime(&[ 2267 "run", 2268 "--config", 2269 cfg_path.to_str().unwrap(), 2270 wasm.path().to_str().unwrap(), 2271 ]); 2272 assert!( 2273 output 2274 .as_ref() 2275 .unwrap_err() 2276 .to_string() 2277 .contains("unknown field `this-key-does-not-exist`"), 2278 "'{output:?}' did not contain expected error message" 2279 ); 2280 2281 // Test invalid TOML table 2282 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2283 cfg.write_all( 2284 br#" 2285 [invalid_table] 2286 "#, 2287 )?; 2288 let output = run_wasmtime(&[ 2289 "run", 2290 "--config", 2291 cfg_path.to_str().unwrap(), 2292 wasm.path().to_str().unwrap(), 2293 ]); 2294 assert!( 2295 output 2296 .as_ref() 2297 .unwrap_err() 2298 .to_string() 2299 .contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"), 2300 "'{output:?}' did not contain expected error message", 2301 ); 2302 2303 Ok(()) 2304 } 2305 2306 #[test] 2307 fn invalid_subcommand() -> Result<()> { 2308 let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?; 2309 dbg!(&output); 2310 assert!(!output.status.success()); 2311 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand")); 2312 Ok(()) 2313 } 2314 2315 #[test] 2316 fn numeric_args() -> Result<()> { 2317 let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?; 2318 // Test decimal i32 2319 let output = run_wasmtime_for_output( 2320 &[ 2321 "run", 2322 "--invoke", 2323 "i32_test", 2324 wasm.path().to_str().unwrap(), 2325 "42", 2326 ], 2327 None, 2328 )?; 2329 assert_eq!(output.status.success(), true); 2330 assert_eq!(output.stdout, b"42\n"); 2331 // Test hexadecimal i32 with lowercase prefix 2332 let output = run_wasmtime_for_output( 2333 &[ 2334 "run", 2335 "--invoke", 2336 "i32_test", 2337 wasm.path().to_str().unwrap(), 2338 "0x2A", 2339 ], 2340 None, 2341 )?; 2342 assert_eq!(output.status.success(), true); 2343 assert_eq!(output.stdout, b"42\n"); 2344 // Test hexadecimal i32 with uppercase prefix 2345 let output = run_wasmtime_for_output( 2346 &[ 2347 "run", 2348 "--invoke", 2349 "i32_test", 2350 wasm.path().to_str().unwrap(), 2351 "0X2a", 2352 ], 2353 None, 2354 )?; 2355 assert_eq!(output.status.success(), true); 2356 assert_eq!(output.stdout, b"42\n"); 2357 // Test that non-prefixed hex strings are not interpreted as hex 2358 let output = run_wasmtime_for_output( 2359 &[ 2360 "run", 2361 "--invoke", 2362 "i32_test", 2363 wasm.path().to_str().unwrap(), 2364 "ff", 2365 ], 2366 None, 2367 )?; 2368 assert!(!output.status.success()); // Should fail as "ff" is not a valid decimal number 2369 2370 // Test decimal i64 2371 let output = run_wasmtime_for_output( 2372 &[ 2373 "run", 2374 "--invoke", 2375 "i64_test", 2376 wasm.path().to_str().unwrap(), 2377 "42", 2378 ], 2379 None, 2380 )?; 2381 assert_eq!(output.status.success(), true); 2382 assert_eq!(output.stdout, b"42\n"); 2383 // Test hexadecimal i64 2384 let output = run_wasmtime_for_output( 2385 &[ 2386 "run", 2387 "--invoke", 2388 "i64_test", 2389 wasm.path().to_str().unwrap(), 2390 "0x2A", 2391 ], 2392 None, 2393 )?; 2394 assert_eq!(output.status.success(), true); 2395 assert_eq!(output.stdout, b"42\n"); 2396 Ok(()) 2397 } 2398 2399 #[test] 2400 fn compilation_logs() -> Result<()> { 2401 let temp = tempfile::NamedTempFile::new()?; 2402 let output = get_wasmtime_command()? 2403 .args(&[ 2404 "compile", 2405 "-Wgc", 2406 "tests/all/cli_tests/issue-10353.wat", 2407 "--output", 2408 &temp.path().display().to_string(), 2409 ]) 2410 .env("WASMTIME_LOG", "trace") 2411 .env("RUST_BACKTRACE", "1") 2412 .output()?; 2413 if !output.status.success() { 2414 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 2415 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 2416 panic!("wasmtime compilation failed when logs requested"); 2417 } 2418 Ok(()) 2419 } 2420 2421 #[test] 2422 fn big_table_in_pooling_allocator() -> Result<()> { 2423 // Works by default 2424 run_wasmtime(&["tests/all/cli_tests/big_table.wat"])?; 2425 2426 // Does not work by default in the pooling allocator, and the error message 2427 // should mention something about the pooling allocator. 2428 let output = run_wasmtime_for_output( 2429 &["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"], 2430 None, 2431 )?; 2432 assert!(!output.status.success()); 2433 println!("{}", String::from_utf8_lossy(&output.stderr)); 2434 assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator")); 2435 2436 // Does work with `-Wmax-table-elements` 2437 run_wasmtime(&[ 2438 "-Opooling-allocator", 2439 "-Wmax-table-elements=25000", 2440 "tests/all/cli_tests/big_table.wat", 2441 ])?; 2442 // Also works with `-Opooling-table-elements` 2443 run_wasmtime(&[ 2444 "-Opooling-allocator", 2445 "-Opooling-table-elements=25000", 2446 "tests/all/cli_tests/big_table.wat", 2447 ])?; 2448 Ok(()) 2449 } 2450