mod common;
use common::deep_eq;
use serpent_serializer::load::LoadError;
use serpent_serializer::value::{Key, Table, Value};
use serpent_serializer::{block, dump, line, load, LoadOptions};
#[test]
fn load_number() {
let src = line(&Value::Number(10.0)).unwrap();
let v = load(&src, &LoadOptions::default()).unwrap();
assert!(matches!(v, Value::Number(n) if n == 10.0));
}
#[test]
fn load_boolean() {
let src = line(&Value::Bool(true)).unwrap();
let v = load(&src, &LoadOptions::default()).unwrap();
assert!(matches!(v, Value::Bool(true)));
}
#[test]
fn load_pretty_table() {
let t = Table::new();
t.push(Value::Number(3.0));
t.push(Value::Number(4.0));
let src = line(&Value::Table(t)).unwrap();
let v = load(&src, &LoadOptions::default()).unwrap();
let Value::Table(back) = v else {
panic!("not a table")
};
let e = back.entries();
assert_eq!(e.len(), 2);
assert!(matches!(e[0].1, Value::Number(n) if n == 3.0));
assert!(matches!(e[1].1, Value::Number(n) if n == 4.0));
}
#[test]
fn load_dumped_table() {
let t = Table::new();
t.push(Value::Number(3.0));
t.push(Value::Number(4.0));
let src = dump(&Value::Table(t)).unwrap();
let v = load(&src, &LoadOptions::default()).unwrap();
let Value::Table(back) = v else {
panic!("not a table")
};
assert_eq!(back.border(), 2);
}
#[test]
fn safe_rejects_calls() {
let r = load("{a = math.random()}", &LoadOptions::default());
assert_eq!(r.unwrap_err(), LoadError::UnsafeCall);
}
#[test]
fn unsafe_allows_calls() {
let opts = LoadOptions { safe: Some(false) };
let r = load("{a = math.random()}", &opts);
assert!(r.is_ok());
}
#[test]
fn safe_rejects_do_error() {
let r = load("do error('not allowed') end", &LoadOptions::default());
assert_eq!(r.unwrap_err(), LoadError::UnsafeCall);
}
#[test]
fn rejects_trailing_call_after_expression() {
let r = load("{a=1}; os.execute(\"x\")", &LoadOptions::default());
assert!(r.is_err(), "expected error, got {r:?}");
}
#[test]
fn rejects_do_block_without_end() {
let r = load("do local _={}; return _", &LoadOptions::default());
assert!(r.is_err(), "expected error, got {r:?}");
}
#[test]
fn sandbox_allows_global_assignment() {
for src in [
"do print = error end",
"do _G.print = error end",
"do _G._G.print = error end",
"do _G = nil _G.print = error end",
] {
let r = load(src, &LoadOptions::default());
assert!(r.is_ok(), "expected ok for {src}, got {r:?}");
}
}
#[test]
fn block_round_trips() {
let t = Table::new();
t.set(Key::Str(b"a".to_vec()), Value::Str(b"a".to_vec()));
t.set(Key::Str(b"b".to_vec()), Value::Str(b"b".to_vec()));
t.push(Value::Number(1.0));
t.push(Value::Number(2.0));
let src = block(&Value::Table(t.clone())).unwrap();
let back = load(&src, &LoadOptions::default()).unwrap();
assert!(deep_eq(&Value::Table(t), &back));
}
#[test]
fn leading_comment_in_table_parses() {
let v = load("{--[[x]] 1, 2}", &LoadOptions::default()).unwrap();
let Value::Table(t) = v else { panic!() };
let e = t.entries();
assert_eq!(e.len(), 2);
assert!(matches!(e[0].1, Value::Number(n) if n == 1.0));
assert!(matches!(e[1].1, Value::Number(n) if n == 2.0));
}
#[test]
fn empty_table_with_only_comment_parses() {
let v = load("{--[[x]]}", &LoadOptions::default()).unwrap();
let Value::Table(t) = v else { panic!() };
assert!(t.is_empty());
}
#[test]
fn string_escaping_round_trips() {
let t = Table::new();
t.set(Key::Str(b"str".to_vec()), Value::Str(b"\"\n'\\\0".to_vec()));
let src = block(&Value::Table(t.clone())).unwrap();
let back = load(&src, &LoadOptions::default()).unwrap();
let Value::Table(bt) = back else {
panic!("not table")
};
let got = bt.get(&Key::Str(b"str".to_vec())).unwrap();
assert!(matches!(got, Value::Str(ref s) if s == b"\"\n'\\\0"));
}