use luna_core::version::LuaVersion;
use luna_core::vm::Vm;
#[test]
fn coroutine_hook_isolation_does_not_leak_to_main() {
let mut vm = Vm::new(LuaVersion::Lua55);
let r = vm
.eval(
"
local main_hook_fires = 0
local co_hook_fires = 0
local co = coroutine.create(function()
debug.sethook(function() co_hook_fires = co_hook_fires + 1 end, 'l')
local s = 0
for i = 1, 5 do s = s + i end
debug.sethook()
return s
end)
local _ok, result = coroutine.resume(co)
-- After coroutine finished, run some main-thread Lua;
-- main_hook_fires must stay 0 because the coroutine's
-- sethook applied only to that coroutine's state.
local sum = 0
for i = 1, 10 do sum = sum + i end
return co_hook_fires, main_hook_fires, sum, result
",
)
.expect("coroutine hook isolation must not panic");
let main_fires = match &r[1] {
luna_core::runtime::Value::Int(i) => *i,
other => panic!("expected Int for main_hook_fires, got {other:?}"),
};
let sum = match &r[2] {
luna_core::runtime::Value::Int(i) => *i,
other => panic!("expected Int for sum, got {other:?}"),
};
assert_eq!(
main_fires, 0,
"main thread hook fires must remain 0 (got {main_fires})"
);
assert_eq!(
sum, 55,
"main thread arithmetic must still work after coroutine"
);
}
#[test]
fn coroutine_yield_under_count_hook_preserves_counter() {
let mut vm = Vm::new(LuaVersion::Lua55);
let r = vm
.eval(
"
local hook_fires = 0
local co = coroutine.create(function()
-- PUC 5.5: 'l' = line, 'c' = call, 'r' = return; count is
-- additive via the integer arg. Empty mask + count > 0 is
-- a sub-set of PUC behavior some impls treat as 'count only';
-- to keep this test portable across the audit scope, use
-- explicit 'l' mask + count to ensure firing.
debug.sethook(function() hook_fires = hook_fires + 1 end, 'l', 5)
for i = 1, 100 do
coroutine.yield(i)
end
debug.sethook()
end)
-- Resume 30 times; hook fires on lines / every 5 instructions
-- inside the coroutine. Across yield boundaries the hook state
-- must continue from where it left off, not reset.
for _ = 1, 30 do coroutine.resume(co) end
return hook_fires
",
)
.expect("yield under count hook must not panic");
let fires = match &r[0] {
luna_core::runtime::Value::Int(i) => *i,
other => panic!("expected Int, got {other:?}"),
};
assert!(
fires >= 1,
"count hook never fired across 30 yield/resume cycles (fires={fires})"
);
}
#[test]
fn coroutine_generic_for_closeable_iterator_runs_close() {
let mut vm = Vm::new(LuaVersion::Lua55);
let r = vm
.eval(
"
local close_count = 0
local function make_iter()
local i = 0
local state = setmetatable({}, { __close = function() close_count = close_count + 1 end })
return function() i = i + 1; if i <= 3 then return i end end, state, nil, state
end
local co = coroutine.create(function()
for v in make_iter() do
coroutine.yield(v)
end
end)
for _ = 1, 5 do coroutine.resume(co) end
return close_count
",
)
.expect("coroutine + closeable iterator must not panic");
let count = match &r[0] {
luna_core::runtime::Value::Int(i) => *i,
other => panic!("expected Int, got {other:?}"),
};
assert!(
count >= 1,
"expected __close to run at least once across coroutine generic-for; got {count}"
);
}
#[test]
fn coroutine_hook_installed_from_main_persists_across_yield_resume() {
let mut vm = Vm::new(LuaVersion::Lua55);
let r = vm
.eval(
"
local main_fires = 0
-- Install on main thread (no thread arg) before driving the
-- coroutine. Pre-fix this hit is_current_thread(None) on the
-- main thread (current=None, target=None) — the (None,None)
-- arm matched — so it always worked from main. Pin it as a
-- regression in case the future predicate refactor regresses.
debug.sethook(function() main_fires = main_fires + 1 end, 'l', 1)
local co = coroutine.create(function()
for i = 1, 5 do coroutine.yield(i) end
end)
local sum = 0
for _ = 1, 6 do
coroutine.resume(co)
sum = sum + 1 -- main-thread line that should fire the hook
end
debug.sethook()
return main_fires, sum
",
)
.expect("hook installed from main + coroutine drive must not panic");
let fires = match &r[0] {
luna_core::runtime::Value::Int(i) => *i,
other => panic!("expected Int for main_fires, got {other:?}"),
};
let sum = match &r[1] {
luna_core::runtime::Value::Int(i) => *i,
other => panic!("expected Int for sum, got {other:?}"),
};
assert_eq!(sum, 6, "main-thread loop must complete (got {sum})");
assert!(
fires >= 1,
"main-thread line hook must fire at least once across yield/resume cycles (fires={fires})"
);
}