local which_tests, remaining_tests = {}, {} for k,v in ipairs{...} do print("got arg:", type(v), tostring(v)) which_tests[v] = true remaining_tests[v] = true end -- ################################################################################################## local lanes = require "lanes" .configure{ with_timers = false} local SLEEP = function(...) -- just for fun: start a lane that will do the sleeping for us local sleeperBody = function(...) local lanes = require "lanes" local k, v = lanes.sleep(...) assert(k == nil and v == "timeout") return true end local sleeper = lanes.gen("*", sleeperBody)(...) -- then wait for the lane to terminate sleeper:join() end local linda = lanes.linda() -- a numeric value to read linda:set("val", 33.0) -- so that we can easily swap between lanes.gen and lanes.coro, to try stuff -- TODO: looks like the result changes when using LuaJIT and coro together. to be investigated local generator = lanes.gen -- ################################################################################################## if not next(which_tests) or which_tests.genlock then remaining_tests.genlock = nil print "\n\n####################################################################\nbegin genlock & genatomic cancel test\n" -- get a lock and a atomic operator local lock = lanes.genlock(linda, "lock", 1) local atomic = lanes.genatomic(linda, "atomic") local check_returned_cancel_error = function(_status, _err) assert(_status == nil and _err == lanes.cancel_error) end -- check that cancelled lindas give cancel_error as they should linda:cancel() check_returned_cancel_error(linda:set("empty", 42)) check_returned_cancel_error(linda:get("empty")) check_returned_cancel_error(linda:send("empty", 42)) check_returned_cancel_error(linda:receive("empty")) check_returned_cancel_error(linda:limit("empty", 5)) check_returned_cancel_error(linda:restrict("empty", "set/get")) assert(lanes.genlock(linda, "any", 1) == lanes.cancel_error) assert(lanes.genatomic(linda, "any") == lanes.cancel_error) -- check that lock and atomic functions return cancel_error if the linda was cancelled assert(lock(1) == lanes.cancel_error) assert(lock(-1) == lanes.cancel_error) assert(atomic(1) == lanes.cancel_error) -- reset the linda so that the other tests work linda:cancel("none") linda:limit("lock", "unlimited") linda:set("lock") linda:limit("atomic", "unlimited") linda:set("atomic") print "test OK" end -- ################################################################################################## local waitCancellation = function(h, expected_status) local l = lanes.linda() if expected_status ~= "running" then repeat -- print("lane status:", h.status) SLEEP(0.1) -- wait a bit until h.status ~= "running" end print("lane status:", h.status) assert(h.status == expected_status, "lane status " .. h.status .. " (actual) ~= " .. expected_status .. " (expected)") print "test OK" end local laneBody = function(mode_, payload_) local name = "laneBody("..tostring(mode_)..","..tostring(payload_)..")" lane_threadname(name) set_finalizer(function(err, stk) if err == lanes.cancel_error then -- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it print(" laneBody after cancel" ) elseif err then print(" laneBody error: "..tostring(err)) else print(" laneBody finalized") end end) print(" entering " , name) repeat if mode_ == "receive" then -- linda mode io.stdout:write(" lane calling receive() ... ") local key, val = linda:receive(payload_, "boob") print(tostring(key), val == lanes.cancel_error and "cancel_error" or tostring(val)) if val == lanes.cancel_error then break -- gracefully abort loop end elseif mode_ == "get" then -- busy wait mode getting data from the linda io.stdout:write(" lane busy waiting ... ") for i = 1, payload_ do -- force a non-jitable call local _, a = linda:get("val") a = a * 2 end print("again?") elseif mode_ == "busy" then -- busy wait mode in pure Lua code io.stdout:write(" lane busy waiting ... ") local _, a = linda:get("val") for i = 1, payload_ do a = a * 2 a = math.sin(a) * math.sin(a) + math.cos(a) * math.cos(a) -- aka 1 end print("again?") else error "no mode: raise an error" end until cancel_test() -- soft cancel self test print " lane shutting down after breaking out of loop" end local protectedBody = function(...) local ce = lanes.cancel_error local errorHandler = function(_msg) -- forward the message to the main thread that will display it with a popup print(" error handler got ", ce == _msg and "cancel_error" or tostring(_msg)) return _msg end -- Lua 5.1 doesn't pass additional xpcall arguments to the called function -- therefore we need to create a closure that has no arguments but pulls everything from its upvalue local params = {...} local unpack = table.unpack or unpack -- unpack for 5.1, table.unpack for 5.2+ local paramLessClosure = function() laneBody(unpack(params)) end local status, message = xpcall(paramLessClosure, errorHandler) if status == false then print(" error handler rethrowing '" .. (ce == message and "cancel_error"or tostring(message)) .. "'") -- if the error isn't rethrown, the lane's finalizer won't get it error(message) end end -- ################################################################################################## -- ################################################################################################## if not next(which_tests) or which_tests.linda then remaining_tests.linda = nil print "\n\n####################################################################\nbegin linda cancel test\n" h = generator("*", { name = 'auto' }, laneBody)("receive", nil) -- start an infinite wait on the linda print "wait 1s" SLEEP(1) -- linda cancel: linda:receive() returns nil,cancel_error immediately print "cancelling - both" linda:cancel("both") -- wait until cancellation is effective. waitCancellation(h, "done") -- reset the linda so that the other tests work linda:cancel("none") end -- ################################################################################################## if not next(which_tests) or which_tests.soft then remaining_tests.soft = nil print "\n\n####################################################################\nbegin soft cancel test\n" h = generator("*", { name = 'auto' }, protectedBody)("receive") -- start an infinite wait on the linda print "wait 1s" SLEEP(1) -- soft cancel, no awakening of waiting linda operations, should timeout local a, b = h:cancel("soft", 1, false) -- cancellation should fail as the lane is still waiting on its linda assert(a == false and b == "timeout") waitCancellation(h, "waiting") -- soft cancel, this time awakens waiting linda operations, which returns cancel_error immediately, no timeout. print "cancelling - soft" h:cancel("soft", true) -- wait until cancellation is effective. the lane will interrupt its loop and print the exit message waitCancellation(h, "done") end -- ################################################################################################## if not next(which_tests) or which_tests.hook then remaining_tests.hook = nil print "\n\n####################################################################\nbegin hook cancel test\n" h = generator("*", { name = 'auto' }, protectedBody)("get", 300000) print "wait 2s" SLEEP(2) -- count hook cancel after some instruction instructions print "cancelling - line" h:cancel("line", 300, 5.0) -- wait until cancellation is effective. the lane will interrupt its loop and print the exit message waitCancellation(h, "cancelled") end -- ################################################################################################## if not next(which_tests) or which_tests.hard then remaining_tests.hard = nil print "\n\n####################################################################\nbegin hard cancel test\n" h = lanes.gen("*", { name = 'auto' }, protectedBody)("receive", nil) -- infinite timeout -- wait 2s before cancelling the lane print "wait 2s" SLEEP(2) -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it print "cancelling - hard" h:cancel() -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error waitCancellation(h, "cancelled") end -- ################################################################################################## if not next(which_tests) or which_tests.hard_unprotected then remaining_tests.hard_unprotected = nil print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n" h = generator("*", { name = 'auto' }, laneBody)("receive", nil) -- wait 2s before cancelling the lane print "wait 2s" SLEEP(2) -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it print "cancelling - hard" h:cancel() -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error waitCancellation(h, "cancelled") end -- ################################################################################################## local unknown_test, val = next(remaining_tests) assert(not unknown_test, tostring(unknown_test) .. " test is unknown") print "\nTHE END"