Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/cli_tests.rs
1692 views
1
#![cfg(not(miri))]
2
3
use anyhow::{Result, bail};
4
use std::fs::File;
5
use std::io::Write;
6
use std::path::Path;
7
use std::process::{Command, ExitStatus, Output, Stdio};
8
use tempfile::{NamedTempFile, TempDir};
9
10
// Run the wasmtime CLI with the provided args and return the `Output`.
11
// If the `stdin` is `Some`, opens the file and redirects to the child's stdin.
12
pub fn run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result<Output> {
13
let mut cmd = get_wasmtime_command()?;
14
cmd.args(args);
15
if let Some(file) = stdin {
16
cmd.stdin(File::open(file)?);
17
}
18
cmd.output().map_err(Into::into)
19
}
20
21
/// Get the Wasmtime CLI as a [Command].
22
pub fn get_wasmtime_command() -> Result<Command> {
23
let mut cmd = wasmtime_test_util::command(get_wasmtime_path());
24
25
// Ignore this if it's specified in the environment to allow tests to run in
26
// "default mode" by default.
27
cmd.env_remove("WASMTIME_NEW_CLI");
28
29
Ok(cmd)
30
}
31
32
fn get_wasmtime_path() -> &'static str {
33
env!("CARGO_BIN_EXE_wasmtime")
34
}
35
36
// Run the wasmtime CLI with the provided args and, if it succeeds, return
37
// the standard output in a `String`.
38
pub fn run_wasmtime(args: &[&str]) -> Result<String> {
39
let output = run_wasmtime_for_output(args, None)?;
40
if !output.status.success() {
41
bail!(
42
"Failed to execute wasmtime with: {:?}\nstatus: {}\n{}",
43
args,
44
output.status,
45
String::from_utf8_lossy(&output.stderr)
46
);
47
}
48
Ok(String::from_utf8(output.stdout).unwrap())
49
}
50
51
fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> {
52
let mut wasm_file = NamedTempFile::new()?;
53
let wasm = wat::parse_file(wat_path)?;
54
wasm_file.write(&wasm)?;
55
Ok(wasm_file)
56
}
57
58
// Very basic use case: compile binary wasm file and run specific function with arguments.
59
#[test]
60
fn run_wasmtime_simple() -> Result<()> {
61
let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
62
run_wasmtime(&[
63
"run",
64
"--invoke",
65
"simple",
66
"-Ccache=n",
67
wasm.path().to_str().unwrap(),
68
"4",
69
])?;
70
Ok(())
71
}
72
73
// Wasmtime shall fail when not enough arguments were provided.
74
#[test]
75
fn run_wasmtime_simple_fail_no_args() -> Result<()> {
76
let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
77
assert!(
78
run_wasmtime(&[
79
"run",
80
"-Ccache=n",
81
"--invoke",
82
"simple",
83
wasm.path().to_str().unwrap(),
84
])
85
.is_err(),
86
"shall fail"
87
);
88
Ok(())
89
}
90
91
#[test]
92
fn run_coredump_smoketest() -> Result<()> {
93
let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?;
94
let coredump_file = NamedTempFile::new()?;
95
let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display());
96
let err = run_wasmtime(&[
97
"run",
98
"--invoke",
99
"a",
100
"-Ccache=n",
101
&coredump_arg,
102
wasm.path().to_str().unwrap(),
103
])
104
.unwrap_err();
105
assert!(err.to_string().contains(&format!(
106
"core dumped at {}",
107
coredump_file.path().display()
108
)));
109
Ok(())
110
}
111
112
// Running simple wat
113
#[test]
114
fn run_wasmtime_simple_wat() -> Result<()> {
115
let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
116
run_wasmtime(&[
117
"run",
118
"--invoke",
119
"simple",
120
"-Ccache=n",
121
wasm.path().to_str().unwrap(),
122
"4",
123
])?;
124
assert_eq!(
125
run_wasmtime(&[
126
"run",
127
"--invoke",
128
"get_f32",
129
"-Ccache=n",
130
wasm.path().to_str().unwrap(),
131
])?,
132
"100\n"
133
);
134
assert_eq!(
135
run_wasmtime(&[
136
"run",
137
"--invoke",
138
"get_f64",
139
"-Ccache=n",
140
wasm.path().to_str().unwrap(),
141
])?,
142
"100\n"
143
);
144
Ok(())
145
}
146
147
// Running a wat that traps.
148
#[test]
149
fn run_wasmtime_unreachable_wat() -> Result<()> {
150
let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?;
151
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
152
153
assert_ne!(output.stderr, b"");
154
assert_eq!(output.stdout, b"");
155
156
assert_trap_code(&output.status);
157
Ok(())
158
}
159
160
fn assert_trap_code(status: &ExitStatus) {
161
let code = status
162
.code()
163
.expect("wasmtime process should exit normally");
164
165
// Test for the specific error code Wasmtime uses to indicate a trap return.
166
#[cfg(unix)]
167
assert_eq!(code, 128 + libc::SIGABRT);
168
#[cfg(windows)]
169
assert_eq!(code, 3);
170
}
171
172
// Run a simple WASI hello world, snapshot0 edition.
173
#[test]
174
fn hello_wasi_snapshot0() -> Result<()> {
175
let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
176
for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
177
let stdout = run_wasmtime(&["-Ccache=n", preview2, wasm.path().to_str().unwrap()])?;
178
assert_eq!(stdout, "Hello, world!\n");
179
}
180
Ok(())
181
}
182
183
// Run a simple WASI hello world, snapshot1 edition.
184
#[test]
185
fn hello_wasi_snapshot1() -> Result<()> {
186
let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
187
let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
188
assert_eq!(stdout, "Hello, world!\n");
189
Ok(())
190
}
191
192
#[test]
193
fn timeout_in_start() -> Result<()> {
194
let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?;
195
let output = run_wasmtime_for_output(
196
&[
197
"run",
198
"-Wtimeout=1ms",
199
"-Ccache=n",
200
wasm.path().to_str().unwrap(),
201
],
202
None,
203
)?;
204
assert!(!output.status.success());
205
assert_eq!(output.stdout, b"");
206
let stderr = String::from_utf8_lossy(&output.stderr);
207
assert!(
208
stderr.contains("wasm trap: interrupt"),
209
"bad stderr: {stderr}"
210
);
211
Ok(())
212
}
213
214
#[test]
215
fn timeout_in_invoke() -> Result<()> {
216
let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?;
217
let output = run_wasmtime_for_output(
218
&[
219
"run",
220
"-Wtimeout=1ms",
221
"-Ccache=n",
222
wasm.path().to_str().unwrap(),
223
],
224
None,
225
)?;
226
assert!(!output.status.success());
227
assert_eq!(output.stdout, b"");
228
let stderr = String::from_utf8_lossy(&output.stderr);
229
assert!(
230
stderr.contains("wasm trap: interrupt"),
231
"bad stderr: {stderr}"
232
);
233
Ok(())
234
}
235
236
// Exit with a valid non-zero exit code, snapshot0 edition.
237
#[test]
238
fn exit2_wasi_snapshot0() -> Result<()> {
239
let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?;
240
241
for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
242
let output = run_wasmtime_for_output(
243
&["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
244
None,
245
)?;
246
assert_eq!(output.status.code().unwrap(), 2);
247
}
248
Ok(())
249
}
250
251
// Exit with a valid non-zero exit code, snapshot1 edition.
252
#[test]
253
fn exit2_wasi_snapshot1() -> Result<()> {
254
let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?;
255
let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
256
assert_eq!(output.status.code().unwrap(), 2);
257
Ok(())
258
}
259
260
// Exit with a valid non-zero exit code, snapshot0 edition.
261
#[test]
262
fn exit125_wasi_snapshot0() -> Result<()> {
263
let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?;
264
for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
265
let output = run_wasmtime_for_output(
266
&["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
267
None,
268
)?;
269
dbg!(&output);
270
assert_eq!(output.status.code().unwrap(), 125);
271
}
272
Ok(())
273
}
274
275
// Exit with a valid non-zero exit code, snapshot1 edition.
276
#[test]
277
fn exit125_wasi_snapshot1() -> Result<()> {
278
let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?;
279
let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
280
assert_eq!(output.status.code().unwrap(), 125);
281
Ok(())
282
}
283
284
// Exit with an invalid non-zero exit code, snapshot0 edition.
285
#[test]
286
fn exit126_wasi_snapshot0() -> Result<()> {
287
let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?;
288
289
for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
290
let output = run_wasmtime_for_output(
291
&["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
292
None,
293
)?;
294
assert_eq!(output.status.code().unwrap(), 1);
295
assert!(output.stdout.is_empty());
296
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
297
}
298
Ok(())
299
}
300
301
// Exit with an invalid non-zero exit code, snapshot1 edition.
302
#[test]
303
fn exit126_wasi_snapshot1() -> Result<()> {
304
let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?;
305
let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
306
assert_eq!(output.status.code().unwrap(), 1);
307
assert!(output.stdout.is_empty());
308
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
309
Ok(())
310
}
311
312
// Run a minimal command program.
313
#[test]
314
fn minimal_command() -> Result<()> {
315
let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
316
let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
317
assert_eq!(stdout, "");
318
Ok(())
319
}
320
321
// Run a minimal reactor program.
322
#[test]
323
fn minimal_reactor() -> Result<()> {
324
let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
325
let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
326
assert_eq!(stdout, "");
327
Ok(())
328
}
329
330
// Attempt to call invoke on a command.
331
#[test]
332
fn command_invoke() -> Result<()> {
333
let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
334
run_wasmtime(&[
335
"run",
336
"--invoke",
337
"_start",
338
"-Ccache=n",
339
wasm.path().to_str().unwrap(),
340
])?;
341
Ok(())
342
}
343
344
// Attempt to call invoke on a command.
345
#[test]
346
fn reactor_invoke() -> Result<()> {
347
let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
348
run_wasmtime(&[
349
"run",
350
"--invoke",
351
"_initialize",
352
"-Ccache=n",
353
wasm.path().to_str().unwrap(),
354
])?;
355
Ok(())
356
}
357
358
// Run the greeter test, which runs a preloaded reactor and a command.
359
#[test]
360
fn greeter() -> Result<()> {
361
let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
362
let stdout = run_wasmtime(&[
363
"run",
364
"-Ccache=n",
365
"--preload",
366
"reactor=tests/all/cli_tests/greeter_reactor.wat",
367
wasm.path().to_str().unwrap(),
368
])?;
369
assert_eq!(
370
stdout,
371
"Hello _initialize\nHello _start\nHello greet\nHello done\n"
372
);
373
Ok(())
374
}
375
376
// Run the greeter test, but this time preload a command.
377
#[test]
378
fn greeter_preload_command() -> Result<()> {
379
let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?;
380
let stdout = run_wasmtime(&[
381
"run",
382
"-Ccache=n",
383
"--preload",
384
"reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat",
385
wasm.path().to_str().unwrap(),
386
])?;
387
assert_eq!(stdout, "Hello _initialize\n");
388
Ok(())
389
}
390
391
// Run the greeter test, which runs a preloaded reactor and a command.
392
#[test]
393
fn greeter_preload_callable_command() -> Result<()> {
394
let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
395
let stdout = run_wasmtime(&[
396
"run",
397
"-Ccache=n",
398
"--preload",
399
"reactor=tests/all/cli_tests/greeter_callable_command.wat",
400
wasm.path().to_str().unwrap(),
401
])?;
402
assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n");
403
Ok(())
404
}
405
406
// Ensure successful WASI exit call with FPR saving frames on stack for Windows x64
407
// See https://github.com/bytecodealliance/wasmtime/issues/1967
408
#[test]
409
fn exit_with_saved_fprs() -> Result<()> {
410
let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?;
411
let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
412
assert_eq!(output.status.code().unwrap(), 0);
413
assert!(output.stdout.is_empty());
414
Ok(())
415
}
416
417
#[test]
418
fn run_cwasm() -> Result<()> {
419
let td = TempDir::new()?;
420
let cwasm = td.path().join("foo.cwasm");
421
let stdout = run_wasmtime(&[
422
"compile",
423
"tests/all/cli_tests/simple.wat",
424
"-o",
425
cwasm.to_str().unwrap(),
426
])?;
427
assert_eq!(stdout, "");
428
let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?;
429
assert_eq!(stdout, "");
430
Ok(())
431
}
432
433
#[cfg(unix)]
434
#[test]
435
fn hello_wasi_snapshot0_from_stdin() -> Result<()> {
436
// Run a simple WASI hello world, snapshot0 edition.
437
// The module is piped from standard input.
438
let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
439
for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
440
let stdout = {
441
let path = wasm.path();
442
let args: &[&str] = &["-Ccache=n", preview2, "-"];
443
let output = run_wasmtime_for_output(args, Some(path))?;
444
if !output.status.success() {
445
bail!(
446
"Failed to execute wasmtime with: {:?}\n{}",
447
args,
448
String::from_utf8_lossy(&output.stderr)
449
);
450
}
451
Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap())
452
}?;
453
assert_eq!(stdout, "Hello, world!\n");
454
}
455
Ok(())
456
}
457
458
#[test]
459
fn specify_env() -> Result<()> {
460
// By default no env is inherited
461
let output = get_wasmtime_command()?
462
.args(&["run", "tests/all/cli_tests/print_env.wat"])
463
.env("THIS_WILL_NOT", "show up in the output")
464
.output()?;
465
assert!(output.status.success());
466
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
467
468
// Specify a single env var
469
let output = get_wasmtime_command()?
470
.args(&[
471
"run",
472
"--env",
473
"FOO=bar",
474
"tests/all/cli_tests/print_env.wat",
475
])
476
.output()?;
477
assert!(output.status.success());
478
assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
479
480
// Inherit a single env var
481
let output = get_wasmtime_command()?
482
.args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"])
483
.env("FOO", "bar")
484
.output()?;
485
assert!(output.status.success());
486
assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");
487
488
// Inherit a nonexistent env var
489
let output = get_wasmtime_command()?
490
.args(&[
491
"run",
492
"--env",
493
"SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT",
494
"tests/all/cli_tests/print_env.wat",
495
])
496
.output()?;
497
assert!(output.status.success());
498
499
// Inherit all env vars
500
let output = get_wasmtime_command()?
501
.args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"])
502
.env("FOO", "bar")
503
.output()?;
504
assert!(output.status.success());
505
let stdout = String::from_utf8_lossy(&output.stdout);
506
assert!(stdout.contains("FOO=bar"), "bad output: {stdout}");
507
508
Ok(())
509
}
510
511
#[cfg(unix)]
512
#[test]
513
fn run_cwasm_from_stdin() -> Result<()> {
514
use std::process::Stdio;
515
516
let td = TempDir::new()?;
517
let cwasm = td.path().join("foo.cwasm");
518
let stdout = run_wasmtime(&[
519
"compile",
520
"tests/all/cli_tests/simple.wat",
521
"-o",
522
cwasm.to_str().unwrap(),
523
])?;
524
assert_eq!(stdout, "");
525
526
// If stdin is literally the file itself then that should work
527
let args: &[&str] = &["run", "--allow-precompiled", "-"];
528
let output = get_wasmtime_command()?
529
.args(args)
530
.stdin(File::open(&cwasm)?)
531
.output()?;
532
assert!(output.status.success(), "a file as stdin should work");
533
534
// If stdin is a pipe, that should also work
535
let input = std::fs::read(&cwasm)?;
536
let mut child = get_wasmtime_command()?
537
.args(args)
538
.stdin(Stdio::piped())
539
.stdout(Stdio::piped())
540
.stderr(Stdio::piped())
541
.spawn()?;
542
let mut stdin = child.stdin.take().unwrap();
543
let t = std::thread::spawn(move || {
544
let _ = stdin.write_all(&input);
545
});
546
let output = child.wait_with_output()?;
547
assert!(output.status.success());
548
t.join().unwrap();
549
Ok(())
550
}
551
552
#[cfg(feature = "wasi-threads")]
553
#[test]
554
fn run_threads() -> Result<()> {
555
// Only run threaded tests on platforms that support threads. Also skip
556
// these tests with ASAN as it, rightfully, complains about a memory leak.
557
// The memory leak at this time is that child threads aren't joined with the
558
// main thread, meaning that allocations done on child threads are indeed
559
// leaked.
560
if crate::threads::engine().is_none() || cfg!(asan) {
561
return Ok(());
562
}
563
let wasm = build_wasm("tests/all/cli_tests/threads.wat")?;
564
let stdout = run_wasmtime(&[
565
"run",
566
"-Wthreads",
567
"-Sthreads",
568
"-Ccache=n",
569
wasm.path().to_str().unwrap(),
570
])?;
571
572
assert!(
573
stdout
574
== "Called _start\n\
575
Running wasi_thread_start\n\
576
Running wasi_thread_start\n\
577
Running wasi_thread_start\n\
578
Done\n"
579
);
580
Ok(())
581
}
582
583
#[cfg(feature = "wasi-threads")]
584
#[test]
585
fn run_simple_with_wasi_threads() -> Result<()> {
586
// Skip this test on platforms that don't support threads.
587
if crate::threads::engine().is_none() {
588
return Ok(());
589
}
590
// We expect to be able to run Wasm modules that do not have correct
591
// wasi-thread entry points or imported shared memory as long as no threads
592
// are spawned.
593
let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
594
let stdout = run_wasmtime(&[
595
"run",
596
"-Wthreads",
597
"-Sthreads",
598
"-Ccache=n",
599
"--invoke",
600
"simple",
601
wasm.path().to_str().unwrap(),
602
"4",
603
])?;
604
assert_eq!(stdout, "4\n");
605
Ok(())
606
}
607
608
#[test]
609
fn wasm_flags() -> Result<()> {
610
// Any argument after the wasm module should be interpreted as for the
611
// command itself
612
let stdout = run_wasmtime(&[
613
"run",
614
"--",
615
"tests/all/cli_tests/print-arguments.wat",
616
"--argument",
617
"-for",
618
"the",
619
"command",
620
])?;
621
assert_eq!(
622
stdout,
623
"\
624
print-arguments.wat\n\
625
--argument\n\
626
-for\n\
627
the\n\
628
command\n\
629
"
630
);
631
let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?;
632
assert_eq!(
633
stdout,
634
"\
635
print-arguments.wat\n\
636
-\n\
637
"
638
);
639
let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?;
640
assert_eq!(
641
stdout,
642
"\
643
print-arguments.wat\n\
644
--\n\
645
"
646
);
647
let stdout = run_wasmtime(&[
648
"run",
649
"--",
650
"tests/all/cli_tests/print-arguments.wat",
651
"--",
652
"--",
653
"-a",
654
"b",
655
])?;
656
assert_eq!(
657
stdout,
658
"\
659
print-arguments.wat\n\
660
--\n\
661
--\n\
662
-a\n\
663
b\n\
664
"
665
);
666
Ok(())
667
}
668
669
#[test]
670
fn name_same_as_builtin_command() -> Result<()> {
671
// a bare subcommand shouldn't run successfully
672
let output = get_wasmtime_command()?
673
.current_dir("tests/all/cli_tests")
674
.arg("run")
675
.output()?;
676
assert!(!output.status.success());
677
678
// a `--` prefix should let everything else get interpreted as a wasm
679
// module and arguments, even if the module has a name like `run`
680
let output = get_wasmtime_command()?
681
.current_dir("tests/all/cli_tests")
682
.arg("--")
683
.arg("run")
684
.output()?;
685
assert!(output.status.success(), "expected success got {output:#?}");
686
687
// Passing options before the subcommand should work and doesn't require
688
// `--` to disambiguate
689
let output = get_wasmtime_command()?
690
.current_dir("tests/all/cli_tests")
691
.arg("-Ccache=n")
692
.arg("run")
693
.output()?;
694
assert!(output.status.success(), "expected success got {output:#?}");
695
Ok(())
696
}
697
698
#[test]
699
#[cfg(unix)]
700
fn run_just_stdin_argument() -> Result<()> {
701
let output = get_wasmtime_command()?
702
.arg("-")
703
.stdin(File::open("tests/all/cli_tests/simple.wat")?)
704
.output()?;
705
assert!(output.status.success());
706
Ok(())
707
}
708
709
#[test]
710
fn wasm_flags_without_subcommand() -> Result<()> {
711
let output = get_wasmtime_command()?
712
.current_dir("tests/all/cli_tests/")
713
.arg("print-arguments.wat")
714
.arg("-foo")
715
.arg("bar")
716
.output()?;
717
assert!(output.status.success());
718
assert_eq!(
719
String::from_utf8_lossy(&output.stdout),
720
"\
721
print-arguments.wat\n\
722
-foo\n\
723
bar\n\
724
"
725
);
726
Ok(())
727
}
728
729
#[test]
730
fn wasi_misaligned_pointer() -> Result<()> {
731
let output = get_wasmtime_command()?
732
.arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat")
733
.output()?;
734
assert!(!output.status.success());
735
let stderr = String::from_utf8_lossy(&output.stderr);
736
assert!(
737
stderr.contains("Pointer not aligned"),
738
"bad stderr: {stderr}",
739
);
740
Ok(())
741
}
742
743
#[test]
744
#[cfg_attr(not(feature = "component-model"), ignore)]
745
fn hello_with_preview2() -> Result<()> {
746
let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
747
let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?;
748
assert_eq!(stdout, "Hello, world!\n");
749
Ok(())
750
}
751
752
#[test]
753
#[cfg_attr(not(feature = "component-model"), ignore)]
754
fn component_missing_feature() -> Result<()> {
755
let path = "tests/all/cli_tests/empty-component.wat";
756
let wasm = build_wasm(path)?;
757
let output = get_wasmtime_command()?
758
.arg("-Ccache=n")
759
.arg("-Wcomponent-model=n")
760
.arg(wasm.path())
761
.output()?;
762
assert!(!output.status.success());
763
let stderr = String::from_utf8_lossy(&output.stderr);
764
assert!(
765
stderr.contains("cannot execute a component without `--wasm component-model`"),
766
"bad stderr: {stderr}"
767
);
768
769
// also tests with raw *.wat input
770
let output = get_wasmtime_command()?
771
.arg("-Ccache=n")
772
.arg("-Wcomponent-model=n")
773
.arg(path)
774
.output()?;
775
assert!(!output.status.success());
776
let stderr = String::from_utf8_lossy(&output.stderr);
777
assert!(
778
stderr.contains("cannot execute a component without `--wasm component-model`"),
779
"bad stderr: {stderr}"
780
);
781
782
Ok(())
783
}
784
785
#[test]
786
#[cfg_attr(not(feature = "component-model"), ignore)]
787
fn component_enabled_by_default() -> Result<()> {
788
let path = "tests/all/cli_tests/component-basic.wat";
789
let wasm = build_wasm(path)?;
790
let output = get_wasmtime_command()?
791
.arg("-Ccache=n")
792
.arg(wasm.path())
793
.output()?;
794
assert!(output.status.success());
795
796
// also tests with raw *.wat input
797
let output = get_wasmtime_command()?
798
.arg("-Ccache=n")
799
.arg(path)
800
.output()?;
801
assert!(output.status.success());
802
803
Ok(())
804
}
805
806
// If the text format is invalid then the filename should be mentioned in the
807
// error message.
808
#[test]
809
fn bad_text_syntax() -> Result<()> {
810
let output = get_wasmtime_command()?
811
.arg("-Ccache=n")
812
.arg("tests/all/cli_tests/bad-syntax.wat")
813
.output()?;
814
assert!(!output.status.success());
815
let stderr = String::from_utf8_lossy(&output.stderr);
816
assert!(
817
stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"),
818
"bad stderr: {stderr}"
819
);
820
Ok(())
821
}
822
823
#[test]
824
#[cfg_attr(not(feature = "component-model"), ignore)]
825
fn run_basic_component() -> Result<()> {
826
let path = "tests/all/cli_tests/component-basic.wat";
827
let wasm = build_wasm(path)?;
828
829
// Run both the `*.wasm` binary and the text format
830
run_wasmtime(&[
831
"-Ccache=n",
832
"-Wcomponent-model",
833
wasm.path().to_str().unwrap(),
834
])?;
835
run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?;
836
837
Ok(())
838
}
839
840
#[test]
841
#[cfg_attr(not(feature = "component-model"), ignore)]
842
fn run_precompiled_component() -> Result<()> {
843
let td = TempDir::new()?;
844
let cwasm = td.path().join("component-basic.cwasm");
845
let stdout = run_wasmtime(&[
846
"compile",
847
"tests/all/cli_tests/component-basic.wat",
848
"-o",
849
cwasm.to_str().unwrap(),
850
"-Wcomponent-model",
851
])?;
852
assert_eq!(stdout, "");
853
let stdout = run_wasmtime(&[
854
"run",
855
"-Wcomponent-model",
856
"--allow-precompiled",
857
cwasm.to_str().unwrap(),
858
])?;
859
assert_eq!(stdout, "");
860
861
Ok(())
862
}
863
864
// Disable test on s390x because the large allocation may actually succeed;
865
// the whole 64-bit address space is available on this platform.
866
#[test]
867
#[cfg(not(target_arch = "s390x"))]
868
fn memory_growth_failure() -> Result<()> {
869
let output = get_wasmtime_command()?
870
.args(&[
871
"run",
872
"-Wmemory64",
873
"-Wtrap-on-grow-failure",
874
"tests/all/cli_tests/memory-grow-failure.wat",
875
])
876
.output()?;
877
assert!(!output.status.success());
878
let stderr = String::from_utf8_lossy(&output.stderr);
879
assert!(
880
stderr.contains("forcing a memory growth failure to be a trap"),
881
"bad stderr: {stderr}"
882
);
883
Ok(())
884
}
885
886
#[test]
887
fn table_growth_failure() -> Result<()> {
888
let output = get_wasmtime_command()?
889
.args(&[
890
"run",
891
"-Wtrap-on-grow-failure",
892
"tests/all/cli_tests/table-grow-failure.wat",
893
])
894
.output()?;
895
assert!(!output.status.success());
896
let stderr = String::from_utf8_lossy(&output.stderr);
897
assert!(
898
stderr.contains("forcing trap when growing table"),
899
"bad stderr: {stderr}"
900
);
901
Ok(())
902
}
903
904
#[test]
905
fn table_growth_failure2() -> Result<()> {
906
let output = get_wasmtime_command()?
907
.args(&[
908
"run",
909
"-Wtrap-on-grow-failure",
910
"tests/all/cli_tests/table-grow-failure2.wat",
911
])
912
.output()?;
913
assert!(!output.status.success());
914
let stderr = String::from_utf8_lossy(&output.stderr);
915
let expected = if cfg!(target_pointer_width = "32") {
916
"overflow calculating new table size"
917
} else {
918
"forcing trap when growing table to 4294967296 elements"
919
};
920
assert!(stderr.contains(expected), "bad stderr: {stderr}");
921
Ok(())
922
}
923
924
#[test]
925
fn option_group_help() -> Result<()> {
926
run_wasmtime(&["run", "-Whelp"])?;
927
run_wasmtime(&["run", "-O", "help"])?;
928
run_wasmtime(&["run", "--codegen", "help"])?;
929
run_wasmtime(&["run", "--debug=help"])?;
930
run_wasmtime(&["run", "-Shelp"])?;
931
run_wasmtime(&["run", "-Whelp-long"])?;
932
Ok(())
933
}
934
935
#[test]
936
fn option_group_comma_separated() -> Result<()> {
937
run_wasmtime(&[
938
"run",
939
"-Wrelaxed-simd,simd",
940
"tests/all/cli_tests/simple.wat",
941
])?;
942
Ok(())
943
}
944
945
#[test]
946
fn option_group_boolean_parsing() -> Result<()> {
947
run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?;
948
run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?;
949
run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?;
950
run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?;
951
run_wasmtime(&[
952
"run",
953
"-Wrelaxed-simd=yes",
954
"tests/all/cli_tests/simple.wat",
955
])?;
956
run_wasmtime(&[
957
"run",
958
"-Wrelaxed-simd=true",
959
"tests/all/cli_tests/simple.wat",
960
])?;
961
run_wasmtime(&[
962
"run",
963
"-Wrelaxed-simd=false",
964
"tests/all/cli_tests/simple.wat",
965
])?;
966
Ok(())
967
}
968
969
#[test]
970
fn preview2_stdin() -> Result<()> {
971
let test = "tests/all/cli_tests/count-stdin.wat";
972
let cmd = || -> Result<_> {
973
let mut cmd = get_wasmtime_command()?;
974
cmd.arg("--invoke=count").arg("-Spreview2").arg(test);
975
Ok(cmd)
976
};
977
978
// read empty pipe is ok
979
let output = cmd()?.output()?;
980
assert!(output.status.success());
981
assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");
982
983
// read itself is ok
984
let file = File::open(test)?;
985
let size = file.metadata()?.len();
986
let output = cmd()?.stdin(File::open(test)?).output()?;
987
assert!(output.status.success());
988
assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n"));
989
990
// read piped input ok is ok
991
let mut child = cmd()?
992
.stdin(Stdio::piped())
993
.stdout(Stdio::piped())
994
.stderr(Stdio::piped())
995
.spawn()?;
996
let mut stdin = child.stdin.take().unwrap();
997
std::thread::spawn(move || {
998
stdin.write_all(b"hello").unwrap();
999
});
1000
let output = child.wait_with_output()?;
1001
assert!(output.status.success());
1002
assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n");
1003
1004
let count_up_to = |n: usize| -> Result<_> {
1005
let mut child = get_wasmtime_command()?
1006
.arg("--invoke=count-up-to")
1007
.arg("-Spreview2")
1008
.arg(test)
1009
.arg(n.to_string())
1010
.stdin(Stdio::piped())
1011
.stdout(Stdio::piped())
1012
.stderr(Stdio::piped())
1013
.spawn()?;
1014
let mut stdin = child.stdin.take().unwrap();
1015
let t = std::thread::spawn(move || {
1016
let mut written = 0;
1017
let bytes = [0; 64 * 1024];
1018
loop {
1019
written += match stdin.write(&bytes) {
1020
Ok(n) => n,
1021
Err(_) => break written,
1022
};
1023
}
1024
});
1025
let output = child.wait_with_output()?;
1026
assert!(output.status.success());
1027
let written = t.join().unwrap();
1028
let read = String::from_utf8_lossy(&output.stdout)
1029
.trim()
1030
.parse::<usize>()
1031
.unwrap();
1032
// The test reads in 1000 byte chunks so make sure that it doesn't read
1033
// more than 1000 bytes than requested.
1034
assert!(read < n + 1000, "test read too much {read}");
1035
Ok(written)
1036
};
1037
1038
// wasmtime shouldn't eat information that the guest never actually tried to
1039
// read.
1040
//
1041
// NB: this may be a bit flaky. Exactly how much we wrote in the above
1042
// helper thread depends on how much the OS buffers for us. For now give
1043
// some some slop and assume that OSes are unlikely to buffer more than
1044
// that.
1045
let slop = 256 * 1024;
1046
for amt in [0, 100, 100_000] {
1047
let written = count_up_to(amt)?;
1048
assert!(written < slop + amt, "wrote too much {written}");
1049
}
1050
Ok(())
1051
}
1052
1053
#[test]
1054
fn float_args() -> Result<()> {
1055
let result = run_wasmtime(&[
1056
"--invoke",
1057
"echo_f32",
1058
"tests/all/cli_tests/simple.wat",
1059
"1.0",
1060
])?;
1061
assert_eq!(result, "1\n");
1062
let result = run_wasmtime(&[
1063
"--invoke",
1064
"echo_f64",
1065
"tests/all/cli_tests/simple.wat",
1066
"1.1",
1067
])?;
1068
assert_eq!(result, "1.1\n");
1069
Ok(())
1070
}
1071
1072
#[test]
1073
fn mpk_without_pooling() -> Result<()> {
1074
let output = get_wasmtime_command()?
1075
.args(&[
1076
"run",
1077
"-O",
1078
"memory-protection-keys=y",
1079
"--invoke",
1080
"echo_f32",
1081
"tests/all/cli_tests/simple.wat",
1082
"1.0",
1083
])
1084
.env("WASMTIME_NEW_CLI", "1")
1085
.output()?;
1086
assert!(!output.status.success());
1087
Ok(())
1088
}
1089
1090
// Very basic use case: compile binary wasm file and run specific function with arguments.
1091
#[test]
1092
fn increase_stack_size() -> Result<()> {
1093
run_wasmtime(&[
1094
"run",
1095
"--invoke",
1096
"simple",
1097
&format!("-Wmax-wasm-stack={}", 5 << 20),
1098
"-Ccache=n",
1099
"tests/all/cli_tests/simple.wat",
1100
"4",
1101
])?;
1102
Ok(())
1103
}
1104
1105
mod test_programs {
1106
use super::{get_wasmtime_command, run_wasmtime};
1107
use anyhow::{Context, Result, bail};
1108
use http_body_util::BodyExt;
1109
use hyper::header::HeaderValue;
1110
use std::io::{BufRead, BufReader, Read, Write};
1111
use std::iter;
1112
use std::net::SocketAddr;
1113
use std::process::{Child, Command, Stdio};
1114
use test_programs_artifacts::*;
1115
use tokio::net::TcpStream;
1116
1117
macro_rules! assert_test_exists {
1118
($name:ident) => {
1119
#[expect(unused_imports, reason = "just here to assert the test is here")]
1120
use self::$name as _;
1121
};
1122
}
1123
foreach_cli!(assert_test_exists);
1124
1125
#[test]
1126
fn cli_hello_stdout() -> Result<()> {
1127
run_wasmtime(&["run", "-Wcomponent-model", CLI_HELLO_STDOUT_COMPONENT])?;
1128
Ok(())
1129
}
1130
1131
#[test]
1132
fn cli_args() -> Result<()> {
1133
run_wasmtime(&[
1134
"run",
1135
"-Wcomponent-model",
1136
CLI_ARGS_COMPONENT,
1137
"hello",
1138
"this",
1139
"",
1140
"is an argument",
1141
"with 🚩 emoji",
1142
])?;
1143
Ok(())
1144
}
1145
1146
#[test]
1147
fn cli_stdin_empty() -> Result<()> {
1148
let mut child = get_wasmtime_command()?
1149
.args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT])
1150
.stdout(Stdio::piped())
1151
.stderr(Stdio::piped())
1152
.stdin(Stdio::piped())
1153
.spawn()?;
1154
child
1155
.stdin
1156
.take()
1157
.unwrap()
1158
.write_all(b"not to be read")
1159
.unwrap();
1160
let output = child.wait_with_output()?;
1161
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1162
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1163
assert!(output.status.success());
1164
Ok(())
1165
}
1166
1167
#[test]
1168
fn cli_stdin() -> Result<()> {
1169
let mut child = get_wasmtime_command()?
1170
.args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT])
1171
.stdout(Stdio::piped())
1172
.stderr(Stdio::piped())
1173
.stdin(Stdio::piped())
1174
.spawn()?;
1175
child
1176
.stdin
1177
.take()
1178
.unwrap()
1179
.write_all(b"So rested he by the Tumtum tree")
1180
.unwrap();
1181
let output = child.wait_with_output()?;
1182
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
1183
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
1184
assert!(output.status.success());
1185
Ok(())
1186
}
1187
1188
#[test]
1189
fn cli_splice_stdin() -> Result<()> {
1190
let mut child = get_wasmtime_command()?
1191
.args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT])
1192
.stdout(Stdio::piped())
1193
.stderr(Stdio::piped())
1194
.stdin(Stdio::piped())
1195
.spawn()?;
1196
let msg = "So rested he by the Tumtum tree";
1197
child
1198
.stdin
1199
.take()
1200
.unwrap()
1201
.write_all(msg.as_bytes())
1202
.unwrap();
1203
let output = child.wait_with_output()?;
1204
assert!(output.status.success());
1205
let stdout = String::from_utf8_lossy(&output.stdout);
1206
let stderr = String::from_utf8_lossy(&output.stderr);
1207
if !stderr.is_empty() {
1208
eprintln!("{stderr}");
1209
}
1210
1211
assert_eq!(
1212
format!(
1213
"before splice\n{msg}\ncompleted splicing {} bytes\n",
1214
msg.as_bytes().len()
1215
),
1216
stdout
1217
);
1218
Ok(())
1219
}
1220
1221
#[test]
1222
fn cli_env() -> Result<()> {
1223
run_wasmtime(&[
1224
"run",
1225
"-Wcomponent-model",
1226
"--env=frabjous=day",
1227
"--env=callooh=callay",
1228
CLI_ENV_COMPONENT,
1229
])?;
1230
Ok(())
1231
}
1232
1233
#[test]
1234
fn cli_file_read() -> Result<()> {
1235
let dir = tempfile::tempdir()?;
1236
1237
std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?;
1238
1239
run_wasmtime(&[
1240
"run",
1241
"-Wcomponent-model",
1242
&format!("--dir={}::/", dir.path().to_str().unwrap()),
1243
CLI_FILE_READ_COMPONENT,
1244
])?;
1245
Ok(())
1246
}
1247
1248
#[test]
1249
fn cli_file_append() -> Result<()> {
1250
let dir = tempfile::tempdir()?;
1251
1252
std::fs::File::create(dir.path().join("bar.txt"))?
1253
.write_all(b"'Twas brillig, and the slithy toves.\n")?;
1254
1255
run_wasmtime(&[
1256
"run",
1257
"-Wcomponent-model",
1258
&format!("--dir={}::/", dir.path().to_str().unwrap()),
1259
CLI_FILE_APPEND_COMPONENT,
1260
])?;
1261
1262
let contents = std::fs::read(dir.path().join("bar.txt"))?;
1263
assert_eq!(
1264
std::str::from_utf8(&contents).unwrap(),
1265
"'Twas brillig, and the slithy toves.\n\
1266
Did gyre and gimble in the wabe;\n\
1267
All mimsy were the borogoves,\n\
1268
And the mome raths outgrabe.\n"
1269
);
1270
Ok(())
1271
}
1272
1273
#[test]
1274
fn cli_file_dir_sync() -> Result<()> {
1275
let dir = tempfile::tempdir()?;
1276
1277
std::fs::File::create(dir.path().join("bar.txt"))?
1278
.write_all(b"'Twas brillig, and the slithy toves.\n")?;
1279
1280
run_wasmtime(&[
1281
"run",
1282
"-Wcomponent-model",
1283
&format!("--dir={}::/", dir.path().to_str().unwrap()),
1284
CLI_FILE_DIR_SYNC_COMPONENT,
1285
])?;
1286
1287
Ok(())
1288
}
1289
1290
#[test]
1291
fn cli_exit_success() -> Result<()> {
1292
run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?;
1293
Ok(())
1294
}
1295
1296
#[test]
1297
fn cli_exit_default() -> Result<()> {
1298
run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?;
1299
Ok(())
1300
}
1301
1302
#[test]
1303
fn cli_exit_failure() -> Result<()> {
1304
let output = get_wasmtime_command()?
1305
.args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT])
1306
.output()?;
1307
assert!(!output.status.success());
1308
assert_eq!(output.status.code(), Some(1));
1309
Ok(())
1310
}
1311
1312
#[test]
1313
fn cli_exit_with_code() -> Result<()> {
1314
let output = get_wasmtime_command()?
1315
.args(&[
1316
"run",
1317
"-Wcomponent-model",
1318
"-Scli-exit-with-code",
1319
CLI_EXIT_WITH_CODE_COMPONENT,
1320
])
1321
.output()?;
1322
assert!(!output.status.success());
1323
assert_eq!(output.status.code(), Some(42));
1324
Ok(())
1325
}
1326
1327
#[test]
1328
fn cli_exit_panic() -> Result<()> {
1329
let output = get_wasmtime_command()?
1330
.args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT])
1331
.output()?;
1332
assert!(!output.status.success());
1333
let stderr = String::from_utf8_lossy(&output.stderr);
1334
assert!(stderr.contains("Curiouser and curiouser!"));
1335
Ok(())
1336
}
1337
1338
#[test]
1339
fn cli_directory_list() -> Result<()> {
1340
let dir = tempfile::tempdir()?;
1341
1342
std::fs::File::create(dir.path().join("foo.txt"))?;
1343
std::fs::File::create(dir.path().join("bar.txt"))?;
1344
std::fs::File::create(dir.path().join("baz.txt"))?;
1345
std::fs::create_dir(dir.path().join("sub"))?;
1346
std::fs::File::create(dir.path().join("sub").join("wow.txt"))?;
1347
std::fs::File::create(dir.path().join("sub").join("yay.txt"))?;
1348
1349
run_wasmtime(&[
1350
"run",
1351
"-Wcomponent-model",
1352
&format!("--dir={}::/", dir.path().to_str().unwrap()),
1353
CLI_DIRECTORY_LIST_COMPONENT,
1354
])?;
1355
Ok(())
1356
}
1357
1358
#[test]
1359
fn cli_default_clocks() -> Result<()> {
1360
run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?;
1361
Ok(())
1362
}
1363
1364
#[test]
1365
fn cli_export_cabi_realloc() -> Result<()> {
1366
run_wasmtime(&[
1367
"run",
1368
"-Wcomponent-model",
1369
CLI_EXPORT_CABI_REALLOC_COMPONENT,
1370
])?;
1371
Ok(())
1372
}
1373
1374
#[test]
1375
fn run_wasi_http_component() -> Result<()> {
1376
let output = super::run_wasmtime_for_output(
1377
&[
1378
"-Ccache=no",
1379
"-Wcomponent-model",
1380
"-Scli,http,preview2",
1381
HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT,
1382
],
1383
None,
1384
)?;
1385
println!("{}", String::from_utf8_lossy(&output.stderr));
1386
let stdout = String::from_utf8_lossy(&output.stdout);
1387
println!("{stdout}");
1388
assert!(stdout.starts_with("Called _start\n"));
1389
assert!(stdout.ends_with("Done\n"));
1390
assert!(output.status.success());
1391
Ok(())
1392
}
1393
1394
// Test to ensure that prints in the guest aren't buffered on the host by
1395
// accident. The test here will print something without a newline and then
1396
// wait for input on stdin, and the test here is to ensure that the
1397
// character shows up here even as the guest is waiting on input via stdin.
1398
#[test]
1399
fn cli_stdio_write_flushes() -> Result<()> {
1400
fn run(args: &[&str]) -> Result<()> {
1401
println!("running {args:?}");
1402
let mut child = get_wasmtime_command()?
1403
.args(args)
1404
.stdin(Stdio::piped())
1405
.stdout(Stdio::piped())
1406
.spawn()?;
1407
let mut stdout = child.stdout.take().unwrap();
1408
let mut buf = [0; 10];
1409
match stdout.read(&mut buf) {
1410
Ok(2) => assert_eq!(&buf[..2], b"> "),
1411
e => panic!("unexpected read result {e:?}"),
1412
}
1413
drop(stdout);
1414
drop(child.stdin.take().unwrap());
1415
let status = child.wait()?;
1416
assert!(status.success());
1417
Ok(())
1418
}
1419
1420
run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?;
1421
run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?;
1422
run(&[
1423
"run",
1424
"-Wcomponent-model",
1425
CLI_STDIO_WRITE_FLUSHES_COMPONENT,
1426
])?;
1427
Ok(())
1428
}
1429
1430
#[test]
1431
fn cli_no_tcp() -> Result<()> {
1432
let output = super::run_wasmtime_for_output(
1433
&[
1434
"-Wcomponent-model",
1435
// Turn on network but turn off TCP
1436
"-Sinherit-network,tcp=no",
1437
CLI_NO_TCP_COMPONENT,
1438
],
1439
None,
1440
)?;
1441
println!("{}", String::from_utf8_lossy(&output.stderr));
1442
assert!(output.status.success());
1443
Ok(())
1444
}
1445
1446
#[test]
1447
fn cli_no_udp() -> Result<()> {
1448
let output = super::run_wasmtime_for_output(
1449
&[
1450
"-Wcomponent-model",
1451
// Turn on network but turn off UDP
1452
"-Sinherit-network,udp=no",
1453
CLI_NO_UDP_COMPONENT,
1454
],
1455
None,
1456
)?;
1457
println!("{}", String::from_utf8_lossy(&output.stderr));
1458
assert!(output.status.success());
1459
Ok(())
1460
}
1461
1462
#[test]
1463
fn cli_no_ip_name_lookup() -> Result<()> {
1464
let output = super::run_wasmtime_for_output(
1465
&[
1466
"-Wcomponent-model",
1467
// Turn on network but ensure name lookup is disabled
1468
"-Sinherit-network,allow-ip-name-lookup=no",
1469
CLI_NO_IP_NAME_LOOKUP_COMPONENT,
1470
],
1471
None,
1472
)?;
1473
println!("{}", String::from_utf8_lossy(&output.stderr));
1474
assert!(output.status.success());
1475
Ok(())
1476
}
1477
1478
#[test]
1479
fn cli_sleep() -> Result<()> {
1480
run_wasmtime(&["run", CLI_SLEEP])?;
1481
run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?;
1482
Ok(())
1483
}
1484
1485
#[test]
1486
fn cli_sleep_forever() -> Result<()> {
1487
for timeout in [
1488
// Tests still pass when we race with going to sleep.
1489
"-Wtimeout=1ns",
1490
// Tests pass when we wait till the Wasm has (likely) gone to sleep.
1491
"-Wtimeout=250ms",
1492
] {
1493
let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER]).unwrap_err();
1494
let e = e.to_string();
1495
println!("Got error: {e}");
1496
assert!(e.contains("interrupt"));
1497
1498
let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err();
1499
let e = e.to_string();
1500
println!("Got error: {e}");
1501
assert!(e.contains("interrupt"));
1502
}
1503
1504
Ok(())
1505
}
1506
1507
/// Helper structure to manage an invocation of `wasmtime serve`
1508
struct WasmtimeServe {
1509
child: Option<Child>,
1510
addr: SocketAddr,
1511
shutdown_addr: SocketAddr,
1512
}
1513
1514
impl WasmtimeServe {
1515
/// Creates a new server which will serve the wasm component pointed to
1516
/// by `wasm`.
1517
///
1518
/// A `configure` callback is provided to specify how `wasmtime serve`
1519
/// will be invoked and configure arguments such as headers.
1520
fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
1521
// Spawn `wasmtime serve` on port 0 which will randomly assign it a
1522
// port.
1523
let mut cmd = super::get_wasmtime_command()?;
1524
cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm);
1525
configure(&mut cmd);
1526
Self::spawn(&mut cmd)
1527
}
1528
1529
fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> {
1530
cmd.arg("--shutdown-addr=127.0.0.1:0");
1531
cmd.stdin(Stdio::null());
1532
cmd.stdout(Stdio::piped());
1533
cmd.stderr(Stdio::piped());
1534
let mut child = cmd.spawn()?;
1535
1536
// Read the first few lines of stderr which will say which address
1537
// it's listening on. The first line is the shutdown line (with
1538
// `--shutdown-addr`) and the second is what `--addr` was bound to.
1539
// This is done to figure out what `:0` was bound to in the child
1540
// process.
1541
let mut line = String::new();
1542
let mut reader = BufReader::new(child.stderr.take().unwrap());
1543
let mut read_addr_from_line = |prefix: &str| -> Result<SocketAddr> {
1544
reader.read_line(&mut line)?;
1545
1546
if !line.starts_with(prefix) {
1547
bail!("input line `{line}` didn't start with `{prefix}`");
1548
}
1549
match line.find("127.0.0.1").and_then(|addr_start| {
1550
let addr = &line[addr_start..];
1551
let addr_end = addr.find("/")?;
1552
addr[..addr_end].parse().ok()
1553
}) {
1554
Some(addr) => {
1555
line.truncate(0);
1556
Ok(addr)
1557
}
1558
None => bail!("failed to address from: {line}"),
1559
}
1560
};
1561
let shutdown_addr = read_addr_from_line("Listening for shutdown");
1562
let addr = read_addr_from_line("Serving HTTP on");
1563
let (shutdown_addr, addr) = match (shutdown_addr, addr) {
1564
(Ok(a), Ok(b)) => (a, b),
1565
// If either failed kill the child and otherwise try to shepherd
1566
// along any contextual information we have.
1567
(Err(a), _) | (_, Err(a)) => {
1568
child.kill()?;
1569
child.wait()?;
1570
reader.read_to_string(&mut line)?;
1571
return Err(a.context(line));
1572
}
1573
};
1574
assert!(reader.buffer().is_empty());
1575
child.stderr = Some(reader.into_inner());
1576
Ok(WasmtimeServe {
1577
child: Some(child),
1578
addr,
1579
shutdown_addr,
1580
})
1581
}
1582
1583
/// Completes this server gracefully by printing the output on failure.
1584
fn finish(mut self) -> Result<(String, String)> {
1585
let mut child = self.child.take().unwrap();
1586
1587
// If the child process has already exited, then great! Otherwise
1588
// the server is still running and it shouldn't be possible to exit
1589
// until a shutdown signal is sent, so do that here. Make a TCP
1590
// connection to the shutdown port which is used as a shutdown
1591
// signal.
1592
if child.try_wait()?.is_none() {
1593
std::net::TcpStream::connect(&self.shutdown_addr)
1594
.context("failed to initiate graceful shutdown")?;
1595
}
1596
1597
// Regardless of whether we just shut the server down or whether it
1598
// was already shut down (e.g. panicked or similar), wait for the
1599
// result here. The result should succeed (e.g. 0 exit status), and
1600
// if it did then the stdout/stderr are the caller's problem.
1601
let output = child.wait_with_output()?;
1602
if !output.status.success() {
1603
bail!("child failed {output:?}");
1604
}
1605
1606
Ok((
1607
String::from_utf8_lossy(&output.stdout).into_owned(),
1608
String::from_utf8_lossy(&output.stderr).into_owned(),
1609
))
1610
}
1611
1612
/// Send a request to this server and wait for the response.
1613
async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> {
1614
let (mut send, conn_task) = self.start_requests().await?;
1615
1616
let response = send
1617
.send_request(req)
1618
.await
1619
.context("error sending request")?;
1620
drop(send);
1621
let (parts, body) = response.into_parts();
1622
1623
let body = body.collect().await.context("failed to read body")?;
1624
assert!(body.trailers().is_none());
1625
let body = std::str::from_utf8(&body.to_bytes())?.to_string();
1626
1627
conn_task.await??;
1628
1629
Ok(http::Response::from_parts(parts, body))
1630
}
1631
1632
async fn start_requests(
1633
&self,
1634
) -> Result<(
1635
hyper::client::conn::http1::SendRequest<String>,
1636
tokio::task::JoinHandle<hyper::Result<()>>,
1637
)> {
1638
let tcp = TcpStream::connect(&self.addr)
1639
.await
1640
.context("failed to connect")?;
1641
let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp);
1642
let (send, conn) = hyper::client::conn::http1::handshake(tcp)
1643
.await
1644
.context("failed http handshake")?;
1645
Ok((send, tokio::task::spawn(conn)))
1646
}
1647
}
1648
1649
// Don't leave child processes running by accident so kill the child process
1650
// if our server goes away.
1651
impl Drop for WasmtimeServe {
1652
fn drop(&mut self) {
1653
let mut child = match self.child.take() {
1654
Some(child) => child,
1655
None => return,
1656
};
1657
if child.kill().is_err() {
1658
return;
1659
}
1660
let output = match child.wait_with_output() {
1661
Ok(output) => output,
1662
Err(_) => return,
1663
};
1664
1665
println!("server status: {}", output.status);
1666
if !output.stdout.is_empty() {
1667
println!(
1668
"server stdout:\n{}",
1669
String::from_utf8_lossy(&output.stdout)
1670
);
1671
}
1672
if !output.stderr.is_empty() {
1673
println!(
1674
"server stderr:\n{}",
1675
String::from_utf8_lossy(&output.stderr)
1676
);
1677
}
1678
}
1679
}
1680
1681
#[tokio::test]
1682
async fn cli_serve_echo_env() -> Result<()> {
1683
let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1684
cmd.arg("--env=FOO=bar");
1685
cmd.arg("--env=BAR");
1686
cmd.arg("-Scli");
1687
cmd.env_remove("BAR");
1688
})?;
1689
1690
let foo_env = server
1691
.send_request(
1692
hyper::Request::builder()
1693
.uri("http://localhost/")
1694
.header("env", "FOO")
1695
.body(String::new())
1696
.context("failed to make request")?,
1697
)
1698
.await?;
1699
1700
assert!(foo_env.status().is_success());
1701
assert!(foo_env.body().is_empty());
1702
let headers = foo_env.headers();
1703
assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar")));
1704
1705
let bar_env = server
1706
.send_request(
1707
hyper::Request::builder()
1708
.uri("http://localhost/")
1709
.header("env", "BAR")
1710
.body(String::new())
1711
.context("failed to make request")?,
1712
)
1713
.await?;
1714
1715
assert!(bar_env.status().is_success());
1716
assert!(bar_env.body().is_empty());
1717
let headers = bar_env.headers();
1718
assert_eq!(headers.get("env"), None);
1719
1720
server.finish()?;
1721
Ok(())
1722
}
1723
1724
#[tokio::test]
1725
async fn cli_serve_outgoing_body_config() -> Result<()> {
1726
let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1727
cmd.arg("-Scli");
1728
cmd.arg("-Shttp-outgoing-body-buffer-chunks=2");
1729
cmd.arg("-Shttp-outgoing-body-chunk-size=1024");
1730
})?;
1731
1732
let resp = server
1733
.send_request(
1734
hyper::Request::builder()
1735
.uri("http://localhost/")
1736
.header("env", "FOO")
1737
.body(String::new())
1738
.context("failed to make request")?,
1739
)
1740
.await?;
1741
1742
assert!(resp.status().is_success());
1743
1744
server.finish()?;
1745
Ok(())
1746
}
1747
1748
#[tokio::test]
1749
#[ignore] // TODO: printing stderr in the child and killing the child at the
1750
// end of this test race so the stderr may be present or not. Need
1751
// to implement a more graceful shutdown routine for `wasmtime
1752
// serve`.
1753
async fn cli_serve_respect_pooling_options() -> Result<()> {
1754
let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
1755
cmd.arg("-Opooling-total-memories=0").arg("-Scli");
1756
})?;
1757
1758
let result = server
1759
.send_request(
1760
hyper::Request::builder()
1761
.uri("http://localhost/")
1762
.header("env", "FOO")
1763
.body(String::new())
1764
.context("failed to make request")?,
1765
)
1766
.await;
1767
assert!(result.is_err());
1768
let (_, stderr) = server.finish()?;
1769
assert!(
1770
stderr.contains("maximum concurrent memory limit of 0 reached"),
1771
"bad stderr: {stderr}",
1772
);
1773
Ok(())
1774
}
1775
1776
#[test]
1777
fn cli_large_env() -> Result<()> {
1778
for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] {
1779
println!("run {wasm:?}");
1780
let mut cmd = get_wasmtime_command()?;
1781
cmd.arg("run").arg("-Sinherit-env").arg(wasm);
1782
1783
let debug_cmd = format!("{cmd:?}");
1784
for i in 0..512 {
1785
let var = format!("KEY{i}");
1786
let val = (0..1024).map(|_| 'x').collect::<String>();
1787
cmd.env(&var, &val);
1788
}
1789
let output = cmd.output()?;
1790
if !output.status.success() {
1791
bail!(
1792
"Failed to execute wasmtime with: {debug_cmd}\n{}",
1793
String::from_utf8_lossy(&output.stderr)
1794
);
1795
}
1796
}
1797
Ok(())
1798
}
1799
1800
#[tokio::test]
1801
async fn cli_serve_only_one_process_allowed() -> Result<()> {
1802
let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
1803
let server = WasmtimeServe::new(wasm, |cmd| {
1804
cmd.arg("-Scli");
1805
})?;
1806
1807
let err = WasmtimeServe::spawn(
1808
super::get_wasmtime_command()?
1809
.arg("serve")
1810
.arg("-Scli")
1811
.arg(format!("--addr={}", server.addr))
1812
.arg(wasm),
1813
)
1814
.err()
1815
.expect("server spawn should have failed but it succeeded");
1816
drop(server);
1817
1818
let err = format!("{err:?}");
1819
println!("{err}");
1820
assert!(err.contains("os error"));
1821
Ok(())
1822
}
1823
1824
// Technically this test is a little racy. This binds port 0 to acquire a
1825
// random port, issues a single request to this port, but then kills this
1826
// server while the request is still processing. The port is then rebound
1827
// in the next process while it technically could be stolen by another
1828
// process.
1829
#[tokio::test]
1830
async fn cli_serve_quick_rebind_allowed() -> Result<()> {
1831
let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
1832
let server = WasmtimeServe::new(wasm, |cmd| {
1833
cmd.arg("-Scli");
1834
})?;
1835
let addr = server.addr;
1836
1837
// Start up a `send` and `conn_task` which represents a connection to
1838
// this server.
1839
let (mut send, conn_task) = server.start_requests().await?;
1840
let _ = send
1841
.send_request(
1842
hyper::Request::builder()
1843
.uri("http://localhost/")
1844
.header("env", "FOO")
1845
.body(String::new())
1846
.context("failed to make request")?,
1847
)
1848
.await;
1849
1850
// ... once a response has been received (or at least the status
1851
// code/headers) then kill the server. THis is done while `conn_task`
1852
// and `send` are still alive so we're guaranteed that the other side
1853
// got a request (we got a response) and our connection is still open.
1854
//
1855
// This forces the address/port into the `TIME_WAIT` state. The rebind
1856
// below in the next process will fail if `SO_REUSEADDR` isn't set.
1857
drop(server);
1858
drop(send);
1859
let _ = conn_task.await;
1860
1861
// If this is successfully bound then we'll create `WasmtimeServe`
1862
// which reads off the first line of output to know which address was
1863
// bound.
1864
let _server2 = WasmtimeServe::spawn(
1865
super::get_wasmtime_command()?
1866
.arg("serve")
1867
.arg("-Scli")
1868
.arg(format!("--addr={addr}"))
1869
.arg(wasm),
1870
)?;
1871
1872
Ok(())
1873
}
1874
1875
#[tokio::test]
1876
async fn cli_serve_with_print() -> Result<()> {
1877
let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1878
cmd.arg("-Scli");
1879
})?;
1880
1881
for _ in 0..2 {
1882
let resp = server
1883
.send_request(
1884
hyper::Request::builder()
1885
.uri("http://localhost/")
1886
.body(String::new())
1887
.context("failed to make request")?,
1888
)
1889
.await?;
1890
assert!(resp.status().is_success());
1891
}
1892
1893
let (out, err) = server.finish()?;
1894
assert_eq!(
1895
out,
1896
"\
1897
stdout [0] :: this is half a print to stdout
1898
stdout [0] :: \n\
1899
stdout [0] :: after empty
1900
stdout [1] :: this is half a print to stdout
1901
stdout [1] :: \n\
1902
stdout [1] :: after empty
1903
"
1904
);
1905
assert!(
1906
err.contains(
1907
"\
1908
stderr [0] :: this is half a print to stderr
1909
stderr [0] :: \n\
1910
stderr [0] :: after empty
1911
stderr [0] :: start a print 1234
1912
stderr [1] :: this is half a print to stderr
1913
stderr [1] :: \n\
1914
stderr [1] :: after empty
1915
stderr [1] :: start a print 1234
1916
"
1917
),
1918
"bad stderr: {err}"
1919
);
1920
1921
Ok(())
1922
}
1923
1924
#[tokio::test]
1925
async fn cli_serve_with_print_no_prefix() -> Result<()> {
1926
let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
1927
cmd.arg("-Scli");
1928
cmd.arg("--no-logging-prefix");
1929
})?;
1930
1931
for _ in 0..2 {
1932
let resp = server
1933
.send_request(
1934
hyper::Request::builder()
1935
.uri("http://localhost/")
1936
.body(String::new())
1937
.context("failed to make request")?,
1938
)
1939
.await?;
1940
assert!(resp.status().is_success());
1941
}
1942
1943
let (out, err) = server.finish()?;
1944
assert_eq!(
1945
out,
1946
"\
1947
this is half a print to stdout
1948
\n\
1949
after empty
1950
this is half a print to stdout
1951
\n\
1952
after empty
1953
"
1954
);
1955
assert!(
1956
err.contains(
1957
"\
1958
this is half a print to stderr
1959
\n\
1960
after empty
1961
start a print 1234
1962
this is half a print to stderr
1963
\n\
1964
after empty
1965
start a print 1234
1966
"
1967
),
1968
"bad stderr {err}",
1969
);
1970
1971
Ok(())
1972
}
1973
1974
#[tokio::test]
1975
async fn cli_serve_authority_and_scheme() -> Result<()> {
1976
let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| {
1977
cmd.arg("-Scli");
1978
})?;
1979
1980
let resp = server
1981
.send_request(
1982
hyper::Request::builder()
1983
.uri("/")
1984
.header("Host", "localhost")
1985
.body(String::new())
1986
.context("failed to make request")?,
1987
)
1988
.await?;
1989
assert!(resp.status().is_success());
1990
1991
let resp = server
1992
.send_request(
1993
hyper::Request::builder()
1994
.method("CONNECT")
1995
.uri("http://localhost/")
1996
.body(String::new())
1997
.context("failed to make request")?,
1998
)
1999
.await?;
2000
assert!(resp.status().is_success());
2001
2002
Ok(())
2003
}
2004
2005
#[test]
2006
fn cli_argv0() -> Result<()> {
2007
run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?;
2008
run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?;
2009
run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?;
2010
Ok(())
2011
}
2012
2013
#[tokio::test]
2014
async fn cli_serve_config() -> Result<()> {
2015
let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| {
2016
cmd.arg("-Scli");
2017
cmd.arg("-Sconfig");
2018
cmd.arg("-Sconfig-var=hello=world");
2019
})?;
2020
2021
let resp = server
2022
.send_request(
2023
hyper::Request::builder()
2024
.uri("http://localhost/")
2025
.body(String::new())
2026
.context("failed to make request")?,
2027
)
2028
.await?;
2029
2030
assert!(resp.status().is_success());
2031
assert_eq!(resp.body(), "world");
2032
Ok(())
2033
}
2034
2035
#[test]
2036
fn cli_config() -> Result<()> {
2037
run_wasmtime(&[
2038
"run",
2039
"-Sconfig",
2040
"-Sconfig-var=hello=world",
2041
CONFIG_GET_COMPONENT,
2042
])?;
2043
Ok(())
2044
}
2045
2046
#[tokio::test]
2047
async fn cli_serve_keyvalue() -> Result<()> {
2048
let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| {
2049
cmd.arg("-Scli");
2050
cmd.arg("-Skeyvalue");
2051
cmd.arg("-Skeyvalue-in-memory-data=hello=world");
2052
})?;
2053
2054
let resp = server
2055
.send_request(
2056
hyper::Request::builder()
2057
.uri("http://localhost/")
2058
.body(String::new())
2059
.context("failed to make request")?,
2060
)
2061
.await?;
2062
2063
assert!(resp.status().is_success());
2064
assert_eq!(resp.body(), "world");
2065
Ok(())
2066
}
2067
2068
#[test]
2069
fn cli_keyvalue() -> Result<()> {
2070
run_wasmtime(&[
2071
"run",
2072
"-Skeyvalue",
2073
"-Skeyvalue-in-memory-data=atomics_key=5",
2074
KEYVALUE_MAIN_COMPONENT,
2075
])?;
2076
Ok(())
2077
}
2078
2079
#[test]
2080
fn cli_multiple_preopens() -> Result<()> {
2081
run_wasmtime(&[
2082
"run",
2083
"--dir=/::/a",
2084
"--dir=/::/b",
2085
"--dir=/::/c",
2086
CLI_MULTIPLE_PREOPENS_COMPONENT,
2087
])?;
2088
Ok(())
2089
}
2090
2091
async fn cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()> {
2092
let server = WasmtimeServe::new(wasm, |cmd| {
2093
cmd.arg("-Scli");
2094
})?;
2095
2096
for _ in 0..2 {
2097
let res = server
2098
.send_request(
2099
hyper::Request::builder()
2100
.uri("http://localhost/")
2101
.body(String::new())
2102
.context("failed to make request")?,
2103
)
2104
.await
2105
.expect("got response from wasmtime");
2106
assert_eq!(res.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
2107
}
2108
2109
let (stdout, stderr) = server.finish()?;
2110
println!("stdout: {stdout}");
2111
println!("stderr: {stderr}");
2112
assert!(stderr.contains("guest never invoked `response-outparam::set` method"));
2113
assert!(!stderr.contains("panicked"));
2114
Ok(())
2115
}
2116
2117
#[tokio::test]
2118
async fn cli_serve_return_before_set() -> Result<()> {
2119
cli_serve_guest_never_invoked_set(CLI_SERVE_RETURN_BEFORE_SET_COMPONENT).await
2120
}
2121
2122
#[tokio::test]
2123
async fn cli_serve_trap_before_set() -> Result<()> {
2124
cli_serve_guest_never_invoked_set(CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await
2125
}
2126
2127
#[test]
2128
fn cli_p3_hello_stdout() -> Result<()> {
2129
let output = run_wasmtime(&[
2130
"run",
2131
"-Wcomponent-model-async",
2132
"-Sp3",
2133
CLI_P3_HELLO_STDOUT_COMPONENT,
2134
]);
2135
if cfg!(feature = "component-model-async") {
2136
let output = output?;
2137
assert_eq!(output, "hello, world\n");
2138
} else {
2139
assert!(output.is_err());
2140
}
2141
Ok(())
2142
}
2143
2144
mod invoke {
2145
use super::*;
2146
2147
#[test]
2148
fn cli_hello_stdout() -> Result<()> {
2149
println!("{CLI_HELLO_STDOUT_COMPONENT}");
2150
let output = run_wasmtime(&[
2151
"run",
2152
"-Wcomponent-model",
2153
"--invoke",
2154
"run()",
2155
CLI_HELLO_STDOUT_COMPONENT,
2156
])?;
2157
// First this component prints "hello, world", then the invoke
2158
// result is printed as "ok".
2159
assert_eq!(output, "hello, world\nok\n");
2160
Ok(())
2161
}
2162
}
2163
2164
#[test]
2165
#[cfg_attr(not(feature = "component-model-async"), ignore)]
2166
fn cli_invoke_async() -> Result<()> {
2167
let output = run_wasmtime(&[
2168
"run",
2169
"-Wcomponent-model-async",
2170
"--invoke",
2171
"echo(\"hello?\")",
2172
CLI_INVOKE_ASYNC_COMPONENT,
2173
])?;
2174
assert_eq!(output, "\"hello?\"\n");
2175
Ok(())
2176
}
2177
2178
fn run_much_stdout(component: &str, extra_flags: &[&str]) -> Result<()> {
2179
let total_write_size = 1 << 19;
2180
let expected = iter::repeat('a').take(total_write_size).collect::<String>();
2181
2182
for i in 0..15 {
2183
let string = iter::repeat('a').take(1 << i).collect::<String>();
2184
let times = (total_write_size >> i).to_string();
2185
println!("writing {} bytes {times} times", string.len());
2186
2187
let mut args = Vec::new();
2188
args.push("run");
2189
args.extend_from_slice(extra_flags);
2190
args.push(component);
2191
args.push(&string);
2192
args.push(&times);
2193
let output = run_wasmtime(&args)?;
2194
println!(
2195
"expected {} bytes, got {} bytes",
2196
expected.len(),
2197
output.len()
2198
);
2199
assert!(output == expected);
2200
}
2201
2202
Ok(())
2203
}
2204
2205
#[test]
2206
fn cli_p1_much_stdout() -> Result<()> {
2207
run_much_stdout(CLI_P1_MUCH_STDOUT_COMPONENT, &[])
2208
}
2209
2210
#[test]
2211
fn cli_p2_much_stdout() -> Result<()> {
2212
run_much_stdout(CLI_P2_MUCH_STDOUT_COMPONENT, &[])
2213
}
2214
2215
#[test]
2216
#[cfg_attr(not(feature = "component-model-async"), ignore)]
2217
fn cli_p3_much_stdout() -> Result<()> {
2218
run_much_stdout(
2219
CLI_P3_MUCH_STDOUT_COMPONENT,
2220
&["-Wcomponent-model-async", "-Sp3"],
2221
)
2222
}
2223
}
2224
2225
#[test]
2226
fn settings_command() -> Result<()> {
2227
// Skip this test on platforms that Cranelift doesn't support.
2228
if cranelift_native::builder().is_err() {
2229
return Ok(());
2230
}
2231
let output = run_wasmtime(&["settings"])?;
2232
assert!(output.contains("Cranelift settings for target"));
2233
Ok(())
2234
}
2235
2236
#[cfg(target_arch = "x86_64")]
2237
#[test]
2238
fn profile_with_vtune() -> Result<()> {
2239
if !is_vtune_available() {
2240
println!("> `vtune` is not available on the system path; skipping test");
2241
return Ok(());
2242
}
2243
2244
let mut bin = Command::new("vtune");
2245
bin.args(&[
2246
// Configure VTune...
2247
"-verbose",
2248
"-collect",
2249
"hotspots",
2250
"-user-data-dir",
2251
&std::env::temp_dir().to_string_lossy(),
2252
// ...then run Wasmtime with profiling enabled:
2253
get_wasmtime_path(),
2254
"--profile=vtune",
2255
"tests/all/cli_tests/simple.wat",
2256
]);
2257
2258
println!("> executing: {bin:?}");
2259
let output = bin.output()?;
2260
2261
assert!(output.status.success());
2262
let stdout = String::from_utf8_lossy(&output.stdout);
2263
let stderr = String::from_utf8_lossy(&output.stderr);
2264
println!("> stdout:\n{stdout}");
2265
assert!(stdout.contains("CPU Time"));
2266
println!("> stderr:\n{stderr}");
2267
assert!(!stderr.contains("Error"));
2268
Ok(())
2269
}
2270
2271
#[cfg(target_arch = "x86_64")]
2272
fn is_vtune_available() -> bool {
2273
Command::new("vtune").arg("-version").output().is_ok()
2274
}
2275
2276
#[test]
2277
fn unreachable_without_wasi() -> Result<()> {
2278
let output = run_wasmtime_for_output(
2279
&[
2280
"-Scli=n",
2281
"-Ccache=n",
2282
"tests/all/cli_tests/unreachable.wat",
2283
],
2284
None,
2285
)?;
2286
2287
assert_ne!(output.stderr, b"");
2288
assert_eq!(output.stdout, b"");
2289
assert_trap_code(&output.status);
2290
Ok(())
2291
}
2292
2293
#[test]
2294
fn config_cli_flag() -> Result<()> {
2295
let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
2296
2297
// Test some valid TOML values
2298
let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2299
cfg.write_all(
2300
br#"
2301
[optimize]
2302
opt-level = 2
2303
regalloc-algorithm = "single-pass"
2304
signals-based-traps = false
2305
2306
[codegen]
2307
collector = "null"
2308
2309
[debug]
2310
debug-info = true
2311
2312
[wasm]
2313
max-wasm-stack = 65536
2314
2315
[wasi]
2316
cli = true
2317
"#,
2318
)?;
2319
let output = run_wasmtime(&[
2320
"run",
2321
"--config",
2322
cfg_path.to_str().unwrap(),
2323
"--invoke",
2324
"get_f64",
2325
wasm.path().to_str().unwrap(),
2326
])?;
2327
assert_eq!(output, "100\n");
2328
2329
// Make sure CLI flags overrides TOML values
2330
let output = run_wasmtime(&[
2331
"run",
2332
"--config",
2333
cfg_path.to_str().unwrap(),
2334
"--invoke",
2335
"get_f64",
2336
"-W",
2337
"max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail
2338
wasm.path().to_str().unwrap(),
2339
]);
2340
assert!(
2341
output
2342
.as_ref()
2343
.unwrap_err()
2344
.to_string()
2345
.contains("max_wasm_stack size cannot be zero"),
2346
"'{output:?}' did not contain expected error message",
2347
);
2348
2349
// Test invalid TOML key
2350
let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2351
cfg.write_all(
2352
br#"
2353
[optimize]
2354
this-key-does-not-exist = true
2355
"#,
2356
)?;
2357
let output = run_wasmtime(&[
2358
"run",
2359
"--config",
2360
cfg_path.to_str().unwrap(),
2361
wasm.path().to_str().unwrap(),
2362
]);
2363
assert!(
2364
output
2365
.as_ref()
2366
.unwrap_err()
2367
.to_string()
2368
.contains("unknown field `this-key-does-not-exist`"),
2369
"'{output:?}' did not contain expected error message"
2370
);
2371
2372
// Test invalid TOML table
2373
let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
2374
cfg.write_all(
2375
br#"
2376
[invalid_table]
2377
"#,
2378
)?;
2379
let output = run_wasmtime(&[
2380
"run",
2381
"--config",
2382
cfg_path.to_str().unwrap(),
2383
wasm.path().to_str().unwrap(),
2384
]);
2385
assert!(
2386
output
2387
.as_ref()
2388
.unwrap_err()
2389
.to_string()
2390
.contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"),
2391
"'{output:?}' did not contain expected error message",
2392
);
2393
2394
Ok(())
2395
}
2396
2397
#[test]
2398
fn invalid_subcommand() -> Result<()> {
2399
let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?;
2400
dbg!(&output);
2401
assert!(!output.status.success());
2402
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand"));
2403
Ok(())
2404
}
2405
2406
#[test]
2407
fn numeric_args() -> Result<()> {
2408
let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?;
2409
// Test decimal i32
2410
let output = run_wasmtime_for_output(
2411
&[
2412
"run",
2413
"--invoke",
2414
"i32_test",
2415
wasm.path().to_str().unwrap(),
2416
"42",
2417
],
2418
None,
2419
)?;
2420
assert_eq!(output.status.success(), true);
2421
assert_eq!(output.stdout, b"42\n");
2422
// Test hexadecimal i32 with lowercase prefix
2423
let output = run_wasmtime_for_output(
2424
&[
2425
"run",
2426
"--invoke",
2427
"i32_test",
2428
wasm.path().to_str().unwrap(),
2429
"0x2A",
2430
],
2431
None,
2432
)?;
2433
assert_eq!(output.status.success(), true);
2434
assert_eq!(output.stdout, b"42\n");
2435
// Test hexadecimal i32 with uppercase prefix
2436
let output = run_wasmtime_for_output(
2437
&[
2438
"run",
2439
"--invoke",
2440
"i32_test",
2441
wasm.path().to_str().unwrap(),
2442
"0X2a",
2443
],
2444
None,
2445
)?;
2446
assert_eq!(output.status.success(), true);
2447
assert_eq!(output.stdout, b"42\n");
2448
// Test that non-prefixed hex strings are not interpreted as hex
2449
let output = run_wasmtime_for_output(
2450
&[
2451
"run",
2452
"--invoke",
2453
"i32_test",
2454
wasm.path().to_str().unwrap(),
2455
"ff",
2456
],
2457
None,
2458
)?;
2459
assert!(!output.status.success()); // Should fail as "ff" is not a valid decimal number
2460
2461
// Test decimal i64
2462
let output = run_wasmtime_for_output(
2463
&[
2464
"run",
2465
"--invoke",
2466
"i64_test",
2467
wasm.path().to_str().unwrap(),
2468
"42",
2469
],
2470
None,
2471
)?;
2472
assert_eq!(output.status.success(), true);
2473
assert_eq!(output.stdout, b"42\n");
2474
// Test hexadecimal i64
2475
let output = run_wasmtime_for_output(
2476
&[
2477
"run",
2478
"--invoke",
2479
"i64_test",
2480
wasm.path().to_str().unwrap(),
2481
"0x2A",
2482
],
2483
None,
2484
)?;
2485
assert_eq!(output.status.success(), true);
2486
assert_eq!(output.stdout, b"42\n");
2487
Ok(())
2488
}
2489
2490
#[test]
2491
fn compilation_logs() -> Result<()> {
2492
let temp = tempfile::NamedTempFile::new()?;
2493
let output = get_wasmtime_command()?
2494
.args(&[
2495
"compile",
2496
"-Wgc",
2497
"tests/all/cli_tests/issue-10353.wat",
2498
"--output",
2499
&temp.path().display().to_string(),
2500
])
2501
.env("WASMTIME_LOG", "trace")
2502
.env("RUST_BACKTRACE", "1")
2503
.output()?;
2504
if !output.status.success() {
2505
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
2506
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
2507
panic!("wasmtime compilation failed when logs requested");
2508
}
2509
Ok(())
2510
}
2511
2512
#[test]
2513
fn big_table_in_pooling_allocator() -> Result<()> {
2514
// Works by default
2515
run_wasmtime(&["tests/all/cli_tests/big_table.wat"])?;
2516
2517
// Does not work by default in the pooling allocator, and the error message
2518
// should mention something about the pooling allocator.
2519
let output = run_wasmtime_for_output(
2520
&["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"],
2521
None,
2522
)?;
2523
assert!(!output.status.success());
2524
println!("{}", String::from_utf8_lossy(&output.stderr));
2525
assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator"));
2526
2527
// Does work with `-Wmax-table-elements`
2528
run_wasmtime(&[
2529
"-Opooling-allocator",
2530
"-Wmax-table-elements=25000",
2531
"tests/all/cli_tests/big_table.wat",
2532
])?;
2533
// Also works with `-Opooling-table-elements`
2534
run_wasmtime(&[
2535
"-Opooling-allocator",
2536
"-Opooling-table-elements=25000",
2537
"tests/all/cli_tests/big_table.wat",
2538
])?;
2539
Ok(())
2540
}
2541
2542