--- source: src/main.rs expression: compiled input_file: test-data/lua5.3-tests/coroutine.lua --- print("testing coroutines"); local debug = require('debug') local f local main, ismain = coroutine.running() assert(type(main) == "thread" && ismain); assert(!coroutine.resume(main)); assert(!coroutine.isyieldable()); assert(!pcall(coroutine.yield)); assert(!pcall(coroutine.resume, 0)); assert(!pcall(coroutine.status, 0)); local fn eqtab(t1, t2) { assert(#t1 == #t2); for i = 1, #t1 { local v = t1[(i)] assert(t2[(i)] == v); } } _G.x = nil global fn foo(a, ...) { local x, y = coroutine.running() assert(x == f && y == false); assert(coroutine.resume(f) == false); assert(coroutine.status(f) == "running"); local arg = { ... } assert(coroutine.isyieldable()); for i = 1, #arg { _G.x = { coroutine.yield(table.unpack(arg[(i)])) } } return table.unpack(a) } f = coroutine.create(foo) assert(type(f) == "thread" && 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(s && a == nil && coroutine.status(f) == "suspended"); s, a, b, c, d = coroutine.resume(f) eqtab(_G.x, {}); assert(s && a == 1 && b == nil); s, a, b, c, d = coroutine.resume(f, 1, 2, 3) eqtab(_G.x, { 1, 2, 3 }); assert(s && a == 'a' && b == 'b' && c == 'c' && d == nil); s, a, b, c, d = coroutine.resume(f, "xuxu") eqtab(_G.x, { "xuxu" }); assert(s && a == 1 && b == 2 && c == 3 && d == nil); assert(coroutine.status(f) == "dead"); s, a = coroutine.resume(f, "xuxu") assert(!s && string.find(a, "dead") && coroutine.status(f) == "dead"); local fn foo(i) { return coroutine.yield(i) } f = coroutine.wrap(fn () { for i = 1, 10 { assert(foo(i) == _G.x); } return 'a' }) for i = 1, 10 { _G.x = i assert(f(i) == i); } _G.x = 'xuxu' assert(f('xuxu') == 'a'); global fn pf(n, i) { coroutine.yield(n); pf(n * i, i + 1); } f = coroutine.wrap(pf) local s = 1 for i = 1, 10 { assert(f(1, 1) == s); s = s * i } global fn gen(n) { return coroutine.wrap(fn () { for i = 2, n { coroutine.yield(i); } }) } global fn filter(p, g) { return coroutine.wrap(fn () { while 1 { local n = g() if n == nil { return } if math.fmod(n, p) != 0 { coroutine.yield(n); } } }) } local x = gen(100) local a = {} while 1 { local n = x() if n == nil { break } table.insert(a, n); x = filter(n, x) } assert(#a == 25 && a[(#a)] == 97); x, a = nil co = coroutine.wrap(fn () { assert(!pcall(table.sort, { 1, 2, 3 }, coroutine.yield)); assert(coroutine.isyieldable()); coroutine.yield(20); return 30 }) assert(co() == 20); assert(co() == 30); local f = fn (s, i) { return coroutine.yield(i) } local f1 = coroutine.wrap(fn () { return xpcall(pcall, fn (...) { return ... }, fn () { local s = 0 { local _internal_expr_2, _internal_stop_2, _internal_acc_2 = f, nil, 1; while true { local i = _internal_expr_2(_internal_stop_2, _internal_acc_2); _internal_acc_2 = i; if _internal_acc_2 == nil { break; } pcall(fn () { s = s + i }); } } error({ s }); }) }) f1(); for i = 1, 10 { assert(f1(i) == i); } local r1, r2, v = f1(nil) assert(r1 && !r2 && v[(1)] == (10 + 1) * 10 / 2); global fn f(a, b) { a = coroutine.yield(a) error({ a + b }); } global fn g(x) { return x[(1)] * 2 } co = coroutine.wrap(fn () { coroutine.yield(xpcall(f, g, 10, 20)); }) assert(co() == 10); r, msg = co(100) assert(!r && msg == 240); { local fn f(c) { assert(!coroutine.isyieldable()); return c .. c } local co = coroutine.wrap(fn (c) { assert(coroutine.isyieldable()); local s = string.gsub("a", ".", f) return s }) assert(co() == "aa"); } global fn 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); } global fn goo() { foo(); } x = coroutine.wrap(goo) assert(x() == 3); local a, b = pcall(x) assert(!a && b == foo); x = coroutine.create(goo) a, b = coroutine.resume(x) assert(a && b == 3); a, b = coroutine.resume(x) assert(!a && b == foo && coroutine.status(x) == "dead"); a, b = coroutine.resume(x) assert(!a && string.find(b, "dead") && coroutine.status(x) == "dead"); global fn all(a, n, k) { if k == 0 { coroutine.yield(a); } else { for i = 1, n { a[(k)] = i all(a, n, k - 1); } } } local a = 0 for t with coroutine.wrap(fn () { all({}, 5, 4); }) { a = a + 1 } assert(a == 5 ^ 4); local C = {} setmetatable(C, { __mode = "kv" }); local x = coroutine.wrap(fn () { local a = 10 local fn f() { a = a + 10 return a } while true { a = a + 1 coroutine.yield(f); } }) C[(1)] = x local f = x() assert(f() == 21 && x()() == 32 && x() == f); x = nil collectgarbage(); assert(C[(1)] == nil); assert(f() == 43 && f() == 53); global fn 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 } local co = coroutine.create(co_func) local a, b, c = coroutine.resume(co, co) assert(a == true && b == 10 && c == 20); a, b = coroutine.resume(co, co) assert(a == true && b == 23); a, b = coroutine.resume(co, co) assert(a == true && b == 10); assert(coroutine.resume(co, co) == false); assert(coroutine.resume(co, co) == false); { local A = coroutine.running() local B = coroutine.create(fn () { return coroutine.resume(A) }) local st, res = coroutine.resume(B) assert(st == true && res == false); A = coroutine.wrap(fn () { return pcall(A, 1) }) st, res = A() assert(!st && string.find(res, "non%-suspended")); } local co1, co2 co1 = coroutine.create(fn () { return co2() }) co2 = coroutine.wrap(fn () { assert(coroutine.status(co1) == 'normal'); assert(!coroutine.resume(co1)); coroutine.yield(3); }) a, b = coroutine.resume(co1) assert(a && b == 3); assert(coroutine.status(co1) == 'dead'); a = fn (a) { coroutine.wrap(a)(a); } assert(!pcall(a, a)); a = nil local x = coroutine.create(fn () { local a = 10 _G.f = fn () { a = a + 1 return a } error('x'); }) assert(!coroutine.resume(x)); assert(!coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1)); assert(_G.f() == 11); assert(_G.f() == 12); if !T { (Message || print)('\n >>> testC not active: skipping yield/hook tests <<<\n'); } else { print("testing yields inside hooks"); local turn global fn fact(t, x) { assert(turn == t); if x == 0 { return 1 } else { return x * fact(t, x - 1) } } local A, B = 0, 0 local x = coroutine.create(fn () { T.sethook("yield 0", "", 2); A = fact("A", 6) }) local y = coroutine.create(fn () { T.sethook("yield 0", "", 3); B = fact("B", 7) }) while A == 0 || B == 0 { if A == 0 { turn = "A" assert(T.resume(x)); } if B == 0 { turn = "B" assert(T.resume(y)); } } assert(B /_ A == 7); local line = debug.getinfo(1, "l").currentline + 2 local fn foo() { local x = 10 x = x + 10 _G.XX = x } local co = coroutine.wrap(fn () { T.sethook("setglobal X; yield 0", "l", 0); foo(); return 10 }) _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 && _G.XX == nil); _G.X = nil co(); assert(_G.X == line + 3 && _G.XX == 20); assert(co() == 10); co = coroutine.wrap(fn () { T.sethook("yield 0", "", 1); foo(); return 10 }) _G.XX = nil local c = 0 loop { c = c + 1 local a = co() } until a == 10 assert(_G.XX == 20 && c >= 5); co = coroutine.wrap(fn () { T.sethook("yield 0", "", 2); foo(); return 10 }) _G.XX = nil local c = 0 loop { c = c + 1 local a = co() } until a == 10 assert(_G.XX == 20 && c >= 5); _G.X = nil _G.XX = nil { c = coroutine.create(fn (a, ...) { T.sethook("yield 0", "l"); assert(a == 10); return ... }) assert(coroutine.resume(c, 1, 2, 3)); local n, v = debug.getlocal(c, 0, 1) assert(n == "a" && v == 1); n, v = debug.getlocal(c, 0, -1) assert(v == 2); n, v = debug.getlocal(c, 0, -2) assert(v == 3); assert(debug.setlocal(c, 0, 1, 10)); assert(debug.setlocal(c, 0, -2, 20)); local t = debug.getinfo(c, 0) assert(t.currentline == t.linedefined + 1); assert(!debug.getinfo(c, 1)); assert(coroutine.resume(c)); v = { coroutine.resume(c) } assert(v[(1)] == true && v[(2)] == 2 && v[(3)] == 20 && v[(4)] == nil); assert(!coroutine.resume(c)); } { local c = coroutine.create(fn () { T.testC("yield 1", 10, 20); }) local a, b = coroutine.resume(c) assert(a && b == 20); assert(debug.getinfo(c, 0).linedefined == -1); a, b = debug.getlocal(c, 0, 2) assert(b == 10); } print("testing coroutine API"); 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 `, fn (...) { return ... }) == 'b b b'); assert(X == 'a a a' && Y == 'OK'); C = coroutine.create(fn () { return T.testC(` pushnum 10; pushnum 20; resume -3 2; pushstatus gettop; return 3`, C) }) local a, b, c, d = coroutine.resume(C) assert(a == true && string.find(b, "non%-suspended") && c == "ERRRUN" && d == 4); a, b, c, d = T.testC(` rawgeti R 1 # get main thread pushnum 10; pushnum 20; resume -3 2; pushstatus gettop; return 4`) assert(a == coroutine.running() && string.find(b, "non%-suspended") && c == "ERRRUN" && d == 4); local state = T.newstate() T.loadlib(state); assert(T.doremote(state, ` coroutine = require'coroutine'; X = function (x) coroutine.yield(x, 'BB'); return 'CC' end; return 'ok'`)); t = table.pack(T.testC(state, ` rawgeti R 1 # 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 1 # get main thread pushnum 5 # arg (noise) resume 1 1 # after coroutine ends, previous stack is back pushstatus return * `)) assert(t.n == 4 && t[(2)] == 'XX' && t[(3)] == 'CC' && 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('+'); } _X = coroutine.wrap(fn () { local a = 10 local x = fn () { a = a + 1 } coroutine.yield(); }) _X(); if !_soft { local j = 2 ^ 9 local lim = 1000000 local t = { lim - 10, lim - 5, lim - 1, lim, lim + 1 } for i = 1, #t { local j = t[(i)] co = coroutine.create(fn () { local t = {} for i = 1, j { t[(i)] = i } return table.unpack(t) }) local r, msg = coroutine.resume(co) assert(!r); } co = nil } assert(coroutine.running() == main); print("+"); print("testing yields inside metamethods"); local mt = { __eq = fn (a, b) { coroutine.yield(nil, "eq"); return a.x == b.x }, __lt = fn (a, b) { coroutine.yield(nil, "lt"); return a.x < b.x }, __le = fn (a, b) { coroutine.yield(nil, "le"); return a - b <= 0 }, __add = fn (a, b) { coroutine.yield(nil, "add"); return a.x + b.x }, __sub = fn (a, b) { coroutine.yield(nil, "sub"); return a.x - b.x }, __mod = fn (a, b) { coroutine.yield(nil, "mod"); return a.x % b.x }, __unm = fn (a, b) { coroutine.yield(nil, "unm"); return -a.x }, __bnot = fn (a, b) { coroutine.yield(nil, "bnot"); return ^^a.x }, __shl = fn (a, b) { coroutine.yield(nil, "shl"); return a.x << b.x }, __shr = fn (a, b) { coroutine.yield(nil, "shr"); return a.x >> b.x }, __band = fn (a, b) { a = type(a) == "table" && a.x || a b = type(b) == "table" && b.x || b coroutine.yield(nil, "band"); return a & b }, __bor = fn (a, b) { coroutine.yield(nil, "bor"); return a.x | b.x }, __bxor = fn (a, b) { coroutine.yield(nil, "bxor"); return a.x ~ b.x }, __concat = fn (a, b) { coroutine.yield(nil, "concat"); a = type(a) == "table" && a.x || a b = type(b) == "table" && b.x || b return a .. b }, __index = fn (t, k) { coroutine.yield(nil, "idx"); return t.k[(k)] }, __newindex = fn (t, k, v) { coroutine.yield(nil, "nidx"); t.k[(k)] = v } } local fn new(x) { return setmetatable({ x = x, k = {} }, mt) } local a = new(10) local b = new(12) local c = new("hello") local fn run(f, t) { local i = 1 local c = coroutine.wrap(f) while true { local res, stat = c() if res { assert(t[(i)] == nil); return res, t } assert(stat == t[(i)]); i = i + 1 } } assert(run(fn () { if (a >= b) { return '>=' } else { return '<' } }, { "le", "sub" }) == "<"); mt.__le = nil assert(run(fn () { if (a <= b) { return '<=' } else { return '>' } }, { "lt" }) == "<="); assert(run(fn () { if (a == b) { return '==' } else { return '~=' } }, { "eq" }) == "~="); assert(run(fn () { return a & b + a }, { "add", "band" }) == 2); assert(run(fn () { return a % b }, { "mod" }) == 10); assert(run(fn () { return ^^a & b }, { "bnot", "band" }) == ^^10 & 12); assert(run(fn () { return a | b }, { "bor" }) == 10 | 12); assert(run(fn () { return a ~ b }, { "bxor" }) == 10 ~ 12); assert(run(fn () { return a << b }, { "shl" }) == 10 << 12); assert(run(fn () { return a >> b }, { "shr" }) == 10 >> 12); assert(run(fn () { return a .. b }, { "concat" }) == "1012"); assert(run(fn () { return a .. b .. c .. a }, { "concat", "concat", "concat" }) == "1012hello10"); assert(run(fn () { return "a" .. "b" .. a .. "c" .. c .. b .. "x" }, { "concat", "concat", "concat" }) == "ab10chello12x"); { local mt1 = { __le = fn (a, b) { coroutine.yield(10); return (type(a) == "table" && a.x || a) <= (type(b) == "table" && b.x || b) }, __lt = fn (a, b) { coroutine.yield(10); return (type(a) == "table" && a.x || a) < (type(b) == "table" && b.x || b) } } local mt2 = { __lt = mt1.__lt } local fn run(f) { local co = coroutine.wrap(f) local res loop { res = co() } until res != 10 return res } local fn 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 } run(test); } assert(run(fn () { a.BB = print return a.BB }, { "nidx", "idx" }) == print); { local _ENV = _ENV f = fn () { AAA = BBB + 1 return AAA } } 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 = fn (s, i) { if i % 2 == 0 { coroutine.yield(nil, "for"); } if i < s { return i + 1 } } assert(run(fn () { local s = 0 { local _internal_expr_1, _internal_stop_1, _internal_acc_1 = f, 4, 0; while true { local i = _internal_expr_1(_internal_stop_1, _internal_acc_1); _internal_acc_1 = i; if _internal_acc_1 == nil { break; } s = s + i } } return s }, { "for", "for", "for" }) == 10); if T == nil { (Message || print)('\n >>> testC not active: skipping coroutine API tests <<<\n'); return } print('testing coroutine API'); local fn apico(...) { local x = { ... } return coroutine.wrap(fn () { return T.testC(table.unpack(x)) }) } local a = { apico(` pushstring errorcode pcallk 1 0 2; invalid command (should not arrive here) `, `return *`, "stackmark", error)() } assert(#a == 4 && a[(3)] == "stackmark" && a[(4)] == "errorcode" && _G.status == "ERRRUN" && _G.ctx == 2); 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 && a[(2)] == coroutine.yield && a[(5)] == 20 && a[(6)] == 30 && a[(7)] == "YIELD" && a[(8)] == 3 && a[(9)] == "YIELD" && a[(10)] == 4); assert(!pcall(co)); f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;") co = coroutine.wrap(fn () { assert(f() == 23); assert(f() == 23); return 10 }) assert(co(23, 16) == 5); assert(co(23, 16) == 5); assert(co(23, 16) == 10); 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' && b == 'a' && c == 102 && d == 'OK'); local count = 3 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 `, fn () { count = count - 1 if count == 0 { return coroutine.yield } else { return f } }) co = coroutine.wrap(fn () { return f(nil) }) assert(co() == "hello"); a = { co() } assert(#a == 3 && a[(1)] == a[(2)] && a[(2)] == a[(3)] && a[(3)] == 34); co = coroutine.wrap(fn (...) { 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 * `, fn (a, b) { x = a y = b error("errmsg"); }, ...) }) local a = { co(3, 4, 6) } assert(a[(1)] == 6 && a[(2)] == nil); a = { co() } assert(a[(1)] == nil && _G.status == "YIELD" && _G.ctx == 2); a = { co() } assert(a[(1)] == nil && _G.status == "YIELD" && _G.ctx == 3); a = { co(7, 8) } assert(type(a[(1)]) == 'string' && type(a[(2)]) == 'string' && type(a[(3)]) == 'string' && type(a[(4)]) == 'string' && type(a[(5)]) == 'string' && type(a[(6)]) == 'function'); assert(a[(7)] == 3 && a[(8)] == 4); assert(a[(9)] == 7 && a[(10)] == 8); assert(a[(11)]::find("errmsg") && #a == 11); assert(x == "YIELD" && y == 4); assert(!pcall(co)); local co = coroutine.wrap(fn () { local a = { pcall(pcall, pcall, pcall, pcall, pcall, pcall, pcall, error, "hi") } return pcall(assert, table.unpack(a)) }) local a = { co() } assert(a[(10)] == "hi"); print('OK');