-- $Id: api.lua,v 1.147 2016/11/07 13:06:25 roberto Exp $
-- See Copyright Notice in file all.lua

if T==nil then
  (Message or print)('\n >>> testC not active: skipping API tests <<<\n')
  return
end

local debug = require "debug"

local pack = table.pack


function tcheck (t1, t2)
  assert(t1.n == (t2.n or #t2) + 1)
  for i = 2, t1.n do assert(t1[i] == t2[i - 1]) end
end


local function checkerr (msg, f, ...)
  local stat, err = pcall(f, ...)
  assert(not stat and string.find(err, msg))
end


print('testing C API')

--[=[
do
  local f = T.makeCfunc[[
    getglobal error
    pushstring bola
    pcall 1 1 1   # call 'error' with given handler
    pushstatus
    return 2     # return error message and status
  ]]

  local msg, st = f({})     -- invalid handler
  assert(st == "ERRERR" and string.find(msg, "error handling"))
  local msg, st = f(nil)     -- invalid handler
  assert(st == "ERRERR" and string.find(msg, "error handling"))

  local a = setmetatable({}, {__call = function (_, x) return x:upper() end})
  local msg, st = f(a)   -- callable handler
  assert(st == "ERRRUN" and msg == "BOLA")
end

do  -- test returning more results than fit in the caller stack
  local a = {}
  for i=1,1000 do a[i] = true end; a[999] = 10
  local b = T.testC([[pcall 1 -1 0; pop 1; tostring -1; return 1]],
                    table.unpack, a)
  assert(b == "10")
end


-- testing globals
_G.a = 14; _G.b = "a31"
local a = {T.testC[[
  getglobal a;
  getglobal b;
  getglobal b;
  setglobal a;
  return *
]]}
assert(a[2] == 14 and a[3] == "a31" and a[4] == nil and _G.a == "a31")


-- colect in cl the `val' of all collected userdata
tt = {}
cl = {n=0}
A = nil; B = nil
local F
F = function (x)
  local udval = T.udataval(x)
  table.insert(cl, udval)
  local d = T.newuserdata(100)   -- cria lixo
  d = nil
  assert(debug.getmetatable(x).__gc == F)
  assert(load("table.insert({}, {})"))()   -- cria mais lixo
  collectgarbage()   -- forca coleta de lixo durante coleta!
  assert(debug.getmetatable(x).__gc == F)   -- coleta anterior nao melou isso?
  local dummy = {}    -- cria lixo durante coleta
  if A ~= nil then
    assert(type(A) == "userdata")
    assert(T.udataval(A) == B)
    debug.getmetatable(A)    -- just acess it
  end
  A = x   -- ressucita userdata
  B = udval
  return 1,2,3
end
tt.__gc = F

-- test whether udate collection frees memory in the right time
do
  collectgarbage();
  collectgarbage();
  local x = collectgarbage("count");
  local a = T.newuserdata(5001)
  assert(T.testC("objsize 2; return 1", a) == 5001)
  assert(collectgarbage("count") >= x+4)
  a = nil
  collectgarbage();
  assert(collectgarbage("count") <= x+1)
  -- udata without finalizer
  x = collectgarbage("count")
  collectgarbage("stop")
  for i=1,1000 do T.newuserdata(0) end
  assert(collectgarbage("count") > x+10)
  collectgarbage()
  assert(collectgarbage("count") <= x+1)
  -- udata with finalizer
  collectgarbage()
  x = collectgarbage("count")
  collectgarbage("stop")
  a = {__gc = function () end}
  for i=1,1000 do debug.setmetatable(T.newuserdata(0), a) end
  assert(collectgarbage("count") >= x+10)
  collectgarbage()  -- this collection only calls TM, without freeing memory
  assert(collectgarbage("count") >= x+10)
  collectgarbage()  -- now frees memory
  assert(collectgarbage("count") <= x+1)
  collectgarbage("restart")
end


collectgarbage("stop")

-- create 3 userdatas with tag `tt'
a = T.newuserdata(0); debug.setmetatable(a, tt); na = T.udataval(a)
b = T.newuserdata(0); debug.setmetatable(b, tt); nb = T.udataval(b)
c = T.newuserdata(0); debug.setmetatable(c, tt); nc = T.udataval(c)

-- create userdata without meta table
x = T.newuserdata(4)
y = T.newuserdata(0)

-- checkerr("FILE%* expected, got userdata", io.input, a)
-- checkerr("FILE%* expected, got userdata", io.input, x)

assert(debug.getmetatable(x) == nil and debug.getmetatable(y) == nil)

d=T.ref(a);
e=T.ref(b);
f=T.ref(c);
t = {T.getref(d), T.getref(e), T.getref(f)}
assert(t[1] == a and t[2] == b and t[3] == c)

t=nil; a=nil; c=nil;
T.unref(e); T.unref(f)

collectgarbage()

]=]

-------------------------------------------------------------------------
-- testing memory limits
-------------------------------------------------------------------------
T.totalmem(T.totalmem()+45*2048)   -- set low memory limit
local function fillstack(x,a,b,c,d,e,f,g,h,i)
  local j,k,l,m,n,o,p,q,r = a,b,c,d,e,f,g,h,i
  if x > 0 then fillstack(x-1,j,k,l,m,n,o,p,q,r) end
end

local calls = 1
local function gcfunc(ud) -- upval: calls
  local j,k,l,m,n,o,p,q,r = 0,0,0,0,0,0,0,0,0
  print (('GC for %s called with %u fillstacks'):format(tostring(ud),calls))
  calls = calls + 10
  fillstack(calls,j,k,l,m,n,o,p,q,r)
end
local a ={}
collectgarbage("restart")
collectgarbage("setpause",120)
collectgarbage("setstepmul",1000)
for i = 1, 20 do
  local UD = T.newuserdata(2048); debug.setmetatable(UD, {__gc = gcfunc})
  a[i] = UD
  print (('a[%u] = %s'):format(i, tostring(UD)))
end

for i = 1, 20 do
  j = (513 * i % 20) + 1
  k = (13 + 257 * i % 20) + 1
  calls = calls + 1
  local UD = T.newuserdata(2048); debug.setmetatable(UD, {__gc = gcfunc})
  print (('setting a[%u] = nil, a[%u] = %s'):format(j, k, tostring(UD)))
  a[j]=nil; a[k] = UD
end
print ("Done")
--[=[
checkerr("block too big", T.newuserdata, math.maxinteger)
collectgarbage()
T.totalmem(T.totalmem()+5000)   -- set low memory limit (+5k)
checkerr("not enough memory", load"local a={}; for i=1,100000 do a[i]=i end")
T.totalmem(0)          -- restore high limit

-- test memory errors; increase memory limit in small steps, so that
-- we get memory errors in different parts of a given task, up to there
-- is enough memory to complete the task without errors
function testamem (s, f)
  collectgarbage(); collectgarbage()
  local M = T.totalmem()
  local oldM = M
  local a,b = nil
  while 1 do
    M = M+7   -- increase memory limit in small steps
    T.totalmem(M)
    a, b = pcall(f)
    T.totalmem(0)  -- restore high limit
    if a and b then break end       -- stop when no more errors
    collectgarbage()
    if not a and not    -- `real' error?
      (string.find(b, "memory") or string.find(b, "overflow")) then
      error(b, 0)   -- propagate it
    end
  end
  print("\nlimit for " .. s .. ": " .. M-oldM)
  return b
end


-- testing memory errors when creating a new state

b = testamem("state creation", T.newstate)
T.closestate(b);  -- close new state

-- testing luaL_newmetatable
local mt_xuxu, res, top = T.testC("newmetatable xuxu; gettop; return 3")
assert(type(mt_xuxu) == "table" and res and top == 3)
local d, res, top = T.testC("newmetatable xuxu; gettop; return 3")
assert(mt_xuxu == d and not res and top == 3)
d, res, top = T.testC("newmetatable xuxu1; gettop; return 3")
assert(mt_xuxu ~= d and res and top == 3)

x = T.newuserdata(0);
y = T.newuserdata(0);
T.testC("pushstring xuxu; gettable R; setmetatable 2", x)
assert(getmetatable(x) == mt_xuxu)

-- testing luaL_testudata
-- correct metatable
local res1, res2, top = T.testC([[testudata -1 xuxu
   	 			  testudata 2 xuxu
				  gettop
				  return 3]], x)
assert(res1 and res2 and top == 4)

-- wrong metatable
res1, res2, top = T.testC([[testudata -1 xuxu1
			    testudata 2 xuxu1
			    gettop
			    return 3]], x)
assert(not res1 and not res2 and top == 4)

-- non-existent type
res1, res2, top = T.testC([[testudata -1 xuxu2
			    testudata 2 xuxu2
			    gettop
			    return 3]], x)
assert(not res1 and not res2 and top == 4)

-- userdata has no metatable
res1, res2, top = T.testC([[testudata -1 xuxu
			    testudata 2 xuxu
			    gettop
			    return 3]], y)
assert(not res1 and not res2 and top == 4)

-- erase metatables
do
  local r = debug.getregistry()
  assert(r.xuxu == mt_xuxu and r.xuxu1 == d)
  r.xuxu = nil; r.xuxu1 = nil
end
]=]
print'OK'