use std::future::Future;
use std::sync::Arc;
use super::Vm;
#[derive(Clone)]
pub struct AsyncBuiltinCtx {
child: Arc<parking_lot::Mutex<Vm>>,
}
impl AsyncBuiltinCtx {
fn new(vm: Vm) -> Self {
Self {
child: Arc::new(parking_lot::Mutex::new(vm)),
}
}
pub fn from_vm(vm: Vm) -> Self {
Self::new(vm)
}
#[cfg(test)]
pub fn for_test(vm: Vm) -> Self {
Self::new(vm)
}
pub fn child_vm(&self) -> Vm {
self.child.lock().child_vm()
}
pub(crate) fn pool_registry(&self) -> Arc<crate::stdlib::pool::PoolRegistry> {
self.child.lock().pool_registry.clone()
}
pub fn child_ctx(&self) -> Self {
Self::new(self.child_vm())
}
pub fn forward_output(&self, text: &str) {
if text.is_empty() {
return;
}
self.child.lock().append_output(text);
}
}
pub(crate) fn run_async_builtin_with<F, M>(
child: Vm,
make_fut: M,
) -> impl Future<Output = (F::Output, String)>
where
F: Future + Send,
M: FnOnce(AsyncBuiltinCtx) -> F,
{
let ctx = AsyncBuiltinCtx::new(child);
let registry = ctx.pool_registry();
let sink = Arc::clone(&ctx.child);
let fut = make_fut(ctx);
async move {
let output = crate::stdlib::pool::with_pool_registry_scope(registry, fut).await;
let captured = sink.lock().take_output();
(output, captured)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Vm;
#[tokio::test]
async fn explicit_ctx_mints_child_and_captures_forwarded_output() {
let (present, captured) = run_async_builtin_with(Vm::new(), |ctx| async move {
let _child = ctx.child_vm();
ctx.forward_output("hello ");
ctx.forward_output("world");
true
})
.await;
assert!(present);
assert_eq!(captured, "hello world");
}
#[tokio::test]
async fn child_context_has_independent_output_buffer() {
let (_result, captured) = run_async_builtin_with(Vm::new(), |ctx| async move {
let child = ctx.child_ctx();
child.forward_output("child");
ctx.forward_output("parent");
})
.await;
assert_eq!(captured, "parent");
}
#[tokio::test]
async fn cancelled_scope_strands_nothing() {
use std::future::pending;
let never = run_async_builtin_with(Vm::new(), |_ctx| pending::<()>());
drop(never);
}
}