LuaJIT-to-Go/wrapper_test.go

694 lines
16 KiB
Go

package luajit
import (
"fmt"
"os"
"path/filepath"
"testing"
)
func TestNew(t *testing.T) {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
}
func TestLoadString(t *testing.T) {
tests := []struct {
name string
code string
wantErr bool
}{
{
name: "valid function",
code: "function add(a, b) return a + b end",
wantErr: false,
},
{
name: "valid expression",
code: "return 1 + 1",
wantErr: false,
},
{
name: "syntax error",
code: "function bad syntax",
wantErr: true,
},
}
for _, tt := range tests {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
err := L.LoadString(tt.code)
if (err != nil) != tt.wantErr {
t.Errorf("LoadString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
// Verify the function is on the stack
if L.GetTop() != 1 {
t.Error("LoadString() did not leave exactly one value on stack")
}
if !L.IsFunction(-1) {
t.Error("LoadString() did not leave a function on the stack")
}
}
}
}
func TestExecuteString(t *testing.T) {
tests := []struct {
name string
code string
wantResults int
checkResults func(*State) error
wantErr bool
wantStackSize int
}{
{
name: "no results",
code: "local x = 1",
wantResults: 0,
wantErr: false,
},
{
name: "single result",
code: "return 42",
wantResults: 1,
checkResults: func(L *State) error {
if n := L.ToNumber(-1); n != 42 {
return fmt.Errorf("got %v, want 42", n)
}
return nil
},
wantErr: false,
},
{
name: "multiple results",
code: "return 1, 'test', true",
wantResults: 3,
checkResults: func(L *State) error {
if n := L.ToNumber(-3); n != 1 {
return fmt.Errorf("first result: got %v, want 1", n)
}
if s := L.ToString(-2); s != "test" {
return fmt.Errorf("second result: got %v, want 'test'", s)
}
if b := L.ToBoolean(-1); !b {
return fmt.Errorf("third result: got %v, want true", b)
}
return nil
},
wantErr: false,
},
{
name: "syntax error",
code: "this is not valid lua",
wantErr: true,
},
{
name: "runtime error",
code: "error('test error')",
wantErr: true,
},
}
for _, tt := range tests {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
// Record initial stack size
initialStack := L.GetTop()
results, err := L.ExecuteString(tt.code)
if (err != nil) != tt.wantErr {
t.Errorf("ExecuteString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil {
if results != tt.wantResults {
t.Errorf("ExecuteString() returned %d results, want %d", results, tt.wantResults)
}
if tt.checkResults != nil {
if err := tt.checkResults(L); err != nil {
t.Errorf("Result check failed: %v", err)
}
}
// Verify stack size matches expected results
if got := L.GetTop() - initialStack; got != tt.wantResults {
t.Errorf("Stack size grew by %d, want %d", got, tt.wantResults)
}
}
}
}
func TestDoString(t *testing.T) {
tests := []struct {
name string
code string
wantErr bool
}{
{"simple addition", "return 1 + 1", false},
{"set global", "test = 42", false},
{"syntax error", "this is not valid lua", true},
{"runtime error", "error('test error')", true},
}
for _, tt := range tests {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
initialStack := L.GetTop()
err := L.DoString(tt.code)
if (err != nil) != tt.wantErr {
t.Errorf("DoString() error = %v, wantErr %v", err, tt.wantErr)
}
// Verify stack is unchanged
if finalStack := L.GetTop(); finalStack != initialStack {
t.Errorf("Stack size changed from %d to %d", initialStack, finalStack)
}
}
}
func TestPushAndGetValues(t *testing.T) {
values := []struct {
name string
push func(*State)
check func(*State) error
}{
{
name: "string",
push: func(L *State) { L.PushString("hello") },
check: func(L *State) error {
if got := L.ToString(-1); got != "hello" {
return fmt.Errorf("got %q, want %q", got, "hello")
}
return nil
},
},
{
name: "number",
push: func(L *State) { L.PushNumber(42.5) },
check: func(L *State) error {
if got := L.ToNumber(-1); got != 42.5 {
return fmt.Errorf("got %f, want %f", got, 42.5)
}
return nil
},
},
{
name: "boolean",
push: func(L *State) { L.PushBoolean(true) },
check: func(L *State) error {
if got := L.ToBoolean(-1); !got {
return fmt.Errorf("got %v, want true", got)
}
return nil
},
},
{
name: "nil",
push: func(L *State) { L.PushNil() },
check: func(L *State) error {
if typ := L.GetType(-1); typ != TypeNil {
return fmt.Errorf("got type %v, want TypeNil", typ)
}
return nil
},
},
}
for _, v := range values {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
v.push(L)
if err := v.check(L); err != nil {
t.Error(err)
}
}
}
func TestStackManipulation(t *testing.T) {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
// Push values
values := []string{"first", "second", "third"}
for _, v := range values {
L.PushString(v)
}
// Check size
if top := L.GetTop(); top != len(values) {
t.Errorf("stack size = %d, want %d", top, len(values))
}
// Pop one value
L.Pop(1)
// Check new top
if str := L.ToString(-1); str != "second" {
t.Errorf("top value = %q, want 'second'", str)
}
// Check new size
if top := L.GetTop(); top != len(values)-1 {
t.Errorf("stack size after pop = %d, want %d", top, len(values)-1)
}
}
func TestGlobals(t *testing.T) {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
// Test via Lua
if err := L.DoString(`globalVar = "test"`); err != nil {
t.Fatalf("DoString error: %v", err)
}
// Get the global
L.GetGlobal("globalVar")
if str := L.ToString(-1); str != "test" {
t.Errorf("global value = %q, want 'test'", str)
}
L.Pop(1)
// Set and get via API
L.PushNumber(42)
L.SetGlobal("testNum")
L.GetGlobal("testNum")
if num := L.ToNumber(-1); num != 42 {
t.Errorf("global number = %f, want 42", num)
}
}
func TestCall(t *testing.T) {
tests := []struct {
funcName string // Add explicit function name field
setup string
args []interface{}
nresults int
checkStack func(*State) error
wantErr bool
}{
{
funcName: "add",
setup: "function add(a, b) return a + b end",
args: []interface{}{float64(40), float64(2)},
nresults: 1,
checkStack: func(L *State) error {
if n := L.ToNumber(-1); n != 42 {
return fmt.Errorf("got %v, want 42", n)
}
return nil
},
},
{
funcName: "multi",
setup: "function multi() return 1, 'test', true end",
args: []interface{}{},
nresults: 3,
checkStack: func(L *State) error {
if n := L.ToNumber(-3); n != 1 {
return fmt.Errorf("first result: got %v, want 1", n)
}
if s := L.ToString(-2); s != "test" {
return fmt.Errorf("second result: got %v, want 'test'", s)
}
if b := L.ToBoolean(-1); !b {
return fmt.Errorf("third result: got %v, want true", b)
}
return nil
},
},
{
funcName: "err",
setup: "function err() error('test error') end",
args: []interface{}{},
nresults: 0,
wantErr: true,
},
}
for _, tt := range tests {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
// Setup function
if err := L.DoString(tt.setup); err != nil {
t.Fatalf("Setup failed: %v", err)
}
// Get function
L.GetGlobal(tt.funcName)
if !L.IsFunction(-1) {
t.Fatal("Failed to get function")
}
// Push arguments
for _, arg := range tt.args {
if err := L.PushValue(arg); err != nil {
t.Fatalf("Failed to push argument: %v", err)
}
}
// Call function
err := L.Call(len(tt.args), tt.nresults)
if (err != nil) != tt.wantErr {
t.Errorf("Call() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil && tt.checkStack != nil {
if err := tt.checkStack(L); err != nil {
t.Errorf("Stack check failed: %v", err)
}
}
}
}
func TestDoFile(t *testing.T) {
L := New()
defer L.Close()
// Create test file
content := []byte(`
function add(a, b)
return a + b
end
result = add(40, 2)
`)
tmpDir := t.TempDir()
filename := filepath.Join(tmpDir, "test.lua")
if err := os.WriteFile(filename, content, 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
if err := L.DoFile(filename); err != nil {
t.Fatalf("DoFile failed: %v", err)
}
L.GetGlobal("result")
if result := L.ToNumber(-1); result != 42 {
t.Errorf("Expected result=42, got %v", result)
}
}
func TestRequireAndPackagePath(t *testing.T) {
L := New()
defer L.Close()
tmpDir := t.TempDir()
// Create module file
moduleContent := []byte(`
local M = {}
function M.multiply(a, b)
return a * b
end
return M
`)
if err := os.WriteFile(filepath.Join(tmpDir, "mathmod.lua"), moduleContent, 0644); err != nil {
t.Fatalf("Failed to create module file: %v", err)
}
// Add module path and test require
if err := L.AddPackagePath(filepath.Join(tmpDir, "?.lua")); err != nil {
t.Fatalf("AddPackagePath failed: %v", err)
}
if err := L.DoString(`
local math = require("mathmod")
result = math.multiply(6, 7)
`); err != nil {
t.Fatalf("Failed to require module: %v", err)
}
L.GetGlobal("result")
if result := L.ToNumber(-1); result != 42 {
t.Errorf("Expected result=42, got %v", result)
}
}
func TestSetPackagePath(t *testing.T) {
L := New()
defer L.Close()
customPath := "./custom/?.lua"
if err := L.SetPackagePath(customPath); err != nil {
t.Fatalf("SetPackagePath failed: %v", err)
}
L.GetGlobal("package")
L.GetField(-1, "path")
if path := L.ToString(-1); path != customPath {
t.Errorf("Expected package.path=%q, got %q", customPath, path)
}
// Test that the old path is completely replaced
initialPath := L.ToString(-1)
anotherPath := "./another/?.lua"
if err := L.SetPackagePath(anotherPath); err != nil {
t.Fatalf("Second SetPackagePath failed: %v", err)
}
L.GetGlobal("package")
L.GetField(-1, "path")
if path := L.ToString(-1); path != anotherPath {
t.Errorf("Expected package.path=%q, got %q", anotherPath, path)
}
if path := L.ToString(-1); path == initialPath {
t.Error("SetPackagePath did not replace the old path")
}
}
func TestStackDebug(t *testing.T) {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
t.Log("Testing LoadString:")
initialTop := L.GetTop()
t.Logf("Initial stack size: %d", initialTop)
err := L.LoadString("return 42")
if err != nil {
t.Errorf("LoadString failed: %v", err)
}
afterLoad := L.GetTop()
t.Logf("Stack size after load: %d", afterLoad)
t.Logf("Type of top element: %s", L.GetType(-1))
if L.IsFunction(-1) {
t.Log("Top element is a function")
} else {
t.Log("Top element is NOT a function")
}
// Clean up after LoadString test
L.SetTop(0)
t.Log("\nTesting ExecuteString:")
if err := L.DoString("function test() return 1, 'hello', true end"); err != nil {
t.Errorf("DoString failed: %v", err)
}
beforeExec := L.GetTop()
t.Logf("Stack size before execute: %d", beforeExec)
nresults, err := L.ExecuteString("return test()")
if err != nil {
t.Errorf("ExecuteString failed: %v", err)
}
afterExec := L.GetTop()
t.Logf("Stack size after execute: %d", afterExec)
t.Logf("Reported number of results: %d", nresults)
// Print each stack element
for i := 1; i <= afterExec; i++ {
t.Logf("Stack[-%d] type: %s", i, L.GetType(-i))
}
if afterExec != nresults {
t.Errorf("Stack size (%d) doesn't match number of results (%d)", afterExec, nresults)
}
}
func TestTemplateRendering(t *testing.T) {
L := New()
if L == nil {
t.Fatal("Failed to create Lua state")
}
defer L.Close()
defer L.Cleanup()
// Create a simple render.template function
renderFunc := func(s *State) int {
// Template will be at index 1, data at index 2
data, err := s.ToTable(2)
if err != nil {
s.PushString(fmt.Sprintf("failed to get data table: %v", err))
return -1
}
// Push data back as global for template access
if err := s.PushTable(data); err != nil {
s.PushString(fmt.Sprintf("failed to push data table: %v", err))
return -1
}
s.SetGlobal("data")
// Template processing code
luaCode := `
local result = {}
if data.user.logged_in then
table.insert(result, '<div class="profile">')
table.insert(result, string.format(' <h1>Welcome, %s!</h1>', tostring(data.user.name)))
table.insert(result, ' <div class="user-info">')
table.insert(result, string.format(' <p>Email: %s</p>', tostring(data.user.email)))
table.insert(result, string.format(' <p>Member since: %s</p>', tostring(data.user.joined_date)))
table.insert(result, ' </div>')
if data.user.is_admin then
table.insert(result, ' <div class="admin-panel">')
table.insert(result, ' <h2>Admin Controls</h2>')
table.insert(result, ' <!-- admin content -->')
table.insert(result, ' </div>')
end
table.insert(result, '</div>')
else
table.insert(result, '<div class="profile">')
table.insert(result, ' <h1>Please log in to view your profile</h1>')
table.insert(result, '</div>')
end
return table.concat(result, '\n')`
result, err := s.DoStringResult(luaCode)
if err != nil {
s.PushString(fmt.Sprintf("template execution failed: %v", err))
return -1
}
// Push the string result
if str, ok := result.(string); ok {
s.PushString(str)
return 1
}
s.PushString(fmt.Sprintf("expected string result, got %T", result))
return -1
}
// Create render table and add template function
L.NewTable()
if err := L.PushGoFunction(renderFunc); err != nil {
t.Fatalf("Failed to create render function: %v", err)
}
L.SetField(-2, "template")
L.SetGlobal("render")
// Test with logged in admin user
testCode := `
local data = {
user = {
logged_in = true,
name = "John Doe",
email = "john@example.com",
joined_date = "2024-02-09",
is_admin = true
}
}
return render.template("test.html", data)
`
result, err := L.DoStringResult(testCode)
if err != nil {
t.Fatalf("Failed to execute test: %v", err)
}
str, ok := result.(string)
if !ok {
t.Fatalf("Expected string result, got %T", result)
}
expectedResult := `<div class="profile">
<h1>Welcome, John Doe!</h1>
<div class="user-info">
<p>Email: john@example.com</p>
<p>Member since: 2024-02-09</p>
</div>
<div class="admin-panel">
<h2>Admin Controls</h2>
<!-- admin content -->
</div>
</div>`
if str != expectedResult {
t.Errorf("\nExpected:\n%s\n\nGot:\n%s", expectedResult, str)
}
// Test with logged out user
testCode = `
local data = {
user = {
logged_in = false
}
}
return render.template("test.html", data)
`
result, err = L.DoStringResult(testCode)
if err != nil {
t.Fatalf("Failed to execute logged out test: %v", err)
}
str, ok = result.(string)
if !ok {
t.Fatalf("Expected string result, got %T", result)
}
expectedResult = `<div class="profile">
<h1>Please log in to view your profile</h1>
</div>`
if str != expectedResult {
t.Errorf("\nExpected:\n%s\n\nGot:\n%s", expectedResult, str)
}
}