package luajit_test import ( "os" "reflect" "testing" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) func TestStateLifecycle(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } state.Close() state.Close() // Test idempotent close } func TestStackOperations(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Test stack manipulation if state.GetTop() != 0 { t.Fatalf("Expected empty stack, got %d", state.GetTop()) } state.PushNil() state.PushBoolean(true) state.PushNumber(42) state.PushString("hello") if state.GetTop() != 4 { t.Fatalf("Expected 4 elements, got %d", state.GetTop()) } state.SetTop(2) if state.GetTop() != 2 { t.Fatalf("Expected 2 elements after SetTop, got %d", state.GetTop()) } state.PushCopy(2) if !state.IsBoolean(-1) { t.Fatal("Expected boolean at top") } state.Pop(1) state.PushNumber(99) state.Remove(1) if !state.IsBoolean(1) { t.Fatal("Expected boolean at index 1 after Remove") } } func TestTypeChecking(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() values := []struct { push func() luaType luajit.LuaType checkFn func(int) bool }{ {state.PushNil, luajit.TypeNil, state.IsNil}, {func() { state.PushBoolean(true) }, luajit.TypeBoolean, state.IsBoolean}, {func() { state.PushNumber(42) }, luajit.TypeNumber, state.IsNumber}, {func() { state.PushString("test") }, luajit.TypeString, state.IsString}, {state.NewTable, luajit.TypeTable, state.IsTable}, } for i, v := range values { v.push() idx := i + 1 if state.GetType(idx) != v.luaType { t.Fatalf("Type mismatch at %d: expected %s, got %s", idx, v.luaType, state.GetType(idx)) } if !v.checkFn(idx) { t.Fatalf("Type check failed at %d", idx) } } state.DoString("function test() return true end") state.GetGlobal("test") if !state.IsFunction(-1) { t.Fatal("IsFunction failed") } } func TestValueConversion(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() state.PushBoolean(true) state.PushNumber(42.5) state.PushString("hello") if !state.ToBoolean(1) { t.Fatal("ToBoolean failed") } if state.ToNumber(2) != 42.5 { t.Fatalf("ToNumber failed: expected 42.5, got %f", state.ToNumber(2)) } if state.ToString(3) != "hello" { t.Fatalf("ToString failed: expected 'hello', got '%s'", state.ToString(3)) } } func TestTableOperations(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() state.CreateTable(0, 3) // Set fields state.PushNumber(42) state.SetField(-2, "answer") state.PushString("hello") state.SetField(-2, "greeting") state.PushBoolean(true) state.SetField(-2, "flag") // Get fields state.GetField(-1, "answer") if state.ToNumber(-1) != 42 { t.Fatal("GetField failed for 'answer'") } state.Pop(1) // Test iteration state.PushNil() count := 0 for state.Next(-2) { count++ state.Pop(1) } if count != 3 { t.Fatalf("Expected 3 entries, found %d", count) } state.Pop(1) } func TestGlobalOperations(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() state.PushNumber(42) state.SetGlobal("answer") state.GetGlobal("answer") if state.ToNumber(-1) != 42 { t.Fatalf("GetGlobal failed: expected 42, got %f", state.ToNumber(-1)) } state.Pop(1) state.GetGlobal("nonexistent") if !state.IsNil(-1) { t.Fatal("Expected nil for non-existent global") } state.Pop(1) } func TestCodeExecution(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Test LoadString and Call if err := state.LoadString("return 42"); err != nil { t.Fatalf("LoadString failed: %v", err) } if err := state.Call(0, 1); err != nil { t.Fatalf("Call failed: %v", err) } if state.ToNumber(-1) != 42 { t.Fatalf("Call result incorrect: expected 42, got %f", state.ToNumber(-1)) } state.Pop(1) // Test DoString if err := state.DoString("answer = 42 + 1"); err != nil { t.Fatalf("DoString failed: %v", err) } state.GetGlobal("answer") if state.ToNumber(-1) != 43 { t.Fatalf("DoString result incorrect: expected 43, got %f", state.ToNumber(-1)) } state.Pop(1) // Test Execute nresults, err := state.Execute("return 5, 10, 15") if err != nil { t.Fatalf("Execute failed: %v", err) } if nresults != 3 { t.Fatalf("Execute returned %d results, expected 3", nresults) } if state.ToNumber(-3) != 5 || state.ToNumber(-2) != 10 || state.ToNumber(-1) != 15 { t.Fatal("Execute results incorrect") } state.Pop(3) // Test ExecuteWithResult result, err := state.ExecuteWithResult("return 'hello'") if err != nil { t.Fatalf("ExecuteWithResult failed: %v", err) } if result != "hello" { t.Fatalf("ExecuteWithResult returned %v, expected 'hello'", result) } // Test error handling if err := state.DoString("invalid lua code"); err == nil { t.Fatal("Expected error for invalid code") } } func TestFileOperations(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Create temp file content := []byte("answer = 42") tmpfile, err := os.CreateTemp("", "test-*.lua") if err != nil { t.Fatalf("Failed to create temp file: %v", err) } defer os.Remove(tmpfile.Name()) if _, err := tmpfile.Write(content); err != nil { t.Fatalf("Failed to write temp file: %v", err) } tmpfile.Close() // Test DoFile if err := state.DoFile(tmpfile.Name()); err != nil { t.Fatalf("DoFile failed: %v", err) } state.GetGlobal("answer") if state.ToNumber(-1) != 42 { t.Fatalf("DoFile result incorrect: expected 42, got %f", state.ToNumber(-1)) } state.Pop(1) } func TestPackagePath(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() testPath := "/test/path/?.lua" if err := state.SetPackagePath(testPath); err != nil { t.Fatalf("SetPackagePath failed: %v", err) } result, err := state.ExecuteWithResult("return package.path") if err != nil { t.Fatalf("Failed to get package.path: %v", err) } if result != testPath { t.Fatalf("SetPackagePath failed: expected '%s', got '%s'", testPath, result) } addPath := "/another/path/?.lua" if err := state.AddPackagePath(addPath); err != nil { t.Fatalf("AddPackagePath failed: %v", err) } result, err = state.ExecuteWithResult("return package.path") if err != nil { t.Fatalf("Failed to get package.path: %v", err) } expected := testPath + ";" + addPath if result != expected { t.Fatalf("AddPackagePath failed: expected '%s', got '%s'", expected, result) } } func TestEnhancedTypes(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Test typed arrays testCases := []struct { input any expected any }{ // Primitive types {nil, nil}, {true, true}, {42, 42}, // Should preserve as int {42.5, 42.5}, // Should be float64 {"hello", "hello"}, // Typed arrays {[]int{1, 2, 3}, []int{1, 2, 3}}, {[]string{"a", "b"}, []string{"a", "b"}}, {[]bool{true, false}, []bool{true, false}}, {[]float64{1.1, 2.2}, []float64{1.1, 2.2}}, // Typed maps {map[string]string{"name": "John"}, map[string]string{"name": "John"}}, {map[string]int{"age": 25}, map[string]int{"age": 25}}, {map[int]any{10: "first", 20: 42}, map[string]any{"10": "first", "20": 42}}, } for i, tc := range testCases { // Push and retrieve value if err := state.PushValue(tc.input); err != nil { t.Fatalf("Case %d: PushValue failed: %v", i, err) } result, err := state.ToValue(-1) if err != nil { t.Fatalf("Case %d: ToValue failed: %v", i, err) } if !reflect.DeepEqual(result, tc.expected) { t.Fatalf("Case %d: expected %v (%T), got %v (%T)", i, tc.expected, tc.expected, result, result) } state.Pop(1) } // Test mixed array (should become []any) state.DoString("mixed = {1, 'hello', true}") state.GetGlobal("mixed") result, err := state.ToValue(-1) if err != nil { t.Fatalf("Mixed array conversion failed: %v", err) } if _, ok := result.([]any); !ok { t.Fatalf("Expected []any for mixed array, got %T", result) } state.Pop(1) // Test mixed map (should become map[string]any) state.DoString("mixedMap = {name='John', age=25, active=true}") state.GetGlobal("mixedMap") result, err = state.ToValue(-1) if err != nil { t.Fatalf("Mixed map conversion failed: %v", err) } if _, ok := result.(map[string]any); !ok { t.Fatalf("Expected map[string]any for mixed map, got %T", result) } state.Pop(1) } func TestIntegerPreservation(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Test that integers are preserved state.DoString("num = 42") state.GetGlobal("num") result, err := state.ToValue(-1) if err != nil { t.Fatalf("Integer conversion failed: %v", err) } if val, ok := result.(int); !ok || val != 42 { t.Fatalf("Expected int 42, got %T %v", result, result) } state.Pop(1) // Test that floats remain floats state.DoString("fnum = 42.5") state.GetGlobal("fnum") result, err = state.ToValue(-1) if err != nil { t.Fatalf("Float conversion failed: %v", err) } if val, ok := result.(float64); !ok || val != 42.5 { t.Fatalf("Expected float64 42.5, got %T %v", result, result) } state.Pop(1) } func TestErrorHandling(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Test unsupported type type customStruct struct{ Field int } if err := state.PushValue(customStruct{Field: 42}); err == nil { t.Fatal("Expected error for unsupported type") } // Test invalid stack index _, err := state.ToValue(100) if err == nil { t.Fatal("Expected error for invalid index") } }