-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
-- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes
print('testing garbage collection')

collectgarbage()

_G["while"] = 234

limit = 5000



contCreate = 0

print('tables')
while contCreate <= limit do
  local a = {}; a = nil
  contCreate = contCreate+1
end

a = "a"

contCreate = 0
print('strings')
while contCreate <= limit do
  a = contCreate .. "b";
  a = string.gsub(a, '(%d%d*)', string.upper)
  a = "a"
  contCreate = contCreate+1
end


contCreate = 0

a = {}

print('functions')
function a:test ()
  while contCreate <= limit do
    loadstring(string.format("function temp(a) return 'a%d' end", contCreate))()
    assert(temp() == string.format('a%d', contCreate))
    contCreate = contCreate+1
  end
end

a:test()

-- collection of functions without locals, globals, etc.
do local f = function () end end


print('long strings')
x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
assert(string.len(x)==80)
s = ''
n = 0
k = 300
while n < k do s = s..x; n=n+1; j=tostring(n)  end
assert(string.len(s) == k*80)
s = string.sub(s, 1, 20000)
s, i = string.gsub(s, '(%d%d%d%d)', math.sin)
assert(i==20000/4)
s = nil
x = nil

assert(_G["while"] == 234)


local bytes = gcinfo()
while 1 do
  local nbytes = gcinfo()
  if nbytes < bytes then break end   -- run until gc
  bytes = nbytes
  a = {}
end


local function dosteps (siz)
  collectgarbage()
  collectgarbage("stop")
  local a = {}
  for i=1,100 do a[i] = {{}}; local b = {} end
  local x = gcinfo()
  local i = 0
  repeat
    i = i+1
  until collectgarbage("step", siz)
  assert(gcinfo() < x)
  return i
end

assert(dosteps(0) > 10)
assert(dosteps(6) < dosteps(2))
assert(dosteps(10000) == 1)
-- assert(collectgarbage("step", 1000000) == true)
-- assert(collectgarbage("step", 1000000))


do
  local x = gcinfo()
  collectgarbage()
  collectgarbage("stop")
  repeat
    local a = {}
  until gcinfo() > 1000
  collectgarbage("restart")
  repeat
    local a = {}
  until gcinfo() < 1000
end

lim = 15
a = {}
-- fill a with `collectable' indices
for i=1,lim do a[{}] = i end
b = {}
for k,v in pairs(a) do b[k]=v end
-- remove all indices and collect them
for n in pairs(b) do
  a[n] = nil
  assert(type(n) == 'table' and next(n) == nil)
  collectgarbage()
end
b = nil
collectgarbage()
for n in pairs(a) do error("cannot be here") end
for i=1,lim do a[i] = i end
for i=1,lim do assert(a[i] == i) end


print('weak tables')
a = {}; setmetatable(a, {__mode = 'k'});
-- fill a with some `collectable' indices
for i=1,lim do a[{}] = i end
-- and some non-collectable ones
for i=1,lim do local t={}; a[t]=t end
for i=1,lim do a[i] = i end
for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end
collectgarbage()
local i = 0
for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end
assert(i == 3*lim)

a = {}; setmetatable(a, {__mode = 'v'});
a[1] = string.rep('b', 21)
collectgarbage()
assert(a[1])   -- strings are *values*
a[1] = nil
-- fill a with some `collectable' values (in both parts of the table)
for i=1,lim do a[i] = {} end
for i=1,lim do a[i..'x'] = {} end
-- and some non-collectable ones
for i=1,lim do local t={}; a[t]=t end
for i=1,lim do a[i+lim]=i..'x' end
collectgarbage()
local i = 0
for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end
assert(i == 2*lim)

a = {}; setmetatable(a, {__mode = 'vk'});
local x, y, z = {}, {}, {}
-- keep only some items
a[1], a[2], a[3] = x, y, z
a[string.rep('$', 11)] = string.rep('$', 11)
-- fill a with some `collectable' values
for i=4,lim do a[i] = {} end
for i=1,lim do a[{}] = i end
for i=1,lim do local t={}; a[t]=t end
collectgarbage()
assert(next(a) ~= nil)
local i = 0
for k,v in pairs(a) do
  assert((k == 1 and v == x) or
         (k == 2 and v == y) or
         (k == 3 and v == z) or k==v);
  i = i+1
end
assert(i == 4)
x,y,z=nil
collectgarbage()
assert(next(a) == string.rep('$', 11))

