426 lines
9.9 KiB
Go
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")
|
|
}
|
|
}
|