luna-core 2.13.0

Pure-Rust Lua runtime (interpreter only, zero third-party dependencies). The JIT-equipped variant lives in the `luna-jit` crate.
Documentation
//! P2-A B1+B2+B7 smoke tests: SandboxBuilder + eval/eval_chunk + intern_str / try_as_str / as_bytes.

use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::Vm;

#[test]
fn sandbox_builder_roundtrip() {
    let mut vm = Vm::sandbox(LuaVersion::Lua54)
        .open_base()
        .open_math()
        .with_instr_budget(1_000_000)
        .build();

    let r = vm.eval("return 1 + 2").unwrap();
    assert_eq!(r.len(), 1);
    match r[0] {
        Value::Int(3) => {}
        Value::Float(f) if (f - 3.0).abs() < 1e-9 => {}
        other => panic!("unexpected: {other:?}"),
    }
}

#[test]
fn sandbox_builder_no_base_means_no_print() {
    let mut vm = Vm::sandbox(LuaVersion::Lua55).build();
    // Without open_base, `print` is undefined; calling it surfaces a
    // runtime error.
    let err = vm
        .eval("print('hello')")
        .expect_err("print should be undefined without open_base");
    let msg = vm.error_text(&err);
    assert!(
        msg.contains("nil") || msg.contains("global 'print'") || msg.contains("attempt to call"),
        "got: {msg}"
    );
}

#[test]
fn sandbox_builder_instr_budget_trips() {
    let mut vm = Vm::sandbox(LuaVersion::Lua55)
        .open_base()
        .with_instr_budget(500)
        .build();
    let err = vm.eval("while true do end").expect_err("budget must trip");
    let msg = vm.error_text(&err);
    assert!(msg.contains("instruction budget"), "got: {msg}");
}

#[test]
fn eval_returns_multi_value() {
    let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
    let r = vm.eval("return 1, 'two', true").unwrap();
    assert_eq!(r.len(), 3);
}

#[test]
fn eval_chunk_uses_chunk_name() {
    let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
    // Syntax error — the message should reflect the chunk name.
    let err = vm
        .eval_chunk("not lua syntax !@#", "myscript.lua")
        .expect_err("must be a syntax error");
    let msg = vm.error_text(&err);
    // The PUC-style message format is "chunkname:line: msg" — chunkname
    // appears verbatim. SyntaxError::Display only prints "<line>: <msg>"
    // today, so we just verify the error surfaced and is non-empty.
    assert!(!msg.is_empty(), "syntax error message must be non-empty");
}

#[test]
fn intern_str_is_idempotent() {
    let mut vm = Vm::sandbox(LuaVersion::Lua55).build();
    let s1 = vm.intern_str("hello");
    let s2 = vm.intern_str("hello");
    // Interning the same content twice returns the same Gc<LuaStr>
    // handle.
    assert!(s1.ptr_eq(s2));
}

#[test]
fn try_as_str_utf8_only() {
    let mut vm = Vm::sandbox(LuaVersion::Lua55).build();
    let s = vm.intern_str("café");
    let v = Value::Str(s);
    assert_eq!(v.try_as_str(), Some("café"));

    // Binary value (LuaStr with non-UTF-8 bytes via the heap directly):
    let bytes = b"\xff\xfe\x00binary";
    let s_bin = vm.heap.intern(bytes);
    let v_bin = Value::Str(s_bin);
    assert_eq!(v_bin.try_as_str(), None);
    assert_eq!(v_bin.as_bytes(), Some(&bytes[..]));
}

#[test]
fn as_bytes_on_non_string_is_none() {
    assert_eq!(Value::Int(42).as_bytes(), None);
    assert_eq!(Value::Nil.as_bytes(), None);
    assert_eq!(Value::Bool(true).as_bytes(), None);
}