package luajit import ( "fmt" "os" "path/filepath" "testing" ) func TestNew(t *testing.T) { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() } func TestLoadString(t *testing.T) { tests := []struct { name string code string wantErr bool }{ { name: "valid function", code: "function add(a, b) return a + b end", wantErr: false, }, { name: "valid expression", code: "return 1 + 1", wantErr: false, }, { name: "syntax error", code: "function bad syntax", wantErr: true, }, } for _, tt := range tests { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() err := L.LoadString(tt.code) if (err != nil) != tt.wantErr { t.Errorf("LoadString() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { // Verify the function is on the stack if L.GetTop() != 1 { t.Error("LoadString() did not leave exactly one value on stack") } if !L.IsFunction(-1) { t.Error("LoadString() did not leave a function on the stack") } } } } func TestExecuteString(t *testing.T) { tests := []struct { name string code string wantResults int checkResults func(*State) error wantErr bool wantStackSize int }{ { name: "no results", code: "local x = 1", wantResults: 0, wantErr: false, }, { name: "single result", code: "return 42", wantResults: 1, checkResults: func(L *State) error { if n := L.ToNumber(-1); n != 42 { return fmt.Errorf("got %v, want 42", n) } return nil }, wantErr: false, }, { name: "multiple results", code: "return 1, 'test', true", wantResults: 3, checkResults: func(L *State) error { if n := L.ToNumber(-3); n != 1 { return fmt.Errorf("first result: got %v, want 1", n) } if s := L.ToString(-2); s != "test" { return fmt.Errorf("second result: got %v, want 'test'", s) } if b := L.ToBoolean(-1); !b { return fmt.Errorf("third result: got %v, want true", b) } return nil }, wantErr: false, }, { name: "syntax error", code: "this is not valid lua", wantErr: true, }, { name: "runtime error", code: "error('test error')", wantErr: true, }, } for _, tt := range tests { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() // Record initial stack size initialStack := L.GetTop() results, err := L.ExecuteString(tt.code) if (err != nil) != tt.wantErr { t.Errorf("ExecuteString() error = %v, wantErr %v", err, tt.wantErr) return } if err == nil { if results != tt.wantResults { t.Errorf("ExecuteString() returned %d results, want %d", results, tt.wantResults) } if tt.checkResults != nil { if err := tt.checkResults(L); err != nil { t.Errorf("Result check failed: %v", err) } } // Verify stack size matches expected results if got := L.GetTop() - initialStack; got != tt.wantResults { t.Errorf("Stack size grew by %d, want %d", got, tt.wantResults) } } } } func TestDoString(t *testing.T) { tests := []struct { name string code string wantErr bool }{ {"simple addition", "return 1 + 1", false}, {"set global", "test = 42", false}, {"syntax error", "this is not valid lua", true}, {"runtime error", "error('test error')", true}, } for _, tt := range tests { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() initialStack := L.GetTop() err := L.DoString(tt.code) if (err != nil) != tt.wantErr { t.Errorf("DoString() error = %v, wantErr %v", err, tt.wantErr) } // Verify stack is unchanged if finalStack := L.GetTop(); finalStack != initialStack { t.Errorf("Stack size changed from %d to %d", initialStack, finalStack) } } } func TestPushAndGetValues(t *testing.T) { values := []struct { name string push func(*State) check func(*State) error }{ { name: "string", push: func(L *State) { L.PushString("hello") }, check: func(L *State) error { if got := L.ToString(-1); got != "hello" { return fmt.Errorf("got %q, want %q", got, "hello") } return nil }, }, { name: "number", push: func(L *State) { L.PushNumber(42.5) }, check: func(L *State) error { if got := L.ToNumber(-1); got != 42.5 { return fmt.Errorf("got %f, want %f", got, 42.5) } return nil }, }, { name: "boolean", push: func(L *State) { L.PushBoolean(true) }, check: func(L *State) error { if got := L.ToBoolean(-1); !got { return fmt.Errorf("got %v, want true", got) } return nil }, }, { name: "nil", push: func(L *State) { L.PushNil() }, check: func(L *State) error { if typ := L.GetType(-1); typ != TypeNil { return fmt.Errorf("got type %v, want TypeNil", typ) } return nil }, }, } for _, v := range values { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() v.push(L) if err := v.check(L); err != nil { t.Error(err) } } } func TestStackManipulation(t *testing.T) { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() // Push values values := []string{"first", "second", "third"} for _, v := range values { L.PushString(v) } // Check size if top := L.GetTop(); top != len(values) { t.Errorf("stack size = %d, want %d", top, len(values)) } // Pop one value L.Pop(1) // Check new top if str := L.ToString(-1); str != "second" { t.Errorf("top value = %q, want 'second'", str) } // Check new size if top := L.GetTop(); top != len(values)-1 { t.Errorf("stack size after pop = %d, want %d", top, len(values)-1) } } func TestGlobals(t *testing.T) { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() // Test via Lua if err := L.DoString(`globalVar = "test"`); err != nil { t.Fatalf("DoString error: %v", err) } // Get the global L.GetGlobal("globalVar") if str := L.ToString(-1); str != "test" { t.Errorf("global value = %q, want 'test'", str) } L.Pop(1) // Set and get via API L.PushNumber(42) L.SetGlobal("testNum") L.GetGlobal("testNum") if num := L.ToNumber(-1); num != 42 { t.Errorf("global number = %f, want 42", num) } } func TestCall(t *testing.T) { tests := []struct { funcName string // Add explicit function name field setup string args []interface{} nresults int checkStack func(*State) error wantErr bool }{ { funcName: "add", setup: "function add(a, b) return a + b end", args: []interface{}{float64(40), float64(2)}, nresults: 1, checkStack: func(L *State) error { if n := L.ToNumber(-1); n != 42 { return fmt.Errorf("got %v, want 42", n) } return nil }, }, { funcName: "multi", setup: "function multi() return 1, 'test', true end", args: []interface{}{}, nresults: 3, checkStack: func(L *State) error { if n := L.ToNumber(-3); n != 1 { return fmt.Errorf("first result: got %v, want 1", n) } if s := L.ToString(-2); s != "test" { return fmt.Errorf("second result: got %v, want 'test'", s) } if b := L.ToBoolean(-1); !b { return fmt.Errorf("third result: got %v, want true", b) } return nil }, }, { funcName: "err", setup: "function err() error('test error') end", args: []interface{}{}, nresults: 0, wantErr: true, }, } for _, tt := range tests { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() // Setup function if err := L.DoString(tt.setup); err != nil { t.Fatalf("Setup failed: %v", err) } // Get function L.GetGlobal(tt.funcName) if !L.IsFunction(-1) { t.Fatal("Failed to get function") } // Push arguments for _, arg := range tt.args { if err := L.PushValue(arg); err != nil { t.Fatalf("Failed to push argument: %v", err) } } // Call function err := L.Call(len(tt.args), tt.nresults) if (err != nil) != tt.wantErr { t.Errorf("Call() error = %v, wantErr %v", err, tt.wantErr) return } if err == nil && tt.checkStack != nil { if err := tt.checkStack(L); err != nil { t.Errorf("Stack check failed: %v", err) } } } } func TestDoFile(t *testing.T) { L := New() defer L.Close() // Create test file content := []byte(` function add(a, b) return a + b end result = add(40, 2) `) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.lua") if err := os.WriteFile(filename, content, 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } if err := L.DoFile(filename); err != nil { t.Fatalf("DoFile failed: %v", err) } L.GetGlobal("result") if result := L.ToNumber(-1); result != 42 { t.Errorf("Expected result=42, got %v", result) } } func TestRequireAndPackagePath(t *testing.T) { L := New() defer L.Close() tmpDir := t.TempDir() // Create module file moduleContent := []byte(` local M = {} function M.multiply(a, b) return a * b end return M `) if err := os.WriteFile(filepath.Join(tmpDir, "mathmod.lua"), moduleContent, 0644); err != nil { t.Fatalf("Failed to create module file: %v", err) } // Add module path and test require if err := L.AddPackagePath(filepath.Join(tmpDir, "?.lua")); err != nil { t.Fatalf("AddPackagePath failed: %v", err) } if err := L.DoString(` local math = require("mathmod") result = math.multiply(6, 7) `); err != nil { t.Fatalf("Failed to require module: %v", err) } L.GetGlobal("result") if result := L.ToNumber(-1); result != 42 { t.Errorf("Expected result=42, got %v", result) } } func TestSetPackagePath(t *testing.T) { L := New() defer L.Close() customPath := "./custom/?.lua" if err := L.SetPackagePath(customPath); err != nil { t.Fatalf("SetPackagePath failed: %v", err) } L.GetGlobal("package") L.GetField(-1, "path") if path := L.ToString(-1); path != customPath { t.Errorf("Expected package.path=%q, got %q", customPath, path) } // Test that the old path is completely replaced initialPath := L.ToString(-1) anotherPath := "./another/?.lua" if err := L.SetPackagePath(anotherPath); err != nil { t.Fatalf("Second SetPackagePath failed: %v", err) } L.GetGlobal("package") L.GetField(-1, "path") if path := L.ToString(-1); path != anotherPath { t.Errorf("Expected package.path=%q, got %q", anotherPath, path) } if path := L.ToString(-1); path == initialPath { t.Error("SetPackagePath did not replace the old path") } } func TestStackDebug(t *testing.T) { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() t.Log("Testing LoadString:") initialTop := L.GetTop() t.Logf("Initial stack size: %d", initialTop) err := L.LoadString("return 42") if err != nil { t.Errorf("LoadString failed: %v", err) } afterLoad := L.GetTop() t.Logf("Stack size after load: %d", afterLoad) t.Logf("Type of top element: %s", L.GetType(-1)) if L.IsFunction(-1) { t.Log("Top element is a function") } else { t.Log("Top element is NOT a function") } // Clean up after LoadString test L.SetTop(0) t.Log("\nTesting ExecuteString:") if err := L.DoString("function test() return 1, 'hello', true end"); err != nil { t.Errorf("DoString failed: %v", err) } beforeExec := L.GetTop() t.Logf("Stack size before execute: %d", beforeExec) nresults, err := L.ExecuteString("return test()") if err != nil { t.Errorf("ExecuteString failed: %v", err) } afterExec := L.GetTop() t.Logf("Stack size after execute: %d", afterExec) t.Logf("Reported number of results: %d", nresults) // Print each stack element for i := 1; i <= afterExec; i++ { t.Logf("Stack[-%d] type: %s", i, L.GetType(-i)) } if afterExec != nresults { t.Errorf("Stack size (%d) doesn't match number of results (%d)", afterExec, nresults) } } func TestTemplateRendering(t *testing.T) { L := New() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() defer L.Cleanup() // Create a simple render.template function renderFunc := func(s *State) int { // Template will be at index 1, data at index 2 data, err := s.ToTable(2) if err != nil { s.PushString(fmt.Sprintf("failed to get data table: %v", err)) return -1 } // Push data back as global for template access if err := s.PushTable(data); err != nil { s.PushString(fmt.Sprintf("failed to push data table: %v", err)) return -1 } s.SetGlobal("data") // Template processing code luaCode := ` local result = {} if data.user.logged_in then table.insert(result, '