LuaJIT-to-Go/tests/wrapper_test.go
2025-02-26 07:00:01 -06:00

474 lines
11 KiB
Go

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