sandkiste_lua 0.4.0

Sandbox for Lua scripts
Documentation
use sandkiste::prelude::*;

#[cfg(feature = "Lua5_3")]
use super::v5_3::*;

#[cfg(feature = "Lua5_4")]
use super::v5_4::*;

#[test]
fn test_memory_limit() {
    let m = LuaMachine::new();
    m.set_memory_limit(1024 * 1024);
    const CODE: &'static str = "t = {}; for i = 1, 1024*1024 do t[i] = true end";
    let err = m.compile(None, CODE).unwrap().call([]).unwrap_err();
    assert_eq!(err.kind, MachineErrorKind::Memory);
}

#[test]
fn test_execution_limit() {
    let m = LuaMachine::new();
    m.set_execution_limit(16 * 1024);
    const CODE: &'static str = "for i = 1, 1024*1024 do end";
    let err = m.compile(None, CODE).unwrap().call([]).unwrap_err();
    assert_eq!(err.kind, MachineErrorKind::ExecLimit);
}

#[test]
fn test_stdlib() {
    let m = LuaMachine::new();
    m.load_stdlib().unwrap();
    const CODE: &'static str = "os.execute('echo'); return math.sqrt(4.0)";
    let d = m.compile(None, CODE).unwrap().call_1ret([]).unwrap();
    assert_eq!(d.try_as_f64().unwrap(), 2.0);
}

fn assert_std_sealed<'a>(m: &LuaMachine<'a>) {
    const CODE1: &'static str = "os.execute('echo')";
    let err = m.compile(None, CODE1).unwrap().call([]).unwrap_err();
    assert_eq!(err.kind, MachineErrorKind::Runtime);
    const CODE2: &'static str = "return math.sqrt(9.0)";
    let d = m.compile(None, CODE2).unwrap().call_1ret([]).unwrap();
    assert_eq!(d.try_as_f64().unwrap(), 3.0);
    const CODE3: &'static str = "\
        local expected = {
            _G = true,\n\
            _VERSION = true,\n\
            assert = true,\n\
            bit32 = 'optional', -- deprecated in Lua 5.3 and not in Lua 5.4\n\
            collectgarbage = true,\n\
            error = true,\n\
            getmetatable = true,\n\
            ipairs = true,\n\
            next = true,\n\
            pairs = true,\n\
            rawequal = true,\n\
            rawget = true,\n\
            rawlen = true,\n\
            rawset = true,\n\
            select = true,\n\
            setmetatable = true,\n\
            tonumber = true,\n\
            tostring = true,\n\
            type = true,\n\
            warn = 'optional', -- not in Lua 5.3\n\
            math = true,\n\
            string = true,\n\
            table = true,\n\
            utf8 = true,\n\
        }\n\
        for key in pairs(_G) do\n\
            if not expected[key] then error('unexpected variable set: ' .. tostring(key)) end\n\
            expected[key] = nil\n\
        end\n\
        for k, v in pairs(expected) do\n\
            if v == 'optional' then\n\
                expected[k] = nil\n\
            end\n\
        end\n\
        local found = next(expected)\n\
        if found ~= nil then \n\
            error('variable missing: ' .. tostring(found))\n\
        end\n\
        return 'noerror'
        ";
    let d = m.compile(None, CODE3).unwrap().call_1ret([]).unwrap();
    assert_eq!(d.try_as_str().unwrap(), "noerror");
}

#[test]
fn test_stdlib_sealed() {
    let m = LuaMachine::new();
    m.load_stdlib_sealed().unwrap();
    assert_std_sealed(&m);
}

#[test]
fn test_seal() {
    let m = LuaMachine::new();
    m.load_stdlib().unwrap();
    m.seal().unwrap();
    assert_std_sealed(&m);
}

#[test]
fn test_error_reporting() {
    let m = LuaMachine::new();
    m.load_stdlib().unwrap();
    let err: MachineError = m
        .compile(Some("testchunk".to_string()), "\n\nerror('testerror')")
        .unwrap()
        .call([])
        .unwrap_err();
    assert_eq!(err.chunk_name.as_deref(), Some("testchunk"));
    assert_eq!(err.line, Some(3));
}

#[test]
fn test_callbacks() {
    use std::cell::RefCell;
    let output = RefCell::new(String::new());
    let m = LuaMachine::new();
    m.load_stdlib_sealed().unwrap();
    let print = m
        .callback_1arg(|s| {
            let s = s.try_as_str()?;
            output.borrow_mut().push_str(s);
            Ok([])
        })
        .unwrap();
    let doubleadd = m
        .callback_2arg(|a, b| {
            let a = a.try_as_i32()?;
            let b = b.try_as_i32()?;
            Ok([LuaDatum::from(2 * (a + b))])
        })
        .unwrap();
    m.compile(None, "print, doubleadd = ...")
        .unwrap()
        .call([print, doubleadd])
        .unwrap();
    const CODE: &'static str = "print('Hello '); print('World'); print(tostring(doubleadd(2, 5)))";
    m.compile(None, CODE).unwrap().call([]).unwrap();
    drop(m);
    let output = output.into_inner();
    assert_eq!(output, "Hello World14");
}

