694 lines
16 KiB
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)
|
|
}
|
|
}
|