LuaJIT-to-Go/tests/wrapper_test.go
Sky Johnson f4bfff470f massive rewrite
fix go func mallocs
add helper utils
2025-05-31 17:42:58 -05:00

426 lines
9.9 KiB
Go

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