#[test]
fn test_array_read() {
    let m = LuaMachine::new();
    const CODE: &'static str = "return {12, 'thirteen'}";
    let d = m.compile(None, CODE).unwrap().call_1ret([]).unwrap();
    assert!(d.try_array().is_ok());
    assert_eq!(d.array_len().unwrap(), 2);
    assert_eq!(d.array_get(0).unwrap().try_as_i32().unwrap(), 12);
    assert_eq!(d.array_get(1).unwrap().try_as_str().unwrap(), "thirteen");
    assert!(d.array_to_vec(1).is_err());
    let v = d.array_to_vec(2).unwrap();
    assert_eq!(v.len(), 2);
    assert_eq!(v[0].try_as_i32().unwrap(), 12);
    assert_eq!(v[1].try_as_str().unwrap(), "thirteen");
    let mut iter = d.array_to_iter().unwrap();
    assert_eq!(iter.next().unwrap().unwrap().try_as_i32().unwrap(), 12);
    assert_eq!(
        iter.next().unwrap().unwrap().try_as_str().unwrap(),
        "thirteen"
    );
    assert!(iter.next().is_none());
}

#[test]
fn test_array_write() {
    let m = LuaMachine::new();
    let d = m
        .new_array([LuaDatum::from(14), false.into(), "fifteen".into()])
        .unwrap();
    d.array_set(1, true.into()).unwrap();
    d.array_push(LuaDatum::from(0.5)).unwrap();
    d.array_push(LuaDatum::from(1.5)).unwrap();
    d.array_push(LuaDatum::from(2.5)).unwrap();
    d.array_truncate(4).unwrap();
    const CODE: &'static str = "\
        local t = ...\n\
        if\n\
            #t == 4 and\n\
            t[1] == 14 and\n\
            t[2] == true and\n\
            t[3] == 'fifteen' and\n\
            t[4] == 0.5\n\
        then\n\
            return 'magictoken'\n\
        end\n\
        ";
    assert_eq!(
        m.compile(None, CODE)
            .unwrap()
            .call_1ret([d])
            .unwrap()
            .try_as_str()
            .unwrap(),
        "magictoken"
    );
}

#[test]
fn test_string_map_read() {
    let m = LuaMachine::new();
    const CODE: &'static str = "return {a = 5, b = 20}";
    let d = m.compile(None, CODE).unwrap().call_1ret([]).unwrap();
    assert!(d.try_string_map().is_ok());
    assert_eq!(d.string_map_get("a").unwrap().try_as_i32().unwrap(), 5);
    assert_eq!(d.string_map_get("b").unwrap().try_as_i32().unwrap(), 20);
    match d.string_map_get("c") {
        Ok(d) => assert!(d.is_null()),
        Err(_) => (),
    };
}

#[test]
fn test_string_map_write() {
    use std::collections::HashMap;
    let mut hash_map: HashMap<&'static str, LuaDatum> = HashMap::new();
    hash_map.insert("a", LuaDatum::from(7));
    hash_map.insert("b", LuaDatum::from(11));
    let m = LuaMachine::new();
    let d = m.new_string_map(hash_map).unwrap();
    const CODE: &'static str = "local t = ...; return t.a * t.b";
    let r = m.compile(None, CODE).unwrap().call_1ret([d]).unwrap();
    assert_eq!(r.try_as_i32().unwrap(), 77);
}

#[test]
fn test_globals() {
    let m = LuaMachine::new();
    m.set("triplenine", LuaDatum::from(999)).unwrap();
    m.set("minusone", LuaDatum::from(-1)).unwrap();
    eprintln!("DEBUG: {:?}", m.get("triplenine"));
    assert_eq!(m.get("triplenine").unwrap().try_as_i32().unwrap(), 999);
    assert_eq!(m.get("minusone").unwrap().try_as_i32().unwrap(), -1);
}

#[test]
fn test_load_module() {
    let m = LuaMachine::new();
    let mymod = m.module("mymod").unwrap();
    mymod.set("pi_approx", 3.14.into()).unwrap();
    mymod
        .set(
            "double",
            m.callback_1arg(|x| Ok([(x.try_as_f64()? * 2.0).into()]))
                .unwrap(),
        )
        .unwrap();
    const CODE: &'static str =
        "if mymod.pi_approx > 3 and mymod.pi_approx < 4 then return 'pimatch' end";
    let d = m.compile(None, CODE).unwrap().call_1ret([]).unwrap();
    assert_eq!(d.try_as_str().unwrap(), "pimatch");
}