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) if type(x) ~= "number" then error("type missmatch") 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) != 3 { 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") }