mod common;
use common::norm_addr;
use serpent_serializer::options::Options;
use serpent_serializer::quote::quote;
use serpent_serializer::value::{Func, Key, Table, Value};
use serpent_serializer::{line, line_with, load, serialize, LoadOptions};
fn line_norm(t: &Table) -> String {
norm_addr(&line(&Value::Table(t.clone())).unwrap())
}
#[test]
fn escape_control_bytes() {
assert_eq!(quote(&[26]), "\"\\026\"");
assert_eq!(quote(&[13]), "\"\\r\"");
assert_eq!(quote(&[0]), "\"\\0\"");
assert_eq!(quote(&[9]), "\"\t\"");
}
#[test]
fn nul_before_digit_uses_padded_form() {
assert_eq!(quote(&[0, b'5']), "\"\\0005\"");
assert_eq!(quote(&[0, 0]), "\"\\0\\0\"");
}
#[test]
fn high_bytes_escape_to_decimal() {
assert_eq!(quote(&[128]), "\"\\128\"");
assert_eq!(quote(&[200]), "\"\\200\"");
assert_eq!(quote(&[255]), "\"\\255\"");
}
#[test]
fn high_and_control_bytes_round_trip() {
let blob = vec![0u8, 10, 13, 26, 34, 92, 200, 255];
let t = Table::new();
t.set(Key::Str(b"s".to_vec()), Value::Str(blob.clone()));
let src = line(&Value::Table(t)).unwrap();
let back = load(&src, &LoadOptions::default()).unwrap();
let Value::Table(bt) = back else { panic!() };
assert_eq!(bt.get(&Key::Str(b"s".to_vec())), Some(Value::Str(blob)));
}
#[test]
fn single_high_bytes_round_trip() {
for byte in [128u8, 200, 255] {
let t = Table::new();
t.set(Key::Str(b"s".to_vec()), Value::Str(vec![byte]));
let src = line(&Value::Table(t)).unwrap();
let back = load(&src, &LoadOptions::default()).unwrap();
let Value::Table(bt) = back else { panic!() };
assert_eq!(
bt.get(&Key::Str(b"s".to_vec())),
Some(Value::Str(vec![byte])),
"byte {byte}"
);
}
}
#[test]
fn bytecode_blob_does_not_panic() {
let code: Vec<u8> = (0u8..=255).collect();
let f = Value::Function(Func {
id: 1,
bytecode: Some(code),
});
let t = Table::new();
t.set(Key::Str(b"f".to_vec()), f);
let got = line(&Value::Table(t)).unwrap();
assert!(got.contains("(loadstring or load)("), "{got}");
}
#[test]
fn maxlength_breaks_partway_strings() {
let t = Table::new();
for s in ["aaa", "bbb", "ccc", "ddd"] {
t.push(Value::Str(s.as_bytes().to_vec()));
}
let opts = Options {
maxlength: Some(8),
..Options::line()
};
assert_eq!(
norm_addr(&line_with(&Value::Table(t), opts).unwrap()),
"{\"aaa\", \"bbb\"} --[[table: 0xADDR]]"
);
}
#[test]
fn maxlength_breaks_partway_numbers() {
let t = Table::new();
for n in 1..=6 {
t.push(Value::Number(n as f64));
}
let opts = Options {
maxlength: Some(4),
..Options::line()
};
assert_eq!(
norm_addr(&line_with(&Value::Table(t), opts).unwrap()),
"{1, 2, 3, 4, 5} --[[table: 0xADDR]]"
);
}
#[test]
fn special_number_key_comment_inside_brackets() {
let t = Table::new();
t.set(Key::Number(f64::INFINITY), Value::Number(f64::NEG_INFINITY));
assert_eq!(
line_norm(&t),
"{[1/0 --[[math.huge]]] = -1/0 --[[-math.huge]]} --[[table: 0xADDR]]"
);
}
#[test]
fn boolean_keys_sort_false_before_true() {
let t = Table::new();
t.set(Key::Bool(true), Value::Str(b"t".to_vec()));
t.set(Key::Bool(false), Value::Str(b"f".to_vec()));
assert_eq!(
line_norm(&t),
"{[false] = \"f\", [true] = \"t\"} --[[table: 0xADDR]]"
);
}
#[test]
fn empty_string_key_uses_brackets() {
let t = Table::new();
t.set(Key::Str(b"".to_vec()), Value::Str(b"v".to_vec()));
assert_eq!(line_norm(&t), "{[\"\"] = \"v\"} --[[table: 0xADDR]]");
}
#[test]
fn comment_depth_two_stops_below_level_two() {
let z = Table::new();
z.set(Key::Str(b"z".to_vec()), Value::Number(1.0));
let y = Table::new();
y.set(Key::Str(b"y".to_vec()), Value::Table(z));
let x = Table::new();
x.set(Key::Str(b"x".to_vec()), Value::Table(y));
let opts = Options {
comment: Some(2),
..Options::block()
};
let got = serialize(&Value::Table(x), &opts).unwrap();
assert_eq!(got.matches("--[[table:").count(), 2, "{got}");
}
#[test]
fn nohuge_formats_inf_and_nan_in_table() {
let t = Table::new();
t.set(Key::Str(b"a".to_vec()), Value::Number(f64::INFINITY));
t.set(Key::Str(b"b".to_vec()), Value::Number(f64::NAN));
let opts = Options {
nohuge: true,
sortkeys: true,
..Options::line()
};
let got = norm_addr(&line_with(&Value::Table(t), opts).unwrap());
assert!(got.contains("a = inf"), "{got}");
assert!(got.contains("b = nan"), "{got}");
assert!(!got.contains("1/0"), "{got}");
}