-- $Id: testes/memerr.lua $
-- See Copyright Notice in file lua.h


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

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

print("testing memory-allocation errors")

local debug = require "debug"

local pack = table.pack

-- standard error message for memory errors
local MEMERRMSG = "not enough memory"


-- memory error in panic function
T.totalmem(T.totalmem()+10000)   -- set low memory limit (+10k)
assert(T.checkpanic("newuserdata 20000") == MEMERRMSG)
T.totalmem(0)          -- restore high limit



-- {==================================================================
-- Testing memory limits
-- ===================================================================

checkerr("block too big", T.newuserdata, math.maxinteger)
collectgarbage()
local f = load"local a={}; for i=1,100000 do a[i]=i end"
T.alloccount(10)
checkerr(MEMERRMSG, f)
T.alloccount()          -- remove limit


-- preallocate stack space
local function deep (n) if n > 0 then deep(n - 1) end end


-- test memory errors; increase limit for maximum memory by steps,
-- so that we get memory errors in all allocations of a given
-- task, until there is enough memory to complete the task without
-- errors.
local function testbytes (s, f)
  collectgarbage()
  local M = T.totalmem()
  local oldM = M
  local a,b = nil
  while true do
    collectgarbage(); collectgarbage()
    deep(4)
    T.totalmem(M)
    a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f)
    T.totalmem(0)  -- remove limit
    if a and b == "OK" then break end       -- stop when no more errors
    if b ~= "OK" and b ~= MEMERRMSG then    -- not a memory error?
      error(a, 0)   -- propagate it
    end
    M = M + 7   -- increase memory limit
  end
  print(string.format("minimum memory for %s: %d bytes", s, M - oldM))
  return a
end

-- test memory errors; increase limit for number of allocations one
-- by one, so that we get memory errors in all allocations of a given
-- task, until there is enough allocations to complete the task without
-- errors.

local function testalloc (s, f)
  collectgarbage()
  local M = 0
  local a,b = nil
  while true do
    collectgarbage(); collectgarbage()
    deep(4)
    T.alloccount(M)
    a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f)
    T.alloccount()  -- remove limit
    if a and b == "OK" then break end       -- stop when no more errors
    if b ~= "OK" and b ~= MEMERRMSG then    -- not a memory error?
      error(a, 0)   -- propagate it
    end
    M = M + 1   -- increase allocation limit
  end
  print(string.format("minimum allocations for %s: %d allocations", s, M))
  return M
end


local function testamem (s, f)
  local aloc = testalloc(s, f)
  local res = testbytes(s, f)
  return {aloc = aloc, res = res}
end


local b = testamem("function call", function () return 10 end)
assert(b.res == 10 and b.aloc == 0)

testamem("state creation", function ()
  local st = T.newstate()
  if st then T.closestate(st) end   -- close new state
  return st
end)

testamem("empty-table creation", function ()
  return {}
end)

testamem("string creation", function ()
  return "XXX" .. "YYY"
end)

testamem("coroutine creation", function()
           return coroutine.create(print)
end)

do  -- vararg tables
  local function pack (...t) return t end
  local b = testamem("vararg table", function ()
    return pack(10, 20, 30, 40, "hello")
  end)
  assert(b.aloc == 3)   -- new table uses three memory blocks
  -- table optimized away
  local function sel (n, ...arg) return arg[n] + arg.n end
  local b = testamem("optimized vararg table",
        function () return sel(2.0, 20, 30) end)
  assert(b.res == 32 and b.aloc == 0)   -- no memory needed for this case
end

-- testing to-be-closed variables
testamem("to-be-closed variables", function()
  local flag
  do
    local x <close> =
              setmetatable({}, {__close = function () flag = true end})
    flag = false
    local x = {}
  end
  return flag
end)


-- testing threads

-- get main thread from registry
local mt = T.testC("rawgeti R !M; return 1")
assert(type(mt) == "thread" and coroutine.running() == mt)



local function expand (n,s)
  if n==0 then return "" end
  local e = string.rep("=", n)
  return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n",
                              e, s, expand(n-1,s), e)
end

G=0; collectgarbage()
load(expand(20,"G=G+1"))()
assert(G==20); collectgarbage()
G = nil

testamem("running code on new thread", function ()
  return T.doonnewstack("local x=1") == 0  -- try to create thread
end)


do   -- external strings
  local str = string.rep("a", 100)
  testamem("creating external strings", function ()
    return T.externstr(str)
  end)
end


-- testing memory x compiler

testamem("loadstring", function ()
  return load("x=1")  -- try to do load a string
end)


local testprog = [[
local function foo () return end
local t = {"x"}
AA = "aaa"
for i = 1, #t do AA = AA .. t[i] end
return true
]]

-- testing memory x dofile
_G.AA = nil
local t =os.tmpname()
local f = assert(io.open(t, "w"))
f:write(testprog)
f:close()
testamem("dofile", function ()
  local a = loadfile(t)
  return a and a()
end)
assert(os.remove(t))
assert(_G.AA == "aaax")


-- other generic tests

testamem("gsub", function ()
  local a, b = string.gsub("alo alo", "(a)", function (x) return x..'b' end)
  return (a == 'ablo ablo')
end)

testamem("dump/undump", function ()
  local a = load(testprog)
  local b = a and string.dump(a)
  a = b and load(b)
  return a and a()
end)

_G.AA = nil

local t = os.tmpname()
testamem("file creation", function ()
  local f = assert(io.open(t, 'w'))
  assert (not io.open"nomenaoexistente")
  io.close(f);
  return not loadfile'nomenaoexistente'
end)
assert(os.remove(t))

testamem("table creation", function ()
  local a, lim = {}, 10
  for i=1,lim do a[i] = i; a[i..'a'] = {} end
  return (type(a[lim..'a']) == 'table' and a[lim] == lim)
end)

testamem("constructors", function ()
  local a = {10, 20, 30, 40, 50; a=1, b=2, c=3, d=4, e=5}
  return (type(a) == 'table' and a.e == 5)
end)

local a = 1
local close = nil
testamem("closure creation", function ()
  function close (b)
   return function (x) return b + x end
  end
  return (close(2)(4) == 6)
end)

testamem("using coroutines", function ()
  local a = coroutine.wrap(function ()
              coroutine.yield(string.rep("a", 10))
              return {}
            end)
  assert(string.len(a()) == 10)
  return a()
end)

do   -- auxiliary buffer
  local lim = 100
  local a = {}; for i = 1, lim do a[i] = "01234567890123456789" end
  testamem("auxiliary buffer", function ()
    return (#table.concat(a, ",") == 20*lim + lim - 1)
  end)
end

testamem("growing stack", function ()
  local function foo (n)
    if n == 0 then return 1 else return 1 + foo(n - 1) end
  end
  return foo(100)
end)


collectgarbage()
collectgarbage()
global io, T, setmetatable, collectgarbage, print

local Count = 0
testamem("finalizers", function ()
  local X = false
  local obj = setmetatable({}, {__gc = function ()  X = true end})
  obj = nil
  T.resetCI()   -- remove extra CallInfos
  T.reallocstack(18)   -- remove extra stack slots
  Count = Count + 1
  io.stderr:write(Count, "\n")
  T.trick(io)
  collectgarbage()
  return X
end)

-- }==================================================================


print "Ok"


