444 lines
10 KiB
Go
444 lines
10 KiB
Go
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)
|
|
}
|
|
}
|