474 lines
11 KiB
Go
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")
|
|
}
|
|
}
|