mirror of https://github.com/tidwall/tile38.git
434 lines
11 KiB
Go
434 lines
11 KiB
Go
|
package lua
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func TestCallStackOverflow(t *testing.T) {
|
||
|
L := NewState(Options{
|
||
|
CallStackSize: 3,
|
||
|
})
|
||
|
defer L.Close()
|
||
|
errorIfScriptNotFail(t, L, `
|
||
|
local function a()
|
||
|
end
|
||
|
local function b()
|
||
|
a()
|
||
|
end
|
||
|
local function c()
|
||
|
print(_printregs())
|
||
|
b()
|
||
|
end
|
||
|
c()
|
||
|
`, "stack overflow")
|
||
|
}
|
||
|
|
||
|
func TestSkipOpenLibs(t *testing.T) {
|
||
|
L := NewState(Options{SkipOpenLibs: true})
|
||
|
defer L.Close()
|
||
|
errorIfScriptNotFail(t, L, `print("")`,
|
||
|
"attempt to call a non-function object")
|
||
|
L2 := NewState()
|
||
|
defer L2.Close()
|
||
|
errorIfScriptFail(t, L2, `print("")`)
|
||
|
}
|
||
|
|
||
|
func TestGetAndReplace(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LString("a"))
|
||
|
L.Replace(1, LString("b"))
|
||
|
L.Replace(0, LString("c"))
|
||
|
errorIfNotEqual(t, LNil, L.Get(0))
|
||
|
errorIfNotEqual(t, LNil, L.Get(-10))
|
||
|
errorIfNotEqual(t, L.Env, L.Get(EnvironIndex))
|
||
|
errorIfNotEqual(t, LString("b"), L.Get(1))
|
||
|
L.Push(LString("c"))
|
||
|
L.Push(LString("d"))
|
||
|
L.Replace(-2, LString("e"))
|
||
|
errorIfNotEqual(t, LString("e"), L.Get(-2))
|
||
|
registry := L.NewTable()
|
||
|
L.Replace(RegistryIndex, registry)
|
||
|
L.G.Registry = registry
|
||
|
errorIfGFuncNotFail(t, L, func(L *LState) int {
|
||
|
L.Replace(RegistryIndex, LNil)
|
||
|
return 0
|
||
|
}, "registry must be a table")
|
||
|
errorIfGFuncFail(t, L, func(L *LState) int {
|
||
|
env := L.NewTable()
|
||
|
L.Replace(EnvironIndex, env)
|
||
|
errorIfNotEqual(t, env, L.Get(EnvironIndex))
|
||
|
return 0
|
||
|
})
|
||
|
errorIfGFuncNotFail(t, L, func(L *LState) int {
|
||
|
L.Replace(EnvironIndex, LNil)
|
||
|
return 0
|
||
|
}, "environment must be a table")
|
||
|
errorIfGFuncFail(t, L, func(L *LState) int {
|
||
|
gbl := L.NewTable()
|
||
|
L.Replace(GlobalsIndex, gbl)
|
||
|
errorIfNotEqual(t, gbl, L.G.Global)
|
||
|
return 0
|
||
|
})
|
||
|
errorIfGFuncNotFail(t, L, func(L *LState) int {
|
||
|
L.Replace(GlobalsIndex, LNil)
|
||
|
return 0
|
||
|
}, "_G must be a table")
|
||
|
|
||
|
L2 := NewState()
|
||
|
defer L2.Close()
|
||
|
clo := L2.NewClosure(func(L2 *LState) int {
|
||
|
L2.Replace(UpvalueIndex(1), LNumber(3))
|
||
|
errorIfNotEqual(t, LNumber(3), L2.Get(UpvalueIndex(1)))
|
||
|
return 0
|
||
|
}, LNumber(1), LNumber(2))
|
||
|
L2.SetGlobal("clo", clo)
|
||
|
errorIfScriptFail(t, L2, `clo()`)
|
||
|
}
|
||
|
|
||
|
func TestRemove(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LString("a"))
|
||
|
L.Push(LString("b"))
|
||
|
L.Push(LString("c"))
|
||
|
|
||
|
L.Remove(4)
|
||
|
errorIfNotEqual(t, LString("a"), L.Get(1))
|
||
|
errorIfNotEqual(t, LString("b"), L.Get(2))
|
||
|
errorIfNotEqual(t, LString("c"), L.Get(3))
|
||
|
errorIfNotEqual(t, 3, L.GetTop())
|
||
|
|
||
|
L.Remove(3)
|
||
|
errorIfNotEqual(t, LString("a"), L.Get(1))
|
||
|
errorIfNotEqual(t, LString("b"), L.Get(2))
|
||
|
errorIfNotEqual(t, LNil, L.Get(3))
|
||
|
errorIfNotEqual(t, 2, L.GetTop())
|
||
|
L.Push(LString("c"))
|
||
|
|
||
|
L.Remove(-10)
|
||
|
errorIfNotEqual(t, LString("a"), L.Get(1))
|
||
|
errorIfNotEqual(t, LString("b"), L.Get(2))
|
||
|
errorIfNotEqual(t, LString("c"), L.Get(3))
|
||
|
errorIfNotEqual(t, 3, L.GetTop())
|
||
|
|
||
|
L.Remove(2)
|
||
|
errorIfNotEqual(t, LString("a"), L.Get(1))
|
||
|
errorIfNotEqual(t, LString("c"), L.Get(2))
|
||
|
errorIfNotEqual(t, LNil, L.Get(3))
|
||
|
errorIfNotEqual(t, 2, L.GetTop())
|
||
|
}
|
||
|
|
||
|
func TestToInt(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewTable())
|
||
|
errorIfNotEqual(t, 10, L.ToInt(1))
|
||
|
errorIfNotEqual(t, 99, L.ToInt(2))
|
||
|
errorIfNotEqual(t, 0, L.ToInt(3))
|
||
|
}
|
||
|
|
||
|
func TestToInt64(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewTable())
|
||
|
errorIfNotEqual(t, int64(10), L.ToInt64(1))
|
||
|
errorIfNotEqual(t, int64(99), L.ToInt64(2))
|
||
|
errorIfNotEqual(t, int64(0), L.ToInt64(3))
|
||
|
}
|
||
|
|
||
|
func TestToNumber(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewTable())
|
||
|
errorIfNotEqual(t, LNumber(10), L.ToNumber(1))
|
||
|
errorIfNotEqual(t, LNumber(99.9), L.ToNumber(2))
|
||
|
errorIfNotEqual(t, LNumber(0), L.ToNumber(3))
|
||
|
}
|
||
|
|
||
|
func TestToString(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewTable())
|
||
|
errorIfNotEqual(t, "10", L.ToString(1))
|
||
|
errorIfNotEqual(t, "99.9", L.ToString(2))
|
||
|
errorIfNotEqual(t, "", L.ToString(3))
|
||
|
}
|
||
|
|
||
|
func TestToTable(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewTable())
|
||
|
errorIfFalse(t, L.ToTable(1) == nil, "index 1 must be nil")
|
||
|
errorIfFalse(t, L.ToTable(2) == nil, "index 2 must be nil")
|
||
|
errorIfNotEqual(t, L.Get(3), L.ToTable(3))
|
||
|
}
|
||
|
|
||
|
func TestToFunction(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewFunction(func(L *LState) int { return 0 }))
|
||
|
errorIfFalse(t, L.ToFunction(1) == nil, "index 1 must be nil")
|
||
|
errorIfFalse(t, L.ToFunction(2) == nil, "index 2 must be nil")
|
||
|
errorIfNotEqual(t, L.Get(3), L.ToFunction(3))
|
||
|
}
|
||
|
|
||
|
func TestToUserData(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
L.Push(L.NewUserData())
|
||
|
errorIfFalse(t, L.ToUserData(1) == nil, "index 1 must be nil")
|
||
|
errorIfFalse(t, L.ToUserData(2) == nil, "index 2 must be nil")
|
||
|
errorIfNotEqual(t, L.Get(3), L.ToUserData(3))
|
||
|
}
|
||
|
|
||
|
func TestToChannel(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Push(LNumber(10))
|
||
|
L.Push(LString("99.9"))
|
||
|
var ch chan LValue
|
||
|
L.Push(LChannel(ch))
|
||
|
errorIfFalse(t, L.ToChannel(1) == nil, "index 1 must be nil")
|
||
|
errorIfFalse(t, L.ToChannel(2) == nil, "index 2 must be nil")
|
||
|
errorIfNotEqual(t, ch, L.ToChannel(3))
|
||
|
}
|
||
|
|
||
|
func TestObjLen(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
errorIfNotEqual(t, 3, L.ObjLen(LString("abc")))
|
||
|
tbl := L.NewTable()
|
||
|
tbl.Append(LTrue)
|
||
|
tbl.Append(LTrue)
|
||
|
errorIfNotEqual(t, 2, L.ObjLen(tbl))
|
||
|
mt := L.NewTable()
|
||
|
L.SetField(mt, "__len", L.NewFunction(func(L *LState) int {
|
||
|
tbl := L.CheckTable(1)
|
||
|
L.Push(LNumber(tbl.Len() + 1))
|
||
|
return 1
|
||
|
}))
|
||
|
L.SetMetatable(tbl, mt)
|
||
|
errorIfNotEqual(t, 3, L.ObjLen(tbl))
|
||
|
errorIfNotEqual(t, 0, L.ObjLen(LNumber(10)))
|
||
|
}
|
||
|
|
||
|
func TestConcat(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
errorIfNotEqual(t, "a1c", L.Concat(LString("a"), LNumber(1), LString("c")))
|
||
|
}
|
||
|
|
||
|
func TestPCall(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
L.Register("f1", func(L *LState) int {
|
||
|
panic("panic!")
|
||
|
return 0
|
||
|
})
|
||
|
errorIfScriptNotFail(t, L, `f1()`, "panic!")
|
||
|
L.Push(L.GetGlobal("f1"))
|
||
|
err := L.PCall(0, 0, L.NewFunction(func(L *LState) int {
|
||
|
L.Push(LString("by handler"))
|
||
|
return 1
|
||
|
}))
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "by handler"), "")
|
||
|
|
||
|
err = L.PCall(0, 0, L.NewFunction(func(L *LState) int {
|
||
|
L.RaiseError("error!")
|
||
|
return 1
|
||
|
}))
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "error!"), "")
|
||
|
|
||
|
err = L.PCall(0, 0, L.NewFunction(func(L *LState) int {
|
||
|
panic("panicc!")
|
||
|
return 1
|
||
|
}))
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "panicc!"), "")
|
||
|
}
|
||
|
|
||
|
func TestCoroutineApi1(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
co, _ := L.NewThread()
|
||
|
errorIfScriptFail(t, L, `
|
||
|
function coro(v)
|
||
|
assert(v == 10)
|
||
|
local ret1, ret2 = coroutine.yield(1,2,3)
|
||
|
assert(ret1 == 11)
|
||
|
assert(ret2 == 12)
|
||
|
coroutine.yield(4)
|
||
|
return 5
|
||
|
end
|
||
|
`)
|
||
|
fn := L.GetGlobal("coro").(*LFunction)
|
||
|
st, err, values := L.Resume(co, fn, LNumber(10))
|
||
|
errorIfNotEqual(t, ResumeYield, st)
|
||
|
errorIfNotNil(t, err)
|
||
|
errorIfNotEqual(t, 3, len(values))
|
||
|
errorIfNotEqual(t, LNumber(1), values[0].(LNumber))
|
||
|
errorIfNotEqual(t, LNumber(2), values[1].(LNumber))
|
||
|
errorIfNotEqual(t, LNumber(3), values[2].(LNumber))
|
||
|
|
||
|
st, err, values = L.Resume(co, fn, LNumber(11), LNumber(12))
|
||
|
errorIfNotEqual(t, ResumeYield, st)
|
||
|
errorIfNotNil(t, err)
|
||
|
errorIfNotEqual(t, 1, len(values))
|
||
|
errorIfNotEqual(t, LNumber(4), values[0].(LNumber))
|
||
|
|
||
|
st, err, values = L.Resume(co, fn)
|
||
|
errorIfNotEqual(t, ResumeOK, st)
|
||
|
errorIfNotNil(t, err)
|
||
|
errorIfNotEqual(t, 1, len(values))
|
||
|
errorIfNotEqual(t, LNumber(5), values[0].(LNumber))
|
||
|
|
||
|
L.Register("myyield", func(L *LState) int {
|
||
|
return L.Yield(L.ToNumber(1))
|
||
|
})
|
||
|
errorIfScriptFail(t, L, `
|
||
|
function coro_error()
|
||
|
coroutine.yield(1,2,3)
|
||
|
myyield(4)
|
||
|
assert(false, "--failed--")
|
||
|
end
|
||
|
`)
|
||
|
fn = L.GetGlobal("coro_error").(*LFunction)
|
||
|
co, _ = L.NewThread()
|
||
|
st, err, values = L.Resume(co, fn)
|
||
|
errorIfNotEqual(t, ResumeYield, st)
|
||
|
errorIfNotNil(t, err)
|
||
|
errorIfNotEqual(t, 3, len(values))
|
||
|
errorIfNotEqual(t, LNumber(1), values[0].(LNumber))
|
||
|
errorIfNotEqual(t, LNumber(2), values[1].(LNumber))
|
||
|
errorIfNotEqual(t, LNumber(3), values[2].(LNumber))
|
||
|
|
||
|
st, err, values = L.Resume(co, fn)
|
||
|
errorIfNotEqual(t, ResumeYield, st)
|
||
|
errorIfNotNil(t, err)
|
||
|
errorIfNotEqual(t, 1, len(values))
|
||
|
errorIfNotEqual(t, LNumber(4), values[0].(LNumber))
|
||
|
|
||
|
st, err, values = L.Resume(co, fn)
|
||
|
errorIfNotEqual(t, ResumeError, st)
|
||
|
errorIfNil(t, err)
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "--failed--"), "error message must be '--failed--'")
|
||
|
st, err, values = L.Resume(co, fn)
|
||
|
errorIfNotEqual(t, ResumeError, st)
|
||
|
errorIfNil(t, err)
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "can not resume a dead thread"), "can not resume a dead thread")
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestContextTimeout(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||
|
defer cancel()
|
||
|
L.SetContext(ctx)
|
||
|
errorIfNotEqual(t, ctx, L.Context())
|
||
|
err := L.DoString(`
|
||
|
local clock = os.clock
|
||
|
function sleep(n) -- seconds
|
||
|
local t0 = clock()
|
||
|
while clock() - t0 <= n do end
|
||
|
end
|
||
|
sleep(3)
|
||
|
`)
|
||
|
errorIfNil(t, err)
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "context deadline exceeded"), "execution must be canceled")
|
||
|
|
||
|
oldctx := L.RemoveContext()
|
||
|
errorIfNotEqual(t, ctx, oldctx)
|
||
|
errorIfNotNil(t, L.ctx)
|
||
|
}
|
||
|
|
||
|
func TestContextCancel(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
errch := make(chan error, 1)
|
||
|
L.SetContext(ctx)
|
||
|
go func() {
|
||
|
errch <- L.DoString(`
|
||
|
local clock = os.clock
|
||
|
function sleep(n) -- seconds
|
||
|
local t0 = clock()
|
||
|
while clock() - t0 <= n do end
|
||
|
end
|
||
|
sleep(3)
|
||
|
`)
|
||
|
}()
|
||
|
time.Sleep(1 * time.Second)
|
||
|
cancel()
|
||
|
err := <-errch
|
||
|
errorIfNil(t, err)
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "context canceled"), "execution must be canceled")
|
||
|
}
|
||
|
|
||
|
func TestContextWithCroutine(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
L.SetContext(ctx)
|
||
|
defer cancel()
|
||
|
L.DoString(`
|
||
|
function coro()
|
||
|
local i = 0
|
||
|
while true do
|
||
|
coroutine.yield(i)
|
||
|
i = i+1
|
||
|
end
|
||
|
return i
|
||
|
end
|
||
|
`)
|
||
|
co, cocancel := L.NewThread()
|
||
|
defer cocancel()
|
||
|
fn := L.GetGlobal("coro").(*LFunction)
|
||
|
_, err, values := L.Resume(co, fn)
|
||
|
errorIfNotNil(t, err)
|
||
|
errorIfNotEqual(t, LNumber(0), values[0])
|
||
|
// cancel the parent context
|
||
|
cancel()
|
||
|
_, err, values = L.Resume(co, fn)
|
||
|
errorIfNil(t, err)
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "context canceled"), "coroutine execution must be canceled when the parent context is canceled")
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestPCallAfterFail(t *testing.T) {
|
||
|
L := NewState()
|
||
|
defer L.Close()
|
||
|
errFn := L.NewFunction(func(L *LState) int {
|
||
|
L.RaiseError("error!")
|
||
|
return 0
|
||
|
})
|
||
|
changeError := L.NewFunction(func(L *LState) int {
|
||
|
L.Push(errFn)
|
||
|
err := L.PCall(0, 0, nil)
|
||
|
if err != nil {
|
||
|
L.RaiseError("A New Error")
|
||
|
}
|
||
|
return 0
|
||
|
})
|
||
|
L.Push(changeError)
|
||
|
err := L.PCall(0, 0, nil)
|
||
|
errorIfFalse(t, strings.Contains(err.Error(), "A New Error"), "error not propogated correctly")
|
||
|
}
|