# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua::Stream;

repeat_each(2);

plan tests => repeat_each() * (blocks() * 3 + 27);

$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';

our $StapScript = <<'_EOC_';
global ids, cur

function gen_id(k) {
    if (ids[k]) return ids[k]
    ids[k] = ++cur
    return cur
}

F(ngx_http_handler) {
    delete ids
    cur = 0
}

/*
F(ngx_http_lua_run_thread) {
    id = gen_id($ctx->cur_co)
    printf("run thread %d\n", id)
}

probe process("/usr/local/openresty-debug/luajit/lib/libluajit-5.1.so.2").function("lua_resume") {
    id = gen_id($L)
    printf("lua resume %d\n", id)
}
*/

M(http-lua-user-coroutine-resume) {
    p = gen_id($arg2)
    c = gen_id($arg3)
    printf("resume %x in %x\n", c, p)
}

M(http-lua-thread-yield) {
    println("thread yield")
}

/*
F(ngx_http_lua_coroutine_yield) {
    printf("yield %x\n", gen_id($L))
}
*/

M(http-lua-user-coroutine-yield) {
    p = gen_id($arg2)
    c = gen_id($arg3)
    printf("yield %x in %x\n", c, p)
}

F(ngx_http_lua_atpanic) {
    printf("lua atpanic(%d):", gen_id($L))
    print_ubacktrace();
}

M(http-lua-user-coroutine-create) {
    p = gen_id($arg2)
    c = gen_id($arg3)
    printf("create %x in %x\n", c, p)
}

F(ngx_http_lua_ngx_exec) { println("exec") }

F(ngx_http_lua_ngx_exit) { println("exit") }
F(ngx_http_lua_ffi_exit) { println("exit") }
_EOC_

no_shuffle();
no_long_string();
run_tests();

__DATA__

=== TEST 1: basic coroutine print
--- stream_server_config
    content_by_lua_block {
        local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield

        function f()
            local cnt = 0
            for i = 1, 20 do
                ngx.say("Hello, ", cnt)
                cy()
                cnt = cnt + 1
            end
        end

        local c = cc(f)
        for i=1,3 do
            cr(c)
            ngx.say("***")
        end
    }

--- config
--- stap2 eval: $::StapScript
--- stream_response
Hello, 0
***
Hello, 1
***
Hello, 2
***
--- no_error_log
[error]



