package luajit import ( "fmt" "os" "path/filepath" "testing" ) type stateFactory struct { name string new func() *State } var factories = []stateFactory{ {"unsafe", New}, {"safe", NewSafe}, } func TestNew(t *testing.T) { for _, f := range factories { t.Run(f.name, func(t *testing.T) { L := f.new() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() }) } } 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 _, f := range factories { for _, tt := range tests { t.Run(f.name+"/"+tt.name, func(t *testing.T) { L := f.new() if L == nil { t.Fatal("Failed to create Lua state") } defer L.Close() err := L.DoString(tt.code) if (err != nil) != tt.wantErr { t.Errorf("DoString() error = %v, wantErr %v", err, tt.wantErr) } }) } } } 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 _, f := range factories { for _, v := range values { t.Run(f.name+"/"+v.name, func(t *testing.T) { L := f.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) { for _, f := range factories { t.Run(f.name, func(t *testing.T) { L := f.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) { for _, f := range factories { t.Run(f.name, func(t *testing.T) { L := f.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 TestDoFile(t *testing.T) { L := NewSafe() 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 := NewSafe() 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 := NewSafe() 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) } }