-- testing debug library

local function dostring(s) return assert(loadstring(s))() end

print "testing debug library and debug information"

do
	local a = 1
end

function test(s, l, p)
	collectgarbage() -- avoid gc during trace
	local function f(event, line)
		assert(event == 'line')
		local l = table.remove(l, 1)
		if p then print(l, line) end
		assert(l == line, "wrong trace: expected " .. tostring(l) .. ", got " .. line)
	end

	debug.sethook(f, "l"); loadstring(s)(); debug.sethook()
	assert(table.getn(l) == 0)
end


do
	local a = debug.getinfo(print)
	assert(a.what == "C" and a.short_src == "[C]")
	local b = debug.getinfo(test, "SfL")
	assert(b.name == nil and b.what == "Lua" and b.linedefined == 11 and
			b.lastlinedefined == b.linedefined + 11 and
			b.func == test and not string.find(b.short_src, "%["))
	assert(b.activelines[b.linedefined + 1] and
			b.activelines[b.lastlinedefined])
	assert(not b.activelines[b.linedefined] and
			not b.activelines[b.lastlinedefined + 1])
end


-- test file and string names truncation
a = "function f () end"
local function dostring(s, x) return loadstring(s, x)() end

f = load("local a")
assert(debug.getinfo(f).short_src == '[string "local a"]', debug.getinfo(f).short_src)
dostring(a .. string.format("; %s\n=1", string.rep('p', 400)))
assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$'))
dostring("\n" .. a)
assert(debug.getinfo(f).short_src == '[string "..."]')
dostring(a, "")
assert(debug.getinfo(f).short_src == '[string ""]')
dostring(a, "@xuxu")
assert(debug.getinfo(f).short_src == "xuxu")
dostring(a, "@" .. string.rep('p', 1000) .. 't')
assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$"))
dostring(a, "=xuxu")
assert(debug.getinfo(f).short_src == "xuxu", debug.getinfo(f).short_src)
dostring(a, string.format("=%s", string.rep('x', 500)))
assert(string.find(debug.getinfo(f).short_src, "^x*"))
dostring(a, "=")
assert(debug.getinfo(f).short_src == "")
a = nil; f = nil;

repeat
	local g = {
		x = function()
			local a = debug.getinfo(2)
			assert(a.name == 'f' and a.namewhat == 'local')
			a = debug.getinfo(1)
			assert(a.name == 'x' and a.namewhat == 'field')
			return 'xixi'
		end
	}
	local f = function() return 1 + 1 and (not 1 or g.x()) end
	assert(f() == 'xixi')
	g = debug.getinfo(f)
	assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name)

	function f(x, name) -- local!
		name = name or 'f'
		local a = debug.getinfo(1)
		assert(a.name == name and a.namewhat == 'local')
		return x
	end

	-- breaks in different conditions
	if 3 > 4 then break end; f()
	if 3 < 4 then a = 1 else break end; f()
	while 1 do local x = 10; break end; f()
	local b = 1
	if 3 > 4 then return math.sin(1) end; f()
	a = 3 < 4; f()
	a = 3 < 4 or 1; f()
	repeat local x = 20; if 4 > 3 then f() else break end; f() until 1
	g = {}
	f(g).x = f(2) and f(10) + f(9)
	assert(g.x == f(19))
	function g(x) if not x then return 3 end return (x('a', 'x')) end

	assert(g(f) == 'a')
until 1

test([[if
math.sin(1)
then
  a=1
else
  a=2
end
]], { 2, 3, 4, 7 })

test([[--
if nil then
  a=1
else
  a=2
end
]], { 2, 5, 6 })

test([[a=1
repeat
  a=a+1
until a==3
]], { 1, 3, 4, 3, 4 })

test([[ do
  return
end
]], { 2 })

test([[local a
a=1
while a<=3 do
  a=a+1
end
]], { 1, 2, 3, 4, 3, 4, 3, 4, 3, 5 })

test([[while math.sin(1) do
  if math.sin(1)
  then
    break
  end
end
a=1]], { 1, 2, 3, 7 })

