diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-07-09 12:33:01 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-07-09 12:33:01 -0300 |
commit | 7c519dfbd0c68b952f0849e01deaa3750e1f8153 (patch) | |
tree | dde3ddbba310877db725df37a0d9f2cbe4e2a8f9 /testes/coroutine.lua | |
parent | f59e6a93c0ad38a27a420e51abf8f13d962446b5 (diff) | |
download | lua-7c519dfbd0c68b952f0849e01deaa3750e1f8153.tar.gz lua-7c519dfbd0c68b952f0849e01deaa3750e1f8153.tar.bz2 lua-7c519dfbd0c68b952f0849e01deaa3750e1f8153.zip |
Added manual and tests for version 5.4-w2
Diffstat (limited to 'testes/coroutine.lua')
-rw-r--r-- | testes/coroutine.lua | 918 |
1 files changed, 918 insertions, 0 deletions
diff --git a/testes/coroutine.lua b/testes/coroutine.lua new file mode 100644 index 00000000..22087320 --- /dev/null +++ b/testes/coroutine.lua | |||
@@ -0,0 +1,918 @@ | |||
1 | -- $Id: coroutine.lua,v 1.48 2018/03/12 14:19:36 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 | |||
181 | do -- testing single trace of coroutines | ||
182 | local X | ||
183 | local co = coroutine.create(function () | ||
184 | coroutine.yield(10) | ||
185 | return 20; | ||
186 | end) | ||
187 | local trace = {} | ||
188 | local function dotrace (event) | ||
189 | trace[#trace + 1] = event | ||
190 | end | ||
191 | debug.sethook(co, dotrace, "clr") | ||
192 | repeat until not coroutine.resume(co) | ||
193 | local correcttrace = {"call", "line", "call", "return", "line", "return"} | ||
194 | assert(#trace == #correcttrace) | ||
195 | for k, v in pairs(trace) do | ||
196 | assert(v == correcttrace[k]) | ||
197 | end | ||
198 | end | ||
199 | |||
200 | -- errors in coroutines | ||
201 | function foo () | ||
202 | assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1) | ||
203 | assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined) | ||
204 | coroutine.yield(3) | ||
205 | error(foo) | ||
206 | end | ||
207 | |||
208 | function goo() foo() end | ||
209 | x = coroutine.wrap(goo) | ||
210 | assert(x() == 3) | ||
211 | local a,b = pcall(x) | ||
212 | assert(not a and b == foo) | ||
213 | |||
214 | x = coroutine.create(goo) | ||
215 | a,b = coroutine.resume(x) | ||
216 | assert(a and b == 3) | ||
217 | a,b = coroutine.resume(x) | ||
218 | assert(not a and b == foo and coroutine.status(x) == "dead") | ||
219 | a,b = coroutine.resume(x) | ||
220 | assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead") | ||
221 | |||
222 | |||
223 | -- co-routines x for loop | ||
224 | function all (a, n, k) | ||
225 | if k == 0 then coroutine.yield(a) | ||
226 | else | ||
227 | for i=1,n do | ||
228 | a[k] = i | ||
229 | all(a, n, k-1) | ||
230 | end | ||
231 | end | ||
232 | end | ||
233 | |||
234 | local a = 0 | ||
235 | for t in coroutine.wrap(function () all({}, 5, 4) end) do | ||
236 | a = a+1 | ||
237 | end | ||
238 | assert(a == 5^4) | ||
239 | |||
240 | |||
241 | -- access to locals of collected corroutines | ||
242 | local C = {}; setmetatable(C, {__mode = "kv"}) | ||
243 | local x = coroutine.wrap (function () | ||
244 | local a = 10 | ||
245 | local function f () a = a+10; return a end | ||
246 | while true do | ||
247 | a = a+1 | ||
248 | coroutine.yield(f) | ||
249 | end | ||
250 | end) | ||
251 | |||
252 | C[1] = x; | ||
253 | |||
254 | local f = x() | ||
255 | assert(f() == 21 and x()() == 32 and x() == f) | ||
256 | x = nil | ||
257 | collectgarbage() | ||
258 | assert(C[1] == undef) | ||
259 | assert(f() == 43 and f() == 53) | ||
260 | |||
261 | |||
262 | -- old bug: attempt to resume itself | ||
263 | |||
264 | function co_func (current_co) | ||
265 | assert(coroutine.running() == current_co) | ||
266 | assert(coroutine.resume(current_co) == false) | ||
267 | coroutine.yield(10, 20) | ||
268 | assert(coroutine.resume(current_co) == false) | ||
269 | coroutine.yield(23) | ||
270 | return 10 | ||
271 | end | ||
272 | |||
273 | local co = coroutine.create(co_func) | ||
274 | local a,b,c = coroutine.resume(co, co) | ||
275 | assert(a == true and b == 10 and c == 20) | ||
276 | a,b = coroutine.resume(co, co) | ||
277 | assert(a == true and b == 23) | ||
278 | a,b = coroutine.resume(co, co) | ||
279 | assert(a == true and b == 10) | ||
280 | assert(coroutine.resume(co, co) == false) | ||
281 | assert(coroutine.resume(co, co) == false) | ||
282 | |||
283 | |||
284 | -- other old bug when attempting to resume itself | ||
285 | -- (trigger C-code assertions) | ||
286 | do | ||
287 | local A = coroutine.running() | ||
288 | local B = coroutine.create(function() return coroutine.resume(A) end) | ||
289 | local st, res = coroutine.resume(B) | ||
290 | assert(st == true and res == false) | ||
291 | |||
292 | A = coroutine.wrap(function() return pcall(A, 1) end) | ||
293 | st, res = A() | ||
294 | assert(not st and string.find(res, "non%-suspended")) | ||
295 | end | ||
296 | |||
297 | |||
298 | -- attempt to resume 'normal' coroutine | ||
299 | local co1, co2 | ||
300 | co1 = coroutine.create(function () return co2() end) | ||
301 | co2 = coroutine.wrap(function () | ||
302 | assert(coroutine.status(co1) == 'normal') | ||
303 | assert(not coroutine.resume(co1)) | ||
304 | coroutine.yield(3) | ||
305 | end) | ||
306 | |||
307 | a,b = coroutine.resume(co1) | ||
308 | assert(a and b == 3) | ||
309 | assert(coroutine.status(co1) == 'dead') | ||
310 | |||
311 | -- infinite recursion of coroutines | ||
312 | a = function(a) coroutine.wrap(a)(a) end | ||
313 | assert(not pcall(a, a)) | ||
314 | a = nil | ||
315 | |||
316 | |||
317 | -- access to locals of erroneous coroutines | ||
318 | local x = coroutine.create (function () | ||
319 | local a = 10 | ||
320 | _G.f = function () a=a+1; return a end | ||
321 | error('x') | ||
322 | end) | ||
323 | |||
324 | assert(not coroutine.resume(x)) | ||
325 | -- overwrite previous position of local `a' | ||
326 | assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1)) | ||
327 | assert(_G.f() == 11) | ||
328 | assert(_G.f() == 12) | ||
329 | |||
330 | |||
331 | if not T then | ||
332 | (Message or print)('\n >>> testC not active: skipping yield/hook tests <<<\n') | ||
333 | else | ||
334 | print "testing yields inside hooks" | ||
335 | |||
336 | local turn | ||
337 | |||
338 | function fact (t, x) | ||
339 | assert(turn == t) | ||
340 | if x == 0 then return 1 | ||
341 | else return x*fact(t, x-1) | ||
342 | end | ||
343 | end | ||
344 | |||
345 | local A, B = 0, 0 | ||
346 | |||
347 | local x = coroutine.create(function () | ||
348 | T.sethook("yield 0", "", 2) | ||
349 | A = fact("A", 6) | ||
350 | end) | ||
351 | |||
352 | local y = coroutine.create(function () | ||
353 | T.sethook("yield 0", "", 3) | ||
354 | B = fact("B", 7) | ||
355 | end) | ||
356 | |||
357 | while A==0 or B==0 do -- A ~= 0 when 'x' finishes (similar for 'B','y') | ||
358 | if A==0 then turn = "A"; assert(T.resume(x)) end | ||
359 | if B==0 then turn = "B"; assert(T.resume(y)) end | ||
360 | end | ||
361 | |||
362 | assert(B // A == 7) -- fact(7) // fact(6) | ||
363 | |||
364 | local line = debug.getinfo(1, "l").currentline + 2 -- get line number | ||
365 | local function foo () | ||
366 | local x = 10 --<< this line is 'line' | ||
367 | x = x + 10 | ||
368 | _G.XX = x | ||
369 | end | ||
370 | |||
371 | -- testing yields in line hook | ||
372 | local co = coroutine.wrap(function () | ||
373 | T.sethook("setglobal X; yield 0", "l", 0); foo(); return 10 end) | ||
374 | |||
375 | _G.XX = nil; | ||
376 | _G.X = nil; co(); assert(_G.X == line) | ||
377 | _G.X = nil; co(); assert(_G.X == line + 1) | ||
378 | _G.X = nil; co(); assert(_G.X == line + 2 and _G.XX == nil) | ||
379 | _G.X = nil; co(); assert(_G.X == line + 3 and _G.XX == 20) | ||
380 | assert(co() == 10) | ||
381 | |||
382 | -- testing yields in count hook | ||
383 | co = coroutine.wrap(function () | ||
384 | T.sethook("yield 0", "", 1); foo(); return 10 end) | ||
385 | |||
386 | _G.XX = nil; | ||
387 | local c = 0 | ||
388 | repeat c = c + 1; local a = co() until a == 10 | ||
389 | assert(_G.XX == 20 and c >= 5) | ||
390 | |||
391 | co = coroutine.wrap(function () | ||
392 | T.sethook("yield 0", "", 2); foo(); return 10 end) | ||
393 | |||
394 | _G.XX = nil; | ||
395 | local c = 0 | ||
396 | repeat c = c + 1; local a = co() until a == 10 | ||
397 | assert(_G.XX == 20 and c >= 5) | ||
398 | _G.X = nil; _G.XX = nil | ||
399 | |||
400 | do | ||
401 | -- testing debug library on a coroutine suspended inside a hook | ||
402 | -- (bug in 5.2/5.3) | ||
403 | c = coroutine.create(function (a, ...) | ||
404 | T.sethook("yield 0", "l") -- will yield on next two lines | ||
405 | assert(a == 10) | ||
406 | return ... | ||
407 | end) | ||
408 | |||
409 | assert(coroutine.resume(c, 1, 2, 3)) -- start coroutine | ||
410 | local n,v = debug.getlocal(c, 0, 1) -- check its local | ||
411 | assert(n == "a" and v == 1) | ||
412 | assert(debug.setlocal(c, 0, 1, 10)) -- test 'setlocal' | ||
413 | local t = debug.getinfo(c, 0) -- test 'getinfo' | ||
414 | assert(t.currentline == t.linedefined + 1) | ||
415 | assert(not debug.getinfo(c, 1)) -- no other level | ||
416 | assert(coroutine.resume(c)) -- run next line | ||
417 | v = {coroutine.resume(c)} -- finish coroutine | ||
418 | assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef) | ||
419 | assert(not coroutine.resume(c)) | ||
420 | end | ||
421 | |||
422 | do | ||
423 | -- testing debug library on last function in a suspended coroutine | ||
424 | -- (bug in 5.2/5.3) | ||
425 | local c = coroutine.create(function () T.testC("yield 1", 10, 20) end) | ||
426 | local a, b = coroutine.resume(c) | ||
427 | assert(a and b == 20) | ||
428 | assert(debug.getinfo(c, 0).linedefined == -1) | ||
429 | a, b = debug.getlocal(c, 0, 2) | ||
430 | assert(b == 10) | ||
431 | end | ||
432 | |||
433 | |||
434 | print "testing coroutine API" | ||
435 | |||
436 | -- reusing a thread | ||
437 | assert(T.testC([[ | ||
438 | newthread # create thread | ||
439 | pushvalue 2 # push body | ||
440 | pushstring 'a a a' # push argument | ||
441 | xmove 0 3 2 # move values to new thread | ||
442 | resume -1, 1 # call it first time | ||
443 | pushstatus | ||
444 | xmove 3 0 0 # move results back to stack | ||
445 | setglobal X # result | ||
446 | setglobal Y # status | ||
447 | pushvalue 2 # push body (to call it again) | ||
448 | pushstring 'b b b' | ||
449 | xmove 0 3 2 | ||
450 | resume -1, 1 # call it again | ||
451 | pushstatus | ||
452 | xmove 3 0 0 | ||
453 | return 1 # return result | ||
454 | ]], function (...) return ... end) == 'b b b') | ||
455 | |||
456 | assert(X == 'a a a' and Y == 'OK') | ||
457 | |||
458 | |||
459 | -- resuming running coroutine | ||
460 | C = coroutine.create(function () | ||
461 | return T.testC([[ | ||
462 | pushnum 10; | ||
463 | pushnum 20; | ||
464 | resume -3 2; | ||
465 | pushstatus | ||
466 | gettop; | ||
467 | return 3]], C) | ||
468 | end) | ||
469 | local a, b, c, d = coroutine.resume(C) | ||
470 | assert(a == true and string.find(b, "non%-suspended") and | ||
471 | c == "ERRRUN" and d == 4) | ||
472 | |||
473 | a, b, c, d = T.testC([[ | ||
474 | rawgeti R 1 # get main thread | ||
475 | pushnum 10; | ||
476 | pushnum 20; | ||
477 | resume -3 2; | ||
478 | pushstatus | ||
479 | gettop; | ||
480 | return 4]]) | ||
481 | assert(a == coroutine.running() and string.find(b, "non%-suspended") and | ||
482 | c == "ERRRUN" and d == 4) | ||
483 | |||
484 | |||
485 | -- using a main thread as a coroutine | ||
486 | local state = T.newstate() | ||
487 | T.loadlib(state) | ||
488 | |||
489 | assert(T.doremote(state, [[ | ||
490 | coroutine = require'coroutine'; | ||
491 | X = function (x) coroutine.yield(x, 'BB'); return 'CC' end; | ||
492 | return 'ok']])) | ||
493 | |||
494 | t = table.pack(T.testC(state, [[ | ||
495 | rawgeti R 1 # get main thread | ||
496 | pushstring 'XX' | ||
497 | getglobal X # get function for body | ||
498 | pushstring AA # arg | ||
499 | resume 1 1 # 'resume' shadows previous stack! | ||
500 | gettop | ||
501 | setglobal T # top | ||
502 | setglobal B # second yielded value | ||
503 | setglobal A # fist yielded value | ||
504 | rawgeti R 1 # get main thread | ||
505 | pushnum 5 # arg (noise) | ||
506 | resume 1 1 # after coroutine ends, previous stack is back | ||
507 | pushstatus | ||
508 | return * | ||
509 | ]])) | ||
510 | assert(t.n == 4 and t[2] == 'XX' and t[3] == 'CC' and t[4] == 'OK') | ||
511 | assert(T.doremote(state, "return T") == '2') | ||
512 | assert(T.doremote(state, "return A") == 'AA') | ||
513 | assert(T.doremote(state, "return B") == 'BB') | ||
514 | |||
515 | T.closestate(state) | ||
516 | |||
517 | print'+' | ||
518 | |||
519 | end | ||
520 | |||
521 | |||
522 | -- leaving a pending coroutine open | ||
523 | _X = coroutine.wrap(function () | ||
524 | local a = 10 | ||
525 | local x = function () a = a+1 end | ||
526 | coroutine.yield() | ||
527 | end) | ||
528 | |||
529 | _X() | ||
530 | |||
531 | |||
532 | if not _soft then | ||
533 | -- bug (stack overflow) | ||
534 | local j = 2^9 | ||
535 | local lim = 1000000 -- (C stack limit; assume 32-bit machine) | ||
536 | local t = {lim - 10, lim - 5, lim - 1, lim, lim + 1} | ||
537 | for i = 1, #t do | ||
538 | local j = t[i] | ||
539 | co = coroutine.create(function() | ||
540 | local t = {} | ||
541 | for i = 1, j do t[i] = i end | ||
542 | return table.unpack(t) | ||
543 | end) | ||
544 | local r, msg = coroutine.resume(co) | ||
545 | assert(not r) | ||
546 | end | ||
547 | co = nil | ||
548 | end | ||
549 | |||
550 | |||
551 | assert(coroutine.running() == main) | ||
552 | |||
553 | print"+" | ||
554 | |||
555 | |||
556 | print"testing yields inside metamethods" | ||
557 | |||
558 | local function val(x) | ||
559 | if type(x) == "table" then return x.x else return x end | ||
560 | end | ||
561 | |||
562 | local mt = { | ||
563 | __eq = function(a,b) coroutine.yield(nil, "eq"); return val(a) == val(b) end, | ||
564 | __lt = function(a,b) coroutine.yield(nil, "lt"); return val(a) < val(b) end, | ||
565 | __le = function(a,b) coroutine.yield(nil, "le"); return a - b <= 0 end, | ||
566 | __add = function(a,b) coroutine.yield(nil, "add"); | ||
567 | return val(a) + val(b) end, | ||
568 | __sub = function(a,b) coroutine.yield(nil, "sub"); return val(a) - val(b) end, | ||
569 | __mul = function(a,b) coroutine.yield(nil, "mul"); return val(a) * val(b) end, | ||
570 | __div = function(a,b) coroutine.yield(nil, "div"); return val(a) / val(b) end, | ||
571 | __idiv = function(a,b) coroutine.yield(nil, "idiv"); | ||
572 | return val(a) // val(b) end, | ||
573 | __pow = function(a,b) coroutine.yield(nil, "pow"); return val(a) ^ val(b) end, | ||
574 | __mod = function(a,b) coroutine.yield(nil, "mod"); return val(a) % val(b) end, | ||
575 | __unm = function(a,b) coroutine.yield(nil, "unm"); return -val(a) end, | ||
576 | __bnot = function(a,b) coroutine.yield(nil, "bnot"); return ~val(a) end, | ||
577 | __shl = function(a,b) coroutine.yield(nil, "shl"); | ||
578 | return val(a) << val(b) end, | ||
579 | __shr = function(a,b) coroutine.yield(nil, "shr"); | ||
580 | return val(a) >> val(b) end, | ||
581 | __band = function(a,b) | ||
582 | coroutine.yield(nil, "band") | ||
583 | return val(a) & val(b) | ||
584 | end, | ||
585 | __bor = function(a,b) coroutine.yield(nil, "bor"); | ||
586 | return val(a) | val(b) end, | ||
587 | __bxor = function(a,b) coroutine.yield(nil, "bxor"); | ||
588 | return val(a) ~ val(b) end, | ||
589 | |||
590 | __concat = function(a,b) | ||
591 | coroutine.yield(nil, "concat"); | ||
592 | return val(a) .. val(b) | ||
593 | end, | ||
594 | __index = function (t,k) coroutine.yield(nil, "idx"); return t.k[k] end, | ||
595 | __newindex = function (t,k,v) coroutine.yield(nil, "nidx"); t.k[k] = v end, | ||
596 | } | ||
597 | |||
598 | |||
599 | local function new (x) | ||
600 | return setmetatable({x = x, k = {}}, mt) | ||
601 | end | ||
602 | |||
603 | |||
604 | local a = new(10) | ||
605 | local b = new(12) | ||
606 | local c = new"hello" | ||
607 | |||
608 | local function run (f, t) | ||
609 | local i = 1 | ||
610 | local c = coroutine.wrap(f) | ||
611 | while true do | ||
612 | local res, stat = c() | ||
613 | if res then assert(t[i] == undef); return res, t end | ||
614 | assert(stat == t[i]) | ||
615 | i = i + 1 | ||
616 | end | ||
617 | end | ||
618 | |||
619 | |||
620 | assert(run(function () if (a>=b) then return '>=' else return '<' end end, | ||
621 | {"le", "sub"}) == "<") | ||
622 | -- '<=' using '<' | ||
623 | mt.__le = nil | ||
624 | assert(run(function () if (a<=b) then return '<=' else return '>' end end, | ||
625 | {"lt"}) == "<=") | ||
626 | assert(run(function () if (a==b) then return '==' else return '~=' end end, | ||
627 | {"eq"}) == "~=") | ||
628 | |||
629 | assert(run(function () return a & b + a end, {"add", "band"}) == 2) | ||
630 | |||
631 | assert(run(function () return 1 + a end, {"add"}) == 11) | ||
632 | assert(run(function () return a - 25 end, {"sub"}) == -15) | ||
633 | assert(run(function () return 2 * a end, {"mul"}) == 20) | ||
634 | assert(run(function () return a ^ 2 end, {"pow"}) == 100) | ||
635 | assert(run(function () return a / 2 end, {"div"}) == 5) | ||
636 | assert(run(function () return a % 6 end, {"mod"}) == 4) | ||
637 | assert(run(function () return a // 3 end, {"idiv"}) == 3) | ||
638 | |||
639 | assert(run(function () return a + b end, {"add"}) == 22) | ||
640 | assert(run(function () return a - b end, {"sub"}) == -2) | ||
641 | assert(run(function () return a * b end, {"mul"}) == 120) | ||
642 | assert(run(function () return a ^ b end, {"pow"}) == 10^12) | ||
643 | assert(run(function () return a / b end, {"div"}) == 10/12) | ||
644 | assert(run(function () return a % b end, {"mod"}) == 10) | ||
645 | assert(run(function () return a // b end, {"idiv"}) == 0) | ||
646 | |||
647 | |||
648 | assert(run(function () return a % b end, {"mod"}) == 10) | ||
649 | |||
650 | assert(run(function () return ~a & b end, {"bnot", "band"}) == ~10 & 12) | ||
651 | assert(run(function () return a | b end, {"bor"}) == 10 | 12) | ||
652 | assert(run(function () return a ~ b end, {"bxor"}) == 10 ~ 12) | ||
653 | assert(run(function () return a << b end, {"shl"}) == 10 << 12) | ||
654 | assert(run(function () return a >> b end, {"shr"}) == 10 >> 12) | ||
655 | |||
656 | assert(run(function () return 10 & b end, {"band"}) == 10 & 12) | ||
657 | assert(run(function () return a | 2 end, {"bor"}) == 10 | 2) | ||
658 | assert(run(function () return a ~ 2 end, {"bxor"}) == 10 ~ 2) | ||
659 | |||
660 | assert(run(function () return a..b end, {"concat"}) == "1012") | ||
661 | |||
662 | assert(run(function() return a .. b .. c .. a end, | ||
663 | {"concat", "concat", "concat"}) == "1012hello10") | ||
664 | |||
665 | assert(run(function() return "a" .. "b" .. a .. "c" .. c .. b .. "x" end, | ||
666 | {"concat", "concat", "concat"}) == "ab10chello12x") | ||
667 | |||
668 | |||
669 | do -- a few more tests for comparsion operators | ||
670 | local mt1 = { | ||
671 | __le = function (a,b) | ||
672 | coroutine.yield(10) | ||
673 | return (val(a) <= val(b)) | ||
674 | end, | ||
675 | __lt = function (a,b) | ||
676 | coroutine.yield(10) | ||
677 | return val(a) < val(b) | ||
678 | end, | ||
679 | } | ||
680 | local mt2 = { __lt = mt1.__lt } -- no __le | ||
681 | |||
682 | local function run (f) | ||
683 | local co = coroutine.wrap(f) | ||
684 | local res | ||
685 | repeat | ||
686 | res = co() | ||
687 | until res ~= 10 | ||
688 | return res | ||
689 | end | ||
690 | |||
691 | local function test () | ||
692 | local a1 = setmetatable({x=1}, mt1) | ||
693 | local a2 = setmetatable({x=2}, mt2) | ||
694 | assert(a1 < a2) | ||
695 | assert(a1 <= a2) | ||
696 | assert(1 < a2) | ||
697 | assert(1 <= a2) | ||
698 | assert(2 > a1) | ||
699 | assert(2 >= a2) | ||
700 | return true | ||
701 | end | ||
702 | |||
703 | run(test) | ||
704 | |||
705 | end | ||
706 | |||
707 | assert(run(function () | ||
708 | a.BB = print | ||
709 | return a.BB | ||
710 | end, {"nidx", "idx"}) == print) | ||
711 | |||
712 | -- getuptable & setuptable | ||
713 | do local _ENV = _ENV | ||
714 | f = function () AAA = BBB + 1; return AAA end | ||
715 | end | ||
716 | g = new(10); g.k.BBB = 10; | ||
717 | debug.setupvalue(f, 1, g) | ||
718 | assert(run(f, {"idx", "nidx", "idx"}) == 11) | ||
719 | assert(g.k.AAA == 11) | ||
720 | |||
721 | print"+" | ||
722 | |||
723 | print"testing yields inside 'for' iterators" | ||
724 | |||
725 | local f = function (s, i) | ||
726 | if i%2 == 0 then coroutine.yield(nil, "for") end | ||
727 | if i < s then return i + 1 end | ||
728 | end | ||
729 | |||
730 | assert(run(function () | ||
731 | local s = 0 | ||
732 | for i in f, 4, 0 do s = s + i end | ||
733 | return s | ||
734 | end, {"for", "for", "for"}) == 10) | ||
735 | |||
736 | |||
737 | |||
738 | -- tests for coroutine API | ||
739 | if T==nil then | ||
740 | (Message or print)('\n >>> testC not active: skipping coroutine API tests <<<\n') | ||
741 | return | ||
742 | end | ||
743 | |||
744 | print('testing coroutine API') | ||
745 | |||
746 | local function apico (...) | ||
747 | local x = {...} | ||
748 | return coroutine.wrap(function () | ||
749 | return T.testC(table.unpack(x)) | ||
750 | end) | ||
751 | end | ||
752 | |||
753 | local a = {apico( | ||
754 | [[ | ||
755 | pushstring errorcode | ||
756 | pcallk 1 0 2; | ||
757 | invalid command (should not arrive here) | ||
758 | ]], | ||
759 | [[return *]], | ||
760 | "stackmark", | ||
761 | error | ||
762 | )()} | ||
763 | assert(#a == 4 and | ||
764 | a[3] == "stackmark" and | ||
765 | a[4] == "errorcode" and | ||
766 | _G.status == "ERRRUN" and | ||
767 | _G.ctx == 2) -- 'ctx' to pcallk | ||
768 | |||
769 | local co = apico( | ||
770 | "pushvalue 2; pushnum 10; pcallk 1 2 3; invalid command;", | ||
771 | coroutine.yield, | ||
772 | "getglobal status; getglobal ctx; pushvalue 2; pushstring a; pcallk 1 0 4; invalid command", | ||
773 | "getglobal status; getglobal ctx; return *") | ||
774 | |||
775 | assert(co() == 10) | ||
776 | assert(co(20, 30) == 'a') | ||
777 | a = {co()} | ||
778 | assert(#a == 10 and | ||
779 | a[2] == coroutine.yield and | ||
780 | a[5] == 20 and a[6] == 30 and | ||
781 | a[7] == "YIELD" and a[8] == 3 and | ||
782 | a[9] == "YIELD" and a[10] == 4) | ||
783 | assert(not pcall(co)) -- coroutine is dead now | ||
784 | |||
785 | |||
786 | f = T.makeCfunc("pushnum 3; pushnum 5; yield 1;") | ||
787 | co = coroutine.wrap(function () | ||
788 | assert(f() == 23); assert(f() == 23); return 10 | ||
789 | end) | ||
790 | assert(co(23,16) == 5) | ||
791 | assert(co(23,16) == 5) | ||
792 | assert(co(23,16) == 10) | ||
793 | |||
794 | |||
795 | -- testing coroutines with C bodies | ||
796 | f = T.makeCfunc([[ | ||
797 | pushnum 102 | ||
798 | yieldk 1 U2 | ||
799 | cannot be here! | ||
800 | ]], | ||
801 | [[ # continuation | ||
802 | pushvalue U3 # accessing upvalues inside a continuation | ||
803 | pushvalue U4 | ||
804 | return * | ||
805 | ]], 23, "huu") | ||
806 | |||
807 | x = coroutine.wrap(f) | ||
808 | assert(x() == 102) | ||
809 | eqtab({x()}, {23, "huu"}) | ||
810 | |||
811 | |||
812 | f = T.makeCfunc[[pushstring 'a'; pushnum 102; yield 2; ]] | ||
813 | |||
814 | a, b, c, d = T.testC([[newthread; pushvalue 2; xmove 0 3 1; resume 3 0; | ||
815 | pushstatus; xmove 3 0 0; resume 3 0; pushstatus; | ||
816 | return 4; ]], f) | ||
817 | |||
818 | assert(a == 'YIELD' and b == 'a' and c == 102 and d == 'OK') | ||
819 | |||
820 | |||
821 | -- testing chain of suspendable C calls | ||
822 | |||
823 | local count = 3 -- number of levels | ||
824 | |||
825 | f = T.makeCfunc([[ | ||
826 | remove 1; # remove argument | ||
827 | pushvalue U3; # get selection function | ||
828 | call 0 1; # call it (result is 'f' or 'yield') | ||
829 | pushstring hello # single argument for selected function | ||
830 | pushupvalueindex 2; # index of continuation program | ||
831 | callk 1 -1 .; # call selected function | ||
832 | errorerror # should never arrive here | ||
833 | ]], | ||
834 | [[ | ||
835 | # continuation program | ||
836 | pushnum 34 # return value | ||
837 | return * # return all results | ||
838 | ]], | ||
839 | function () -- selection function | ||
840 | count = count - 1 | ||
841 | if count == 0 then return coroutine.yield | ||
842 | else return f | ||
843 | end | ||
844 | end | ||
845 | ) | ||
846 | |||
847 | co = coroutine.wrap(function () return f(nil) end) | ||
848 | assert(co() == "hello") -- argument to 'yield' | ||
849 | a = {co()} | ||
850 | -- three '34's (one from each pending C call) | ||
851 | assert(#a == 3 and a[1] == a[2] and a[2] == a[3] and a[3] == 34) | ||
852 | |||
853 | |||
854 | -- testing yields with continuations | ||
855 | |||
856 | co = coroutine.wrap(function (...) return | ||
857 | T.testC([[ # initial function | ||
858 | yieldk 1 2 | ||
859 | cannot be here! | ||
860 | ]], | ||
861 | [[ # 1st continuation | ||
862 | yieldk 0 3 | ||
863 | cannot be here! | ||
864 | ]], | ||
865 | [[ # 2nd continuation | ||
866 | yieldk 0 4 | ||
867 | cannot be here! | ||
868 | ]], | ||
869 | [[ # 3th continuation | ||
870 | pushvalue 6 # function which is last arg. to 'testC' here | ||
871 | pushnum 10; pushnum 20; | ||
872 | pcall 2 0 0 # call should throw an error and return to next line | ||
873 | pop 1 # remove error message | ||
874 | pushvalue 6 | ||
875 | getglobal status; getglobal ctx | ||
876 | pcallk 2 2 5 # call should throw an error and jump to continuation | ||
877 | cannot be here! | ||
878 | ]], | ||
879 | [[ # 4th (and last) continuation | ||
880 | return * | ||
881 | ]], | ||
882 | -- function called by 3th continuation | ||
883 | function (a,b) x=a; y=b; error("errmsg") end, | ||
884 | ... | ||
885 | ) | ||
886 | end) | ||
887 | |||
888 | local a = {co(3,4,6)} | ||
889 | assert(a[1] == 6 and a[2] == undef) | ||
890 | a = {co()}; assert(a[1] == undef and _G.status == "YIELD" and _G.ctx == 2) | ||
891 | a = {co()}; assert(a[1] == undef and _G.status == "YIELD" and _G.ctx == 3) | ||
892 | a = {co(7,8)}; | ||
893 | -- original arguments | ||
894 | assert(type(a[1]) == 'string' and type(a[2]) == 'string' and | ||
895 | type(a[3]) == 'string' and type(a[4]) == 'string' and | ||
896 | type(a[5]) == 'string' and type(a[6]) == 'function') | ||
897 | -- arguments left from fist resume | ||
898 | assert(a[7] == 3 and a[8] == 4) | ||
899 | -- arguments to last resume | ||
900 | assert(a[9] == 7 and a[10] == 8) | ||
901 | -- error message and nothing more | ||
902 | assert(a[11]:find("errmsg") and #a == 11) | ||
903 | -- check arguments to pcallk | ||
904 | assert(x == "YIELD" and y == 4) | ||
905 | |||
906 | assert(not pcall(co)) -- coroutine should be dead | ||
907 | |||
908 | |||
909 | -- bug in nCcalls | ||
910 | local co = coroutine.wrap(function () | ||
911 | local a = {pcall(pcall,pcall,pcall,pcall,pcall,pcall,pcall,error,"hi")} | ||
912 | return pcall(assert, table.unpack(a)) | ||
913 | end) | ||
914 | |||
915 | local a = {co()} | ||
916 | assert(a[10] == "hi") | ||
917 | |||
918 | print'OK' | ||