diff options
Diffstat (limited to 'testes/coroutine.lua')
-rw-r--r-- | testes/coroutine.lua | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/testes/coroutine.lua b/testes/coroutine.lua new file mode 100644 index 00000000..c8fc8572 --- /dev/null +++ b/testes/coroutine.lua | |||
@@ -0,0 +1,874 @@ | |||
1 | -- $Id: coroutine.lua,v 1.42 2016/11/07 13:03:20 roberto Exp $ | ||
2 | -- See Copyright Notice in file all.lua | ||
3 | |||
4 | print "testing coroutines" | ||
5 | |||
6 | local debug = require'debug' | ||
7 | |||
8 | local f | ||
9 | |||
10 | local main, ismain = coroutine.running() | ||
11 | assert(type(main) == "thread" and ismain) | ||
12 | assert(not coroutine.resume(main)) | ||
13 | assert(not coroutine.isyieldable()) | ||
14 | assert(not pcall(coroutine.yield)) | ||
15 | |||
16 | |||
17 | -- trivial errors | ||
18 | assert(not pcall(coroutine.resume, 0)) | ||
19 | assert(not pcall(coroutine.status, 0)) | ||
20 | |||
21 | |||
22 | -- tests for multiple yield/resume arguments | ||
23 | |||
24 | local function eqtab (t1, t2) | ||
25 | assert(#t1 == #t2) | ||
26 | for i = 1, #t1 do | ||
27 | local v = t1[i] | ||
28 | assert(t2[i] == v) | ||
29 | end | ||
30 | end | ||
31 | |||
32 | _G.x = nil -- declare x | ||
33 | function foo (a, ...) | ||
34 | local x, y = coroutine.running() | ||
35 | assert(x == f and y == false) | ||
36 | -- next call should not corrupt coroutine (but must fail, | ||
37 | -- as it attempts to resume the running coroutine) | ||
38 | assert(coroutine.resume(f) == false) | ||
39 | assert(coroutine.status(f) == "running") | ||
40 | local arg = {...} | ||
41 | assert(coroutine.isyieldable()) | ||
42 | for i=1,#arg do | ||
43 | _G.x = {coroutine.yield(table.unpack(arg[i]))} | ||
44 | end | ||
45 | return table.unpack(a) | ||
46 | end | ||
47 | |||
48 | f = coroutine.create(foo) | ||
49 | assert(type(f) == "thread" and coroutine.status(f) == "suspended") | ||
50 | assert(string.find(tostring(f), "thread")) | ||
51 | local s,a,b,c,d | ||
52 | s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'}) | ||
53 | assert(s and a == nil and coroutine.status(f) == "suspended") | ||
54 | s,a,b,c,d = coroutine.resume(f) | ||
55 | eqtab(_G.x, {}) | ||
56 | assert(s and a == 1 and b == nil) | ||
57 | s,a,b,c,d = coroutine.resume(f, 1, 2, 3) | ||
58 | eqtab(_G.x, {1, 2, 3}) | ||
59 | assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil) | ||
60 | s,a,b,c,d = coroutine.resume(f, "xuxu") | ||
61 | eqtab(_G.x, {"xuxu"}) | ||
62 | assert(s and a == 1 and b == 2 and c == 3 and d == nil) | ||
63 | assert(coroutine.status(f) == "dead") | ||
64 | s, a = coroutine.resume(f, "xuxu") | ||
65 | assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead") | ||
66 | |||
67 | |||
68 | -- yields in tail calls | ||
69 | local function foo (i) return coroutine.yield(i) end | ||
70 | f = coroutine.wrap(function () | ||
71 | for i=1,10 do | ||
72 | assert(foo(i) == _G.x) | ||
73 | end | ||
74 | return 'a' | ||
75 | end) | ||
76 | for i=1,10 do _G.x = i; assert(f(i) == i) end | ||
77 | _G.x = 'xuxu'; assert(f('xuxu') == 'a') | ||
78 | |||
79 | -- recursive | ||
80 | function pf (n, i) | ||
81 | coroutine.yield(n) | ||
82 | pf(n*i, i+1) | ||
83 | end | ||
84 | |||
85 | f = coroutine.wrap(pf) | ||
86 | local s=1 | ||
87 | for i=1,10 do | ||
88 | assert(f(1, 1) == s) | ||
89 | s = s*i | ||
90 | end | ||
91 | |||
92 | -- sieve | ||
93 | function gen (n) | ||
94 | return coroutine.wrap(function () | ||
95 | for i=2,n do coroutine.yield(i) end | ||
96 | end) | ||
97 | end | ||
98 | |||
99 | |||
100 | function filter (p, g) | ||
101 | return coroutine.wrap(function () | ||
102 | while 1 do | ||
103 | local n = g() | ||
104 | if n == nil then return end | ||
105 | if math.fmod(n, p) ~= 0 then coroutine.yield(n) end | ||
106 | end | ||
107 | end) | ||
108 | end | ||
109 | |||
110 | local x = gen(100) | ||
111 | local a = {} | ||
112 | while 1 do | ||
113 | local n = x() | ||
114 | if n == nil then break end | ||
115 | table.insert(a, n) | ||
116 | x = filter(n, x) | ||
117 | end | ||
118 | |||
119 | assert(#a == 25 and a[#a] == 97) | ||
120 | x, a = nil | ||
121 | |||
122 | -- yielding across C boundaries | ||
123 | |||
124 | co = coroutine.wrap(function() | ||
125 | assert(not pcall(table.sort,{1,2,3}, coroutine.yield)) | ||
126 | assert(coroutine.isyieldable()) | ||
127 | coroutine.yield(20) | ||
128 | return 30 | ||
129 | end) | ||
130 | |||
131 | assert(co() == 20) | ||
132 | assert(co() == 30) | ||
133 | |||
134 | |||
135 | local f = function (s, i) return coroutine.yield(i) end | ||
136 | |||
137 | local f1 = coroutine.wrap(function () | ||
138 | return xpcall(pcall, function (...) return ... end, | ||
139 | function () | ||
140 | local s = 0 | ||
141 | for i in f, nil, 1 do pcall(function () s = s + i end) end | ||
142 | error({s}) | ||
143 | end) | ||
144 | end) | ||
145 | |||
146 | f1() | ||
147 | for i = 1, 10 do assert(f1(i) == i) end | ||
148 | local r1, r2, v = f1(nil) | ||
149 | assert(r1 and not r2 and v[1] == (10 + 1)*10/2) | ||
150 | |||
151 | |||
152 | function f (a, b) a = coroutine.yield(a); error{a + b} end | ||
153 | function g(x) return x[1]*2 end | ||
154 | |||
155 | co = coroutine.wrap(function () | ||
156 | coroutine.yield(xpcall(f, g, 10, 20)) | ||
157 | end) | ||
158 | |||
159 | assert(co() == 10) | ||
160 | r, msg = co(100) | ||
161 | assert(not r and msg == 240) | ||
162 | |||
163 | |||
164 | -- unyieldable C call | ||
165 | do | ||
166 | local function f (c) | ||
167 | assert(not coroutine.isyieldable()) | ||
168 | return c .. c | ||
169 | end | ||
170 | |||
171 | local co = coroutine.wrap(function (c) | ||
172 | assert(coroutine.isyieldable()) | ||
173 | local s = string.gsub("a", ".", f) | ||
174 | return s | ||
175 | end) | ||
176 | assert(co() == "aa") | ||
177 | end | ||
178 | |||
179 | |||
180 | -- errors in coroutines | ||
181 | function foo () | ||
182 | assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1) | ||
183 | assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined) | ||
184 | coroutine.yield(3) | ||
185 | error(foo) | ||
186 | end | ||
187 | |||
188 | function goo() foo() end | ||
189 | x = coroutine.wrap(goo) | ||
190 | assert(x() == 3) | ||
191 | local a,b = pcall(x) | ||
192 | assert(not a and b == foo) | ||
193 | |||
194 | x = coroutine.create(goo) | ||
195 | a,b = coroutine.resume(x) | ||
196 | assert(a and b == 3) | ||
197 | a,b = coroutine.resume(x) | ||
198 | assert(not a and b == foo and coroutine.status(x) == "dead") | ||
199 | a,b = coroutine.resume(x) | ||
200 | assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead") | ||
201 | |||
202 | |||
203 | -- co-routines x for loop | ||
204 | function all (a, n, k) | ||
205 | if k == 0 then coroutine.yield(a) | ||
206 | else | ||
207 | for i=1,n do | ||
208 | a[k] = i | ||
209 | all(a, n, k-1) | ||
210 | end | ||
211 | end | ||
212 | end | ||
213 | |||
214 | local a = 0 | ||
215 | for t in coroutine.wrap(function () all({}, 5, 4) end) do | ||
216 | a = a+1 | ||
217 | end | ||
218 | assert(a == 5^4) | ||
219 | |||
220 | |||
221 | -- access to locals of collected corroutines | ||
222 | local C = {}; setmetatable(C, {__mode = "kv"}) | ||
223 | local x = coroutine.wrap (function () | ||
224 | local a = 10 | ||
225 | local function f () a = a+10; return a end | ||
226 | while true do | ||
227 | a = a+1 | ||
228 | coroutine.yield(f) | ||
229 | end | ||
230 | end) | ||
231 | |||
232 | C[1] = x; | ||
233 | |||
234 | local f = x() | ||
235 | assert(f() == 21 and x()() == 32 and x() == f) | ||
236 | x = nil | ||
237 | collectgarbage() | ||
238 | assert(C[1] == nil) | ||
239 | assert(f() == 43 and f() == 53) | ||
240 | |||
241 | |||
242 | -- old bug: attempt to resume itself | ||
243 | |||
244 | function co_func (current_co) | ||
245 | assert(coroutine.running() == current_co) | ||
246 | assert(coroutine.resume(current_co) == false) | ||
247 | coroutine.yield(10, 20) | ||
248 | assert(coroutine.resume(current_co) == false) | ||
249 | coroutine.yield(23) | ||
250 | return 10 | ||
251 | end | ||
252 | |||
253 | local co = coroutine.create(co_func) | ||
254 | local a,b,c = coroutine.resume(co, co) | ||
255 | assert(a == true and b == 10 and c == 20) | ||
256 | a,b = coroutine.resume(co, co) | ||
257 | assert(a == true and b == 23) | ||
258 | a,b = coroutine.resume(co, co) | ||
259 | assert(a == true and b == 10) | ||
260 | assert(coroutine.resume(co, co) == false) | ||
261 | assert(coroutine.resume(co, co) == false) | ||
262 | |||
263 | |||
264 | -- other old bug when attempting to resume itself | ||
265 | -- (trigger C-code assertions) | ||
266 | do | ||
267 | local A = coroutine.running() | ||
268 | local B = coroutine.create(function() return coroutine.resume(A) end) | ||
269 | local st, res = coroutine.resume(B) | ||
270 | assert(st == true and res == false) | ||
271 | |||
272 | A = coroutine.wrap(function() return pcall(A, 1) end) | ||
273 | st, res = A() | ||
274 | assert(not st and string.find(res, "non%-suspended")) | ||
275 | end | ||
276 | |||
277 | |||
278 | -- attempt to resume 'normal' coroutine | ||
279 | local co1, co2 | ||
280 | co1 = coroutine.create(function () return co2() end) | ||
281 | co2 = coroutine.wrap(function () | ||
282 | assert(coroutine.status(co1) == 'normal') | ||
283 | assert(not coroutine.resume(co1)) | ||
284 | coroutine.yield(3) | ||
285 | end) | ||
286 | |||
287 | a,b = coroutine.resume(co1) | ||
288 | assert(a and b == 3) | ||
289 | assert(coroutine.status(co1) == 'dead') | ||
290 | |||
291 | -- infinite recursion of coroutines | ||
292 | a = function(a) coroutine.wrap(a)(a) end | ||
293 | assert(not pcall(a, a)) | ||
294 | a = nil | ||
295 | |||
296 | |||
297 | -- access to locals of erroneous coroutines | ||
298 | local x = coroutine.create (function () | ||
299 | local a = 10 | ||
300 | _G.f = function () a=a+1; return a end | ||
301 | error('x') | ||
302 | end) | ||
303 | |||
304 | assert(not coroutine.resume(x)) | ||
305 | -- overwrite previous position of local `a' | ||
306 | assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1)) | ||
307 | assert(_G.f() == 11) | ||
308 | assert(_G.f() == 12) | ||
309 | |||
310 | |||
311 | if not T then | ||
312 | (Message or print)('\n >>> testC not active: skipping yield/hook tests <<<\n') | ||
313 | else | ||
314 | print "testing yields inside hooks" | ||
315 | |||
316 | local turn | ||
317 | |||
318 | function fact (t, x) | ||
319 | assert(turn == t) | ||
320 | if x == 0 then return 1 | ||
321 | else return x*fact(t, x-1) | ||
322 | end | ||
323 | end | ||
324 | |||
325 | local A, B = 0, 0 | ||
326 | |||
327 | local x = coroutine.create(function () | ||
328 | T.sethook("yield 0", "", 2) | ||
329 | A = fact("A", 6) | ||
330 | end) | ||
331 | |||
332 | local y = coroutine.create(function () | ||
333 | T.sethook("yield 0", "", 3) | ||
334 | B = fact("B", 7) | ||
335 | end) | ||
336 | |||
337 | while A==0 or B==0 do -- A ~= 0 when 'x' finishes (similar for 'B','y') | ||
338 | if A==0 then turn = "A"; assert(T.resume(x)) end | ||
339 | if B==0 then turn = "B"; assert(T.resume(y)) end | ||
340 | end | ||
341 | |||
342 | assert(B // A == 7) -- fact(7) // fact(6) | ||
343 | |||
344 | local line = debug.getinfo(1, "l").currentline + 2 -- get line number | ||
345 | local function foo () | ||
346 | local x = 10 --<< this line is 'line' | ||
347 | x = x + 10 | ||
348 | _G.XX = x | ||
349 | end | ||
350 | |||
351 | -- testing yields in line hook | ||
352 | local co = coroutine.wrap(function () | ||
353 | T.sethook("setglobal X; yield 0", "l", 0); foo(); return 10 end) | ||
354 | |||
355 | _G.XX = nil; | ||
356 | _G.X = nil; co(); assert(_G.X == line) | ||
357 | _G.X = nil; co(); assert(_G.X == line + 1) | ||
358 | _G.X = nil; co(); assert(_G.X == line + 2 and _G.XX == nil) | ||
359 | _G.X = nil; co(); assert(_G.X == line + 3 and _G.XX == 20) | ||
360 | assert(co() == 10) | ||
361 | |||
362 | -- testing yields in count hook | ||
363 | co = coroutine.wrap(function () | ||
364 | T.sethook("yield 0", "", 1); foo(); return 10 end) | ||
365 | |||
366 | _G.XX = nil; | ||
367 | local c = 0 | ||
368 | repeat c = c + 1; local a = co() until a == 10 | ||
369 | assert(_G.XX == 20 and c >= 5) | ||
370 | |||
371 | co = coroutine.wrap(function () | ||
372 | T.sethook("yield 0", "", 2); foo(); return 10 end) | ||
373 | |||
374 | _G.XX = nil; | ||
375 | local c = 0 | ||
376 | repeat c = c + 1; local a = co() until a == 10 | ||
377 | assert(_G.XX == 20 and c >= 5) | ||
378 | _G.X = nil; _G.XX = nil | ||
379 | |||
380 | do | ||
381 | -- testing debug library on a coroutine suspended inside a hook | ||
382 | -- (bug in 5.2/5.3) | ||
383 | c = coroutine.create(function (a, ...) | ||
384 | T.sethook("yield 0", "l") -- will yield on next two lines | ||
385 | assert(a == 10) | ||
386 | return ... | ||
387 | end) | ||
388 | |||
389 | assert(coroutine.resume(c, 1, 2, 3)) -- start coroutine | ||
390 | local n,v = debug.getlocal(c, 0, 1) -- check its local | ||
391 | assert(n == "a" and v == 1) | ||
392 | n,v = debug.getlocal(c, 0, -1) -- check varargs | ||
393 | assert(v == 2) | ||
394 | n,v = debug.getlocal(c, 0, -2) | ||
395 | assert(v == 3) | ||
396 | assert(debug.setlocal(c, 0, 1, 10)) -- test 'setlocal' | ||
397 | assert(debug.setlocal(c, 0, -2, 20)) | ||
398 | local t = debug.getinfo(c, 0) -- test 'getinfo' | ||
399 | assert(t.currentline == t.linedefined + 1) | ||
400 | assert(not debug.getinfo(c, 1)) -- no other level | ||
401 | assert(coroutine.resume(c)) -- run next line | ||
402 | v = {coroutine.resume(c)} -- finish coroutine | ||
403 | assert(v[1] == true and v[2] == 2 and v[3] == 20 and v[4] == nil) | ||
404 | assert(not coroutine.resume(c)) | ||
405 | end | ||
406 | |||
407 | do | ||
408 | -- testing debug library on last function in a suspended coroutine | ||
409 | -- (bug in 5.2/5.3) | ||
410 | local c = coroutine.create(function () T.testC("yield 1", 10, 20) end) | ||
411 | local a, b = coroutine.resume(c) | ||
412 | assert(a and b == 20) | ||
413 | assert(debug.getinfo(c, 0).linedefined == -1) | ||
414 | a, b = debug.getlocal(c, 0, 2) | ||
415 | assert(b == 10) | ||
416 | end | ||
417 | |||
418 | |||
419 | print "testing coroutine API" | ||
420 | |||
421 | -- reusing a thread | ||
422 | assert(T.testC([[ | ||
423 | newthread # create thread | ||
424 | pushvalue 2 # push body | ||
425 | pushstring 'a a a' # push argument | ||
426 | xmove 0 3 2 # move values to new thread | ||
427 | resume -1, 1 # call it first time | ||
428 | pushstatus | ||
429 | xmove 3 0 0 # move results back to stack | ||
430 | setglobal X # result | ||
431 | setglobal Y # status | ||
432 | pushvalue 2 # push body (to call it again) | ||
433 | pushstring 'b b b' | ||
434 | xmove 0 3 2 | ||
435 | resume -1, 1 # call it again | ||
436 | pushstatus | ||
437 | xmove 3 0 0 | ||
438 | return 1 # return result | ||
439 | ]], function (...) return ... end) == 'b b b') | ||
440 | |||
441 | assert(X == 'a a a' and Y == 'OK') | ||
442 | |||
443 | |||
444 | -- resuming running coroutine | ||
445 | C = coroutine.create(function () | ||
446 | return T.testC([[ | ||
447 | pushnum 10; | ||
448 | pushnum 20; | ||
449 | resume -3 2; | ||
450 | pushstatus | ||
451 | gettop; | ||
452 | return 3]], C) | ||
453 | end) | ||
454 | local a, b, c, d = coroutine.resume(C) | ||
455 | assert(a == true and string.find(b, "non%-suspended") and | ||
456 | c == "ERRRUN" and d == 4) | ||
457 | |||
458 | a, b, c, d = T.testC([[ | ||
459 | rawgeti R 1 # get main thread | ||
460 | pushnum 10; | ||
461 | pushnum 20; | ||
462 | resume -3 2; | ||
463 | pushstatus | ||
464 | gettop; | ||
465 | return 4]]) | ||
466 | assert(a == coroutine.running() and string.find(b, "non%-suspended") and | ||
467 | c == "ERRRUN" and d == 4) | ||
468 | |||
469 | |||
470 | -- using a main thread as a coroutine | ||
471 | local state = T.newstate() | ||
472 | T.loadlib(state) | ||
473 | |||
474 | assert(T.doremote(state, [[ | ||
475 | coroutine = require'coroutine'; | ||
476 | X = function (x) coroutine.yield(x, 'BB'); return 'CC' end; | ||
477 | return 'ok']])) | ||
478 | |||
479 | t = table.pack(T.testC(state, [[ | ||
480 | rawgeti R 1 # get main thread | ||
481 | pushstring 'XX' | ||
482 | getglobal X # get function for body | ||
483 | pushstring AA # arg | ||
484 | resume 1 1 # 'resume' shadows previous stack! | ||
485 | gettop | ||
486 | setglobal T # top | ||
487 | setglobal B # second yielded value | ||
488 | setglobal A # fist yielded value | ||
489 | rawgeti R 1 # get main thread | ||
490 | pushnum 5 # arg (noise) | ||
491 | resume 1 1 # after coroutine ends, previous stack is back | ||
492 | pushstatus | ||
493 | return * | ||
494 | ]])) | ||
495 | assert(t.n == 4 and t[2] == 'XX' and t[3] == 'CC' and t[4] == 'OK') | ||
496 | assert(T.doremote(state, "return T") == '2') | ||
497 | assert(T.doremote(state, "return A") == 'AA') | ||
498 | assert(T.doremote(state, "return B") == 'BB') | ||
499 | |||
500 | T.closestate(state) | ||
501 | |||
502 | print'+' | ||
503 | |||
504 | end | ||
505 | |||
506 | |||
507 | -- leaving a pending coroutine open | ||
508 | _X = coroutine.wrap(function () | ||
509 | local a = 10 | ||
510 | local x = function () a = a+1 end | ||
511 | coroutine.yield() | ||
512 | end) | ||
513 | |||
514 | _X() | ||
515 | |||
516 | |||
517 | if not _soft then | ||
518 | -- bug (stack overflow) | ||
519 | local j = 2^9 | ||
520 | local lim = 1000000 -- (C stack limit; assume 32-bit machine) | ||
521 | local t = {lim - 10, lim - 5, lim - 1, lim, lim + 1} | ||
522 | for i = 1, #t do | ||
523 | local j = t[i] | ||
524 | co = coroutine.create(function() | ||
525 | local t = {} | ||
526 | for i = 1, j do t[i] = i end | ||
527 | return table.unpack(t) | ||
528 | end) | ||
529 | local r, msg = coroutine.resume(co) | ||
530 | assert(not r) | ||
531 | end | ||
532 | co = nil | ||
533 | end | ||
534 | |||
535 | |||
536 | assert(coroutine.running() == main) | ||
537 | |||
538 | print"+" | ||
539 | |||
540 | |||
541 | print"testing yields inside metamethods" | ||
542 | |||
543 | local mt = { | ||
544 | __eq = function(a,b) coroutine.yield(nil, "eq"); return a.x == b.x end, | ||
545 | __lt = function(a,b) coroutine.yield(nil, "lt"); return a.x < b.x end, | ||
546 | __le = function(a,b) coroutine.yield(nil, "le"); return a - b <= 0 end, | ||
547 | __add = function(a,b) coroutine.yield(nil, "add"); return a.x + b.x end, | ||
548 | __sub = function(a,b) coroutine.yield(nil, "sub"); return a.x - b.x end, | ||
549 | __mod = function(a,b) coroutine.yield(nil, "mod"); return a.x % b.x end, | ||
550 | __unm = function(a,b) coroutine.yield(nil, "unm"); return -a.x end, | ||
551 | __bnot = function(a,b) coroutine.yield(nil, "bnot"); return ~a.x end, | ||
552 | __shl = function(a,b) coroutine.yield(nil, "shl"); return a.x << b.x end, | ||
553 | __shr = function(a,b) coroutine.yield(nil, "shr"); return a.x >> b.x end, | ||
554 | __band = function(a,b) | ||
555 | a = type(a) == "table" and a.x or a | ||
556 | b = type(b) == "table" and b.x or b | ||
557 | coroutine.yield(nil, "band") | ||
558 | return a & b | ||
559 | end, | ||
560 | __bor = function(a,b) coroutine.yield(nil, "bor"); return a.x | b.x end, | ||
561 | __bxor = function(a,b) coroutine.yield(nil, "bxor"); return a.x ~ b.x end, | ||
562 | |||
563 | __concat = function(a,b) | ||
564 | coroutine.yield(nil, "concat"); | ||
565 | a = type(a) == "table" and a.x or a | ||
566 | b = type(b) == "table" and b.x or b | ||
567 | return a .. b | ||
568 | end, | ||
569 | __index = function (t,k) coroutine.yield(nil, "idx"); return t.k[k] end, | ||
570 | __newindex = function (t,k,v) coroutine.yield(nil, "nidx"); t.k[k] = v end, | ||
571 | } | ||
572 | |||
573 | |||
574 | local function new (x) | ||
575 | return setmetatable({x = x, k = {}}, mt) | ||
576 | end | ||
577 | |||
578 | |||
579 | local a = new(10) | ||
580 | local b = new(12) | ||
581 | local c = new"hello" | ||
582 | |||
583 | local function run (f, t) | ||
584 | local i = 1 | ||
585 | local c = coroutine.wrap(f) | ||
586 | while true do | ||
587 | local res, stat = c() | ||
588 | if res then assert(t[i] == nil); return res, t end | ||
589 | assert(stat == t[i]) | ||
590 | i = i + 1 | ||
591 | end | ||
592 | end | ||
593 | |||
594 | |||
595 | assert(run(function () if (a>=b) then return '>=' else return '<' end end, | ||
596 | {"le", "sub"}) == "<") | ||
597 | -- '<=' using '<' | ||
598 | mt.__le = nil | ||
599 | assert(run(function () if (a<=b) then return '<=' else return '>' end end, | ||
600 | {"lt"}) == "<=") | ||
601 | assert(run(function () if (a==b) then return '==' else return '~=' end end, | ||
602 | {"eq"}) == "~=") | ||
603 | |||
604 | assert(run(function () return a & b + a end, {"add", "band"}) == 2) | ||
605 | |||
606 | assert(run(function () return a % b end, {"mod"}) == 10) | ||
607 | |||
608 | assert(run(function () return ~a & b end, {"bnot", "band"}) == ~10 & 12) | ||
609 | assert(run(function () return a | b end, {"bor"}) == 10 | 12) | ||
610 | assert(run(function () return a ~ b end, {"bxor"}) == 10 ~ 12) | ||
611 | assert(run(function () return a << b end, {"shl"}) == 10 << 12) | ||
612 | assert(run(function () return a >> b end, {"shr"}) == 10 >> 12) | ||
613 | |||
614 | assert(run(function () return a..b end, {"concat"}) == "1012") | ||
615 | |||
616 | assert(run(function() return a .. b .. c .. a end, | ||
617 | {"concat", "concat", "concat"}) == "1012hello10") | ||
618 | |||
619 | assert(run(function() return "a" .. "b" .. a .. "c" .. c .. b .. "x" end, | ||
620 | {"concat", "concat", "concat"}) == "ab10chello12x") | ||
621 | |||
622 | |||
623 | do -- a few more tests for comparsion operators | ||
624 | local mt1 = { | ||
625 | __le = function (a,b) | ||
626 | coroutine.yield(10) | ||
627 | return | ||
628 | (type(a) == "table" and a.x or a) <= (type(b) == "table" and b.x or b) | ||
629 | end, | ||
630 | __lt = function (a,b) | ||
631 | coroutine.yield(10) | ||
632 | return | ||
633 | (type(a) == "table" and a.x or a) < (type(b) == "table" and b.x or b) | ||
634 | end, | ||
635 | } | ||
636 | local mt2 = { __lt = mt1.__lt } -- no __le | ||
637 | |||
638 | local function run (f) | ||
639 | local co = coroutine.wrap(f) | ||
640 | local res | ||
641 | repeat | ||
642 | res = co() | ||
643 | until res ~= 10 | ||
644 | return res | ||
645 | end | ||
646 | |||
647 | local function test () | ||
648 | local a1 = setmetatable({x=1}, mt1) | ||
649 | local a2 = setmetatable({x=2}, mt2) | ||
650 | assert(a1 < a2) | ||
651 | assert(a1 <= a2) | ||
652 | assert(1 < a2) | ||
653 | assert(1 <= a2) | ||
654 | assert(2 > a1) | ||
655 | assert(2 >= a2) | ||
656 | return true | ||
657 | end | ||
658 | |||
659 | run(test) | ||
660 | |||
661 | end | ||
662 | |||
663 | assert(run(function () | ||
664 | a.BB = print | ||
665 | return a.BB | ||
666 | end, {"nidx", "idx"}) == print) | ||
667 | |||
668 | -- getuptable & setuptable | ||
669 | do local _ENV = _ENV | ||
670 | f = function () AAA = BBB + 1; return AAA end | ||
671 | end | ||
672 | g = new(10); g.k.BBB = 10; | ||
673 | debug.setupvalue(f, 1, g) | ||
674 | assert(run(f, {"idx", "nidx", "idx"}) == 11) | ||
675 | assert(g.k.AAA == 11) | ||
676 | |||
677 | print"+" | ||
678 | |||
679 | print"testing yields inside 'for' iterators" | ||
680 | |||
681 | local f = function (s, i) | ||
682 | if i%2 == 0 then coroutine.yield(nil, "for") end | ||
683 | if i < s then return i + 1 end | ||
684 | end | ||
685 | |||
686 | assert(run(function () | ||
687 | local s = 0 | ||
688 | for i in f, 4, 0 do s = s + i end | ||
689 | return s | ||
690 | end, {"for", "for", "for"}) == 10) | ||
691 | |||
692 | |||
693 | |||
694 | -- tests for coroutine API | ||
695 | if T==nil then | ||
696 | (Message or print)('\n >>> testC not active: skipping coroutine API tests <<<\n') | ||
697 | return | ||
698 | end | ||
699 | |||
700 | print('testing coroutine API') | ||
701 | |||
702 | local function apico (...) | ||
703 | local x = {...} | ||
704 | return coroutine.wrap(function () | ||
705 | return T.testC(table.unpack(x)) | ||
706 | end) | ||
707 | end | ||
708 | |||
709 | local a = {apico( | ||
710 | [[ | ||
711 | pushstring errorcode | ||
712 | pcallk 1 0 2; | ||
713 | invalid command (should not arrive here) | ||
714 | ]], | ||
715 | [[return *]], | ||
716 | "stackmark", | ||
717 | error | ||
718 | )()} | ||
719 | assert(#a == 4 and | ||
720 | a[3] == "stackmark" and | ||
721 | a[4] == "errorcode" and | ||
722 | _G.status == "ERRRUN" and | ||
723 | _G.ctx == 2) -- 'ctx' to pcallk | ||
724 | |||
725 | local co = apico( | ||
726 | "pushvalue 2; pushnum 10; pcallk 1 2 3; invalid command;", | ||
727 | coroutine.yield, | ||
728 | "getglobal status; getglobal ctx; pushvalue 2; pushstring a; pcallk 1 0 4; invalid command", | ||
729 | "getglobal status; getglobal ctx; return *") | ||
730 | |||
731 | assert(co() == 10) | ||
732 | assert(co(20, 30) == 'a') | ||
733 | a = {co()} | ||
734 | assert(#a == 10 and | ||
735 | a[2] == coroutine.yield and | ||
736 | a[5] == 20 and a[6] == 30 and | ||
737 | a[7] == "YIELD" and a[8] == 3 and | ||
738 | a[9] == "YIELD" and a[10] == 4) | ||
739 | assert(not pcall(co)) -- coroutine is dead now | ||
740 | |||
741 | |||
742 | f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;") | ||
743 | co = coroutine.wrap(function () | ||
744 | assert(f() == 23); assert(f() == 23); return 10 | ||
745 | end) | ||
746 | assert(co(23,16) == 5) | ||
747 | assert(co(23,16) == 5) | ||
748 | assert(co(23,16) == 10) | ||
749 | |||
750 | |||
751 | -- testing coroutines with C bodies | ||
752 | f = T.makeCfunc([[ | ||
753 | pushnum 102 | ||
754 | yieldk 1 U2 | ||
755 | cannot be here! | ||
756 | ]], | ||
757 | [[ # continuation | ||
758 | pushvalue U3 # accessing upvalues inside a continuation | ||
759 | pushvalue U4 | ||
760 | return * | ||
761 | ]], 23, "huu") | ||
762 | |||
763 | x = coroutine.wrap(f) | ||
764 | assert(x() == 102) | ||
765 | eqtab({x()}, {23, "huu"}) | ||
766 | |||
767 | |||
768 | f = T.makeCfunc[[pushstring 'a'; pushnum 102; yield 2; ]] | ||
769 | |||
770 | a, b, c, d = T.testC([[newthread; pushvalue 2; xmove 0 3 1; resume 3 0; | ||
771 | pushstatus; xmove 3 0 0; resume 3 0; pushstatus; | ||
772 | return 4; ]], f) | ||
773 | |||
774 | assert(a == 'YIELD' and b == 'a' and c == 102 and d == 'OK') | ||
775 | |||
776 | |||
777 | -- testing chain of suspendable C calls | ||
778 | |||
779 | local count = 3 -- number of levels | ||
780 | |||
781 | f = T.makeCfunc([[ | ||
782 | remove 1; # remove argument | ||
783 | pushvalue U3; # get selection function | ||
784 | call 0 1; # call it (result is 'f' or 'yield') | ||
785 | pushstring hello # single argument for selected function | ||
786 | pushupvalueindex 2; # index of continuation program | ||
787 | callk 1 -1 .; # call selected function | ||
788 | errorerror # should never arrive here | ||
789 | ]], | ||
790 | [[ | ||
791 | # continuation program | ||
792 | pushnum 34 # return value | ||
793 | return * # return all results | ||
794 | ]], | ||
795 | function () -- selection function | ||
796 | count = count - 1 | ||
797 | if count == 0 then return coroutine.yield | ||
798 | else return f | ||
799 | end | ||
800 | end | ||
801 | ) | ||
802 | |||
803 | co = coroutine.wrap(function () return f(nil) end) | ||
804 | assert(co() == "hello") -- argument to 'yield' | ||
805 | a = {co()} | ||
806 | -- three '34's (one from each pending C call) | ||
807 | assert(#a == 3 and a[1] == a[2] and a[2] == a[3] and a[3] == 34) | ||
808 | |||
809 | |||
810 | -- testing yields with continuations | ||
811 | |||
812 | co = coroutine.wrap(function (...) return | ||
813 | T.testC([[ # initial function | ||
814 | yieldk 1 2 | ||
815 | cannot be here! | ||
816 | ]], | ||
817 | [[ # 1st continuation | ||
818 | yieldk 0 3 | ||
819 | cannot be here! | ||
820 | ]], | ||
821 | [[ # 2nd continuation | ||
822 | yieldk 0 4 | ||
823 | cannot be here! | ||
824 | ]], | ||
825 | [[ # 3th continuation | ||
826 | pushvalue 6 # function which is last arg. to 'testC' here | ||
827 | pushnum 10; pushnum 20; | ||
828 | pcall 2 0 0 # call should throw an error and return to next line | ||
829 | pop 1 # remove error message | ||
830 | pushvalue 6 | ||
831 | getglobal status; getglobal ctx | ||
832 | pcallk 2 2 5 # call should throw an error and jump to continuation | ||
833 | cannot be here! | ||
834 | ]], | ||
835 | [[ # 4th (and last) continuation | ||
836 | return * | ||
837 | ]], | ||
838 | -- function called by 3th continuation | ||
839 | function (a,b) x=a; y=b; error("errmsg") end, | ||
840 | ... | ||
841 | ) | ||
842 | end) | ||
843 | |||
844 | local a = {co(3,4,6)} | ||
845 | assert(a[1] == 6 and a[2] == nil) | ||
846 | a = {co()}; assert(a[1] == nil and _G.status == "YIELD" and _G.ctx == 2) | ||
847 | a = {co()}; assert(a[1] == nil and _G.status == "YIELD" and _G.ctx == 3) | ||
848 | a = {co(7,8)}; | ||
849 | -- original arguments | ||
850 | assert(type(a[1]) == 'string' and type(a[2]) == 'string' and | ||
851 | type(a[3]) == 'string' and type(a[4]) == 'string' and | ||
852 | type(a[5]) == 'string' and type(a[6]) == 'function') | ||
853 | -- arguments left from fist resume | ||
854 | assert(a[7] == 3 and a[8] == 4) | ||
855 | -- arguments to last resume | ||
856 | assert(a[9] == 7 and a[10] == 8) | ||
857 | -- error message and nothing more | ||
858 | assert(a[11]:find("errmsg") and #a == 11) | ||
859 | -- check arguments to pcallk | ||
860 | assert(x == "YIELD" and y == 4) | ||
861 | |||
862 | assert(not pcall(co)) -- coroutine should be dead | ||
863 | |||
864 | |||
865 | -- bug in nCcalls | ||
866 | local co = coroutine.wrap(function () | ||
867 | local a = {pcall(pcall,pcall,pcall,pcall,pcall,pcall,pcall,error,"hi")} | ||
868 | return pcall(assert, table.unpack(a)) | ||
869 | end) | ||
870 | |||
871 | local a = {co()} | ||
872 | assert(a[10] == "hi") | ||
873 | |||
874 | print'OK' | ||