test([[for i=1,3 do
  a=i
end
]], { 1, 2, 1, 2, 1, 2, 1, 3 })

--[=[test([[for i,v in pairs{'a','b'} do
  a=i..v
end
]], { 1, 2, 1, 2, 1, 3 })--]=]

test([[for i=1,4 do a=1 end]], { 1, 1, 1, 1, 1 })



print '+'

-- invalid levels in [gs]etlocal
assert(not pcall(debug.getlocal, 20, 1))
assert(not pcall(debug.setlocal, -1, 1, 10))


-- parameter names
local function foo(a, b, ...) local d, e end

local co = coroutine.create(foo)

assert(debug.getlocal(foo, 1) == 'a')
assert(debug.getlocal(foo, 2) == 'b')
assert(debug.getlocal(foo, 3) == nil)
assert(debug.getlocal(co, foo, 1) == 'a')
assert(debug.getlocal(co, foo, 2) == 'b')
assert(debug.getlocal(co, foo, 3) == nil)

assert(debug.getlocal(print, 1) == nil)

a = {}; L = nil
local glob = 1
local oldglob = glob
debug.sethook(function(e, l)
	collectgarbage() -- force GC during a hook
	local f, m, c = debug.gethook()
	assert(m == 'crl' and c == 0)
	if e == "line" then
		if glob ~= oldglob then
			L = l - 1 -- get the first line where "glob" has changed
			oldglob = glob
		end
	elseif e == "call" then
		local f = debug.getinfo(2, "f").func
		a[f] = 1
	else assert(e == "return")
	end
end, "crl")

function f(a, b)
	collectgarbage()
	local _, x = debug.getlocal(1, 1)
	local _, y = debug.getlocal(1, 2)
	assert(x == a and y == b)
	assert(debug.setlocal(2, 3, "pera") == "AA" .. "AA")
	assert(debug.setlocal(2, 4, "ma��") == "B")
	x = debug.getinfo(2)
	assert(x.func == g and x.what == "Lua" and x.name == 'g' and
			x.nups == 1 and string.find(x.source, "^@.*db%.lua$"))
	glob = glob + 1
	assert(debug.getinfo(1, "l").currentline == L + 1)
	assert(debug.getinfo(1, "l").currentline == L + 2)
end

function foo()
	glob = glob + 1
	assert(debug.getinfo(1, "l").currentline == L + 1)
end; foo() -- set L
-- check line counting inside strings and empty lines

_ = 'alo\
alo' .. [[

]]
--[[
]]
assert(debug.getinfo(1, "l").currentline == L + 11, L) -- check count of lines


function g(...)
	local arg = {...}
	do local a, b, c; a = math.sin(40); end
	local feijao
	local AAAA, B = "xuxu", "mam�o"
	f(AAAA, B)
	assert(AAAA == "pera" and B == "ma��")
	do
		local B = 13
		local x, y = debug.getlocal(1, 5)
		assert(x == 'B' and y == 13)
	end
end

g()


assert(a[f] and a[g] and not a[print]) --  and a[assert] and a[debug.getlocal]


-- tests for manipulating non-registered locals (C and Lua temporaries)
--[[
local n, v = debug.getlocal(0, 1)
assert(v == 0 and n == "(*temporary)", tostring(n))
local n, v = debug.getlocal(0, 2)
assert(v == 2 and n == "(*temporary)")
assert(not debug.getlocal(0, 3))
assert(not debug.getlocal(0, 0))

function f()
	assert(select(2, debug.getlocal(2, 3)) == 1)
	assert(not debug.getlocal(2, 4))
	debug.setlocal(2, 3, 10)
	return 20
end

function g(a, b) return (a + 1) + f() end

assert(g(0, 0) == 30)
]]

debug.sethook(nil);
assert(debug.gethook() == nil)


-- testing access to function arguments

X = nil
a = {}
function a:f(a, b, ...) local arg = {...} local c = 13 end