=== TEST 2: basic coroutine2
--- stream_server_config
    content_by_lua_block {
        function f(fid)
            local cnt = 0
            while true do
                ngx.say("cc", fid, ": ", cnt)
                coroutine.yield()
                cnt = cnt + 1
            end
        end

        local ccs = {}
        for i=1,3 do
            ccs[#ccs+1] = coroutine.create(function() f(i) end)
        end

        for i=1,9 do
            local cc = table.remove(ccs, 1)
            coroutine.resume(cc)
            ccs[#ccs+1] = cc
        end
    }

--- config
--- stream_response
cc1: 0
cc2: 0
cc3: 0
cc1: 1
cc2: 1
cc3: 1
cc1: 2
cc2: 2
cc3: 2
--- no_error_log
[error]



=== TEST 3: basic coroutine and cosocket
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        function worker(url)
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(url, 80)
            coroutine.yield()
            if not ok then
                ngx.say("failed to connect to: ", url, " error: ", err)
                return
            end
            coroutine.yield()
            ngx.say("successfully connected to: ", url)
            sock:close()
        end

        local urls = {
            "agentzh.org",
            "openresty.com",
            "openresty.org"
        }

        local ccs = {}
        for i, url in ipairs(urls) do
            local cc = coroutine.create(function() worker(url) end)
            ccs[#ccs+1] = cc
        end

        while true do
            if #ccs == 0 then break end
            local cc = table.remove(ccs, 1)
            local ok = coroutine.resume(cc)
            if ok then
                ccs[#ccs+1] = cc
            end
        end

        ngx.say("*** All Done ***")
    }

--- config
--- stream_response
successfully connected to: agentzh.org
successfully connected to: openresty.com
successfully connected to: openresty.org
*** All Done ***
--- no_error_log
[error]
--- timeout: 10



=== TEST 4: coroutine.wrap(generate prime numbers)
--- stream_server_config
    content_by_lua_block {
        -- generate all the numbers from 2 to n
        function gen (n)
          return coroutine.wrap(function ()
            for i=2,n do coroutine.yield(i) end
          end)
        end

        -- filter the numbers generated by g, removing multiples of p
        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

        N = 10
        x = gen(N)		-- generate primes up to N
        while 1 do
          local n = x()		-- pick a number until done
          if n == nil then break end
          ngx.say(n)		-- must be a prime number
          x = filter(n, x)	-- now remove its multiples
        end
    }

--- config
--- stream_response
2
3
5
7
--- no_error_log
[error]



=== TEST 5: coroutine.wrap(generate prime numbers,reset create and resume)
--- stream_server_config
    content_by_lua_block {
        coroutine.create = nil
        coroutine.resume = nil
        -- generate all the numbers from 2 to n
        function gen (n)
          return coroutine.wrap(function ()
            for i=2,n do coroutine.yield(i) end
          end)
        end

        -- filter the numbers generated by g, removing multiples of p
        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

        N = 10 
        x = gen(N)		-- generate primes up to N
        while 1 do
          local n = x()		-- pick a number until done
          if n == nil then break end
          ngx.say(n)		-- must be a prime number
          x = filter(n, x)	-- now remove its multiples
        end
    }

--- config
--- stream_response
2
3
5
7
--- no_error_log
[error]



=== TEST 6: coroutine.wrap(generate fib)
--- stream_server_config
    content_by_lua_block {
        function generatefib (n)
          return coroutine.wrap(function ()
            local a,b = 1, 1
            while a <= n do
              coroutine.yield(a)
              a, b = b, a+b
            end
          end)
        end

        -- In lua, because OP_TFORLOOP uses luaD_call to execute the iterator function,
        -- and luaD_call is a C function, so we can not yield in the iterator function.
        -- So the following case(using for loop) will be failed.
        -- Luajit is OK.
        if package.loaded["jit"] then
            for i in generatefib(1000) do ngx.say(i) end
        else
            local gen = generatefib(1000)
            while true do
                local i = gen()
                if not i then break end
                ngx.say(i)
            end
        end
    }

--- config
--- stream_response
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
--- no_error_log
[error]



=== TEST 7: coroutine wrap and cosocket
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        function worker(url)
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(url, 80)
            coroutine.yield()
            if not ok then
                ngx.say("failed to connect to: ", url, " error: ", err)
                return
            end
            coroutine.yield()
            ngx.say("successfully connected to: ", url)
            sock:close()
        end

        local urls = {
            "agentzh.org",
            "openresty.com",
            "openresty.org"
        }

        local cfs = {}
        for i, url in ipairs(urls) do
            local cf = coroutine.wrap(function() worker(url) end)
            cfs[#cfs+1] = cf
        end

        for i=1,3 do cfs[i]() end
        for i=1,3 do cfs[i]() end
        for i=1,3 do cfs[i]() end

        ngx.say("*** All Done ***")
    }

--- config
--- stream_response
successfully connected to: agentzh.org
successfully connected to: openresty.com
successfully connected to: openresty.org
*** All Done ***
--- no_error_log
[error]
--- timeout: 10



=== TEST 8: coroutine status, running
--- stream_server_config
    content_by_lua_block {
        local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield
        local st, rn = coroutine.status, coroutine.running

        function f(self)
            local cnt = 0
            if rn() ~= self then ngx.say("error"); return end
            ngx.say("running: ", st(self)) --running
            cy()
            local c = cc(function(father)
                ngx.say("normal: ", st(father))
            end) -- normal
            cr(c, self)
        end

        local c = cc(f)
        ngx.say("suspended: ", st(c)) -- suspended
        cr(c, c)
        ngx.say("suspended: ", st(c)) -- suspended
        cr(c, c)
        ngx.say("dead: ", st(c)) -- dead
    }

--- config
--- stream_response
suspended: suspended
running: running
suspended: suspended
normal: normal
dead: dead
--- no_error_log
[error]



=== TEST 9: entry coroutine yielded will be resumed immediately
--- stream_server_config
    content_by_lua_block {
        ngx.say("[", {coroutine.yield()}, "]")
        ngx.say("[", {coroutine.yield(1, "a")}, "]")
        ngx.say("done")
    }

--- config
--- stream_response
[]
[]
done
--- no_error_log
[error]



=== TEST 10: thread traceback (multi-thread)
Note: only coroutine.wrap propagates errors to the parent coroutine
(and thus produces a traceback)
--- stream_server_config
    content_by_lua_block {
        local f = function(cr) coroutine.resume(cr) end
        -- emit a error
        local g = function() unknown.unknown = 1 end
        local l1 = coroutine.wrap(f)
        local l2 = coroutine.wrap(g)
        local l3 = coroutine.wrap(function() l1(l2) end)
        l3()
        ngx.say("hello")
    }
--- stream_response
--- error_log eval
["stack traceback:", "coroutine 0:", "coroutine 1:", "coroutine 2:"]
--- no_error
[crit]



=== TEST 11: thread traceback (only the entry thread)
--- stream_server_config
    content_by_lua_block {
        -- emit a error
        unknown.unknown = 1
        ngx.say("hello")
    }

--- config
--- error_log eval
["stack traceback:", "coroutine 0:"]



=== TEST 12: bug: resume dead coroutine with args
--- stream_server_config
    content_by_lua_block {
        function print(...)
            local args = {...}
            local is_first = true
            for i,v in ipairs(args) do
                if is_first then
                    is_first = false
                else
                    ngx.print(" ")
                end
                ngx.print(v)
            end
            ngx.print("\n")
        end

        function foo (a)
            print("foo", a)
            return coroutine.yield(2*a)
        end

        co = coroutine.create(function (a,b)
                print("co-body", a, b)
                local r = foo(a+1)
                print("co-body", r)
                local r, s = coroutine.yield(a+b, a-b)
                print("co-body", r, s)
                return b, "end"
            end)

        print("main", coroutine.resume(co, 1, 10))
        print("main", coroutine.resume(co, "r"))
        print("main", coroutine.resume(co, "x", "y"))
        print("main", coroutine.resume(co, "x", "y"))
    }

--- config
--- stream_response
co-body 1 10
foo 2
main true 4
co-body r
main true 11 -9
co-body x y
main true 10 end
main false cannot resume dead coroutine
--- no_error_log
[error]



=== TEST 13: deeply nested coroutines
--- stream_server_config
    content_by_lua_block {
        local create = coroutine.create
        local resume = coroutine.resume
        local yield = coroutine.yield
        function f()
            ngx.say("f begin")
            yield()
            local c2 = create(g)
            ngx.say("1: resuming c2")
            resume(c2)
            ngx.say("2: resuming c2")
            resume(c2)
            yield()
            ngx.say("3: resuming c2")
            resume(c2)
            ngx.say("f done")
        end

        function g()
            ngx.say("g begin")
            yield()
            ngx.say("g going")
            yield()
            ngx.say("g done")
        end

        local c1 = create(f)
        ngx.say("1: resuming c1")
        resume(c1)
        ngx.say("2: resuming c1")
        resume(c1)
        ngx.say("3: resuming c1")
        resume(c1)
        ngx.say("main done")
    }

--- config
--- stream_response
1: resuming c1
f begin
2: resuming c1
1: resuming c2
g begin
2: resuming c2
g going
3: resuming c1
3: resuming c2
g done
f done
main done
--- no_error_log
[error]



=== TEST 14: using ngx.exit in user coroutines
--- stream_server_config
    content_by_lua_block {
        local create = coroutine.create
        local resume = coroutine.resume
        local yield = coroutine.yield

        local code = 400

        function f()
            local c2 = create(g)
            yield()
            code = code + 1
            resume(c2)
            yield()
            resume(c2)
        end

        function g()
            code = code + 1
            yield()
            code = code + 1
            ngx.exit(code)
        end

        local c1 = create(f)
        resume(c1)
        resume(c1)
        resume(c1)
        ngx.say("done")
    }

--- config
--- stap eval: $::StapScript
--- stap_out
create 2 in 1
resume 2 in 1
create 3 in 2
yield 2 in 1
resume 2 in 1
resume 3 in 2
yield 3 in 2
yield 2 in 1
resume 2 in 1
resume 3 in 2
exit

--- stream_response
--- no_error_log
[error]



=== TEST 15: resume coroutines from within another one that is not its parent
--- stream_server_config
    content_by_lua_block {
        local print = ngx.say

        local c1, c2

        function f()
            print("f 1")
            print(coroutine.resume(c2))
            print("f 2")
        end

        function g()
            print("g 1")
            -- print(coroutine.resume(c1))
            print("g 2")
        end

        c1 = coroutine.create(f)
        c2 = coroutine.create(g)

        coroutine.resume(c1)
    }

--- config
--- stream_response
f 1
g 1
g 2
true
f 2
--- no_error_log
[error]



=== TEST 16: infinite recursive calls of coroutine.resume
--- stream_server_config
    content_by_lua_block {
        local print = ngx.say

        local c1, c2

        function f()
            print("f 1")
            print(coroutine.resume(c2))
            print("f 2")
        end

        function g()
            print("g 1")
            print(coroutine.resume(c1))
            print("g 2")
        end

        c1 = coroutine.create(f)
        c2 = coroutine.create(g)

        coroutine.resume(c1)
    }

--- config
--- stap2 eval: $::StapScript
--- stream_response
f 1
g 1
falsecannot resume normal coroutine
g 2
true
f 2
--- no_error_log
[error]



=== TEST 17: resume running (entry) coroutines
--- stream_server_config
    content_by_lua_block {
        ngx.say(coroutine.status(coroutine.running()))
        ngx.say(coroutine.resume(coroutine.running()))
    }

--- config
--- stream_response
running
falsecannot resume running coroutine
--- no_error_log
[error]



=== TEST 18: resume running (user) coroutines
--- stream_server_config
    content_by_lua_block {
        local co
        function f()
            ngx.say("f: ", coroutine.status(co))
            ngx.say("f: ", coroutine.resume(co))
        end
        co = coroutine.create(f)
        ngx.say("chunk: ", coroutine.status(co))
        ngx.say("chunk: ", coroutine.resume(co))
    }

--- config
--- stream_response
chunk: suspended
f: running
f: falsecannot resume running coroutine
chunk: true
--- no_error_log
[error]



=== TEST 19: user coroutine end with errors, and the parent coroutine gets the right status
--- stream_server_config
    content_by_lua_block {
        local co
        function f()
            error("bad")
        end
        co = coroutine.create(f)
        ngx.say("child: resume: ", coroutine.resume(co))
        ngx.say("child: status: ", coroutine.status(co))
        ngx.say("parent: status: ", coroutine.status(coroutine.running()))
    }

--- config
--- stream_response eval
qr/^child: resume: falsecontent_by_lua\(nginx\.conf:\d+\):4: bad
child: status: dead
parent: status: running
$/s
--- no_error_log
[error]



=== TEST 20: entry coroutine is yielded by hand and still gets the right status
--- stream_server_config
    content_by_lua_block {
        local co = coroutine.running()
        ngx.say("status: ", coroutine.status(co))
        coroutine.yield(co)
        ngx.say("status: ", coroutine.status(co))
    }

--- config
--- stream_response
status: running
status: running
--- no_error_log
[error]



=== TEST 21: github issue #208: coroutine as iterator doesn't work
--- stream_server_config
    content_by_lua_block {
        local say = ngx.say
        local wrap, yield = coroutine.wrap, coroutine.yield

        local function it(it_state)
          for i = 1, it_state.i do
            yield(it_state.path, tostring(i))
          end
          return nil
        end

        local function it_factory(path)
          local it_state = { i = 10, path = path }
          return wrap(it), it_state
        end

        --[[
        for path, value in it_factory("test") do
          say(path, value)
        end
        ]]

        do
          local f, s, var = it_factory("test")
          while true do
            local path, value = f(s, var)
            var = path
            if var == nil then break end
            say(path, value)
          end
        end
    }

--- config
--- more_headers
Cookie: abc=32
--- stap2 eval: $::StapScript
--- stream_response
test1
test2
test3
test4
test5
test6
test7
test8
test9
test10
--- no_error_log
[error]



=== TEST 22: init_by_lua + our own coroutines in content_by_lua
--- stream_config
    init_by_lua_block { return }
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        function worker(url)
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(url, 80)
            coroutine.yield()
            if not ok then
                ngx.say("failed to connect to: ", url, " error: ", err)
                return
            end
            coroutine.yield()
            ngx.say("successfully connected to: ", url)
            sock:close()
        end

        local urls = {
            "agentzh.org",
        }

        local ccs = {}
        for i, url in ipairs(urls) do
            local cc = coroutine.create(function() worker(url) end)
            ccs[#ccs+1] = cc
        end

        while true do
            if #ccs == 0 then break end
            local cc = table.remove(ccs, 1)
            local ok = coroutine.resume(cc)
            if ok then
                ccs[#ccs+1] = cc
            end
        end

        ngx.say("*** All Done ***")
    }

--- config
--- stream_response
successfully connected to: agentzh.org
*** All Done ***
--- no_error_log
[error]
--- timeout: 10



=== TEST 23: init_by_lua_file + our own coroutines in content_by_lua
--- stream_config
    init_by_lua_file html/init.lua;

--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        function worker(url)
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect(url, 80)
            coroutine.yield()
            if not ok then
                ngx.say("failed to connect to: ", url, " error: ", err)
                return
            end
            coroutine.yield()
            ngx.say("successfully connected to: ", url)
            sock:close()
        end

        local urls = {
            "agentzh.org"
        }

        local ccs = {}
        for i, url in ipairs(urls) do
            local cc = coroutine.create(function() worker(url) end)
            ccs[#ccs+1] = cc
        end

        while true do
            if #ccs == 0 then break end
            local cc = table.remove(ccs, 1)
            local ok = coroutine.resume(cc)
            if ok then
                ccs[#ccs+1] = cc
            end
        end

        ngx.say("*** All Done ***")
    }

--- config
--- user_files
>>> init.lua
return

--- stream_response
successfully connected to: agentzh.org
*** All Done ***
--- no_error_log
[error]
--- timeout: 10



=== TEST 24: mixing coroutine.* API between init_by_lua and other contexts (github #304) - init_by_lua
--- stream_config
    init_by_lua_block {
        co_wrap = coroutine.wrap
        co_yield = coroutine.yield
    }

--- stream_server_config
    content_by_lua_block {
        function generator()
            return co_wrap(function()
                co_yield("data")
            end)
        end

        local co = generator()
        local data = co()
        ngx.say(data)
    }

--- config

--- stap2 eval: $::StapScript
--- stream_response
data
--- no_error_log
[error]



=== TEST 25: mixing coroutine.* API between init_by_lua and other contexts (github #304) - init_by_lua_file
--- stream_config
    init_by_lua_file html/init.lua;

--- stream_server_config
    content_by_lua_block {
        function generator()
            return co_wrap(function()
                co_yield("data")
            end)
        end

        local co = generator()
        local data = co()
        ngx.say(data)
    }

--- config

--- user_files
>>> init.lua
co_wrap = coroutine.wrap
co_yield = coroutine.yield

--- stap2 eval: $::StapScript
--- stream_response
data
--- no_error_log
[error]



=== TEST 26: coroutine context collicisions
--- stream_server_config
    content_by_lua_block {
        local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield

        function f()
            return 3
        end

        for i = 1, 10 do
            collectgarbage()
            local c = cc(f)
            if coroutine.status(c) == "dead" then
                ngx.say("found a dead coroutine")
                return
            end
            cr(c)
        end
        ngx.say("ok")
    }

--- config
--- stap2 eval: $::StapScript
--- stream_response
ok
--- no_error_log
[error]



=== TEST 27: require "coroutine"
--- stream_server_config
    content_by_lua_block {
        local coroutine = require "coroutine"
        local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield

        function f()
            local cnt = 0
            for i = 1, 20 do
                ngx.say("Hello, ", cnt)
                ngx.sleep(0.001)
                cy()
                cnt = cnt + 1
            end
        end

        local c = cc(f)
        for i=1,3 do
            cr(c)
            ngx.say("***")
        end
    }

--- config
--- stap2 eval: $::StapScript
--- stream_response
Hello, 0
***
Hello, 1
***
Hello, 2
***
--- no_error_log
[error]



=== TEST 28: coroutine.wrap propagates errors to parent coroutine
--- stream_server_config
    content_by_lua_block {
        local co = coroutine.wrap(function()
            print("in wrapped coroutine")
            error("something went wrong")
        end)

        co()

        ngx.say("ok")
    }
--- stream_response
--- error_log eval
[
    qr/\[notice\] .*? in wrapped coroutine/,
    qr/\[error\] .*? lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):\d+: something went wrong/,
    "stack traceback:",
    "coroutine 0:",
    "coroutine 1:"
]



=== TEST 29: coroutine.wrap propagates nested errors to parent coroutine
Note: in this case, both the error message and the traceback are constructed
from co1's stack level.
--- stream_server_config
    content_by_lua_block {
        local co1 = coroutine.wrap(function()
            error("something went wrong in co1")
        end)

        local co2 = coroutine.wrap(function()
            co1()
        end)

        co2()
    }
--- stream_response
--- error_log eval
[
    qr/\[error\] .*? lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):\d+: something went wrong in co1/,
    "stack traceback:",
    "coroutine 0:",
    "coroutine 1:",
    "coroutine 2:"
]



=== TEST 30: coroutine.wrap propagates nested errors with stack level to parent coroutine
Note: in this case, the error message is constructed at the entry thread stack
level, and the traceback is constructed from co1's stack level.
--- stream_server_config
    content_by_lua_block {
        local co1 = coroutine.wrap(function()
            error("something went wrong in co1", 2)
        end)

        local co2 = coroutine.wrap(function()
            co1()
        end)

        co2()
    }
--- stream_response
--- error_log eval
[
    qr/\[error\] .*? lua entry thread aborted: runtime error: something went wrong in co1/,
    "stack traceback:",
    "coroutine 0:",
    "coroutine 1:",
    "coroutine 2:"
]



=== TEST 31: coroutine.wrap runtime errors do not log errors
--- stream_server_config
    content_by_lua_block {
        local co = coroutine.wrap(function()
            print("in wrapped coroutine")
            error("something went wrong")
        end)

        co()
    }
--- stream_response
--- no_error_log eval
[
    qr/\[error\] .*? lua coroutine: runtime error:/,
    "[alert]",
    "[crit]",
    "[emerg]",
    "[warn]",
]



=== TEST 32: coroutine.wrap does not return status boolean on yield
--- stream_server_config
    content_by_lua_block {
        local co = coroutine.wrap(function()
            coroutine.yield("ok", "err")
        end)

        local ret1, ret2 = co()

        ngx.say(ret1, ", ", ret2)
    }
--- stream_response
ok, err
--- no_error_log
[error]



=== TEST 33: coroutine.wrap does not return status boolean on done
--- stream_server_config
    content_by_lua_block {
        local co = coroutine.wrap(function() end)

        local ret1 = co()

        ngx.say(ret1)
    }
--- stream_response
nil
--- no_error_log
[emerg]
[alert]
[crit]
[error]
[warn]



=== TEST 34: coroutine.wrap does not return status boolean on error
--- SKIP: not supported
--- stream_server_config
    content_by_lua_block {
        local co = coroutine.wrap(function()
            error("something went wrong")
        end)

        local ret1, ret2 = pcall(co)

        ngx.say(ret1, ", ", ret2)
    }
--- response_body
false, something went wrong
--- no_error_log
[error]



=== TEST 35: coroutine.wrap creates different function refs
--- stream_server_config
    content_by_lua_block {
        local f = function() end
        local co = coroutine.wrap(f)
        local co2 = coroutine.wrap(f)

        ngx.say("co == co2: ", co == co2)
    }
--- stream_response
co == co2: false
--- no_error_log
[emerg]
[alert]
[crit]
[error]
[warn]



=== TEST 36: coroutine.wrap supports yielding and resuming
--- stream_server_config
    content_by_lua_block {
        local function f()
            local cnt = 0
            for i = 1, 20 do
                ngx.say("co yield: ", cnt)
                coroutine.yield()
                cnt = cnt + 1
            end
        end

        local f = coroutine.wrap(f)
        for i = 1, 3 do
            ngx.say("co resume")
            f()
        end
    }
--- stream_response
co resume
co yield: 0
co resume
co yield: 1
co resume
co yield: 2
--- no_error_log
[error]



=== TEST 37: coroutine.wrap return values
--- stream_server_config
    content_by_lua_block {
        local function f()
            local cnt = 0
            for i = 1, 20 do
                coroutine.yield(cnt, cnt + 1)
                cnt = cnt + 1
            end
        end

        local f = coroutine.wrap(f)
        for i = 1, 3 do
            ngx.say("co resume")
            local ret1, ret2 = f()
            ngx.say("co yield: ", ret1, ", ", ret2)
        end
    }
--- stream_response
co resume
co yield: 0, 1
co resume
co yield: 1, 2
co resume
co yield: 2, 3
--- no_error_log
[error]



=== TEST 38: coroutine.wrap arguments
--- stream_server_config
    content_by_lua_block {
        local function f(step)
            local cnt = 0
            for i = 1, 20 do
                ngx.say("co yield: ", cnt)
                coroutine.yield()
                cnt = cnt + step
            end
        end

        local f = coroutine.wrap(f)
        for i = 1, 3 do
            ngx.say("co resume")
            f(i)
        end
    }
--- stream_response
co resume
co yield: 0
co resume
co yield: 1
co resume
co yield: 2
--- no_error_log
[error]



=== TEST 39: coroutine.wrap in init_by_lua propagates errors (orig coroutine.wrap)
--- stream_config
    init_by_lua_block {
        local co = coroutine.wrap(function()
            print("in wrapped coroutine")
            error("something went wrong")
        end)

        local err = co()

        ngx.log(ngx.CRIT, "err: ", err)
    }
--- stream_server_config
    content_by_lua_block {
        ngx.say("ok")
    }
--- must_die
--- grep_error_log eval: qr/init_by_lua error: .*? something went wrong/
--- grep_error_log_out
init_by_lua error: init_by_lua:7: init_by_lua:4: something went wrong



=== TEST 40: coroutine.resume runtime errors do not log errors
--- stream_server_config
    content_by_lua_block {
        local function f()
            error("something went wrong")
        end

        local ret1, ret2 = coroutine.resume(coroutine.create(f))
        ngx.say(ret1)
        ngx.say(ret2)
    }
--- stream_response_like
false
content_by_lua\(nginx.conf:\d+\):\d+: something went wrong
--- no_error_log eval
[
    qr/\[error\] .*? lua coroutine: runtime error:",
    "stack traceback:",
]
