LuaJIT-to-Go/tests/bytecode_test.go

444 lines
10 KiB
Go
Raw Normal View History

2025-02-26 07:00:01 -06:00
package luajit_test
import (
"bytes"
"errors"
"testing"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
// TestCompileBytecode tests basic bytecode compilation
func TestCompileBytecode(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
code := "return 42"
bytecode, err := state.CompileBytecode(code, "test")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
if len(bytecode) == 0 {
t.Fatal("Expected non-empty bytecode")
}
}
// TestLoadBytecode tests loading precompiled bytecode
func TestLoadBytecode(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// First compile some bytecode
code := "answer = 42"
bytecode, err := state.CompileBytecode(code, "test")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
// Then load it
err = state.LoadBytecode(bytecode, "test")
if err != nil {
t.Fatalf("LoadBytecode failed: %v", err)
}
// Verify a function is on the stack
if !state.IsFunction(-1) {
t.Fatal("Expected function at top of stack after LoadBytecode")
}
// Pop the function
state.Pop(1)
}
// TestRunBytecode tests running previously loaded bytecode
func TestRunBytecode(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// First compile and load bytecode
code := "answer = 42"
bytecode, err := state.CompileBytecode(code, "test")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
err = state.LoadBytecode(bytecode, "test")
if err != nil {
t.Fatalf("LoadBytecode failed: %v", err)
}
// Run the bytecode
err = state.RunBytecode()
if err != nil {
t.Fatalf("RunBytecode failed: %v", err)
}
// Verify the code has executed correctly
state.GetGlobal("answer")
if !state.IsNumber(-1) || state.ToNumber(-1) != 42 {
t.Fatalf("Expected answer to be 42, got %v", state.ToNumber(-1))
}
state.Pop(1)
}
// TestLoadAndRunBytecode tests the combined load and run functionality
func TestLoadAndRunBytecode(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Compile bytecode
code := "answer = 42"
bytecode, err := state.CompileBytecode(code, "test")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
// Load and run in one step
err = state.LoadAndRunBytecode(bytecode, "test")
if err != nil {
t.Fatalf("LoadAndRunBytecode failed: %v", err)
}
// Verify execution
state.GetGlobal("answer")
if !state.IsNumber(-1) || state.ToNumber(-1) != 42 {
t.Fatalf("Expected answer to be 42, got %v", state.ToNumber(-1))
}
state.Pop(1)
}
// TestCompileAndRun tests compile and run functionality
func TestCompileAndRun(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Compile and run in one step
code := "answer = 42"
err := state.CompileAndRun(code, "test")
if err != nil {
t.Fatalf("CompileAndRun failed: %v", err)
}
// Verify execution
state.GetGlobal("answer")
if !state.IsNumber(-1) || state.ToNumber(-1) != 42 {
t.Fatalf("Expected answer to be 42, got %v", state.ToNumber(-1))
}
state.Pop(1)
}
// TestEmptyBytecode tests error handling for empty bytecode
func TestEmptyBytecode(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Try to load empty bytecode
err := state.LoadBytecode([]byte{}, "empty")
if err == nil {
t.Fatal("Expected error for empty bytecode, got nil")
}
}
// TestInvalidBytecode tests error handling for invalid bytecode
func TestInvalidBytecode(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Create some invalid bytecode
invalidBytecode := []byte("not valid bytecode")
// Try to load invalid bytecode
err := state.LoadBytecode(invalidBytecode, "invalid")
if err == nil {
t.Fatal("Expected error for invalid bytecode, got nil")
}
}
// TestBytecodeSerialization tests serializing and deserializing bytecode
func TestBytecodeSerialization(t *testing.T) {
// First state to compile
state1 := luajit.New()
if state1 == nil {
t.Fatal("Failed to create first Lua state")
}
defer state1.Close()
// Compile bytecode
code := `
function add(a, b)
return a + b
end
result = add(10, 20)
`
bytecode, err := state1.CompileBytecode(code, "test")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
// Second state to execute
state2 := luajit.New()
if state2 == nil {
t.Fatal("Failed to create second Lua state")
}
defer state2.Close()
// Load and run the bytecode in the second state
err = state2.LoadAndRunBytecode(bytecode, "test")
if err != nil {
t.Fatalf("LoadAndRunBytecode failed: %v", err)
}
// Verify execution
state2.GetGlobal("result")
if !state2.IsNumber(-1) || state2.ToNumber(-1) != 30 {
t.Fatalf("Expected result to be 30, got %v", state2.ToNumber(-1))
}
state2.Pop(1)
// Call the function to verify it was properly transferred
state2.GetGlobal("add")
if !state2.IsFunction(-1) {
t.Fatal("Expected add to be a function")
}
state2.PushNumber(5)
state2.PushNumber(7)
if err := state2.Call(2, 1); err != nil {
t.Fatalf("Failed to call function: %v", err)
}
if state2.ToNumber(-1) != 12 {
t.Fatalf("Expected add(5, 7) to return 12, got %v", state2.ToNumber(-1))
}
state2.Pop(1)
}
// TestCompilationError tests error handling for compilation errors
func TestCompilationError(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Invalid Lua code that should fail to compile
code := "function without end"
// Try to compile
_, err := state.CompileBytecode(code, "invalid")
if err == nil {
t.Fatal("Expected compilation error, got nil")
}
// Check error type
var luaErr *luajit.LuaError
if !errors.As(err, &luaErr) {
t.Fatalf("Expected error to wrap *luajit.LuaError, got %T", err)
}
}
// TestExecutionError tests error handling for runtime errors
func TestExecutionError(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Code that compiles but fails at runtime
code := "error('deliberate error')"
// Compile bytecode
bytecode, err := state.CompileBytecode(code, "error")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
// Try to execute
err = state.LoadAndRunBytecode(bytecode, "error")
if err == nil {
t.Fatal("Expected execution error, got nil")
}
// Check error type
if _, ok := err.(*luajit.LuaError); !ok {
t.Fatalf("Expected *luajit.LuaError, got %T", err)
}
}
// TestBytecodeEquivalence tests that bytecode execution produces the same results as direct execution
func TestBytecodeEquivalence(t *testing.T) {
code := `
local result = 0
for i = 1, 10 do
result = result + i
end
return result
`
// First, execute directly
state1 := luajit.New()
if state1 == nil {
t.Fatal("Failed to create first Lua state")
}
defer state1.Close()
directResult, err := state1.ExecuteWithResult(code)
if err != nil {
t.Fatalf("ExecuteWithResult failed: %v", err)
}
// Then, compile and execute bytecode
state2 := luajit.New()
if state2 == nil {
t.Fatal("Failed to create second Lua state")
}
defer state2.Close()
bytecode, err := state2.CompileBytecode(code, "test")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
err = state2.LoadBytecode(bytecode, "test")
if err != nil {
t.Fatalf("LoadBytecode failed: %v", err)
}
err = state2.Call(0, 1)
if err != nil {
t.Fatalf("Call failed: %v", err)
}
bytecodeResult, err := state2.ToValue(-1)
if err != nil {
t.Fatalf("ToValue failed: %v", err)
}
state2.Pop(1)
// Compare results
if directResult != bytecodeResult {
t.Fatalf("Results differ: direct=%v, bytecode=%v", directResult, bytecodeResult)
}
}
// TestBytecodeReuse tests reusing the same bytecode multiple times
func TestBytecodeReuse(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Create a function in bytecode
code := `
return function(x)
return x * 2
end
`
bytecode, err := state.CompileBytecode(code, "func")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
// Execute it several times
for i := 1; i <= 3; i++ {
// Load and run to get the function
err = state.LoadAndRunBytecodeWithResults(bytes.Clone(bytecode), "func", 1)
if err != nil {
t.Fatalf("LoadAndRunBytecodeWithResults failed: %v", err)
}
// Stack now has the function at the top
if !state.IsFunction(-1) {
t.Fatal("Expected function at top of stack")
}
// Call with parameter i
state.PushNumber(float64(i))
if err := state.Call(1, 1); err != nil {
t.Fatalf("Call failed: %v", err)
}
// Check result
expected := float64(i * 2)
if state.ToNumber(-1) != expected {
t.Fatalf("Expected %v, got %v", expected, state.ToNumber(-1))
}
// Pop the result
state.Pop(1)
}
}
// TestBytecodeClosure tests that bytecode properly handles closures and upvalues
func TestBytecodeClosure(t *testing.T) {
state := luajit.New()
if state == nil {
t.Fatal("Failed to create Lua state")
}
defer state.Close()
// Create a closure
code := `
local counter = 0
return function()
counter = counter + 1
return counter
end
`
// Compile to bytecode
bytecode, err := state.CompileBytecode(code, "closure")
if err != nil {
t.Fatalf("CompileBytecode failed: %v", err)
}
// Load and run to get the counter function
err = state.LoadAndRunBytecodeWithResults(bytecode, "closure", 1)
if err != nil {
t.Fatalf("LoadAndRunBytecode failed: %v", err)
}
// Stack now has the function at the top
if !state.IsFunction(-1) {
t.Fatal("Expected function at top of stack")
}
// Store in a global
state.SetGlobal("counter_func")
// Call it multiple times and check the results
for i := 1; i <= 3; i++ {
state.GetGlobal("counter_func")
if err := state.Call(0, 1); err != nil {
t.Fatalf("Call failed: %v", err)
}
if state.ToNumber(-1) != float64(i) {
t.Fatalf("Expected counter to be %d, got %v", i, state.ToNumber(-1))
}
state.Pop(1)
}
}