-- $Id: testes/coroutine.lua $
-- See Copyright Notice in file all.lua

print "testing coroutines"

local debug = require'debug'

local f

local main, ismain = coroutine.running()
assert(type(main) == "thread" and ismain)
assert(not coroutine.resume(main))
assert(not coroutine.isyieldable(main) and not coroutine.isyieldable())
assert(not pcall(coroutine.yield))


-- trivial errors
assert(not pcall(coroutine.resume, 0))
assert(not pcall(coroutine.status, 0))


-- tests for multiple yield/resume arguments

local function eqtab (t1, t2)
  assert(#t1 == #t2)
  for i = 1, #t1 do
    local v = t1[i]
    assert(t2[i] == v)
  end
end

_G.x = nil   -- declare x
_G.f = nil   -- declare f
local function foo (a, ...)
  local x, y = coroutine.running()
  assert(x == f and y == false)
  -- next call should not corrupt coroutine (but must fail,
  -- as it attempts to resume the running coroutine)
  assert(coroutine.resume(f) == false)
  assert(coroutine.status(f) == "running")
  local arg = {...}
  assert(coroutine.isyieldable(x))
  for i=1,#arg do
    _G.x = {coroutine.yield(table.unpack(arg[i]))}
  end
  return table.unpack(a)
end

f = coroutine.create(foo)
assert(coroutine.isyieldable(f))
assert(type(f) == "thread" and coroutine.status(f) == "suspended")
assert(string.find(tostring(f), "thread"))
local s,a,b,c,d
s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'})
assert(coroutine.isyieldable(f))
assert(s and a == nil and coroutine.status(f) == "suspended")
s,a,b,c,d = coroutine.resume(f)
eqtab(_G.x, {})
assert(s and a == 1 and b == nil)
assert(coroutine.isyieldable(f))
s,a,b,c,d = coroutine.resume(f, 1, 2, 3)
eqtab(_G.x, {1, 2, 3})
assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil)
s,a,b,c,d = coroutine.resume(f, "xuxu")
eqtab(_G.x, {"xuxu"})
assert(s and a == 1 and b == 2 and c == 3 and d == nil)
assert(coroutine.status(f) == "dead")
s, a = coroutine.resume(f, "xuxu")
assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead")

_G.f = nil

-- yields in tail calls
local function foo (i) return coroutine.yield(i) end
local f = coroutine.wrap(function ()
  for i=1,10 do
    assert(foo(i) == _G.x)
  end
  return 'a'
end)
for i=1,10 do _G.x = i; assert(f(i) == i) end
_G.x = 'xuxu'; assert(f('xuxu') == 'a')

_G.x = nil

-- recursive
local function pf (n, i)
  coroutine.yield(n)
  pf(n*i, i+1)
end

f = coroutine.wrap(pf)
local s=1
for i=1,10 do
  assert(f(1, 1) == s)
  s = s*i
end

-- sieve
local function gen (n)
  return coroutine.wrap(function ()
    for i=2,n do coroutine.yield(i) end
  end)
end


local function filter (p, g)
  return coroutine.wrap(function ()
    while 1 do
      local n = g()
      if n == nil then return end
      if math.fmod(n, p) ~= 0 then coroutine.yield(n) end
    end
  end)
end

local x = gen(80)
local a = {}
while 1 do
  local n = x()
  if n == nil then break end
  table.insert(a, n)
  x = filter(n, x)
end

