package luajit_test import ( "os" "reflect" "testing" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) func TestStateLifecycle(t *testing.T) { // Test creation state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } // Test close state.Close() // Test close is idempotent (doesn't crash) state.Close() } func TestStackManipulation(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Test initial stack size if state.GetTop() != 0 { t.Fatalf("Expected empty stack, got %d elements", state.GetTop()) } // Push values state.PushNil() state.PushBoolean(true) state.PushNumber(42) state.PushString("hello") // Check stack size if state.GetTop() != 4 { t.Fatalf("Expected 4 elements, got %d", state.GetTop()) } // Test SetTop state.SetTop(2) if state.GetTop() != 2 { t.Fatalf("Expected 2 elements after SetTop, got %d", state.GetTop()) } // Test PushCopy state.PushCopy(2) // Copy the boolean if !state.IsBoolean(-1) { t.Fatalf("Expected boolean at top of stack") } // Test Pop state.Pop(1) if state.GetTop() != 2 { t.Fatalf("Expected 2 elements after Pop, got %d", state.GetTop()) } // Test Remove state.PushNumber(99) state.Remove(1) // Remove the first element (nil) if state.GetTop() != 2 { t.Fatalf("Expected 2 elements after Remove, got %d", state.GetTop()) } // Verify first element is now boolean if !state.IsBoolean(1) { t.Fatalf("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() // Push values of different types state.PushNil() state.PushBoolean(true) state.PushNumber(42) state.PushString("hello") state.NewTable() // Check types with GetType if state.GetType(1) != luajit.TypeNil { t.Fatalf("Expected nil type at index 1, got %s", state.GetType(1)) } if state.GetType(2) != luajit.TypeBoolean { t.Fatalf("Expected boolean type at index 2, got %s", state.GetType(2)) } if state.GetType(3) != luajit.TypeNumber { t.Fatalf("Expected number type at index 3, got %s", state.GetType(3)) } if state.GetType(4) != luajit.TypeString { t.Fatalf("Expected string type at index 4, got %s", state.GetType(4)) } if state.GetType(5) != luajit.TypeTable { t.Fatalf("Expected table type at index 5, got %s", state.GetType(5)) } // Test individual type checking functions if !state.IsNil(1) { t.Fatalf("IsNil failed for nil value") } if !state.IsBoolean(2) { t.Fatalf("IsBoolean failed for boolean value") } if !state.IsNumber(3) { t.Fatalf("IsNumber failed for number value") } if !state.IsString(4) { t.Fatalf("IsString failed for string value") } if !state.IsTable(5) { t.Fatalf("IsTable failed for table value") } // Function test state.DoString("function test() return true end") state.GetGlobal("test") if !state.IsFunction(-1) { t.Fatalf("IsFunction failed for function value") } } func TestValueConversion(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Push values state.PushBoolean(true) state.PushNumber(42.5) state.PushString("hello") // Test conversion if !state.ToBoolean(1) { t.Fatalf("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() // Test CreateTable state.CreateTable(0, 3) // Add fields using SetField state.PushNumber(42) state.SetField(-2, "answer") state.PushString("hello") state.SetField(-2, "greeting") state.PushBoolean(true) state.SetField(-2, "flag") // Test GetField state.GetField(-1, "answer") if state.ToNumber(-1) != 42 { t.Fatalf("GetField for 'answer' failed") } state.Pop(1) state.GetField(-1, "greeting") if state.ToString(-1) != "hello" { t.Fatalf("GetField for 'greeting' failed") } state.Pop(1) // Test Next for iteration state.PushNil() // Start iteration count := 0 for state.Next(-2) { count++ state.Pop(1) // Pop value, leave key for next iteration } if count != 3 { t.Fatalf("Expected 3 table entries, found %d", count) } // Clean up state.Pop(1) // Pop the table } func TestGlobalOperations(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Set a global value state.PushNumber(42) state.SetGlobal("answer") // Get the global value state.GetGlobal("answer") if state.ToNumber(-1) != 42 { t.Fatalf("GetGlobal failed, expected 42, got %f", state.ToNumber(-1)) } state.Pop(1) // Test non-existent global (should be nil) state.GetGlobal("nonexistent") if !state.IsNil(-1) { t.Fatalf("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 if err := state.LoadString("return 42"); err != nil { t.Fatalf("LoadString failed: %v", err) } // Test Call 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 execution 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.Fatalf("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 err = state.DoString("this is not valid lua code") if err == nil { t.Fatalf("Expected error for invalid code, got nil") } } func TestDoFile(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() // Create a temporary Lua 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 to temp file: %v", err) } if err := tmpfile.Close(); err != nil { t.Fatalf("Failed to close temp file: %v", err) } // Test LoadFile and DoFile if err := state.LoadFile(tmpfile.Name()); err != nil { t.Fatalf("LoadFile failed: %v", err) } if err := state.Call(0, 0); err != nil { t.Fatalf("Call failed after LoadFile: %v", err) } state.GetGlobal("answer") if state.ToNumber(-1) != 42 { t.Fatalf("Incorrect result after LoadFile, expected 42, got %f", state.ToNumber(-1)) } state.Pop(1) // Reset global if err := state.DoString("answer = nil"); err != nil { t.Fatalf("Failed to reset answer: %v", err) } // 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("Incorrect result after DoFile, 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() // Test SetPackagePath 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("Expected package.path to be '%s', got '%s'", testPath, result) } // Test AddPackagePath 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("Expected package.path to be '%s', got '%s'", expected, result) } } func TestPushValueAndToValue(t *testing.T) { state := luajit.New() if state == nil { t.Fatal("Failed to create Lua state") } defer state.Close() testCases := []struct { value any }{ {nil}, {true}, {false}, {42}, {42.5}, {"hello"}, {[]float64{1, 2, 3, 4, 5}}, {[]any{1, "test", true}}, {map[string]any{"a": 1, "b": "test", "c": true}}, } for i, tc := range testCases { // Push value err := state.PushValue(tc.value) if err != nil { t.Fatalf("PushValue failed for testCase %d: %v", i, err) } // Check stack if state.GetTop() != i+1 { t.Fatalf("Stack size incorrect after push, expected %d, got %d", i+1, state.GetTop()) } } // Test conversion back to Go for i := range testCases { index := len(testCases) - i value, err := state.ToValue(index) if err != nil { t.Fatalf("ToValue failed for index %d: %v", index, err) } // For tables, we need special handling due to how Go types are stored switch expected := testCases[index-1].value.(type) { case []float64: // Arrays come back as map[string]any with empty key if m, ok := value.(map[string]any); ok { if arr, ok := m[""].([]float64); ok { if !reflect.DeepEqual(arr, expected) { t.Fatalf("Value mismatch for testCase %d: expected %v, got %v", index-1, expected, arr) } } else { t.Fatalf("Invalid array conversion for testCase %d", index-1) } } else { t.Fatalf("Expected map for array value in testCase %d, got %T", index-1, value) } case int: if num, ok := value.(float64); ok { if float64(expected) == num { continue // Values match after type conversion } } case []any: // Skip detailed comparison for mixed arrays case map[string]any: // Skip detailed comparison for maps default: if !reflect.DeepEqual(value, testCases[index-1].value) { t.Fatalf("Value mismatch for testCase %d: expected %v, got %v", index-1, testCases[index-1].value, value) } } } // Test unsupported type complex := complex(1, 2) err := state.PushValue(complex) if err == nil { t.Fatalf("Expected error for unsupported type") } }