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) } }