1 use anyhow::{bail, Result}; 2 use std::io::Write; 3 use std::path::Path; 4 use std::process::{Command, Output}; 5 use tempfile::{NamedTempFile, TempDir}; 6 7 // Run the wasmtime CLI with the provided args and return the `Output`. 8 fn run_wasmtime_for_output(args: &[&str]) -> Result<Output> { 9 let runner = std::env::vars() 10 .filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER")) 11 .next(); 12 let mut me = std::env::current_exe()?; 13 me.pop(); // chop off the file name 14 me.pop(); // chop off `deps` 15 me.push("wasmtime"); 16 17 // If we're running tests with a "runner" then we might be doing something 18 // like cross-emulation, so spin up the emulator rather than the tests 19 // itself, which may not be natively executable. 20 let mut cmd = if let Some((_, runner)) = runner { 21 let mut parts = runner.split_whitespace(); 22 let mut cmd = Command::new(parts.next().unwrap()); 23 for arg in parts { 24 cmd.arg(arg); 25 } 26 cmd.arg(&me); 27 cmd 28 } else { 29 Command::new(&me) 30 }; 31 cmd.args(args).output().map_err(Into::into) 32 } 33 34 // Run the wasmtime CLI with the provided args and, if it succeeds, return 35 // the standard output in a `String`. 36 fn run_wasmtime(args: &[&str]) -> Result<String> { 37 let output = run_wasmtime_for_output(args)?; 38 if !output.status.success() { 39 bail!( 40 "Failed to execute wasmtime with: {:?}\n{}", 41 args, 42 String::from_utf8_lossy(&output.stderr) 43 ); 44 } 45 Ok(String::from_utf8(output.stdout).unwrap()) 46 } 47 48 fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> { 49 let mut wasm_file = NamedTempFile::new()?; 50 let wasm = wat::parse_file(wat_path)?; 51 wasm_file.write(&wasm)?; 52 Ok(wasm_file) 53 } 54 55 // Very basic use case: compile binary wasm file and run specific function with arguments. 56 #[test] 57 fn run_wasmtime_simple() -> Result<()> { 58 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 59 run_wasmtime(&[ 60 "run", 61 wasm.path().to_str().unwrap(), 62 "--invoke", 63 "simple", 64 "--disable-cache", 65 "4", 66 ])?; 67 Ok(()) 68 } 69 70 // Wasmtime shakk when not enough arguments were provided. 71 #[test] 72 fn run_wasmtime_simple_fail_no_args() -> Result<()> { 73 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 74 assert!( 75 run_wasmtime(&[ 76 "run", 77 wasm.path().to_str().unwrap(), 78 "--disable-cache", 79 "--invoke", 80 "simple", 81 ]) 82 .is_err(), 83 "shall fail" 84 ); 85 Ok(()) 86 } 87 88 // Running simple wat 89 #[test] 90 fn run_wasmtime_simple_wat() -> Result<()> { 91 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 92 run_wasmtime(&[ 93 "run", 94 wasm.path().to_str().unwrap(), 95 "--invoke", 96 "simple", 97 "--disable-cache", 98 "4", 99 ])?; 100 assert_eq!( 101 run_wasmtime(&[ 102 "run", 103 wasm.path().to_str().unwrap(), 104 "--invoke", 105 "get_f32", 106 "--disable-cache", 107 ])?, 108 "100\n" 109 ); 110 assert_eq!( 111 run_wasmtime(&[ 112 "run", 113 wasm.path().to_str().unwrap(), 114 "--invoke", 115 "get_f64", 116 "--disable-cache", 117 ])?, 118 "100\n" 119 ); 120 Ok(()) 121 } 122 123 // Running a wat that traps. 124 #[test] 125 fn run_wasmtime_unreachable_wat() -> Result<()> { 126 let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?; 127 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 128 129 assert_ne!(output.stderr, b""); 130 assert_eq!(output.stdout, b""); 131 assert!(!output.status.success()); 132 133 let code = output 134 .status 135 .code() 136 .expect("wasmtime process should exit normally"); 137 138 // Test for the specific error code Wasmtime uses to indicate a trap return. 139 #[cfg(unix)] 140 assert_eq!(code, 128 + libc::SIGABRT); 141 #[cfg(windows)] 142 assert_eq!(code, 3); 143 Ok(()) 144 } 145 146 // Run a simple WASI hello world, snapshot0 edition. 147 #[test] 148 fn hello_wasi_snapshot0() -> Result<()> { 149 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?; 150 let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 151 assert_eq!(stdout, "Hello, world!\n"); 152 Ok(()) 153 } 154 155 // Run a simple WASI hello world, snapshot1 edition. 156 #[test] 157 fn hello_wasi_snapshot1() -> Result<()> { 158 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; 159 let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 160 assert_eq!(stdout, "Hello, world!\n"); 161 Ok(()) 162 } 163 164 #[test] 165 fn timeout_in_start() -> Result<()> { 166 let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?; 167 let output = run_wasmtime_for_output(&[ 168 "run", 169 wasm.path().to_str().unwrap(), 170 "--wasm-timeout", 171 "1ms", 172 "--disable-cache", 173 ])?; 174 assert!(!output.status.success()); 175 assert_eq!(output.stdout, b""); 176 let stderr = String::from_utf8_lossy(&output.stderr); 177 assert!( 178 stderr.contains("wasm trap: interrupt"), 179 "bad stderr: {}", 180 stderr 181 ); 182 Ok(()) 183 } 184 185 #[test] 186 fn timeout_in_invoke() -> Result<()> { 187 let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?; 188 let output = run_wasmtime_for_output(&[ 189 "run", 190 wasm.path().to_str().unwrap(), 191 "--wasm-timeout", 192 "1ms", 193 "--disable-cache", 194 ])?; 195 assert!(!output.status.success()); 196 assert_eq!(output.stdout, b""); 197 let stderr = String::from_utf8_lossy(&output.stderr); 198 assert!( 199 stderr.contains("wasm trap: interrupt"), 200 "bad stderr: {}", 201 stderr 202 ); 203 Ok(()) 204 } 205 206 // Exit with a valid non-zero exit code, snapshot0 edition. 207 #[test] 208 fn exit2_wasi_snapshot0() -> Result<()> { 209 let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?; 210 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 211 assert_eq!(output.status.code().unwrap(), 2); 212 Ok(()) 213 } 214 215 // Exit with a valid non-zero exit code, snapshot1 edition. 216 #[test] 217 fn exit2_wasi_snapshot1() -> Result<()> { 218 let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?; 219 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 220 assert_eq!(output.status.code().unwrap(), 2); 221 Ok(()) 222 } 223 224 // Exit with a valid non-zero exit code, snapshot0 edition. 225 #[test] 226 fn exit125_wasi_snapshot0() -> Result<()> { 227 let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?; 228 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 229 if cfg!(windows) { 230 assert_eq!(output.status.code().unwrap(), 1); 231 } else { 232 assert_eq!(output.status.code().unwrap(), 125); 233 } 234 Ok(()) 235 } 236 237 // Exit with a valid non-zero exit code, snapshot1 edition. 238 #[test] 239 fn exit125_wasi_snapshot1() -> Result<()> { 240 let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?; 241 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 242 if cfg!(windows) { 243 assert_eq!(output.status.code().unwrap(), 1); 244 } else { 245 assert_eq!(output.status.code().unwrap(), 125); 246 } 247 Ok(()) 248 } 249 250 // Exit with an invalid non-zero exit code, snapshot0 edition. 251 #[test] 252 fn exit126_wasi_snapshot0() -> Result<()> { 253 let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?; 254 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 255 if cfg!(windows) { 256 assert_eq!(output.status.code().unwrap(), 3); 257 } else { 258 assert_eq!(output.status.code().unwrap(), 128 + libc::SIGABRT); 259 } 260 assert!(output.stdout.is_empty()); 261 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); 262 Ok(()) 263 } 264 265 // Exit with an invalid non-zero exit code, snapshot1 edition. 266 #[test] 267 fn exit126_wasi_snapshot1() -> Result<()> { 268 let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?; 269 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 270 if cfg!(windows) { 271 assert_eq!(output.status.code().unwrap(), 3); 272 } else { 273 assert_eq!(output.status.code().unwrap(), 128 + libc::SIGABRT); 274 } 275 assert!(output.stdout.is_empty()); 276 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); 277 Ok(()) 278 } 279 280 // Run a minimal command program. 281 #[test] 282 fn minimal_command() -> Result<()> { 283 let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; 284 let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 285 assert_eq!(stdout, ""); 286 Ok(()) 287 } 288 289 // Run a minimal reactor program. 290 #[test] 291 fn minimal_reactor() -> Result<()> { 292 let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; 293 let stdout = run_wasmtime(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 294 assert_eq!(stdout, ""); 295 Ok(()) 296 } 297 298 // Attempt to call invoke on a command. 299 #[test] 300 fn command_invoke() -> Result<()> { 301 let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; 302 run_wasmtime(&[ 303 "run", 304 wasm.path().to_str().unwrap(), 305 "--invoke", 306 "_start", 307 "--disable-cache", 308 ])?; 309 Ok(()) 310 } 311 312 // Attempt to call invoke on a command. 313 #[test] 314 fn reactor_invoke() -> Result<()> { 315 let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; 316 run_wasmtime(&[ 317 "run", 318 wasm.path().to_str().unwrap(), 319 "--invoke", 320 "_initialize", 321 "--disable-cache", 322 ])?; 323 Ok(()) 324 } 325 326 // Run the greeter test, which runs a preloaded reactor and a command. 327 #[test] 328 fn greeter() -> Result<()> { 329 let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; 330 let stdout = run_wasmtime(&[ 331 "run", 332 wasm.path().to_str().unwrap(), 333 "--disable-cache", 334 "--preload", 335 "reactor=tests/all/cli_tests/greeter_reactor.wat", 336 ])?; 337 assert_eq!( 338 stdout, 339 "Hello _initialize\nHello _start\nHello greet\nHello done\n" 340 ); 341 Ok(()) 342 } 343 344 // Run the greeter test, but this time preload a command. 345 #[test] 346 fn greeter_preload_command() -> Result<()> { 347 let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?; 348 let stdout = run_wasmtime(&[ 349 "run", 350 wasm.path().to_str().unwrap(), 351 "--disable-cache", 352 "--preload", 353 "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat", 354 ])?; 355 assert_eq!(stdout, "Hello _initialize\n"); 356 Ok(()) 357 } 358 359 // Run the greeter test, which runs a preloaded reactor and a command. 360 #[test] 361 fn greeter_preload_callable_command() -> Result<()> { 362 let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; 363 let stdout = run_wasmtime(&[ 364 "run", 365 wasm.path().to_str().unwrap(), 366 "--disable-cache", 367 "--preload", 368 "reactor=tests/all/cli_tests/greeter_callable_command.wat", 369 ])?; 370 assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n"); 371 Ok(()) 372 } 373 374 // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64 375 // See https://github.com/bytecodealliance/wasmtime/issues/1967 376 #[test] 377 fn exit_with_saved_fprs() -> Result<()> { 378 let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?; 379 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; 380 assert_eq!(output.status.code().unwrap(), 0); 381 assert!(output.stdout.is_empty()); 382 Ok(()) 383 } 384 385 #[test] 386 fn run_cwasm() -> Result<()> { 387 let td = TempDir::new()?; 388 let cwasm = td.path().join("foo.cwasm"); 389 let stdout = run_wasmtime(&[ 390 "compile", 391 "tests/all/cli_tests/simple.wat", 392 "-o", 393 cwasm.to_str().unwrap(), 394 ])?; 395 assert_eq!(stdout, ""); 396 let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?; 397 assert_eq!(stdout, ""); 398 Ok(()) 399 } 400