Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/misc/component-async-tests/tests/scenario/round_trip.rs
3114 views
1
use super::util::{config, make_component};
2
use component_async_tests::Ctx;
3
use component_async_tests::util::yield_times;
4
use futures::{
5
FutureExt,
6
channel::oneshot,
7
stream::{FuturesUnordered, TryStreamExt},
8
};
9
use std::sync::Arc;
10
use std::sync::atomic::{AtomicU32, Ordering::Relaxed};
11
use wasmtime::component::{
12
Accessor, AccessorTask, HasData, HasSelf, Instance, Linker, ResourceTable, Val,
13
};
14
use wasmtime::{Engine, Result, Store, Trap, format_err};
15
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
16
17
#[tokio::test]
18
pub async fn async_round_trip_stackful() -> Result<()> {
19
test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT).await
20
}
21
22
#[tokio::test]
23
pub async fn async_round_trip_synchronous() -> Result<()> {
24
test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT)
25
.await
26
}
27
28
#[tokio::test]
29
pub async fn async_round_trip_wait() -> Result<()> {
30
test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT).await
31
}
32
33
#[tokio::test]
34
pub async fn async_round_trip_stackless_plus_stackless() -> Result<()> {
35
test_round_trip_composed(
36
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
37
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
38
)
39
.await
40
}
41
42
#[tokio::test]
43
pub async fn async_round_trip_stackless_plus_stackless_plus_stackless() -> Result<()> {
44
test_round_trip_composed_more(
45
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
46
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
47
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
48
)
49
.await
50
}
51
52
#[tokio::test]
53
async fn async_round_trip_synchronous_plus_stackless() -> Result<()> {
54
test_round_trip_composed(
55
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
56
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
57
)
58
.await
59
}
60
61
#[tokio::test]
62
async fn async_round_trip_stackless_plus_synchronous() -> Result<()> {
63
test_round_trip_composed(
64
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
65
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
66
)
67
.await
68
}
69
70
#[tokio::test]
71
async fn async_round_trip_synchronous_plus_synchronous() -> Result<()> {
72
test_round_trip_composed(
73
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
74
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
75
)
76
.await
77
}
78
79
#[tokio::test]
80
async fn async_round_trip_wait_plus_wait() -> Result<()> {
81
test_round_trip_composed(
82
test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
83
test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
84
)
85
.await
86
}
87
88
#[tokio::test]
89
async fn async_round_trip_synchronous_plus_wait() -> Result<()> {
90
test_round_trip_composed(
91
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
92
test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
93
)
94
.await
95
}
96
97
#[tokio::test]
98
async fn async_round_trip_wait_plus_synchronous() -> Result<()> {
99
test_round_trip_composed(
100
test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
101
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
102
)
103
.await
104
}
105
106
#[tokio::test]
107
async fn async_round_trip_stackless_plus_wait() -> Result<()> {
108
test_round_trip_composed(
109
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
110
test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
111
)
112
.await
113
}
114
115
#[tokio::test]
116
async fn async_round_trip_wait_plus_stackless() -> Result<()> {
117
test_round_trip_composed(
118
test_programs_artifacts::ASYNC_ROUND_TRIP_WAIT_COMPONENT,
119
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
120
)
121
.await
122
}
123
124
#[tokio::test]
125
async fn async_round_trip_stackful_plus_stackful() -> Result<()> {
126
test_round_trip_composed(
127
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
128
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
129
)
130
.await
131
}
132
133
#[tokio::test]
134
async fn async_round_trip_stackful_plus_stackless() -> Result<()> {
135
test_round_trip_composed(
136
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
137
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
138
)
139
.await
140
}
141
142
#[tokio::test]
143
async fn async_round_trip_stackless_plus_stackful() -> Result<()> {
144
test_round_trip_composed(
145
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
146
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
147
)
148
.await
149
}
150
151
#[tokio::test]
152
async fn async_round_trip_synchronous_plus_stackful() -> Result<()> {
153
test_round_trip_composed(
154
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
155
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
156
)
157
.await
158
}
159
160
#[tokio::test]
161
async fn async_round_trip_stackful_plus_synchronous() -> Result<()> {
162
test_round_trip_composed(
163
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKFUL_COMPONENT,
164
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
165
)
166
.await
167
}
168
169
#[tokio::test]
170
pub async fn async_round_trip_stackless() -> Result<()> {
171
test_round_trip_uncomposed(test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT).await
172
}
173
174
#[tokio::test]
175
pub async fn async_round_trip_stackless_joined() -> Result<()> {
176
tokio::join!(
177
async {
178
test_round_trip_uncomposed(
179
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
180
)
181
.await
182
.unwrap()
183
},
184
async {
185
test_round_trip_uncomposed(
186
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
187
)
188
.await
189
.unwrap()
190
},
191
);
192
193
Ok(())
194
}
195
196
#[tokio::test]
197
pub async fn async_round_trip_stackless_sync_import() -> Result<()> {
198
test_round_trip_uncomposed(
199
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_SYNC_IMPORT_COMPONENT,
200
)
201
.await
202
}
203
204
#[tokio::test]
205
pub async fn async_round_trip_stackless_recurse() -> Result<()> {
206
test_round_trip_recurse(
207
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
208
false,
209
)
210
.await
211
}
212
213
#[tokio::test]
214
pub async fn async_round_trip_stackless_recurse_trap() -> Result<()> {
215
let error = test_round_trip_recurse(
216
test_programs_artifacts::ASYNC_ROUND_TRIP_STACKLESS_COMPONENT,
217
true,
218
)
219
.await
220
.unwrap_err();
221
222
assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);
223
224
Ok(())
225
}
226
227
#[tokio::test]
228
pub async fn async_round_trip_synchronous_recurse() -> Result<()> {
229
test_round_trip_recurse(
230
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
231
false,
232
)
233
.await
234
}
235
236
#[tokio::test]
237
pub async fn async_round_trip_synchronous_recurse_trap() -> Result<()> {
238
let error = test_round_trip_recurse(
239
test_programs_artifacts::ASYNC_ROUND_TRIP_SYNCHRONOUS_COMPONENT,
240
true,
241
)
242
.await
243
.unwrap_err();
244
245
assert_eq!(error.downcast::<Trap>()?, Trap::CannotEnterComponent);
246
247
Ok(())
248
}
249
250
async fn test_round_trip_recurse(component: &str, same_instance: bool) -> Result<()> {
251
pub struct MyCtx {
252
wasi: WasiCtx,
253
table: ResourceTable,
254
instance: Option<Arc<Instance>>,
255
}
256
257
impl WasiView for MyCtx {
258
fn ctx(&mut self) -> WasiCtxView<'_> {
259
WasiCtxView {
260
ctx: &mut self.wasi,
261
table: &mut self.table,
262
}
263
}
264
}
265
266
impl HasData for MyCtx {
267
type Data<'a> = &'a mut Self;
268
}
269
270
impl component_async_tests::round_trip::bindings::local::local::baz::HostWithStore for MyCtx {
271
async fn foo<T: Send>(accessor: &Accessor<T, Self>, s: String) -> wasmtime::Result<String> {
272
if let Some(instance) = accessor.with(|mut access| access.get().instance.take()) {
273
run(accessor, &instance).await?;
274
accessor.with(|mut access| access.get().instance = Some(instance));
275
}
276
Ok(format!("{s} - entered host - exited host"))
277
}
278
}
279
280
impl component_async_tests::round_trip::bindings::local::local::baz::Host for MyCtx {}
281
282
async fn run<T: Send>(accessor: &Accessor<T, MyCtx>, instance: &Instance) -> Result<()> {
283
let round_trip = accessor.with(|mut access| {
284
component_async_tests::round_trip::bindings::RoundTrip::new(&mut access, &instance)
285
})?;
286
287
let input = "hello, world!";
288
let expected = "hello, world! - entered guest - entered host - exited host - exited guest";
289
290
let actual = round_trip
291
.local_local_baz()
292
.call_foo(accessor, input.into())
293
.await?;
294
295
assert_eq!(expected, actual);
296
297
Ok(())
298
}
299
300
let engine = Engine::new(&config())?;
301
302
let mut linker = Linker::new(&engine);
303
304
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
305
component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, MyCtx>(
306
&mut linker,
307
|ctx| ctx,
308
)?;
309
310
let component = make_component(&engine, &[component]).await?;
311
312
let mut store = Store::new(
313
&engine,
314
MyCtx {
315
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
316
table: ResourceTable::default(),
317
instance: None,
318
},
319
);
320
321
let instance = Arc::new(linker.instantiate_async(&mut store, &component).await?);
322
store.data_mut().instance = Some(instance.clone());
323
324
let instance = if same_instance {
325
instance
326
} else {
327
Arc::new(linker.instantiate_async(&mut store, &component).await?)
328
};
329
330
store
331
.run_concurrent(async move |accessor| {
332
run(&accessor.with_getter(|ctx| ctx), &instance).await
333
})
334
.await??;
335
336
store.assert_concurrent_state_empty();
337
338
Ok(())
339
}
340
341
pub async fn test_round_trip(
342
components: &[&str],
343
inputs_and_outputs: &[(&str, &str)],
344
) -> Result<()> {
345
let engine = Engine::new(&config())?;
346
347
let make_store = || {
348
Store::new(
349
&engine,
350
Ctx {
351
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
352
table: ResourceTable::default(),
353
continue_: false,
354
},
355
)
356
};
357
358
let component = make_component(&engine, components).await?;
359
360
// On miri, we only use one call style per test since they take so long to
361
// run. On non-miri, we use every call style for each test.
362
static CALL_STYLE_COUNTER: AtomicU32 = AtomicU32::new(0);
363
let call_style = CALL_STYLE_COUNTER.fetch_add(1, Relaxed) % 5;
364
365
// First, test the `wasmtime-wit-bindgen` static API:
366
{
367
let mut linker = Linker::new(&engine);
368
369
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
370
component_async_tests::round_trip::bindings::local::local::baz::add_to_linker::<_, Ctx>(
371
&mut linker,
372
|ctx| ctx,
373
)?;
374
375
let mut store = make_store();
376
377
let instance = linker.instantiate_async(&mut store, &component).await?;
378
let round_trip =
379
component_async_tests::round_trip::bindings::RoundTrip::new(&mut store, &instance)?;
380
381
if call_style == 0 || !cfg!(miri) {
382
// Run the test using `StoreContextMut::run_concurrent`:
383
store
384
.run_concurrent({
385
let inputs_and_outputs = inputs_and_outputs
386
.iter()
387
.map(|(a, b)| (String::from(*a), String::from(*b)))
388
.collect::<Vec<_>>();
389
390
async move |accessor| {
391
let mut futures = FuturesUnordered::new();
392
for (input, output) in &inputs_and_outputs {
393
let output = output.clone();
394
futures.push(
395
round_trip
396
.local_local_baz()
397
.call_foo(accessor, input.clone())
398
.map(move |v| v.map(move |v| (v, output)))
399
.boxed(),
400
);
401
}
402
403
while let Some((actual, expected)) = futures.try_next().await? {
404
assert_eq!(expected, actual);
405
}
406
407
Ok::<_, wasmtime::Error>(())
408
}
409
})
410
.await??;
411
412
store.assert_concurrent_state_empty();
413
}
414
415
if call_style == 1 || !cfg!(miri) {
416
// And again using `Instance::spawn`:
417
struct Task {
418
instance: Instance,
419
inputs_and_outputs: Vec<(String, String)>,
420
tx: oneshot::Sender<()>,
421
}
422
423
impl AccessorTask<Ctx, HasSelf<Ctx>> for Task {
424
async fn run(self, accessor: &Accessor<Ctx>) -> Result<()> {
425
let round_trip = accessor.with(|mut store| {
426
component_async_tests::round_trip::bindings::RoundTrip::new(
427
&mut store,
428
&self.instance,
429
)
430
})?;
431
432
let mut futures = FuturesUnordered::new();
433
for (input, output) in &self.inputs_and_outputs {
434
let output = output.clone();
435
futures.push(
436
round_trip
437
.local_local_baz()
438
.call_foo(accessor, input.clone())
439
.map(move |v| v.map(move |v| (v, output)))
440
.boxed(),
441
);
442
}
443
444
while let Some((actual, expected)) = futures.try_next().await? {
445
assert_eq!(expected, actual);
446
}
447
448
_ = self.tx.send(());
449
450
Ok(())
451
}
452
}
453
454
let (tx, rx) = oneshot::channel();
455
store.spawn(Task {
456
instance,
457
inputs_and_outputs: inputs_and_outputs
458
.iter()
459
.map(|(a, b)| (String::from(*a), String::from(*b)))
460
.collect::<Vec<_>>(),
461
tx,
462
});
463
464
store.run_concurrent(async |_| rx.await).await??;
465
466
store.assert_concurrent_state_empty();
467
}
468
469
if call_style == 2 || !cfg!(miri) {
470
// And again using `TypedFunc::call_async`-based bindings:
471
let round_trip =
472
component_async_tests::round_trip::non_concurrent_export_bindings::RoundTrip::new(
473
&mut store, &instance,
474
)?;
475
476
for (input, expected) in inputs_and_outputs {
477
assert_eq!(
478
*expected,
479
&round_trip
480
.local_local_baz()
481
.call_foo(&mut store, input)
482
.await?
483
);
484
}
485
486
store.assert_concurrent_state_empty();
487
}
488
}
489
490
// Now do it again using the dynamic API (except for WASI, where we stick with the static API):
491
{
492
let mut linker = Linker::new(&engine);
493
494
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
495
linker
496
.root()
497
.instance("local:local/baz")?
498
.func_new_concurrent("foo", |_, _, params, results| {
499
Box::pin(async move {
500
yield_times(5).await;
501
let Some(Val::String(s)) = params.into_iter().next() else {
502
unreachable!()
503
};
504
results[0] = Val::String(format!("{s} - entered host - exited host"));
505
Ok(())
506
})
507
})?;
508
509
let mut store = make_store();
510
511
let instance = linker.instantiate_async(&mut store, &component).await?;
512
let baz_instance = instance
513
.get_export_index(&mut store, None, "local:local/baz")
514
.ok_or_else(|| format_err!("can't find `local:local/baz` in instance"))?;
515
let foo_function = instance
516
.get_export_index(&mut store, Some(&baz_instance), "foo")
517
.ok_or_else(|| format_err!("can't find `foo` in instance"))?;
518
let foo_function = instance
519
.get_func(&mut store, foo_function)
520
.ok_or_else(|| format_err!("can't find `foo` in instance"))?;
521
522
if call_style == 3 || !cfg!(miri) {
523
store
524
.run_concurrent(async |store| {
525
// Start three concurrent calls and then join them all:
526
let mut futures = FuturesUnordered::new();
527
for (input, output) in inputs_and_outputs {
528
let output = (*output).to_owned();
529
futures.push(async move {
530
let mut results = vec![Val::Bool(false)];
531
foo_function
532
.call_concurrent(
533
store,
534
&[Val::String((*input).to_owned())],
535
&mut results,
536
)
537
.await?;
538
wasmtime::error::Ok((results, output))
539
});
540
}
541
542
while let Some((actual, expected)) = futures.try_next().await? {
543
let Some(Val::String(actual)) = actual.into_iter().next() else {
544
unreachable!()
545
};
546
assert_eq!(expected, actual);
547
}
548
wasmtime::error::Ok(())
549
})
550
.await??;
551
552
store.assert_concurrent_state_empty();
553
}
554
555
if call_style == 4 || !cfg!(miri) {
556
// Now do it again using `Func::call_async`:
557
for (input, expected) in inputs_and_outputs {
558
let mut results = [Val::Bool(false)];
559
foo_function
560
.call_async(
561
&mut store,
562
&[Val::String((*input).to_owned())],
563
&mut results,
564
)
565
.await?;
566
let Val::String(actual) = &results[0] else {
567
unreachable!()
568
};
569
assert_eq!(*expected, actual);
570
}
571
572
store.assert_concurrent_state_empty();
573
}
574
}
575
576
Ok(())
577
}
578
579
pub async fn test_round_trip_uncomposed(component: &str) -> Result<()> {
580
test_round_trip(
581
&[component],
582
&[
583
(
584
"hello, world!",
585
"hello, world! - entered guest - entered host - exited host - exited guest",
586
),
587
(
588
"¡hola, mundo!",
589
"¡hola, mundo! - entered guest - entered host - exited host - exited guest",
590
),
591
(
592
"hi y'all!",
593
"hi y'all! - entered guest - entered host - exited host - exited guest",
594
),
595
],
596
)
597
.await
598
}
599
600
pub async fn test_round_trip_composed(a: &str, b: &str) -> Result<()> {
601
test_round_trip(
602
&[a, b],
603
&[
604
(
605
"hello, world!",
606
"hello, world! - entered guest - entered guest - entered host \
607
- exited host - exited guest - exited guest",
608
),
609
(
610
"¡hola, mundo!",
611
"¡hola, mundo! - entered guest - entered guest - entered host \
612
- exited host - exited guest - exited guest",
613
),
614
(
615
"hi y'all!",
616
"hi y'all! - entered guest - entered guest - entered host \
617
- exited host - exited guest - exited guest",
618
),
619
],
620
)
621
.await
622
}
623
624
pub async fn test_round_trip_composed_more(a: &str, b: &str, c: &str) -> Result<()> {
625
test_round_trip(
626
&[a, b, c],
627
&[
628
(
629
"hello, world!",
630
"hello, world! - entered guest - entered guest - entered guest - entered host \
631
- exited host - exited guest - exited guest - exited guest",
632
),
633
(
634
"¡hola, mundo!",
635
"¡hola, mundo! - entered guest - entered guest - entered guest - entered host \
636
- exited host - exited guest - exited guest - exited guest",
637
),
638
(
639
"hi y'all!",
640
"hi y'all! - entered guest - entered guest - entered guest - entered host \
641
- exited host - exited guest - exited guest - exited guest",
642
),
643
],
644
)
645
.await
646
}
647
648