debug.sethook(function(e)
	assert(e == "call")
	dostring("XX = 12") -- test dostring inside hooks
	-- testing errors inside hooks
	assert(not pcall(loadstring("a='joao'+1")))
	debug.sethook(function(e, l)
		assert(debug.getinfo(2, "l").currentline == l)
		local f, m, c = debug.gethook()
		assert(e == "line")
		assert(m == 'l' and c == 0)
		debug.sethook(nil) -- hook is called only once
		assert(not X) -- check that
		X = {}; local i = 1
		local x, y
		while 1 do
			x, y = debug.getlocal(2, i)
			if x == nil then break end
			X[x] = y
			i = i + 1
		end
	end, "l")
end, "c")

a:f(1, 2, 3, 4, 5)
assert(X.self == a and X.a == 1 and X.b == 2 and X.c == nil)
assert(XX == 12)
assert(debug.gethook() == nil)


-- testing upvalue access
local function getupvalues(f)
	local t = {}
	local i = 1
	while true do
		local name, value = debug.getupvalue(f, i)
		if not name then break end
		assert(not t[name])
		t[name] = value
		i = i + 1
	end
	return t
end

local a, b, c = 1, 2, 3
local function foo1(a) b = a; return c end

local function foo2(x) a = x; return c + b end

assert(debug.getupvalue(foo1, 3) == nil)
assert(debug.getupvalue(foo1, 0) == nil)
assert(debug.setupvalue(foo1, 3, "xuxu") == nil)
local t = getupvalues(foo1)
assert(t.a == nil and t.b == 2 and t.c == 3)
t = getupvalues(foo2)
assert(t.a == 1 and t.b == 2 and t.c == 3)
assert(debug.setupvalue(foo1, 1, "xuxu") == "b")
assert(({ debug.getupvalue(foo2, 3) })[2] == "xuxu")
-- cannot manipulate C upvalues from Lua
assert(debug.getupvalue(io.read, 1) == nil)
assert(debug.setupvalue(io.read, 1, 10) == nil)


-- testing count hooks
local a = 0
debug.sethook(function(e) a = a + 1 end, "", 1)
a = 0; for i = 1, 1000 do end; assert(1000 < a and a < 1012)
debug.sethook(function(e) a = a + 1 end, "", 4)
a = 0; for i = 1, 1000 do end; assert(250 < a and a < 255)
local f, m, c = debug.gethook()
assert(m == "" and c == 4)
debug.sethook(function(e) a = a + 1 end, "", 4000)
a = 0; for i = 1, 1000 do end; assert(a == 0)
debug.sethook(print, "", 2 ^ 24 - 1) -- count upperbound
local f, m, c = debug.gethook()
assert(({ debug.gethook() })[3] == 2 ^ 24 - 1)
debug.sethook()


-- tests for tail calls
--[[
local function f(x)
	if x then
		assert(debug.getinfo(1, "S").what == "Lua")
		local tail = debug.getinfo(2)
		assert(not pcall(getfenv, 3))
		assert(tail.what == "tail" and tail.short_src == "(tail call)" and
				tail.linedefined == -1 and tail.func == nil)
		assert(debug.getinfo(3, "f").func == g1)
		assert(getfenv(3))
		assert(debug.getinfo(4, "S").what == "tail")
		assert(not pcall(getfenv, 5))
		assert(debug.getinfo(5, "S").what == "main")
		assert(getfenv(5))
		print "+"
	end
end

function g(x) return f(x) end

function g1(x) g(x) end

local function h(x) local f = g1; return f(x) end

h(true)

local b = {}
debug.sethook(function(e) table.insert(b, e) end, "cr")
h(false)
debug.sethook()
local res = {
	"return", -- first return (from sethook)
	"call", "call", "call", "call",
	"return", "tail return", "return", "tail return",
	"call", -- last call (to sethook)
}
for _, k in ipairs(res) do assert(k == table.remove(b, 1)) end


lim = 30000
local function foo(x)
	if x == 0 then
		assert(debug.getinfo(lim + 2).what == "main")
		for i = 2, lim do assert(debug.getinfo(i, "S").what == "tail") end
	else return foo(x - 1)
	end
end

foo(lim)


print "+"
]]

-- testing traceback