-- shrinking tables reduce their capacity; confirming the shrinking is difficult but we can at least test the surface level behavior
a = {}; setmetatable(a, {__mode = 'ks'})
for i=1,lim do a[{}] = i end
collectgarbage()
assert(next(a) == nil)

-- testing userdata
collectgarbage("stop")   -- stop collection
local u = newproxy(true)
local s = 0
local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'})
for i=1,10 do a[newproxy(true)] = i end
-- for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end
local a1 = {}; for k,v in pairs(a) do a1[k] = v end
for k,v in pairs(a1) do a[v] = k end
for i =1,10 do assert(a[i]) end
getmetatable(u).a = a1
getmetatable(u).u = u
do
  local u = u
  getmetatable(u).__gc = function (o)
    assert(a[o] == 10-s)
    assert(a[10-s] == nil) -- udata already removed from weak table
    assert(getmetatable(o) == getmetatable(u))
    assert(getmetatable(o).a[o] == 10-s)
    s=s+1
  end
end
a1, u = nil
assert(next(a) ~= nil)
collectgarbage()
-- assert(s==11)
collectgarbage()
assert(next(a) == nil)  -- finalized keys are removed in two cycles


-- __gc x weak tables
local u = newproxy(true)
setmetatable(getmetatable(u), {__mode = "v"})
getmetatable(u).__gc = function (o) os.exit(1) end  -- cannot happen
collectgarbage()

local u = newproxy(true)
local m = getmetatable(u)
m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"});
m.__gc = function (o)
  assert(next(getmetatable(o).x) == nil)
  m = 10
end
u, m = nil
collectgarbage()
-- assert(m==10)


-- errors during collection
u = newproxy(true)
getmetatable(u).__gc = function () error "!!!" end
u = nil
-- assert(not pcall(collectgarbage))
collectgarbage()


if not rawget(_G, "_soft") then
  print("deep structures")
  local a = {}
  for i = 1,200000 do
    a = {next = a}
  end
  collectgarbage()
end

-- create many threads with self-references and open upvalues
do
  local thread_id = 0
  local threads = {}

  function fn(thread)
      local x = {}
      threads[thread_id] = function()
                               thread = x
                           end
      coroutine.yield()
  end

  while thread_id < 1000 do
      local thread = coroutine.create(fn)
      coroutine.resume(thread, thread)
      thread_id = thread_id + 1
  end

  collectgarbage()

  -- ensure that we no longer have a lot of reachable threads for subsequent tests
  threads = {}
end

-- create a userdata to be collected when state is closed
do
  local newproxy,assert,type,print,getmetatable =
        newproxy,assert,type,print,getmetatable
  local u = newproxy(true)
  local tt = getmetatable(u)
  ___Glob = {u}   -- avoid udata being collected before program end
  tt.__gc = function (o)
    assert(getmetatable(o) == tt)
    -- create new objects during GC
    local a = 'xuxu'..(10+3)..'joao', {}
    ___Glob = o  -- resurrect object!
    newproxy(o)  -- creates a new one with same metatable
    print(">>> closing state " .. "<<<\n")
  end
end

-- create several udata to raise errors when collected while closing state
do
  local u = newproxy(true)
  getmetatable(u).__gc = function (o) return o + 1 end
  table.insert(___Glob, u)  -- preserve udata until the end
  for i = 1,10 do table.insert(___Glob, newproxy(true)) end
end

-- create threads that die together with their unmarked upvalues
do
  local t = {}

  for i = 1,100 do
    local c = coroutine.wrap(function()
      local uv = {i + 1}
      local function f()
        return uv[1] * 10
      end
      coroutine.yield(uv[1])
      uv = {i + 2}
      coroutine.yield(f())
    end)

    assert(c() == i + 1)
    table.insert(t, c)
  end

  for i = 1,100 do
    t[i] = nil
  end

  collectgarbage()
end

-- create a lot of threads with upvalues to force a case where full gc happens after we've marked some upvalues
do
  local t = {}
  for i = 1,100 do
    local c = coroutine.wrap(function()
      local uv = {i + 1}
      local function f()
        return uv[1] * 10
      end
      coroutine.yield(uv[1])
      uv = {i + 2}
      coroutine.yield(f())
    end)

    assert(c() == i + 1)
    table.insert(t, c)
  end

  t = {}

  collectgarbage()
end

return('OK')
