mirror of
https://github.com/vxcontrol/golua.git
synced 2026-07-01 22:14:01 -04:00
866 lines
18 KiB
Go
866 lines
18 KiB
Go
package lua
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
type TestStruct struct {
|
|
IntField int
|
|
StringField string
|
|
FloatField float64
|
|
}
|
|
|
|
func TestGoStruct(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
ts := &TestStruct{10, "test", 2.3}
|
|
|
|
L.CheckStack(1)
|
|
|
|
L.PushGoStruct(ts)
|
|
L.SetGlobal("t")
|
|
|
|
L.GetGlobal("t")
|
|
if !L.IsGoStruct(-1) {
|
|
t.Fatal("Not go struct")
|
|
}
|
|
|
|
tsr := L.ToGoStruct(-1).(*TestStruct)
|
|
if tsr != ts {
|
|
t.Fatal("Retrieved something different from what we inserted")
|
|
}
|
|
|
|
L.Pop(1)
|
|
|
|
L.PushString("This is not a struct")
|
|
if L.ToGoStruct(-1) != nil {
|
|
t.Fatal("Non-GoStruct value attempted to convert into GoStruct should result in nil")
|
|
}
|
|
|
|
L.Pop(1)
|
|
}
|
|
|
|
func TestCheckStringSuccess(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
Test := func(L *State) int {
|
|
L.PushString("this is a test")
|
|
L.CheckString(-1)
|
|
return 0
|
|
}
|
|
|
|
L.Register("test", Test)
|
|
err := L.DoString("test()")
|
|
if err != nil {
|
|
t.Fatalf("DoString did return an error: %v\n", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestCheckStringFail(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
Test := func(L *State) int {
|
|
L.CheckString(-1)
|
|
return 0
|
|
}
|
|
|
|
L.Register("test", Test)
|
|
err := L.DoString("test();")
|
|
if err == nil {
|
|
t.Fatal("DoString did not return an error\n")
|
|
}
|
|
}
|
|
|
|
// jea: works under OpenLibs, because generally
|
|
// we will need pcall/xpcall.
|
|
//
|
|
// See https://github.com/vxcontrol/golua#on-error-handling
|
|
// for why they are hidden. We should probably
|
|
// be hiding these then using unsafe_pcall, and
|
|
// verify that Lua code using them doesn't call
|
|
// back into Go code that panics.
|
|
/*
|
|
Shortened version of the on-error-handling link:
|
|
|
|
Lua's exceptions are incompatible with Go.
|
|
golua works around this incompatibility by
|
|
setting up protected execution environments
|
|
in lua.State.DoString, lua.State.DoFile, and
|
|
lua.State.Call and turning every exception
|
|
into a Go panic.
|
|
|
|
This means that:
|
|
|
|
In general you can't do any exception handling
|
|
from Lua, pcall and xpcall are renamed to
|
|
unsafe_pcall and unsafe_xpcall. They are only
|
|
safe to be called from Lua code that never
|
|
calls back to Go. Use at your own risk.
|
|
*/
|
|
func TestPCallHidden(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
err := L.DoString("pcall(print, \"ciao\")")
|
|
if err == nil {
|
|
t.Fatal("Can use pcall\n")
|
|
}
|
|
|
|
err = L.DoString("unsafe_pcall(print, \"ciao\")")
|
|
if err != nil {
|
|
t.Fatal("Can not use unsafe_pcall\n")
|
|
}
|
|
}
|
|
|
|
func TestCall(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
test := func(L *State) int {
|
|
arg1 := L.ToString(1)
|
|
arg2 := L.ToString(2)
|
|
arg3 := L.ToString(3)
|
|
|
|
if arg1 != "Argument1" {
|
|
t.Fatal("Got wrong argument (1)")
|
|
}
|
|
|
|
if arg2 != "Argument2" {
|
|
t.Fatal("Got wrong argument (2)")
|
|
}
|
|
|
|
if arg3 != "Argument3" {
|
|
t.Fatal("Got wrong argument (3)")
|
|
}
|
|
|
|
L.PushString("Return1")
|
|
L.PushString("Return2")
|
|
|
|
return 2
|
|
}
|
|
|
|
L.Register("test", test)
|
|
|
|
L.PushString("Dummy")
|
|
L.GetGlobal("test")
|
|
L.PushString("Argument1")
|
|
L.PushString("Argument2")
|
|
L.PushString("Argument3")
|
|
err := L.Call(3, 2)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error executing call: %v\n", err)
|
|
}
|
|
|
|
dummy := L.ToString(1)
|
|
ret1 := L.ToString(2)
|
|
ret2 := L.ToString(3)
|
|
|
|
if dummy != "Dummy" {
|
|
t.Fatal("The stack was disturbed")
|
|
}
|
|
|
|
if ret1 != "Return1" {
|
|
t.Fatalf("Wrong return value (1) got: <%s>", ret1)
|
|
}
|
|
|
|
if ret2 != "Return2" {
|
|
t.Fatalf("Wrong return value (2) got: <%s>", ret2)
|
|
}
|
|
}
|
|
|
|
// equivalent to basic.go
|
|
func TestLikeBasic(t *testing.T) {
|
|
L := NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
testCalled := 0
|
|
|
|
test := func(L *State) int {
|
|
testCalled++
|
|
return 0
|
|
}
|
|
|
|
test2Arg := -1
|
|
test2Argfrombottom := -1
|
|
test2 := func(L *State) int {
|
|
test2Arg = L.CheckInteger(-1)
|
|
test2Argfrombottom = L.CheckInteger(1)
|
|
return 0
|
|
}
|
|
|
|
L.GetGlobal("print")
|
|
L.PushString("Hello World!")
|
|
if err := L.Call(1, 0); err != nil {
|
|
t.Fatalf("Call to print returned error")
|
|
}
|
|
|
|
L.PushGoFunction(test)
|
|
L.PushGoFunction(test)
|
|
L.PushGoFunction(test)
|
|
L.PushGoFunction(test2)
|
|
L.PushInteger(42)
|
|
if err := L.Call(1, 0); err != nil {
|
|
t.Fatalf("Call to print returned error")
|
|
}
|
|
if (test2Arg != 42) || (test2Argfrombottom != 42) {
|
|
t.Fatalf("Call to test2 didn't work")
|
|
}
|
|
|
|
if err := L.Call(0, 0); err != nil {
|
|
t.Fatalf("Call to print returned error")
|
|
}
|
|
if err := L.Call(0, 0); err != nil {
|
|
t.Fatalf("Call to print returned error")
|
|
}
|
|
if err := L.Call(0, 0); err != nil {
|
|
t.Fatalf("Call to print returned error")
|
|
}
|
|
if testCalled != 3 {
|
|
t.Fatalf("Test function not called the correct number of times: %d\n", testCalled)
|
|
}
|
|
|
|
// this will fail as we didn't register test2 function
|
|
if err := L.DoString("test2(42)"); err == nil {
|
|
t.Fatal("No error when calling unregistered function")
|
|
}
|
|
}
|
|
|
|
// equivalent to quickstart.go
|
|
func TestLikeQuickstart(t *testing.T) {
|
|
adder := func(L *State) int {
|
|
a := L.ToInteger(1)
|
|
b := L.ToInteger(2)
|
|
L.PushInteger(int64(a + b))
|
|
return 1
|
|
}
|
|
|
|
L := NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
L.Register("adder", adder)
|
|
|
|
if err := L.DoString("return adder(2, 2)"); err != nil {
|
|
t.Fatalf("Error during call to adder: %v\n", err)
|
|
}
|
|
if r := L.ToInteger(1); r != 4 {
|
|
t.Fatalf("Wrong return value from adder (was: %d)\n", r)
|
|
}
|
|
}
|
|
|
|
// equivalent to userdata.go
|
|
func TestLikeUserdata(t *testing.T) {
|
|
type Userdata struct {
|
|
a, b int
|
|
}
|
|
|
|
userDataProper := func(L *State) {
|
|
rawptr := L.NewUserdata(uintptr(unsafe.Sizeof(Userdata{})))
|
|
ptr := (*Userdata)(rawptr)
|
|
ptr.a = 2
|
|
ptr.b = 3
|
|
|
|
rawptr2 := L.ToUserdata(-1)
|
|
ptr2 := (*Userdata)(rawptr2)
|
|
|
|
if ptr != ptr2 {
|
|
t.Fatalf("Failed to create userdata\n")
|
|
}
|
|
}
|
|
|
|
testCalled := 0
|
|
test := func(L *State) int {
|
|
testCalled++
|
|
return 0
|
|
}
|
|
|
|
goDefinedFunctions := func(L *State) {
|
|
// example_function is registered inside Lua VM
|
|
L.Register("test", test)
|
|
|
|
// This code demonstrates checking that a value on the stack is a go function
|
|
L.CheckStack(1)
|
|
L.GetGlobal("test")
|
|
if !L.IsGoFunction(-1) {
|
|
t.Fatalf("IsGoFunction failed to recognize a Go function object")
|
|
}
|
|
L.Pop(1)
|
|
|
|
// We call example_function from inside Lua VM
|
|
testCalled = 0
|
|
if err := L.DoString("test()"); err != nil {
|
|
t.Fatalf("Error executing test function: %v\n", err)
|
|
}
|
|
if testCalled != 1 {
|
|
t.Fatalf("It appears the test function wasn't actually called\n")
|
|
}
|
|
}
|
|
|
|
type TestObject struct {
|
|
AField int
|
|
}
|
|
|
|
goDefinedObjects := func(L *State) {
|
|
z := &TestObject{42}
|
|
|
|
L.PushGoStruct(z)
|
|
L.SetGlobal("z")
|
|
|
|
// This code demonstrates checking that a value on the stack is a go object
|
|
L.CheckStack(1)
|
|
L.GetGlobal("z")
|
|
if !L.IsGoStruct(-1) {
|
|
t.Fatal("IsGoStruct failed to recognize a Go struct\n")
|
|
}
|
|
L.Pop(1)
|
|
|
|
// This code demonstrates access and assignment to a field of a go object
|
|
if err := L.DoString("return z.AField"); err != nil {
|
|
t.Fatal("Couldn't execute code")
|
|
}
|
|
before := L.ToInteger(-1)
|
|
L.Pop(1)
|
|
if before != 42 {
|
|
t.Fatalf("Wrong value of z.AField before change (%d)\n", before)
|
|
}
|
|
if err := L.DoString("z.AField = 10;"); err != nil {
|
|
t.Fatal("Couldn't execute code")
|
|
}
|
|
if err := L.DoString("return z.AField"); err != nil {
|
|
t.Fatal("Couldn't execute code")
|
|
}
|
|
after := L.ToInteger(-1)
|
|
L.Pop(1)
|
|
if after != 10 {
|
|
t.Fatalf("Wrong value of z.AField after change (%d)\n", after)
|
|
}
|
|
}
|
|
|
|
L := NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
userDataProper(L)
|
|
goDefinedFunctions(L)
|
|
goDefinedObjects(L)
|
|
}
|
|
|
|
func TestStackTrace(t *testing.T) {
|
|
L := NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
err := L.DoFile("../_example/calls.lua")
|
|
if err == nil {
|
|
t.Fatal("No error returned from the execution of calls.lua")
|
|
}
|
|
|
|
le := err.(*LuaError)
|
|
|
|
if le.Code != LUA_ERRRUN {
|
|
t.Fatalf("Wrong kind of error encountered running calls.lua: %v (%d %d)\n", le, le.Code, LUA_ERRERR)
|
|
}
|
|
|
|
if len(le.LuaST) != 6 {
|
|
t.Fatalf("Wrong size of stack trace (%v)\n", le)
|
|
}
|
|
}
|
|
|
|
func TestConv(t *testing.T) {
|
|
L := NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
L.PushString("10")
|
|
n := L.ToNumber(-1)
|
|
if n != 10 {
|
|
t.Fatalf("Wrong conversion (str -> int)")
|
|
}
|
|
if L.Type(-1) != LUA_TSTRING {
|
|
t.Fatalf("Wrong type (str)")
|
|
}
|
|
|
|
L.Pop(1)
|
|
|
|
L.PushInteger(10)
|
|
s := L.ToString(-1)
|
|
if s != "10" {
|
|
t.Fatalf("Wrong conversion (int -> str)")
|
|
}
|
|
|
|
L.Pop(1)
|
|
|
|
L.PushString("a\000test")
|
|
s = L.ToString(-1)
|
|
if s != "a\000test" {
|
|
t.Fatalf("Wrong conversion (str -> str): <%s>", s)
|
|
}
|
|
}
|
|
|
|
func TestDumpAndLoad(t *testing.T) {
|
|
L := NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
loadret := L.LoadString(`print("msg from dump_and_load_test")`)
|
|
if loadret != 0 {
|
|
t.Fatalf("LoadString error: %v", loadret)
|
|
}
|
|
dumpret := L.Dump()
|
|
if dumpret != 0 {
|
|
t.Fatalf("Dump error: %v", dumpret)
|
|
}
|
|
|
|
isstring := L.IsString(-1)
|
|
if !isstring {
|
|
t.Fatalf("stack top not a string")
|
|
}
|
|
bytecodes := L.ToBytes(-1)
|
|
loadret = L.Load(bytecodes, "chunk_from_dump_and_load_test")
|
|
if loadret != 0 {
|
|
t.Fatalf("Load error: %v", loadret)
|
|
}
|
|
err := L.Call(0, 0)
|
|
if err != nil {
|
|
t.Fatalf("Call error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCustomDebugHook(t *testing.T) {
|
|
L := NewState()
|
|
defer L.Close()
|
|
|
|
L.SetHook(func(l *State) {
|
|
l.RaiseError("stop")
|
|
}, 1)
|
|
|
|
err := L.DoString(`
|
|
local x = 0
|
|
while(1 ~= 0) do
|
|
x = 2
|
|
end
|
|
`)
|
|
|
|
if err == nil {
|
|
t.Fatalf("Script should have raised an error")
|
|
} else {
|
|
le := err.(*LuaError)
|
|
if le.Code != LUA_ERRRUN {
|
|
t.Fatalf("Wrong kind of lua error: %v (%d %d)\n", le, le.Code, LUA_ERRRUN)
|
|
}
|
|
if le.Msg != "stop" {
|
|
t.Fatalf("Error should be coming from the hook: %v", le)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunLuaCallback(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
var stCb *State
|
|
var refCb, refTr int
|
|
inc, rounds := 3, 100
|
|
regCb := func(L *State) int {
|
|
if !L.IsFunction(-1) {
|
|
t.Fatalf("Recived callback is not a lua function")
|
|
}
|
|
|
|
stCb = L.NewThread()
|
|
refTr = L.Ref(LUA_REGISTRYINDEX)
|
|
refCb = L.Ref(LUA_REGISTRYINDEX)
|
|
|
|
return 0
|
|
}
|
|
|
|
L.Register("reg_cb", regCb)
|
|
code := `r = 0
|
|
reg_cb(
|
|
function(x)
|
|
r = r + x
|
|
return r
|
|
end
|
|
)`
|
|
|
|
// This code demonstrates checking that a value on the stack is a go function
|
|
L.CheckStack(1)
|
|
L.GetGlobal("reg_cb")
|
|
if !L.IsGoFunction(-1) {
|
|
t.Fatalf("IsGoFunction failed to recognize a Go function object")
|
|
}
|
|
L.Pop(1)
|
|
|
|
// We call example_function from inside Lua VM
|
|
if err := L.DoString(code); err != nil {
|
|
t.Fatalf("Error executing main function: %v", err)
|
|
}
|
|
|
|
if stCb == nil {
|
|
t.Fatalf("Lua callback is not set")
|
|
}
|
|
|
|
for i := 0; i < rounds; i++ {
|
|
ttop := stCb.GetTop()
|
|
stCb.RawGeti(LUA_REGISTRYINDEX, refCb)
|
|
stCb.PushNumber(float64(inc))
|
|
if err := stCb.Call(1, 1); err != nil {
|
|
t.Fatalf("Error executing callback function: %v", err)
|
|
}
|
|
if !stCb.IsNumber(-1) {
|
|
t.Fatalf("Recived result from callback is not a number")
|
|
}
|
|
cbRes := stCb.ToNumber(-1)
|
|
if cbRes != float64(inc*(i+1)) {
|
|
t.Fatalf("Expected callback result is not match value: %0.f", cbRes)
|
|
}
|
|
stCb.SetTop(ttop)
|
|
}
|
|
|
|
L.Unref(LUA_REGISTRYINDEX, refCb)
|
|
L.Unref(LUA_REGISTRYINDEX, refTr)
|
|
|
|
L.GetGlobal("r")
|
|
top := L.GetTop()
|
|
obsR := L.ToNumber(top)
|
|
if obsR != float64(inc*rounds) {
|
|
t.Fatalf("Result is not expected %v", obsR)
|
|
}
|
|
L.Pop(-1)
|
|
}
|
|
|
|
func TestRunLuaCallbackFailFromLuaCode(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
var stCb *State
|
|
var refCb, refTr int
|
|
regCb := func(L *State) int {
|
|
if !L.IsFunction(-1) {
|
|
t.Fatalf("Recived callback is not a lua function")
|
|
}
|
|
|
|
stCb = L.NewThread()
|
|
refTr = L.Ref(LUA_REGISTRYINDEX)
|
|
refCb = L.Ref(LUA_REGISTRYINDEX)
|
|
|
|
return 0
|
|
}
|
|
|
|
L.Register("reg_cb", regCb)
|
|
code := `reg_cb(
|
|
function(x)
|
|
for t=1,x do
|
|
print(t)
|
|
end
|
|
return x
|
|
end
|
|
)`
|
|
|
|
// This code demonstrates checking that a value on the stack is a go function
|
|
L.CheckStack(1)
|
|
L.GetGlobal("reg_cb")
|
|
if !L.IsGoFunction(-1) {
|
|
t.Fatalf("IsGoFunction failed to recognize a Go function object")
|
|
}
|
|
L.Pop(1)
|
|
|
|
// We call example_function from inside Lua VM
|
|
if err := L.DoString(code); err != nil {
|
|
t.Fatalf("Error executing main function: %v", err)
|
|
}
|
|
|
|
if stCb == nil {
|
|
t.Fatalf("Lua callback is not set")
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
stCb.RawGeti(LUA_REGISTRYINDEX, refCb)
|
|
if i%2 == 0 {
|
|
stCb.PushNumber(3)
|
|
} else {
|
|
stCb.PushString("abc")
|
|
}
|
|
if err := stCb.Call(1, 1); err == nil && i%2 == 1 {
|
|
t.Fatal("Call did not return an error")
|
|
} else if err != nil {
|
|
le := err.(*LuaError)
|
|
|
|
if le.Code != LUA_ERRRUN {
|
|
t.Fatalf("Wrong kind of lua error: %v (%d %d)\n", le, le.Code, LUA_ERRRUN)
|
|
}
|
|
if len(le.LuaST) != 2 {
|
|
t.Fatalf("Wrong amount of lines in stacktrace: %v (%d)\n", le, len(le.LuaST))
|
|
}
|
|
}
|
|
stCb.Pop(-1)
|
|
}
|
|
|
|
L.Unref(LUA_REGISTRYINDEX, refCb)
|
|
L.Unref(LUA_REGISTRYINDEX, refTr)
|
|
}
|
|
|
|
func TestRunLuaCallbackFailFromGoCode(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
var stCb *State
|
|
var refCb, refTr int
|
|
regCb := func(L *State) int {
|
|
if !L.IsFunction(-1) {
|
|
t.Fatalf("Recived callback is not a lua function")
|
|
}
|
|
|
|
stCb = L.NewThread()
|
|
refTr = L.Ref(LUA_REGISTRYINDEX)
|
|
refCb = L.Ref(LUA_REGISTRYINDEX)
|
|
|
|
return 0
|
|
}
|
|
test := func(L *State) int {
|
|
L.CheckNumber(-1)
|
|
return 0
|
|
}
|
|
|
|
L.Register("reg_cb", regCb)
|
|
code := `reg_cb(
|
|
function(x)
|
|
test(x)
|
|
return x
|
|
end
|
|
)`
|
|
|
|
// This code demonstrates checking that a value on the stack is a go function
|
|
L.CheckStack(1)
|
|
L.GetGlobal("reg_cb")
|
|
if !L.IsGoFunction(-1) {
|
|
t.Fatalf("IsGoFunction failed to recognize a Go function object")
|
|
}
|
|
L.Pop(1)
|
|
|
|
// We call example_function from inside Lua VM
|
|
if err := L.DoString(code); err != nil {
|
|
t.Fatalf("Error executing main function: %v", err)
|
|
}
|
|
|
|
if stCb == nil {
|
|
t.Fatalf("Lua callback is not set")
|
|
}
|
|
|
|
stCb.Register("test", test)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
stCb.RawGeti(LUA_REGISTRYINDEX, refCb)
|
|
if i%2 == 0 {
|
|
stCb.PushNumber(3)
|
|
} else {
|
|
stCb.PushString("abc")
|
|
}
|
|
if err := stCb.Call(1, 1); err == nil && i%2 == 1 {
|
|
t.Fatal("Call did not return an error")
|
|
} else if err != nil {
|
|
le := err.(*LuaError)
|
|
|
|
if le.Code != LUA_ERRRUN {
|
|
t.Fatalf("Wrong kind of lua error: %v (%d %d)\n", le, le.Code, LUA_ERRRUN)
|
|
}
|
|
// TODO: it's a big problem that previously calls are keeping on the stack
|
|
if len(le.LuaST)%2 != 0 {
|
|
t.Fatalf("Wrong amount of lines in stacktrace: %v (%d)\n", le, len(le.LuaST))
|
|
}
|
|
}
|
|
stCb.Pop(-1)
|
|
}
|
|
|
|
L.Unref(LUA_REGISTRYINDEX, refCb)
|
|
L.Unref(LUA_REGISTRYINDEX, refTr)
|
|
}
|
|
|
|
func TestCoroutineRunning(t *testing.T) {
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
butterCalled := 0
|
|
butter := func(L *State) int {
|
|
butterCalled++
|
|
tot := 0.0
|
|
tot += L.ToNumber(-1)
|
|
tot += L.ToNumber(-2)
|
|
tot += L.ToNumber(-3)
|
|
L.Pop(3)
|
|
L.PushNumber(tot)
|
|
return 1
|
|
}
|
|
|
|
L.Register("butter", butter)
|
|
|
|
// This code demonstrates checking that a value on the stack is a go function
|
|
L.CheckStack(1)
|
|
L.GetGlobal("butter")
|
|
if !L.IsGoFunction(-1) {
|
|
t.Fatalf("IsGoFunction failed to recognize a Go function object")
|
|
}
|
|
L.Pop(1)
|
|
|
|
// We call example_function from inside Lua VM
|
|
butterCalled = 0
|
|
if err := L.DoString("a = {coroutine.resume(coroutine.create(function() return butter(4,5,6); end))}; for k,v in pairs(a) do print('a.key= ',k, ' value:', v); end; b = a[2]"); err != nil {
|
|
t.Fatalf("Error executing butter function: %v\n", err)
|
|
}
|
|
if butterCalled != 1 {
|
|
t.Fatalf("It appears the butter function wasn't actually called\n")
|
|
}
|
|
|
|
L.GetGlobal("b")
|
|
top := L.GetTop()
|
|
obsB := L.ToNumber(top)
|
|
if obsB != 15.0 {
|
|
t.Fatalf("butter() summing 4+5+6 should have given 15, but instead got %v\n", obsB)
|
|
}
|
|
}
|
|
|
|
func TestLuaRegsitryIsPerState(t *testing.T) {
|
|
// We test the assumption
|
|
// that the Lua Registry is shared by all
|
|
// coroutines within a main C.lua State.
|
|
// However two different C.lua_States
|
|
// are expected to have distinct registries.
|
|
//
|
|
// If validated, we'll use this fact to
|
|
// store a pointer to the main C.lua_State
|
|
// in the registry, and have all
|
|
// coroutines use the key to find their
|
|
// main state.
|
|
//
|
|
// Result: the assumption was confirmed.
|
|
// The registry is distinct per main State,
|
|
// and shared by coroutines within one state.
|
|
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
L2 := NewState()
|
|
L2.OpenLibs()
|
|
defer L2.Close()
|
|
|
|
key := "lua_test.my_registry_key"
|
|
val := "lua_test.my_value_for_testing"
|
|
L.PushString(key)
|
|
L.PushString(val)
|
|
L.SetTable(LUA_REGISTRYINDEX)
|
|
top := L.GetTop()
|
|
if top != 0 {
|
|
panic("expected empty stack")
|
|
}
|
|
L.PushString(key)
|
|
L.GetTable(LUA_REGISTRYINDEX)
|
|
if L.IsNil(-1) {
|
|
panic("expected value back")
|
|
}
|
|
obsVal := L.ToString(-1)
|
|
if obsVal != val {
|
|
panic("expected obsVal to match val")
|
|
}
|
|
// good: retreived val from L registry
|
|
L.Pop(1)
|
|
|
|
// now query the L2 registry
|
|
L2.PushString(key)
|
|
L2.GetTable(LUA_REGISTRYINDEX)
|
|
if !L2.IsNil(-1) {
|
|
fmt.Printf("bad, expected nil, got: '%s'\n", L2.LuaStackPosToString(-1))
|
|
panic("expected nil back when querying L2 registry for key")
|
|
}
|
|
// good: did not retreived val from L2 registry under key
|
|
|
|
// now check that a new coroutine in L sees the same registry.
|
|
L3 := L.NewThread()
|
|
L3.PushString(key)
|
|
L3.GetTable(LUA_REGISTRYINDEX)
|
|
if L3.IsNil(-1) {
|
|
panic("expected value back")
|
|
}
|
|
obsVal3 := L3.ToString(-1)
|
|
if obsVal3 != val {
|
|
panic("expected obsVal3 to match val")
|
|
}
|
|
// good: retreived val from L3 registry
|
|
}
|
|
|
|
func assert(t *testing.T, b bool, msg string) {
|
|
if !b {
|
|
t.Fatal(msg)
|
|
}
|
|
}
|
|
|
|
func TestToThreadDeduplicatesCoroutines(t *testing.T) {
|
|
// when ToThread encounters the same coroutine
|
|
// again, it should return the prior *State, and
|
|
// not generate a new wrapper for the same coroutine.
|
|
|
|
L := NewState()
|
|
L.OpenLibs()
|
|
defer L.Close()
|
|
|
|
isMain := L.PushThread()
|
|
if !isMain {
|
|
t.Fatal("should have gotten isMain true!")
|
|
}
|
|
thr := L.ToThread(-1)
|
|
if thr != L {
|
|
t.Fatal("ToThread should dedup")
|
|
}
|
|
|
|
L2 := NewState()
|
|
L2.OpenLibs()
|
|
defer L2.Close()
|
|
|
|
L2.PushThread()
|
|
thr2 := L2.ToThread(-1)
|
|
if thr2 != L2 {
|
|
t.Fatal("ToThread should dedup L2")
|
|
}
|
|
|
|
if thr == thr2 {
|
|
t.Fatal("thr should not equal thr2")
|
|
}
|
|
|
|
// make some new coroutines, make sure they are deduped.
|
|
co2b := L2.NewThread()
|
|
if co2b == thr2 || co2b == thr {
|
|
t.Fatal("co2b should not equal thr2 or thr")
|
|
}
|
|
|
|
co2b_isMain := co2b.PushThread()
|
|
if co2b.ToThread(-1) != co2b {
|
|
t.Fatal("co2b was not deduped!")
|
|
}
|
|
assert(t, !co2b_isMain, "co2b_isMain should not have been a main thread!")
|
|
|
|
// coroutines from Lua first
|
|
err := co2b.DoString("a = 1; return coroutine.create(function() return a; end)")
|
|
if err != nil {
|
|
t.Fatalf("DoString returned an error: %v\n", err)
|
|
}
|
|
|
|
thr4 := co2b.ToThread(-1)
|
|
assert(t, thr4.AllCoro == nil, "non-main coroutines should have nil AllCoro maps")
|
|
assert(t, L2.AllCoro[thr4.Upos] == thr4, "thr4 should be found in L2's AllCoro, at Upos")
|
|
}
|