lunar_wave/src/tests.rs

392 lines
8.1 KiB
Rust

use std::hash::Hash;
use crate::{
instruction::Instruction as Inst,
loader,
state::{
Block,
Chunk,
State,
},
value::Value,
};
fn calculate_hash<T: Hash>(t: &T) -> u64 {
use std::hash::Hasher;
let mut s = std::collections::hash_map::DefaultHasher::new ();
t.hash(&mut s);
s.finish()
}
/// Takes arguments and a parsed Lua chunk, runs its,
/// and returns the output
fn run_chunk (args: &[&str], chunk: &Chunk) -> Vec <Value> {
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (args.into_iter ().map (|s| s.to_string ()));
vm.execute_chunk (chunk, &upvalues)
}
/// Takes arguments and Lua bytecode, loads it, runs it,
/// and return the output
fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec <Value> {
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
run_chunk (args, &chunk)
}
/// Takes arguments and Lua source code,
/// invokes `luac` to compile it to bytecode,
/// runs it,
/// and returns the output
fn run_source (args: &[&str], s: &str) -> Vec <Value> {
let bc = loader::compile_bytecode (s.as_bytes ().to_vec ());
run_bytecode (args, &bc)
}
#[test]
fn bools () {
/*
local function bool_to_x (b)
if b then
return 99
else
return 98
end
end
local x = bool_to_x (not not arg [1])
print (x)
return x
*/
let chunk = Chunk {
blocks: vec! [
Block {
instructions: vec! [
Inst::VarArgPrep (0),
Inst::Closure (0, 0),
Inst::Move (1, 0),
Inst::LoadFalse (2),
Inst::Call (1, 2, 1),
Inst::Move (1, 0),
Inst::LoadTrue (2),
Inst::Call (1, 2, 1),
Inst::Move (1, 0),
Inst::GetTabUp (2, 0, 0),
Inst::GetI (2, 2, 1),
Inst::Not (2, 2),
Inst::Not (2, 2),
Inst::Call (1, 2, 2),
Inst::GetTabUp (2, 0, 1),
Inst::Move (3, 1),
Inst::Call (2, 2, 1),
Inst::Return (1, 2, 1, false),
Inst::Return (2, 1, 1, false),
],
constants: vec! [
"arg".into (),
"print".into (),
],
upvalue_count: 1,
},
Block {
instructions: vec! [
Inst::Test (0, false),
Inst::Jmp (3),
Inst::LoadI (1, 99),
Inst::Return1 (1),
Inst::Jmp (2),
Inst::LoadI (1, 98),
Inst::Return1 (1),
Inst::Return0,
],
constants: vec! [],
upvalue_count: 0,
},
],
};
for (arg, expected) in [
(vec! ["_exe_name"], vec! [98.into ()]),
(vec! ["_exe_name", "asdf"], vec! [99.into ()]),
] {
let expected: Vec <Value> = expected;
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&chunk, &upvalues);
assert_eq! (actual, expected);
}
}
#[test]
fn closure () {
let bytecode = include_bytes! ("../test_vectors/closure.luac");
let mut rdr = std::io::Cursor::new (bytecode);
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
for (arg, expected) in [
// Run the same test twice so clippy won't complain about a vec of 1 element
(vec! ["_exe_name"], vec! [23.0.into ()]),
(vec! ["_exe_name"], vec! [23.0.into ()]),
] {
let expected: Vec <Value> = expected;
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&file, &upvalues);
assert_eq! (actual, expected);
}
}
#[test]
fn floats () {
/*
local a = 0.5
local b = 3
local x = a + b
print (x)
return x
*/
let block = Block {
instructions: vec! [
Inst::VarArgPrep (0),
Inst::LoadK (0, 0),
Inst::LoadI (1, 3),
Inst::Add (2, 0, 1),
Inst::MmBin (0, 1, 6),
Inst::GetTabUp (3, 0, 1),
Inst::Move (4, 2),
Inst::Call (3, 2, 1),
Inst::Return (2, 2, 1, false),
Inst::Return (3, 1, 1, false),
],
constants: vec! [
0.5.into (),
"print".into (),
],
upvalue_count: 1,
};
let chunk = Chunk {
blocks: vec! [block],
};
for (arg, expected) in [
(vec! ["_exe_name"], vec! [3.5.into ()]),
(vec! ["_exe_name", " "], vec! [3.5.into ()]),
] {
let expected: Vec <Value> = expected;
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&chunk, &upvalues);
assert_eq! (actual, expected);
}
}
#[test]
fn fma () {
let bytecode = include_bytes! ("../test_vectors/fma.luac");
let mut rdr = std::io::Cursor::new (bytecode);
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
assert_eq! (file.blocks.len (), 4);
for (arg, expected) in [
(vec! ["_exe_name"], vec! [122.into ()]),
(vec! ["_exe_name"], vec! [122.into ()]),
] {
let expected: Vec <Value> = expected;
let mut vm = State::default ();
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
let actual = vm.execute_chunk (&file, &upvalues);
assert_eq! (actual, expected);
}
}
#[test]
fn heap () {
use std::{
cell::RefCell,
collections::HashMap,
rc::Rc,
};
use crate::value::Table;
{
let mut allocations = HashMap::new ();
let mut ctr = 0;
let c = ctr;
allocations.insert (ctr, Table::default ());
ctr += 1;
let a = ctr;
allocations.insert (ctr, Table::default ());
ctr += 1;
allocations.get_mut (&a).unwrap ().insert (1, c);
allocations.get_mut (&c).unwrap ().insert (2, "eee");
}
if true {
#[derive (Clone, Debug, PartialEq)]
enum Value {
S (Rc <String>),
T (Rc <RefCell <HashMap <i64, Value>>>),
}
let c = Value::T (Default::default ());
let a = Value::T (Default::default ());
match &a {
Value::T (t) => t.borrow_mut ().insert (1, c.clone ()),
_ => panic! ("impossible"),
};
match &c {
Value::T (t) => t.borrow_mut ().insert (2, Value::S (Rc::new (String::from ("eee")))),
_ =>panic! ("impossible"),
};
let actual = match &a {
Value::T (t) => match t.borrow ().get (&1) {
Some (Value::T (t)) => t.borrow ().get (&2).unwrap ().clone (),
_ => panic! ("impossible"),
},
_ => panic! ("impossible"),
};
assert_eq! (actual, Value::S (Rc::new ("eee".into ())));
}
}
#[test]
fn is_93 () {
assert_eq! (Value::from ("93"), Value::from ("93"));
assert_ne! (Value::from ("94"), Value::from ("93"));
assert_ne! (calculate_hash (&Value::from ("94")), calculate_hash (&Value::from ("93")));
assert_ne! (Value::Nil, Value::from ("93"));
let src = r#"
if arg [1] == "93" then
print "it's 93"
return 0
else
print "it's not 93"
return 1
end
"#;
let bc = loader::compile_bytecode (src.as_bytes ().to_vec ());
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
assert_eq! (chunk.blocks [0].instructions [3], Inst::EqK (0, 1, false));
let run = run_chunk;
assert_eq! (run (&[""], &chunk), vec! [Value::from (1)]);
assert_eq! (run (&["", "93"], &chunk), vec! [Value::from (0)]);
assert_eq! (run (&["", "94"], &chunk), vec! [Value::from (1)]);
}
#[test]
fn native_functions () {
fn add (l: &mut State) -> i32 {
0
}
fn multiply (l: &mut State) -> i32 {
0
}
fn greet (l: &mut State) -> i32 {
0
}
let _funcs = [
add,
multiply,
greet,
];
}
#[test]
fn tables_1 () {
// I don't capture stdout yet, but I can at least make sure basic table
// operations don't crash
let src = r#"
print " 1"
print (nil)
print (false)
print (true)
print (1993)
print (1993.00)
print "Hello."
print " 2"
local t = {}
print (t)
print (t [1])
t [1] = "asdf"
print (t [1])
t.x = 1993
print (t.x)
t.t = { 3.14159 }
print (t ["t"][1])
"#;
run_source (&[], src);
}
#[test]
fn tables_2 () {
let src = r#"
print " 3"
local c = {}
local a = { c }
print (a [1])
c [2] = "eee"
print (a [2])
"#;
run_source (&[], src);
}
#[test]
fn value_size () {
// Per https://www.lua.org/doc/jucs05.pdf,
// "The Implementation of Lua 5.0",
//
// Lua's tagged union values are 12-16 bytes on a 32-bit system
// with 64-bit floats
//
// It would be nice if LunarWaveVM is the same or better.
// There are some exploratory things in this test, too
use std::mem::size_of;
assert! (size_of::<Box <()>> () <= 8);
assert! (size_of::<std::rc::Rc <()>> () <= 8);
let sz = size_of::<crate::value::Value> ();
let expected = 16;
assert! (sz <= expected, "{sz} > {expected}");
}