#![cfg(feature = "send")]
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering};
use std::thread;
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::{LuaUserdata, SendVm, UserdataMethods};
#[test]
fn send_vm_is_send() {
fn assert_send<T: Send>() {}
assert_send::<SendVm>();
}
#[test]
fn send_vm_eval_basic() {
let vm = SendVm::new(LuaVersion::Lua55);
vm.open_base();
vm.open_math();
let r = vm.eval("return 1+2").expect("eval 1+2");
assert_eq!(r.len(), 1);
assert!(matches!(r[0], Value::Int(3)));
}
#[test]
fn send_vm_holds_across_thread_move() {
let vm = SendVm::new(LuaVersion::Lua55);
vm.open_base();
let handle = thread::spawn(move || {
vm.set_global("x", 41_i64).unwrap();
let r = vm.eval("return x + 1").unwrap();
assert!(matches!(r[0], Value::Int(42)));
});
handle.join().expect("child thread panicked");
}
#[test]
fn send_vm_100_concurrent_threads() {
let vm = SendVm::new(LuaVersion::Lua55);
vm.open_base();
vm.open_math();
vm.set_global("counter", 0_i64).unwrap();
let total_iters = Arc::new(AtomicI64::new(0));
let mut handles = Vec::with_capacity(100);
for tid in 0..100 {
let vm = vm.clone();
let iters = Arc::clone(&total_iters);
handles.push(thread::spawn(move || {
for _ in 0..10 {
let r = vm.eval("return 1+1").expect("eval ok");
assert!(matches!(r[0], Value::Int(2)));
iters.fetch_add(1, Ordering::Relaxed);
}
let script = format!("counter = counter + {}", tid);
vm.eval(&script).expect("counter mutate");
}));
}
for h in handles {
h.join().expect("worker panicked");
}
assert_eq!(total_iters.load(Ordering::Relaxed), 1000);
let r = vm.eval("return counter").unwrap();
assert!(matches!(r[0], Value::Int(4950)));
}
#[derive(Debug)]
struct Counter {
value: i64,
}
impl LuaUserdata for Counter {
fn type_name() -> &'static str {
"Counter"
}
fn add_methods<M: UserdataMethods<Self>>(m: &mut M) {
m.add_method("get", |_vm, this, ()| Ok::<_, _>(this.value));
m.add_method_mut("incr", |_vm, this, (by,): (i64,)| {
this.value += by;
Ok::<_, _>(())
});
}
}
#[test]
fn send_vm_set_userdata_through_lock() {
let vm = SendVm::new(LuaVersion::Lua55);
vm.open_base();
vm.set_userdata("c", Counter { value: 10 }).unwrap();
vm.eval("c:incr(5); c:incr(2)").unwrap();
let r = vm.eval("return c:get()").unwrap();
assert!(matches!(r[0], Value::Int(17)));
}
#[test]
fn send_vm_pin_unpin_through_lock() {
let vm = SendVm::new(LuaVersion::Lua55);
let t1 = vm.pin_host(Value::Int(123));
let v = vm.read_host(t1).expect("ticket still live");
assert!(matches!(v, Value::Int(123)));
vm.unpin(t1).expect("unpin");
assert!(vm.read_host(t1).is_none(), "stale after unpin");
let t2 = vm.pin_host(Value::Int(456));
assert!(vm.read_host(t1).is_none());
assert!(matches!(vm.read_host(t2), Some(Value::Int(456))));
}
#[test]
fn send_vm_pin_across_clones() {
let vm = SendVm::new(LuaVersion::Lua55);
let vm2 = vm.clone();
let t1 = vm.pin_host(Value::Int(1));
let h = thread::spawn(move || vm2.pin_host(Value::Int(2)));
let t2 = h.join().expect("worker");
assert_ne!(t1.idx(), t2.idx());
assert!(matches!(vm.read_host(t1), Some(Value::Int(1))));
assert!(matches!(vm.read_host(t2), Some(Value::Int(2))));
}
#[test]
fn send_vm_interp_only_loop_runs_correctly() {
let vm = SendVm::new(LuaVersion::Lua55);
vm.open_base();
vm.open_math();
let r = vm
.eval(
r#"
local s = 0
for i = 1, 1000 do s = s + i end
return s
"#,
)
.expect("sum 1..1000");
assert!(matches!(r[0], Value::Int(500500)));
}