xref: /wasmtime-44.0.1/tests/all/cli_tests.rs (revision c8daf0b8)
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("epoch deadline reached during execution"),
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("epoch deadline reached during execution"),
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