assert(debug.traceback(print) == print)
assert(debug.traceback(print, 4) == print)
assert(string.find(debug.traceback("hi", 4), "^hi\n"))
assert(string.find(debug.traceback("hi"), "^hi\n"))
assert(not string.find(debug.traceback("hi"), "'traceback'"))
--assert(string.find(debug.traceback("hi", 0), "'traceback'")) -- Debug.traceback isn't pushed
assert(string.find(debug.traceback(), "^stack traceback:\n"))

-- testing nparams, nups e isvararg
local t = debug.getinfo(print, "u")
assert(t.isvararg == true and t.nparams == 0 and t.nups == 0)

t = debug.getinfo(function(a, b, c) end, "u")
assert(t.isvararg == false and t.nparams == 3 and t.nups == 0)

t = debug.getinfo(function(a, b, ...) return t[a] end, "u")
assert(t.isvararg == true and t.nparams == 2 and t.nups == 1)

-- testing debugging of coroutines

local function checktraceback(co, p)
	local tb = debug.traceback(co)
	local i = 0
	for l in string.gmatch(tb, "[^\n]+\n?") do
		assert(i == 0 or string.find(l, p[i] or error("Nil for line " .. l)))
		i = i + 1
	end
	assert(p[i] == nil)
end


local function f(n)
	if n > 0 then return f(n - 1)
	else coroutine.yield()
	end
end

local co = coroutine.create(f)
coroutine.resume(co, 3)
checktraceback(co, { "yield", "db.lua", "tail" })


co = coroutine.create(function(x)
	local a = 1
	coroutine.yield(debug.getinfo(1, "l"))
	coroutine.yield(debug.getinfo(1, "l").currentline)
	return a
end)

local tr = {}
local foo = function(e, l) table.insert(tr, l) end
debug.sethook(co, foo, "l")

local _, l = coroutine.resume(co, 10)
local x = debug.getinfo(co, 1, "lfLS")
assert(x.currentline == l.currentline and x.activelines[x.currentline])
assert(type(x.func) == "function")
for i = x.linedefined + 1, x.lastlinedefined do
	assert(x.activelines[i])
	x.activelines[i] = nil
end
assert(next(x.activelines) == nil) -- no 'extra' elements
assert(debug.getinfo(co, 2) == nil)
local a, b = debug.getlocal(co, 1, 1)
assert(a == "x" and b == 10)
a, b = debug.getlocal(co, 1, 2)
assert(a == "a" and b == 1)
debug.setlocal(co, 1, 2, "hi")
assert(debug.gethook(co) == foo)
--assert(table.getn(tr) == 2 and
--		tr[1] == l.currentline - 1 and tr[2] == l.currentline)

a, b, c = pcall(coroutine.resume, co)
assert(a and b and c == l.currentline + 1)
--checktraceback(co, { "yield", "in function <" })

a, b = coroutine.resume(co)
assert(a and b == "hi")
--assert(table.getn(tr) == 4 and tr[4] == l.currentline + 2)
assert(debug.gethook(co) == foo)
assert(debug.gethook() == nil)
checktraceback(co, {})


-- check traceback of suspended (or dead with error) coroutines

function f(i) if i == 0 then error(i) else coroutine.yield(); f(i - 1) end end

co = coroutine.create(function(x) f(x) end)
a, b = coroutine.resume(co, 3)
t = { "'yield'", "'f'", "in function <" }
while coroutine.status(co) == "suspended" do
	checktraceback(co, t)
	a, b = coroutine.resume(co)
	table.insert(t, 2, "'f'") -- one more recursive call to 'f'
end
t[1] = "'error'"
checktraceback(co, t)


-- test acessing line numbers of a coroutine from a resume inside
-- a C function (this is a known bug in Lua 5.0)

local function g(x)
	coroutine.yield(x)
end

local function f(i)
	debug.sethook(function() end, "l")
	for j = 1, 1000 do
		g(i + j)
	end
end

local co = coroutine.wrap(f)
co(10)
pcall(co)
pcall(co)


assert(type(debug.getregistry()) == "table")


print "OK"