assert(#a == 22 and a[#a] == 79)
x, a = nil


print("to-be-closed variables in coroutines")

local function func2close (f)
  return setmetatable({}, {__close = f})
end

do
  -- ok to close a dead coroutine
  local co = coroutine.create(print)
  assert(coroutine.resume(co, "testing 'coroutine.close'"))
  assert(coroutine.status(co) == "dead")
  local st, msg = coroutine.close(co)
  assert(st and msg == nil)
  -- also ok to close it again
  st, msg = coroutine.close(co)
  assert(st and msg == nil)


  -- cannot close the running coroutine
  local st, msg = pcall(coroutine.close, coroutine.running())
  assert(not st and string.find(msg, "running"))

  local main = coroutine.running()

  -- cannot close a "normal" coroutine
  ;(coroutine.wrap(function ()
    local st, msg = pcall(coroutine.close, main)
    assert(not st and string.find(msg, "normal"))
  end))()

  -- cannot close a coroutine while closing it
  do
    local co
    co = coroutine.create(
      function()
        local x <close> = func2close(function()
            coroutine.close(co)   -- try to close it again
         end)
        coroutine.yield(20)
      end)
    local st, msg = coroutine.resume(co)
    assert(st and msg == 20)
    st, msg = coroutine.close(co)
    assert(not st and string.find(msg, "running coroutine"))
  end

  -- to-be-closed variables in coroutines
  local X

  -- closing a coroutine after an error
  local co = coroutine.create(error)
  local st, msg = coroutine.resume(co, 100)
  assert(not st and msg == 100)
  st, msg = coroutine.close(co)
  assert(not st and msg == 100)
  -- after closing, no more errors
  st, msg = coroutine.close(co)
  assert(st and msg == nil)

  co = coroutine.create(function ()
    local x <close> = func2close(function (self, err)
      assert(err == nil); X = false
    end)
    X = true
    coroutine.yield()
  end)
  coroutine.resume(co)
  assert(X)
  assert(coroutine.close(co))
  assert(not X and coroutine.status(co) == "dead")

  -- error closing a coroutine
  local x = 0
  co = coroutine.create(function()
    local y <close> = func2close(function (self,err)
      assert(err == 111)
      x = 200
      error(200)
    end)
    local x <close> = func2close(function (self, err)
      assert(err == nil); error(111)
    end)
    coroutine.yield()
  end)
  coroutine.resume(co)
  assert(x == 0)
  local st, msg = coroutine.close(co)
  assert(st == false and coroutine.status(co) == "dead" and msg == 200)
  assert(x == 200)
  -- after closing, no more errors
  st, msg = coroutine.close(co)
  assert(st and msg == nil)
end

do
  -- <close> versus pcall in coroutines
  local X = false
  local Y = false
  local function foo ()
    local x <close> = func2close(function (self, err)
      Y = debug.getinfo(2)
      X = err
    end)
    error(43)
  end
  local co = coroutine.create(function () return pcall(foo) end)
  local st1, st2, err = coroutine.resume(co)
  assert(st1 and not st2 and err == 43)
  assert(X == 43 and Y.what == "C")

  -- recovering from errors in __close metamethods
  local track = {}

  local function h (o)
    local hv <close> = o
    return 1
  end

  local function foo ()
    local x <close> = func2close(function(_,msg)
      track[#track + 1] = msg or false
      error(20)
    end)
    local y <close> = func2close(function(_,msg)
      track[#track + 1] = msg or false
      return 1000
    end)
    local z <close> = func2close(function(_,msg)
      track[#track + 1] = msg or false
      error(10)
    end)
    coroutine.yield(1)
    h(func2close(function(_,msg)
        track[#track + 1] = msg or false
        error(2)
      end))
  end

  local co = coroutine.create(pcall)

  local st, res = coroutine.resume(co, foo)    -- call 'foo' protected
  assert(st and res == 1)   -- yield 1
  local st, res1, res2 = coroutine.resume(co)   -- continue
  assert(coroutine.status(co) == "dead")
  assert(st and not res1 and res2 == 20)   -- last error (20)
  assert(track[1] == false and track[2] == 2 and track[3] == 10 and
         track[4] == 10)
end


-- yielding across C boundaries

local co = coroutine.wrap(function()
       assert(not pcall(table.sort,{1,2,3}, coroutine.yield))
       assert(coroutine.isyieldable())
       coroutine.yield(20)
       return 30
     end)

assert(co() == 20)
assert(co() == 30)


local f = function (s, i) return coroutine.yield(i) end

local f1 = coroutine.wrap(function ()
             return xpcall(pcall, function (...) return ... end,
               function ()
                 local s = 0
                 for i in f, nil, 1 do pcall(function () s = s + i end) end
                 error({s})
               end)
           end)

f1()
for i = 1, 10 do assert(f1(i) == i) end
local r1, r2, v = f1(nil)
assert(r1 and not r2 and v[1] ==  (10 + 1)*10/2)


local function f (a, b) a = coroutine.yield(a);  error{a + b} end
local function g(x) return x[1]*2 end

co = coroutine.wrap(function ()
       coroutine.yield(xpcall(f, g, 10, 20))
     end)

assert(co() == 10)
local r, msg = co(100)
assert(not r and msg == 240)


-- unyieldable C call
do
  local function f (c)
          assert(not coroutine.isyieldable())
          return c .. c
        end

  local co = coroutine.wrap(function (c)
               assert(coroutine.isyieldable())
               local s = string.gsub("a", ".", f)
               return s
             end)
  assert(co() == "aa")
end



do   -- testing single trace of coroutines
  local X
  local co = coroutine.create(function ()
    coroutine.yield(10)
    return 20;
  end)
  local trace = {}
  local function dotrace (event)
    trace[#trace + 1] = event
  end
  debug.sethook(co, dotrace, "clr")
  repeat until not coroutine.resume(co)
  local correcttrace = {"call", "line", "call", "return", "line", "return"}
  assert(#trace == #correcttrace)
  for k, v in pairs(trace) do
    assert(v == correcttrace[k])
  end
end

-- errors in coroutines
function foo ()
  assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1)
  assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined)
  coroutine.yield(3)
  error(foo)
end

function goo() foo() end
x = coroutine.wrap(goo)
assert(x() == 3)
local a,b = pcall(x)
assert(not a and b == foo)

x = coroutine.create(goo)
a,b = coroutine.resume(x)
assert(a and b == 3)
a,b = coroutine.resume(x)
assert(not a and b == foo and coroutine.status(x) == "dead")
a,b = coroutine.resume(x)
assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead")

goo = nil

-- co-routines x for loop
local function all (a, n, k)
  if k == 0 then coroutine.yield(a)
  else
    for i=1,n do
      a[k] = i
      all(a, n, k-1)
    end
  end
end

local a = 0
for t in coroutine.wrap(function () all({}, 5, 4) end) do
  a = a+1
end
assert(a == 5^4)


-- access to locals of collected corroutines
local C = {}; setmetatable(C, {__mode = "kv"})
local x = coroutine.wrap (function ()
            local a = 10
            local function f () a = a+10; return a end
            while true do
              a = a+1
              coroutine.yield(f)
            end
          end)

C[1] = x;

local f = x()
assert(f() == 21 and x()() == 32 and x() == f)
x = nil
collectgarbage()
assert(C[1] == undef)
assert(f() == 43 and f() == 53)


-- old bug: attempt to resume itself

local function co_func (current_co)
  assert(coroutine.running() == current_co)
  assert(coroutine.resume(current_co) == false)
  coroutine.yield(10, 20)
  assert(coroutine.resume(current_co) == false)
  coroutine.yield(23)
  return 10
end

local co = coroutine.create(co_func)
local a,b,c = coroutine.resume(co, co)
assert(a == true and b == 10 and c == 20)
a,b = coroutine.resume(co, co)
assert(a == true and b == 23)
a,b = coroutine.resume(co, co)
assert(a == true and b == 10)
assert(coroutine.resume(co, co) == false)
assert(coroutine.resume(co, co) == false)


-- other old bug when attempting to resume itself
-- (trigger C-code assertions)
do
  local A = coroutine.running()
  local B = coroutine.create(function() return coroutine.resume(A) end)
  local st, res = coroutine.resume(B)
  assert(st == true and res == false)

  local X = false
  A = coroutine.wrap(function()
    local _ <close> = func2close(function () X = true end)
    return pcall(A, 1)
  end)
  st, res = A()
  assert(not st and string.find(res, "non%-suspended") and X == true)
end


-- bug in 5.4.1
do
  -- coroutine ran close metamethods with invalid status during a
  -- reset.
  local co
  co = coroutine.wrap(function()
    local x <close> = func2close(function() return pcall(co) end)
    error(111)
  end)
  local st, errobj = pcall(co)
  assert(not st and errobj == 111)
  st, errobj = pcall(co)
  assert(not st and string.find(errobj, "dead coroutine"))
end


-- attempt to resume 'normal' coroutine
local co1, co2
co1 = coroutine.create(function () return co2() end)
co2 = coroutine.wrap(function ()
        assert(coroutine.status(co1) == 'normal')
        assert(not coroutine.resume(co1))
        coroutine.yield(3)
      end)

a,b = coroutine.resume(co1)
assert(a and b == 3)
assert(coroutine.status(co1) == 'dead')

-- infinite recursion of coroutines
a = function(a) coroutine.wrap(a)(a) end
assert(not pcall(a, a))
a = nil


-- access to locals of erroneous coroutines
local x = coroutine.create (function ()
            local a = 10
            _G.F = function () a=a+1; return a end
            error('x')
          end)

assert(not coroutine.resume(x))
-- overwrite previous position of local `a'
assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1))
assert(_G.F() == 11)
assert(_G.F() == 12)
_G.F = nil


if not T then
  (Message or print)
      ('\n >>> testC not active: skipping coroutine API tests <<<\n')
else
  print "testing yields inside hooks"

  local turn
  
  local function fact (t, x)
    assert(turn == t)
    if x == 0 then return 1
    else return x*fact(t, x-1)
    end
  end

  local A, B = 0, 0

  local x = coroutine.create(function ()
    T.sethook("yield 0", "", 2)
    A = fact("A", 6)
  end)

  local y = coroutine.create(function ()
    T.sethook("yield 0", "", 3)
    B = fact("B", 7)
  end)

  while A==0 or B==0 do    -- A ~= 0 when 'x' finishes (similar for 'B','y')
    if A==0 then turn = "A"; assert(T.resume(x)) end
    if B==0 then turn = "B"; assert(T.resume(y)) end

    -- check that traceback works correctly after yields inside hooks
    debug.traceback(x)
    debug.traceback(y)
  end

  assert(B // A == 7)    -- fact(7) // fact(6)

  do   -- hooks vs. multiple values
    local done
    local function test (n)
      done = false
      return coroutine.wrap(function ()
        local a = {}
        for i = 1, n do a[i] = i end
        -- 'pushint' just to perturb the stack
        T.sethook("pushint 10; yield 0", "", 1)   -- yield at each op.
        local a1 = {table.unpack(a)}   -- must keep top between ops.
        assert(#a1 == n)
        for i = 1, n do assert(a[i] == i) end
        done = true
      end)
    end
    -- arguments to the coroutine are just to perturb its stack
    local co = test(0); while not done do co(30) end
    co = test(1); while not done do co(20, 10) end
    co = test(3); while not done do co() end
    co = test(100); while not done do co() end
  end

  local line = debug.getinfo(1, "l").currentline + 2    -- get line number
  local function foo ()
    local x = 10    --<< this line is 'line'
    x = x + 10
    _G.XX = x
  end

  -- testing yields in line hook
  local co = coroutine.wrap(function ()
    T.sethook("setglobal X; yield 0", "l", 0); foo(); return 10 end)

  _G.XX = nil;
  _G.X = nil; co(); assert(_G.X == line)
  _G.X = nil; co(); assert(_G.X == line + 1)
  _G.X = nil; co(); assert(_G.X == line + 2 and _G.XX == nil)
  _G.X = nil; co(); assert(_G.X == line + 3 and _G.XX == 20)
  assert(co() == 10)
  _G.X = nil

  -- testing yields in count hook
  co = coroutine.wrap(function ()
    T.sethook("yield 0", "", 1); foo(); return 10 end)

  _G.XX = nil;
  local c = 0
  repeat c = c + 1; local a = co() until a == 10
  assert(_G.XX == 20 and c >= 5)

  co = coroutine.wrap(function ()
    T.sethook("yield 0", "", 2); foo(); return 10 end)

  _G.XX = nil;
  local c = 0
  repeat c = c + 1; local a = co() until a == 10
  assert(_G.XX == 20 and c >= 5)
  _G.X = nil; _G.XX = nil

  do
    -- testing debug library on a coroutine suspended inside a hook
    -- (bug in 5.2/5.3)
    c = coroutine.create(function (a, ...)
      T.sethook("yield 0", "l")   -- will yield on next two lines
      local b = a
      return ...
    end)

    assert(coroutine.resume(c, 1, 2, 3))   -- start coroutine
    local n,v = debug.getlocal(c, 0, 1)    -- check its local
    assert(n == "a" and v == 1 and debug.getlocal(c, 0, 2) ~= "b")
    assert(debug.setlocal(c, 0, 1, 10))     -- test 'setlocal'
    local t = debug.getinfo(c, 0)        -- test 'getinfo'
    assert(t.currentline == t.linedefined + 2)
    assert(not debug.getinfo(c, 1))      -- no other level
    assert(coroutine.resume(c))          -- run next line
    local n,v = debug.getlocal(c, 0, 2)    -- check next local
    assert(n == "b" and v == 10)
    v = {coroutine.resume(c)}         -- finish coroutine
    assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef)
    assert(not coroutine.resume(c))
  end

  do
    -- testing debug library on last function in a suspended coroutine
    -- (bug in 5.2/5.3)
    local c = coroutine.create(function () T.testC("yield 1", 10, 20) end)
    local a, b = coroutine.resume(c)
    assert(a and b == 20)
    assert(debug.getinfo(c, 0).linedefined == -1)
    a, b = debug.getlocal(c, 0, 2)
    assert(b == 10)
  end


  print "testing coroutine API"
  
  -- reusing a thread
  assert(T.testC([[
    newthread      # create thread
    pushvalue 2    # push body
    pushstring 'a a a'  # push argument
    xmove 0 3 2   # move values to new thread
    resume -1, 1    # call it first time
    pushstatus
    xmove 3 0 0   # move results back to stack
    setglobal X    # result
    setglobal Y    # status
    pushvalue 2     # push body (to call it again)
    pushstring 'b b b'
    xmove 0 3 2
    resume -1, 1    # call it again
    pushstatus
    xmove 3 0 0
    return 1        # return result
  ]], function (...) return ... end) == 'b b b')

  assert(X == 'a a a' and Y == 'OK')

  X, Y = nil


  -- resuming running coroutine
  C = coroutine.create(function ()
        return T.testC([[
                 pushnum 10;
                 pushnum 20;
                 resume -3 2;
                 pushstatus
                 gettop;
                 return 3]], C)
      end)
  local a, b, c, d = coroutine.resume(C)
  assert(a == true and string.find(b, "non%-suspended") and
         c == "ERRRUN" and d == 4)

  a, b, c, d = T.testC([[
    rawgeti R !M    # get main thread
    pushnum 10;
    pushnum 20;
    resume -3 2;
    pushstatus
    gettop;
    return 4]])
  assert(a == coroutine.running() and string.find(b, "non%-suspended") and
         c == "ERRRUN" and d == 4)


  -- using a main thread as a coroutine  (dubious use!)
  local state = T.newstate()

  -- check that yielddable is working correctly
  assert(T.testC(state, "newthread; isyieldable -1; remove 1; return 1"))

  -- main thread is not yieldable
  assert(not T.testC(state, "rawgeti R !M; isyieldable -1; remove 1; return 1"))

  T.testC(state, "settop 0")

  T.loadlib(state, 1 | 2)   -- load _G and 'package'

  assert(T.doremote(state, [[
    coroutine = require'coroutine';
    X = function (x) coroutine.yield(x, 'BB'); return 'CC' end;
    return 'ok']]))

  local t = table.pack(T.testC(state, [[
    rawgeti R !M     # get main thread
    pushstring 'XX'
    getglobal X    # get function for body
    pushstring AA      # arg
    resume 1 1      # 'resume' shadows previous stack!
    gettop
    setglobal T    # top
    setglobal B    # second yielded value
    setglobal A    # fist yielded value
    rawgeti R !M     # get main thread
    pushnum 5       # arg (noise)
    resume 1 1      # after coroutine ends, previous stack is back
    pushstatus
    return *
  ]]))
  assert(t.n == 4 and t[2] == 'XX' and t[3] == 'CC' and t[4] == 'OK')
  assert(T.doremote(state, "return T") == '2')
  assert(T.doremote(state, "return A") == 'AA')
  assert(T.doremote(state, "return B") == 'BB')

  T.closestate(state)

  print'+'

end


-- leaving a pending coroutine open
_G.TO_SURVIVE = coroutine.wrap(function ()
      local a = 10
      local x = function () a = a+1 end
      coroutine.yield()
    end)

_G.TO_SURVIVE()


if not _soft then
  -- bug (stack overflow)
  local lim = 1000000    -- stack limit; assume 32-bit machine
  local t = {lim - 10, lim - 5, lim - 1, lim, lim + 1, lim + 5}
  for i = 1, #t do
    local j = t[i]
    local co = coroutine.create(function()
           return table.unpack({}, 1, j)
         end)
    local r, msg = coroutine.resume(co)
    -- must fail for unpacking larger than stack limit
    assert(j < lim or not r)
  end
end


assert(coroutine.running() == main)

print"+"


print"testing yields inside metamethods"

local function val(x)
  if type(x) == "table" then return x.x else return x end
end

local mt = {
  __eq = function(a,b) coroutine.yield(nil, "eq"); return val(a) == val(b) end,
  __lt = function(a,b) coroutine.yield(nil, "lt"); return val(a) < val(b) end,
  __le = function(a,b) coroutine.yield(nil, "le"); return a - b <= 0 end,
  __add = function(a,b) coroutine.yield(nil, "add");
                        return val(a) + val(b) end,
  __sub = function(a,b) coroutine.yield(nil, "sub"); return val(a) - val(b) end,
  __mul = function(a,b) coroutine.yield(nil, "mul"); return val(a) * val(b) end,
  __div = function(a,b) coroutine.yield(nil, "div"); return val(a) / val(b) end,
  __idiv = function(a,b) coroutine.yield(nil, "idiv");
                         return val(a) // val(b) end,
  __pow = function(a,b) coroutine.yield(nil, "pow"); return val(a) ^ val(b) end,
  __mod = function(a,b) coroutine.yield(nil, "mod"); return val(a) % val(b) end,
  __unm = function(a,b) coroutine.yield(nil, "unm"); return -val(a) end,
  __bnot = function(a,b) coroutine.yield(nil, "bnot"); return ~val(a) end,
  __shl = function(a,b) coroutine.yield(nil, "shl");
                        return val(a) << val(b) end,
  __shr = function(a,b) coroutine.yield(nil, "shr");
                        return val(a) >> val(b) end,
  __band = function(a,b)
             coroutine.yield(nil, "band")
             return val(a) & val(b)
           end,
  __bor = function(a,b) coroutine.yield(nil, "bor");
                        return val(a) | val(b) end,
  __bxor = function(a,b) coroutine.yield(nil, "bxor");
                         return val(a) ~ val(b) end,

  __concat = function(a,b)
               coroutine.yield(nil, "concat");
               return val(a) .. val(b)
             end,
  __index = function (t,k) coroutine.yield(nil, "idx"); return t.k[k] end,
  __newindex = function (t,k,v) coroutine.yield(nil, "nidx"); t.k[k] = v end,
}


local function new (x)
  return setmetatable({x = x, k = {}}, mt)
end


local a = new(10)
local b = new(12)
local c = new"hello"

local function run (f, t)
  local i = 1
  local c = coroutine.wrap(f)
  while true do
    local res, stat = c()
    if res then assert(t[i] == undef); return res, t end
    assert(stat == t[i])
    i = i + 1
  end
end


assert(run(function () if (a>=b) then return '>=' else return '<' end end,
       {"le", "sub"}) == "<")
assert(run(function () if (a<=b) then return '<=' else return '>' end end,
       {"le", "sub"}) == "<=")
assert(run(function () if (a==b) then return '==' else return '~=' end end,
       {"eq"}) == "~=")

assert(run(function () return a & b + a end, {"add", "band"}) == 2)

assert(run(function () return 1 + a end, {"add"}) == 11)
assert(run(function () return a - 25 end, {"sub"}) == -15)
assert(run(function () return 2 * a end, {"mul"}) == 20)
assert(run(function () return a ^ 2 end, {"pow"}) == 100)
assert(run(function () return a / 2 end, {"div"}) == 5)
assert(run(function () return a % 6 end, {"mod"}) == 4)
assert(run(function () return a // 3 end, {"idiv"}) == 3)

assert(run(function () return a + b end, {"add"}) == 22)
assert(run(function () return a - b end, {"sub"}) == -2)
assert(run(function () return a * b end, {"mul"}) == 120)
assert(run(function () return a ^ b end, {"pow"}) == 10^12)
assert(run(function () return a / b end, {"div"}) == 10/12)
assert(run(function () return a % b end, {"mod"}) == 10)
assert(run(function () return a // b end, {"idiv"}) == 0)

-- repeat tests with larger constants (to use 'K' opcodes)
local a1000 = new(1000)

assert(run(function () return a1000 + 1000 end, {"add"}) == 2000)
assert(run(function () return a1000 - 25000 end, {"sub"}) == -24000)
assert(run(function () return 2000 * a end, {"mul"}) == 20000)
assert(run(function () return a1000 / 1000 end, {"div"}) == 1)
assert(run(function () return a1000 % 600 end, {"mod"}) == 400)
assert(run(function () return a1000 // 500 end, {"idiv"}) == 2)



assert(run(function () return a % b end, {"mod"}) == 10)

assert(run(function () return ~a & b end, {"bnot", "band"}) == ~10 & 12)
assert(run(function () return a | b end, {"bor"}) == 10 | 12)
assert(run(function () return a ~ b end, {"bxor"}) == 10 ~ 12)
assert(run(function () return a << b end, {"shl"}) == 10 << 12)
assert(run(function () return a >> b end, {"shr"}) == 10 >> 12)

assert(run(function () return 10 & b end, {"band"}) == 10 & 12)
assert(run(function () return a | 2 end, {"bor"}) == 10 | 2)
assert(run(function () return a ~ 2 end, {"bxor"}) == 10 ~ 2)
assert(run(function () return a >> 2 end, {"shr"}) == 10 >> 2)
assert(run(function () return 1 >> a end, {"shr"}) == 1 >> 10)
assert(run(function () return a << 2 end, {"shl"}) == 10 << 2)
assert(run(function () return 1 << a end, {"shl"}) == 1 << 10)
assert(run(function () return 2 ~ a end, {"bxor"}) == 2 ~ 10)


assert(run(function () return a..b end, {"concat"}) == "1012")

assert(run(function() return a .. b .. c .. a end,
       {"concat", "concat", "concat"}) == "1012hello10")

assert(run(function() return "a" .. "b" .. a .. "c" .. c .. b .. "x" end,
       {"concat", "concat", "concat"}) == "ab10chello12x")


do   -- a few more tests for comparison operators
  local mt1 = {
    __le = function (a,b)
      coroutine.yield(10)
      return (val(a) <= val(b))
    end,
    __lt = function (a,b)
      coroutine.yield(10)
      return val(a) < val(b)
    end,
  }
  local mt2 = { __lt = mt1.__lt, __le = mt1.__le }

  local function run (f)
    local co = coroutine.wrap(f)
    local res
    repeat
      res = co()
    until res ~= 10
    return res
  end
  
  local function test ()
    local a1 = setmetatable({x=1}, mt1)
    local a2 = setmetatable({x=2}, mt2)
    assert(a1 < a2)
    assert(a1 <= a2)
    assert(1 < a2)
    assert(1 <= a2)
    assert(2 > a1)
    assert(2 >= a2)
    return true
  end
  
  run(test)

end

assert(run(function ()
             a.BB = print
             return a.BB
           end, {"nidx", "idx"}) == print)

-- getuptable & setuptable
do local _ENV = _ENV
  f = function () AAA = BBB + 1; return AAA end
end
local g = new(10); g.k.BBB = 10;
debug.setupvalue(f, 1, g)
assert(run(f, {"idx", "nidx", "idx"}) == 11)
assert(g.k.AAA == 11)

print"+"

print"testing yields inside 'for' iterators"

local f = function (s, i)
      if i%2 == 0 then coroutine.yield(nil, "for") end
      if i < s then return i + 1 end
    end

assert(run(function ()
             local s = 0
             for i in f, 4, 0 do s = s + i end
             return s
           end, {"for", "for", "for"}) == 10)



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

print('testing coroutine API')

local function apico (...)
  local x = {...}
  return coroutine.wrap(function ()
    return T.testC(table.unpack(x))
  end)
end

local a = {apico(
[[
  pushstring errorcode
  pcallk 1 0 2;
  invalid command (should not arrive here)
]],
[[return *]],
"stackmark",
error
)()}
assert(#a == 4 and
       a[3] == "stackmark" and
       a[4] == "errorcode" and
       _G.status == "ERRRUN" and
       _G.ctx == 2)       -- 'ctx' to pcallk

local co = apico(
  "pushvalue 2; pushnum 10; pcallk 1 2 3; invalid command;",
  coroutine.yield,
  "getglobal status; getglobal ctx; pushvalue 2; pushstring a; pcallk 1 0 4; invalid command",
  "getglobal status; getglobal ctx; return *")

assert(co() == 10)
assert(co(20, 30) == 'a')
a = {co()}
assert(#a == 10 and
       a[2] == coroutine.yield and
       a[5] == 20 and a[6] == 30 and
       a[7] == "YIELD" and a[8] == 3 and
       a[9] == "YIELD" and a[10] == 4)
assert(not pcall(co))   -- coroutine is dead now


f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;")
co = coroutine.wrap(function ()
  assert(f() == 23); assert(f() == 23); return 10
end)
assert(co(23,16) == 5)
assert(co(23,16) == 5)
assert(co(23,16) == 10)


-- testing coroutines with C bodies
f = T.makeCfunc([[
        pushnum 102
	yieldk	1 U2
	cannot be here!
]],
[[      # continuation
	pushvalue U3   # accessing upvalues inside a continuation
        pushvalue U4
	return *
]], 23, "huu")

x = coroutine.wrap(f)
assert(x() == 102)
eqtab({x()}, {23, "huu"})


f = T.makeCfunc[[pushstring 'a'; pushnum 102; yield 2; ]]

a, b, c, d = T.testC([[newthread; pushvalue 2; xmove 0 3 1; resume 3 0;
                       pushstatus; xmove 3 0 0;  resume 3 0; pushstatus;
                       return 4; ]], f)

assert(a == 'YIELD' and b == 'a' and c == 102 and d == 'OK')


-- testing chain of suspendable C calls

local count = 3   -- number of levels

f = T.makeCfunc([[
  remove 1;             # remove argument
  pushvalue U3;         # get selection function
  call 0 1;             # call it  (result is 'f' or 'yield')
  pushstring hello      # single argument for selected function
  pushupvalueindex 2;   # index of continuation program
  callk 1 -1 .;		# call selected function
  errorerror		# should never arrive here
]],
[[
  # continuation program
  pushnum 34	# return value
  return *     # return all results
]],
function ()     -- selection function
  count = count - 1
  if count == 0 then return coroutine.yield
  else return f
  end
end
)

co = coroutine.wrap(function () return f(nil) end)
assert(co() == "hello")   -- argument to 'yield'
a = {co()}
-- three '34's (one from each pending C call)
assert(#a == 3 and a[1] == a[2] and a[2] == a[3] and a[3] == 34)


-- testing yields with continuations

local y

co = coroutine.wrap(function (...) return
       T.testC([[ # initial function
          yieldk 1 2
          cannot be here!
       ]],
       [[  # 1st continuation
         yieldk 0 3 
         cannot be here!
       ]],
       [[  # 2nd continuation
         yieldk 0 4 
         cannot be here!
       ]],
       [[  # 3th continuation
          pushvalue 6   # function which is last arg. to 'testC' here
          pushnum 10; pushnum 20;
          pcall 2 0 0   # call should throw an error and return to next line
          pop 1		# remove error message
          pushvalue 6
          getglobal status; getglobal ctx
          pcallk 2 2 5  # call should throw an error and jump to continuation
          cannot be here!
       ]],
       [[  # 4th (and last) continuation
         return *
       ]],
       -- function called by 3th continuation
       function (a,b) x=a; y=b; error("errmsg") end,
       ...
)
end)

local a = {co(3,4,6)}
assert(a[1] == 6 and a[2] == undef)
a = {co()}; assert(a[1] == undef and _G.status == "YIELD" and _G.ctx == 2)
a = {co()}; assert(a[1] == undef and _G.status == "YIELD" and _G.ctx == 3)
a = {co(7,8)};
-- original arguments
assert(type(a[1]) == 'string' and type(a[2]) == 'string' and
     type(a[3]) == 'string' and type(a[4]) == 'string' and
     type(a[5]) == 'string' and type(a[6]) == 'function')
-- arguments left from fist resume
assert(a[7] == 3 and a[8] == 4)
-- arguments to last resume
assert(a[9] == 7 and a[10] == 8)
-- error message and nothing more
assert(a[11]:find("errmsg") and #a == 11)
-- check arguments to pcallk
assert(x == "YIELD" and y == 4)

assert(not pcall(co))   -- coroutine should be dead

_G.ctx = nil
_G.status = nil


-- bug in nCcalls
local co = coroutine.wrap(function ()
  local a = {pcall(pcall,pcall,pcall,pcall,pcall,pcall,pcall,error,"hi")}
  return pcall(assert, table.unpack(a))
end)

local a = {co()}
assert(a[10] == "hi")

print'OK'