-------------------------------------------------------------------------------- -- 0050-coro.lua: tests for coroutine module extensions -- This file is a part of lua-nucleo library -- Copyright (c) lua-nucleo authors (see file `COPYRIGHT` for the license) -------------------------------------------------------------------------------- local make_suite = assert(loadfile('test/test-lib/init/strict.lua'))(...) local select, assert, type, tostring = select, assert, type, tostring local table_concat = table.concat local coroutine_create, coroutine_yield, coroutine_status, coroutine_resume = coroutine.create, coroutine.yield, coroutine.status, coroutine.resume local ensure, ensure_equals, ensure_tequals = import 'lua-nucleo/ensure.lua' { 'ensure', 'ensure_equals', 'ensure_tequals' } local make_concatter = import 'lua-nucleo/string.lua' { 'make_concatter' } local eat_true = import 'lua-nucleo/args.lua' { 'eat_true' } local coro = import 'lua-nucleo/coro.lua' () -------------------------------------------------------------------------------- local eat_tag = function(v, ...) if not coro.is_outer_yield_tag(v) then error("can't eat outer yield tag, it is: "..tostring(v), 2) end return ... end local eat_true_tag = function(...) return eat_tag(eat_true(...)) end -------------------------------------------------------------------------------- local test = make_suite("coro", coro) -------------------------------------------------------------------------------- -- NOTE: Tests below check all these functions in conjunction, -- so we're simply declaring them here as tested. -- When adding function to this list, make sure it has tests first. test:tests_for 'resume_inner' -------------------------------------------------------------------------------- test "yield_inner" (function() local co = coroutine_create(function(A) ensure_equals("A", A, "A") ensure_equals("C", coroutine_yield("B"), "C") return "D" end) ensure_equals("B", eat_true(coro.resume_inner(co, "A")), "B") ensure_equals("D", eat_true(coro.resume_inner(co, "C")), "D") ensure_equals("coroutine dead", coroutine_status(co), "dead") end) -------------------------------------------------------------------------------- test:test_for "yield_outer" (function() local co = coroutine_create(function(A) ensure_equals("A", A, "A") ensure_equals("C", coro.yield_outer("B"), "C") return "D" end) ensure_equals("B", eat_true_tag(coroutine_resume(co, "A")), "B") ensure_equals("D", eat_true(coro.resume_inner(co, "C")), "D") ensure_equals("coroutine dead", coroutine_status(co), "dead") end) -------------------------------------------------------------------------------- test "inner-inner-yield_inner" (function() local inner1 = coroutine_create(function(A) assert(A == "A") local inner2 = coroutine_create(function(B) assert(B == "B") assert(coroutine_yield("C") == "D") return "E" end) assert(eat_true(coro.resume_inner(inner2, "B")) == "C") assert(eat_true(coro.resume_inner(inner2, "D")) == "E") assert(coroutine_status(inner2) == "dead") return "F" end) assert(eat_true(coro.resume_inner(inner1, "A")) == "F") assert(coroutine_status(inner1) == "dead") end) -------------------------------------------------------------------------------- test "outer-inner-inner-yield_outer" (function() local outer = coroutine_create(function(A) ensure_equals("A", A, "A") local inner1 = coroutine_create(function(B) ensure_equals("B", B, "B") local inner2 = coroutine_create(function(C) ensure_equals("C", C, "C") ensure_equals("E", coro.yield_outer("D"), "E") return "F" end) ensure_equals("F", eat_true(coro.resume_inner(inner2, "C")), "F") ensure_equals("inner2 dead", coroutine_status(inner2), "dead") return "G" end) ensure_equals("G", eat_true(coro.resume_inner(inner1, "B")), "G") ensure_equals("inner1 dead", coroutine_status(inner1), "dead") return "H" end) ensure_equals("D", eat_true_tag(coroutine_resume(outer, "A")),"D") ensure_equals("H", eat_true(coroutine_resume(outer, "E")), "H") ensure_equals("outer dead", coroutine_status(outer), "dead") end) -------------------------------------------------------------------------------- test "outer-inner-inner-yield_outer-twice" (function() local outer = coroutine_create(function(A) ensure_equals("A", A, "A") local inner1 = coroutine_create(function(B) ensure_equals("B", B, "B") local inner2 = coroutine_create(function(C) ensure_equals("C", C, "C") ensure_equals("E", coro.yield_outer("D"), "E") ensure_equals("G", coro.yield_outer("F"), "G") return "H" end) ensure_equals("H", eat_true(coro.resume_inner(inner2, "C")), "H") ensure_equals("inner2 dead", coroutine_status(inner2), "dead") return "I" end) ensure_equals("F", eat_true(coro.resume_inner(inner1, "B")), "I") ensure_equals("inner1 dead", coroutine_status(inner1), "dead") return "J" end) ensure_equals("D", eat_true_tag(coroutine_resume(outer, "A")), "D") ensure_equals("J", eat_true_tag(coroutine_resume(outer, "E")), "F") ensure_equals("J", eat_true(coroutine_resume(outer, "G")), "J") ensure_equals("outer dead", coroutine_status(outer), "dead") end) -------------------------------------------------------------------------------- --[[ Mesage passing scheme for basic test: main outer inner1 inner2 | . . . A--------c->* . . | | . . *<=g========B . . | | . . C========g=>* . . | | . . *<-l--------D . . | | . . E--------l->* . . | | . . | F--------c->* . | | | . *<=g========o===========G . | | | . H===========o========g=>* . | | | . | <-l---------I . | | | . | J--------l->* . | | | . | | K----------c->* | | | | *<=g========o===========o=============L | | | | M===========o===========o==========g=>* | | | | | | *<-l----------N | | | | | | O----------l->* | | | | | | *<-r----------P | | | . | *<-r--------Q . | | . . *<-r--------R . . | . . . --]] test "basic" (function() local cat, concat = make_concatter() do local outer = coroutine_create(function(A) cat(A) --> A cat(coro.yield_outer("B")) --> C -- Outer coroutines may do inner yields -- (in case they're held by inner corouines). cat(coroutine_yield("D")) --> E local inner1 = coroutine_create(function(F) cat(F) --> F cat(coro.yield_outer("G")) --> H cat(coroutine_yield("I")) --> J local inner2 = coroutine_create(function(K) cat(K) cat(coro.yield_outer("L")) --> M cat(coroutine_yield("N")) --> O return "P" end) cat(eat_true(coro.resume_inner(inner2, "K"))) --> N cat(eat_true(coro.resume_inner(inner2, "O"))) --> P assert(coroutine_status(inner2) == "dead") return "Q" end) cat(eat_true(coro.resume_inner(inner1, "F"))) --> I cat(eat_true(coro.resume_inner(inner1, "J"))) --> Q assert(coroutine_status(inner1) == "dead") return "R" end) cat(eat_true_tag(coroutine_resume(outer, "A"))) --> B cat(eat_true(coroutine_resume(outer, "C"))) --> D cat(eat_true_tag(coroutine_resume(outer, "E"))) --> G cat(eat_true_tag(coroutine_resume(outer, "H"))) --> L cat(eat_true(coroutine_resume(outer, "M"))) --> R assert(coroutine_status(outer) == "dead") end assert(concat() == "ABCDEFGHIJKLMNOPQR") end) -------------------------------------------------------------------------------- -- NOTE: Tests below check all these functions in conjunction, -- so we're simply declaring them here as tested. -- When adding function to this list, make sure it has tests first. test:tests_for 'eat_tag' 'is_outer_yield_tag' -------------------------------------------------------------------------------- test:test "eat_tag-is_outer_yield_tag-not-tag" (function() ensure_equals("that is not the tag", coro.is_outer_yield_tag(42), false) ensure_tequals( "do not eat non-tag true", { coro.eat_tag(true, 42) }, { true, 42 } ) ensure_tequals( "do not eat non-tag false", { coro.eat_tag(false, 42) }, { false, 42 } ) end) test:test "eat_tag-is_outer_yield_tag-actual-tag" (function() local tag do local co = coroutine_create(function() coro.yield_outer() end) local _ _, tag = assert(coroutine.resume(co)) end ensure_equals("that is the tag", coro.is_outer_yield_tag(tag), true) ensure_tequals( "eat tag true", { coro.eat_tag(true, tag, 42) }, { true, 42 } ) ensure_tequals( "eat tag false", { coro.eat_tag(false, tag, 42) }, { false, 42 } ) end)