aboutsummaryrefslogtreecommitdiff
path: root/unit_tests/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'unit_tests/scripts')
-rw-r--r--unit_tests/scripts/_utils.lua20
-rw-r--r--unit_tests/scripts/_utils54.lua30
-rw-r--r--unit_tests/scripts/coro/basics.lua97
-rw-r--r--unit_tests/scripts/coro/cancelling_suspended.lua31
-rw-r--r--unit_tests/scripts/coro/collect_yielded_lane.lua64
-rw-r--r--unit_tests/scripts/coro/error_handling.lua6
-rw-r--r--unit_tests/scripts/coro/index_suspended.lua28
-rw-r--r--unit_tests/scripts/coro/join_suspended.lua24
-rw-r--r--unit_tests/scripts/coro/linda_in_close_handler.lua43
-rw-r--r--unit_tests/scripts/coro/regular_function.lua38
-rw-r--r--unit_tests/scripts/coro/resume_basics.lua40
-rw-r--r--unit_tests/scripts/coro/yielding_in_non_coro_errors.lua28
-rw-r--r--unit_tests/scripts/lane/body_is_a_c_function.lua28
-rw-r--r--unit_tests/scripts/lane/cooperative_shutdown.lua16
-rw-r--r--unit_tests/scripts/lane/tasking_cancelling.lua27
-rw-r--r--unit_tests/scripts/lane/tasking_join_test.lua16
-rw-r--r--unit_tests/scripts/lane/tasking_send_receive_code.lua18
-rw-r--r--unit_tests/scripts/lane/uncooperative_shutdown.lua2
-rw-r--r--unit_tests/scripts/linda/send_receive_func_and_string.lua13
-rw-r--r--unit_tests/scripts/linda/wake_period.lua5
-rw-r--r--unit_tests/scripts/misc/deeptest.lua161
21 files changed, 610 insertions, 125 deletions
diff --git a/unit_tests/scripts/_utils.lua b/unit_tests/scripts/_utils.lua
index d710702..9f46237 100644
--- a/unit_tests/scripts/_utils.lua
+++ b/unit_tests/scripts/_utils.lua
@@ -68,8 +68,26 @@ local function dump_error_stack(error_reporting_mode_, stack)
68 end 68 end
69end 69end
70 70
71-- a function that yields back what got in, one element at a time
72local yield_one_by_one = function(...)
73 local PRINT = MAKE_PRINT()
74 PRINT "In lane"
75 for _i = 1, select('#', ...) do
76 local _val = select(_i, ...)
77 PRINT("yielding #", _i, _val)
78 local _ack = coroutine.yield(_val)
79 if cancel_test and cancel_test() then -- cancel_test does not exist when run immediately (not in a Lane)
80 return "cancelled!"
81 end
82 -- of course, if we are cancelled, we were not resumed, and yield() didn't return what we expect
83 assert(_ack == _i)
84 end
85 return "bye!"
86end
87
71return { 88return {
72 MAKE_PRINT = MAKE_PRINT, 89 MAKE_PRINT = MAKE_PRINT,
73 tables_match = tables_match, 90 tables_match = tables_match,
74 dump_error_stack = dump_error_stack 91 dump_error_stack = dump_error_stack,
92 yield_one_by_one = yield_one_by_one
75} 93}
diff --git a/unit_tests/scripts/_utils54.lua b/unit_tests/scripts/_utils54.lua
new file mode 100644
index 0000000..a511563
--- /dev/null
+++ b/unit_tests/scripts/_utils54.lua
@@ -0,0 +1,30 @@
1local utils = require "_utils"
2
3-- expand _utils module with Lua5.4 specific stuff
4
5-- a lane body that yields stuff
6utils.yielder_with_to_be_closed = function(out_linda_, wait_)
7 local fixture = assert(require "fixture")
8 -- here is a to-be-closed variable that, when closed, sends "Closed!" in the "out" slot of the provided linda
9 local t <close> = setmetatable(
10 { text = "Closed!" }, {
11 __close = function(self, err)
12 if wait_ then
13 fixture.block_for(wait_)
14 end
15 out_linda_:send("out", self.text)
16 end
17 }
18 )
19 -- yield forever, but be cancel-friendly
20 local n = 1
21 while true do
22 coroutine.yield("I yield!", n)
23 if cancel_test and cancel_test() then -- cancel_test does not exist when run immediately (not in a Lane)
24 return "I am cancelled"
25 end
26 n = n + 1
27 end
28end
29
30return utils
diff --git a/unit_tests/scripts/coro/basics.lua b/unit_tests/scripts/coro/basics.lua
deleted file mode 100644
index cd2f410..0000000
--- a/unit_tests/scripts/coro/basics.lua
+++ /dev/null
@@ -1,97 +0,0 @@
1local lanes = require "lanes"
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9if true then
10 -- a lane body that just returns some value
11 local lane = function(msg_)
12 local utils = lanes.require "_utils"
13 local PRINT = utils.MAKE_PRINT()
14 PRINT "In lane"
15 assert(msg_ == "hi")
16 return "bye"
17 end
18
19 -- the generator
20 local g1 = lanes.coro("*", {name = "auto"}, lane)
21
22 -- launch lane
23 local h1 = g1("hi")
24
25 local r = h1[1]
26 assert(r == "bye")
27end
28
29-- a lane coroutine that yields back what got in, one element at a time
30local yielder = function(...)
31 local utils = lanes.require "_utils"
32 local PRINT = utils.MAKE_PRINT()
33 PRINT "In lane"
34 for _i = 1, select('#', ...) do
35 local _val = select(_i, ...)
36 PRINT("yielding #", _i, _val)
37 local _ack = coroutine.yield(_val)
38 assert(_ack == _i)
39 end
40 return "done!"
41end
42
43if true then
44 -- if we start a non-coroutine lane with a yielding function, we should get an error, right?
45 local fun_g = lanes.gen("*", { name = 'auto' }, yielder)
46 local h = fun_g("hello", "world", "!")
47 local err, status, stack = h:join()
48 PRINT(err, status, stack)
49 -- the actual error message is not the same for Lua 5.1
50 -- of course, it also has to be different for LuaJIT as well
51 -- also, LuaJIT prepends a file:line to the actual error message, which Lua5.1 does not.
52 local msgs = {
53 ["Lua 5.1"] = jit and "attempt to yield across C-call boundary" or "attempt to yield across metamethod/C-call boundary",
54 ["Lua 5.2"] = "attempt to yield from outside a coroutine",
55 ["Lua 5.3"] = "attempt to yield from outside a coroutine",
56 ["Lua 5.4"] = "attempt to yield from outside a coroutine"
57 }
58 local expected_msg = msgs[_VERSION]
59 PRINT("expected_msg = " .. expected_msg)
60 assert(err == nil and string.find(status, expected_msg, 1, true) and stack == nil, "status = " .. status)
61end
62
63-- the generator
64local coro_g = lanes.coro("*", {name = "auto"}, yielder)
65
66if true then
67 -- launch coroutine lane
68 local h2 = coro_g("hello", "world", "!")
69 -- read the yielded values, sending back the expected index
70 assert(h2:resume(1) == "hello")
71 assert(h2:resume(2) == "world")
72 assert(h2:resume(3) == "!")
73 -- the lane return value is available as usual
74 local r = h2[1]
75 assert(r == "done!")
76end
77
78if true then
79 -- another coroutine lane
80 local h3 = coro_g("hello", "world", "!")
81
82 -- yielded values are available as regular return values
83 assert(h3[1] == "hello" and h3.status == "suspended")
84 -- since we consumed the returned values, they should not be here when we resume
85 assert(h3:resume(1) == nil)
86
87 -- similarly, we can get them with join()
88 assert(h3:join() == "world" and h3.status == "suspended")
89 -- since we consumed the returned values, they should not be here when we resume
90 assert(h3:resume(2) == nil)
91
92 -- the rest should work as usual
93 assert(h3:resume(3) == "!")
94
95 -- the final return value of the lane body remains to be read
96 assert(h3:join() == "done!" and h3.status == "done")
97end
diff --git a/unit_tests/scripts/coro/cancelling_suspended.lua b/unit_tests/scripts/coro/cancelling_suspended.lua
new file mode 100644
index 0000000..3a29e55
--- /dev/null
+++ b/unit_tests/scripts/coro/cancelling_suspended.lua
@@ -0,0 +1,31 @@
1local fixture = require "fixture"
2local lanes = require "lanes".configure{on_state_create = fixture.on_state_create}
3
4local fixture = require "fixture"
5lanes.finally(fixture.throwing_finalizer)
6
7local utils = lanes.require "_utils"
8local PRINT = utils.MAKE_PRINT()
9
10--------------------------------------------------
11-- TEST: cancelling a suspended Lane should end it
12--------------------------------------------------
13if true then
14 -- the generator
15 local coro_g = lanes.coro("*", utils.yield_one_by_one)
16
17 -- start the lane
18 local h = coro_g("hello", "world", "!")
19 repeat until h.status == "suspended"
20
21 -- first cancellation attempt: don't wake the lane
22 local b, r = h:cancel("soft", 0.5)
23 -- the lane is still blocked in its suspended state
24 assert(b == false and r == "timeout" and h.status == "suspended", "got " .. tostring(b) .. " " .. tostring(r) .. " " .. h.status)
25
26 -- cancel the Lane again, this time waking it. it will resume, and yielder()'s will break out of its infinite loop
27 h:cancel("soft", nil, true)
28
29 -- lane should be done, because it returned cooperatively when detecting a soft cancel
30 assert(h.status == "done", "got " .. h.status)
31end
diff --git a/unit_tests/scripts/coro/collect_yielded_lane.lua b/unit_tests/scripts/coro/collect_yielded_lane.lua
new file mode 100644
index 0000000..2ee58f8
--- /dev/null
+++ b/unit_tests/scripts/coro/collect_yielded_lane.lua
@@ -0,0 +1,64 @@
1local fixture = require "fixture"
2local lanes = require "lanes".configure{on_state_create = fixture.on_state_create}
3
4local fixture = require "fixture"
5lanes.finally(fixture.throwing_finalizer)
6
7-- this test is only for Lua 5.4+
8local utils = lanes.require "_utils54"
9local PRINT = utils.MAKE_PRINT()
10
11local out_linda = lanes.linda()
12
13------------------------------------------------------------------------------
14-- TEST: to-be-closed variables are properly closed when the lane is collected
15------------------------------------------------------------------------------
16if true then
17 -- the generator
18 local coro_g = lanes.coro("*", utils.yielder_with_to_be_closed)
19
20 -- start the lane
21 local h = coro_g(out_linda)
22
23 -- join the lane. it should be done and give back the values resulting of the first yield point
24 local r, v1, v2 = h:join()
25 assert(r == true and v1 == "I yield!" and v2 == 1, "got " .. tostring(r) .. " " .. tostring(v1) .. " " .. tostring(v2))
26 assert(h.status == "done", "got " .. h.status)
27
28 -- force collection of the lane
29 h = nil
30 collectgarbage()
31
32 -- I want the to-be-closed variable of the coroutine linda to be properly closed
33 local s, r = out_linda:receive(0, "out")
34 assert(s == "out" and r == "Closed!", "coro got " .. tostring(s) .. " " .. tostring(r)) -- THIS TEST FAILS
35end
36
37---------------------------------------------------------------------------------------------------
38-- TEST: if a to-be-closed handler takes longer than the join timeout, everything works as expected
39---------------------------------------------------------------------------------------------------
40if true then
41 -- the generator
42 local coro_g = lanes.coro("*", utils.yielder_with_to_be_closed)
43
44 -- start the lane. The to-be-closed handler will sleep for 1 second
45 local h = coro_g(out_linda, 1)
46
47 -- first join attempt should timeout
48 local r, v = h:join(0.6)
49 assert(r == nil and v == "timeout", "got " .. tostring(r) .. " " .. tostring(v))
50 assert(h.status == "running", "got " .. h.status)
51
52 -- join the lane again. it should be done and give back the values resulting of the first yield point
53 local r, v1, v2 = h:join(0.6)
54 assert(r == true and v1 == "I yield!" and v2 == 1, "got " .. tostring(r) .. " " .. tostring(v1) .. " " .. tostring(v2))
55 assert(h.status == "done", "got " .. h.status)
56
57 -- force collection of the lane
58 h = nil
59 collectgarbage()
60
61 -- I want the to-be-closed variable of the coroutine linda to be properly closed
62 local s, r = out_linda:receive(0, "out")
63 assert(s == "out" and r == "Closed!", "coro got " .. tostring(s) .. " " .. tostring(r)) -- THIS TEST FAILS
64end
diff --git a/unit_tests/scripts/coro/error_handling.lua b/unit_tests/scripts/coro/error_handling.lua
index ba6cff6..1cfb8c8 100644
--- a/unit_tests/scripts/coro/error_handling.lua
+++ b/unit_tests/scripts/coro/error_handling.lua
@@ -38,15 +38,15 @@ local force_error_test = function(error_trace_level_)
38 utils.dump_error_stack(error_trace_level_, c) 38 utils.dump_error_stack(error_trace_level_, c)
39end 39end
40 40
41if false then 41if true then
42 force_error_test("minimal") 42 force_error_test("minimal")
43end 43end
44 44
45if false then 45if true then
46 force_error_test("basic") 46 force_error_test("basic")
47end 47end
48 48
49if false then 49if true then
50 force_error_test("extended") 50 force_error_test("extended")
51end 51end
52 52
diff --git a/unit_tests/scripts/coro/index_suspended.lua b/unit_tests/scripts/coro/index_suspended.lua
new file mode 100644
index 0000000..2cd8c28
--- /dev/null
+++ b/unit_tests/scripts/coro/index_suspended.lua
@@ -0,0 +1,28 @@
1local lanes = require "lanes"
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9-- the coroutine generator
10local coro_g = lanes.coro("*", {name = "auto"}, utils.yield_one_by_one)
11
12-------------------------------------------------------------------------
13-- TEST: if we index a yielded lane, we should get the last yielded value
14-------------------------------------------------------------------------
15if true then
16 -- launch coroutine lane
17 local h = coro_g("hello", "world", "!")
18 -- read the first yielded value, sending back the expected index
19 assert(h:resume(1) == "hello")
20 -- indexing multiple times gives back the same us the same yielded value
21 local r1 = h[1]
22 local r2 = h[1]
23 local r3 = h[1]
24 assert(r1 == "world" and r2 == "world" and r3 == "world", "got " .. r1 .. " " .. r2 .. " " .. r3)
25 -- once the lane was indexed, it is no longer resumable (just like after join)
26 local b, e = pcall(h.resume, h, 2)
27 assert(b == false and e == "cannot resume non-suspended coroutine Lane")
28end
diff --git a/unit_tests/scripts/coro/join_suspended.lua b/unit_tests/scripts/coro/join_suspended.lua
new file mode 100644
index 0000000..33be406
--- /dev/null
+++ b/unit_tests/scripts/coro/join_suspended.lua
@@ -0,0 +1,24 @@
1local lanes = require "lanes"
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9-- the coroutine generator
10local coro_g = lanes.coro("*", {name = "auto"}, utils.yield_one_by_one)
11
12---------------------------------------------------
13-- TEST: if we join a yielded lane, the lane aborts
14---------------------------------------------------
15if true then
16 -- launch coroutine lane
17 local h = coro_g("hello", "world", "!")
18 -- read the first yielded value, sending back the expected index
19 assert(h:resume(1) == "hello")
20 -- join the lane. since it will reach a yield point, it unblocks and ends. last yielded values are returned normally
21 local b, r = h:join(0.5)
22 local s = h.status
23 assert(s == "done" and b == true and r == "world", "got " .. s .. " " .. tostring(b) .. " " .. tostring(r))
24end
diff --git a/unit_tests/scripts/coro/linda_in_close_handler.lua b/unit_tests/scripts/coro/linda_in_close_handler.lua
new file mode 100644
index 0000000..8636f01
--- /dev/null
+++ b/unit_tests/scripts/coro/linda_in_close_handler.lua
@@ -0,0 +1,43 @@
1local fixture = require "fixture"
2local lanes = require "lanes".configure{on_state_create = fixture.on_state_create}
3
4local fixture = require "fixture"
5lanes.finally(fixture.throwing_finalizer)
6
7-- this test is only for Lua 5.4+
8local utils = lanes.require "_utils54"
9local PRINT = utils.MAKE_PRINT()
10
11local out_linda = lanes.linda()
12
13local test_close = function(what_, f_)
14 local c = coroutine.create(f_)
15 for i = 1, 10 do
16 local t, r1, r2 = coroutine.resume(c, out_linda) -- returns true + <yielded values>
17 assert(t == true and r1 == "I yield!" and r2 == i, "got " .. tostring(t) .. " " .. tostring(r1) .. " " .. tostring(r2))
18 local s = coroutine.status(c)
19 assert(s == "suspended")
20 end
21 local r, s = coroutine.close(c)
22 assert(r == true and s == nil)
23 -- the local variable inside the yielder body should be closed
24 local s, r = out_linda:receive(0, "out")
25 assert(s == "out" and r == "Closed!", what_ .. " got " .. tostring(s) .. " " .. tostring(r))
26end
27
28---------------------------------------------------------
29-- TEST: first, try the close mechanism outside of a lane
30---------------------------------------------------------
31if true then
32 assert(type(utils.yielder_with_to_be_closed) == "function")
33 test_close("base", utils.yielder_with_to_be_closed)
34end
35
36---------------------------------------------------------------
37-- TEST: try again with a function obtained through dump/undump
38---------------------------------------------------------------
39if true then
40 -- note this means our yielder implementation can't have upvalues, as they are lost in the process
41 test_close("dumped", load(string.dump(utils.yielder_with_to_be_closed)))
42end
43
diff --git a/unit_tests/scripts/coro/regular_function.lua b/unit_tests/scripts/coro/regular_function.lua
new file mode 100644
index 0000000..09aa3b7
--- /dev/null
+++ b/unit_tests/scripts/coro/regular_function.lua
@@ -0,0 +1,38 @@
1local lanes = require "lanes".configure()
2
3local utils = lanes.require "_utils"
4local PRINT = utils.MAKE_PRINT()
5
6-- a lane body that just returns some value
7local returner = function(msg_)
8 local utils = lanes.require "_utils"
9 local PRINT = utils.MAKE_PRINT()
10 PRINT "In lane"
11 assert(msg_ == "hi")
12 return "bye"
13end
14
15-- a function that returns some value can run in a coroutine
16if true then
17 -- the generator
18 local g = lanes.coro("*", {name = "auto"}, returner)
19
20 -- launch lane
21 local h = g("hi")
22
23 local r = h[1]
24 assert(r == "bye")
25end
26
27-- can't resume a coro after the lane body has returned
28if true then
29 -- the generator
30 local g = lanes.coro("*", {name = "auto"}, returner)
31
32 -- launch lane
33 local h = g("hi")
34
35 -- resuming a lane that terminated execution should raise an error
36 local b, e = pcall(h.resume, h)
37 assert(b == false and type(e) == "string")
38end
diff --git a/unit_tests/scripts/coro/resume_basics.lua b/unit_tests/scripts/coro/resume_basics.lua
new file mode 100644
index 0000000..5b124f5
--- /dev/null
+++ b/unit_tests/scripts/coro/resume_basics.lua
@@ -0,0 +1,40 @@
1local lanes = require "lanes"
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9-- the coroutine generator
10local coro_g = lanes.coro("*", {name = "auto"}, utils.yield_one_by_one)
11
12-------------------------------------------------------------------------------------------------
13-- TEST: we can resume as many times as the lane yields, then read the returned value on indexing
14-------------------------------------------------------------------------------------------------
15if true then
16 -- launch coroutine lane
17 local h = coro_g("hello", "world", "!")
18 -- read the yielded values, sending back the expected index
19 assert(h:resume(1) == "hello")
20 assert(h:resume(2) == "world")
21 assert(h:resume(3) == "!")
22 -- the lane return value is available as usual
23 local r = h[1]
24 assert(r == "bye!")
25end
26
27---------------------------------------------------------------------------------------------
28-- TEST: we can resume as many times as the lane yields, then read the returned value on join
29---------------------------------------------------------------------------------------------
30if true then
31 -- launch coroutine lane
32 local h = coro_g("hello", "world", "!")
33 -- read the yielded values, sending back the expected index
34 assert(h:resume(1) == "hello")
35 assert(h:resume(2) == "world")
36 assert(h:resume(3) == "!")
37 -- the lane return value is available as usual
38 local s, r = h:join()
39 assert(h.status == "done" and s == true and r == "bye!")
40end
diff --git a/unit_tests/scripts/coro/yielding_in_non_coro_errors.lua b/unit_tests/scripts/coro/yielding_in_non_coro_errors.lua
new file mode 100644
index 0000000..fc0c072
--- /dev/null
+++ b/unit_tests/scripts/coro/yielding_in_non_coro_errors.lua
@@ -0,0 +1,28 @@
1local lanes = require "lanes"
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9--------------------------------------------------------------------------------------------------
10-- TEST: if we start a non-coroutine lane with a yielding function, we should get an error, right?
11--------------------------------------------------------------------------------------------------
12local fun_g = lanes.gen("*", { name = 'auto' }, utils.yield_one_by_one)
13local h = fun_g("hello", "world", "!")
14local err, status, stack = h:join()
15PRINT(err, status, stack)
16-- the actual error message is not the same for Lua 5.1
17-- of course, it also has to be different for LuaJIT as well
18-- also, LuaJIT prepends a file:line to the actual error message, which Lua5.1 does not.
19local msgs = {
20 ["Lua 5.1"] = jit and "attempt to yield across C-call boundary" or "attempt to yield across metamethod/C-call boundary",
21 ["Lua 5.2"] = "attempt to yield from outside a coroutine",
22 ["Lua 5.3"] = "attempt to yield from outside a coroutine",
23 ["Lua 5.4"] = "attempt to yield from outside a coroutine",
24 ["Lua 5.5"] = "attempt to yield from outside a coroutine"
25}
26local expected_msg = msgs[_VERSION]
27PRINT("expected_msg = " .. expected_msg)
28assert(err == nil and string.find(status, expected_msg, 1, true) and stack == nil, "status = " .. status)
diff --git a/unit_tests/scripts/lane/body_is_a_c_function.lua b/unit_tests/scripts/lane/body_is_a_c_function.lua
new file mode 100644
index 0000000..d8d329f
--- /dev/null
+++ b/unit_tests/scripts/lane/body_is_a_c_function.lua
@@ -0,0 +1,28 @@
1local lanes = require "lanes".configure()
2
3-- ##################################################################################################
4-- ##################################################################################################
5-- ##################################################################################################
6
7-- we can create a generator where the lane body is a C function
8do
9 local b, g = pcall(lanes.gen, "*", print)
10 assert(b == true and type(g) == "function")
11 -- we can start the lane
12 local b, h = pcall(g, "hello")
13 -- the lane runs normally
14 h:join()
15 assert(h.status == "done")
16end
17
18-- we can create a generator where the lane body is a C function that raises an error
19do
20 local b, g = pcall(lanes.gen, "*", error)
21 assert(b == true and type(g) == "function")
22 -- we can start the lane
23 local b, h = pcall(g, "this is an error")
24 -- this provides the error that occurred in the lane
25 local s, e, t = h:join()
26 assert(h.status == "error")
27 assert(s == nil and e == "this is an error" and t == nil)
28end
diff --git a/unit_tests/scripts/lane/cooperative_shutdown.lua b/unit_tests/scripts/lane/cooperative_shutdown.lua
index 756e33c..0a0943e 100644
--- a/unit_tests/scripts/lane/cooperative_shutdown.lua
+++ b/unit_tests/scripts/lane/cooperative_shutdown.lua
@@ -1,10 +1,10 @@
1local lanes = require "lanes" 1local lanes = require "lanes".configure{on_state_create = require "fixture".on_state_create}
2 2
3-- launch lanes that cooperate properly with cancellation request 3-- launch lanes that cooperate properly with cancellation request
4 4
5local lane1 = function() 5local lane1 = function()
6 lane_threadname("lane1") 6 lane_threadname("lane1")
7 -- loop breaks on cancellation request 7 -- loop breaks on soft cancellation request
8 repeat 8 repeat
9 lanes.sleep(0) 9 lanes.sleep(0)
10 until cancel_test() 10 until cancel_test()
@@ -23,7 +23,6 @@ end
23local lane3 = function() 23local lane3 = function()
24 lane_threadname("lane3") 24 lane_threadname("lane3")
25 -- this one cooperates too, because of the hook cancellation modes that Lanes will be using 25 -- this one cooperates too, because of the hook cancellation modes that Lanes will be using
26 -- but not with LuaJIT, because the function is compiled, and we don't call anyone, so no hook triggers
27 local fixture = require "fixture" 26 local fixture = require "fixture"
28 repeat until fixture.give_me_back(false) 27 repeat until fixture.give_me_back(false)
29end 28end
@@ -43,7 +42,14 @@ local h2 = g2(linda)
43 42
44local h3 = g3() 43local h3 = g3()
45 44
46-- wait until they are both started 45lanes.sleep(0.1)
47repeat until h1.status == "running" and h2.status == "waiting" and h3.status == "running" 46
47local is_running = function(lane_h)
48 local status = lane_h.status
49 return status == "running" or status == "waiting"
50end
51
52-- wait until they are all started
53repeat until is_running(h1) and is_running(h2) and is_running(h3)
48 54
49-- let the script terminate, Lanes should not crash at shutdown 55-- let the script terminate, Lanes should not crash at shutdown
diff --git a/unit_tests/scripts/lane/tasking_cancelling.lua b/unit_tests/scripts/lane/tasking_cancelling.lua
index 873140e..d153ffa 100644
--- a/unit_tests/scripts/lane/tasking_cancelling.lua
+++ b/unit_tests/scripts/lane/tasking_cancelling.lua
@@ -1,4 +1,7 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() 1local require_fixture_result_1, require_fixture_result_2 = require "fixture"
2local fixture = assert(require_fixture_result_1)
3
4local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{on_state_create = fixture.on_state_create}.configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) 5print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1 6local lanes = require_lanes_result_1
4 7
@@ -15,6 +18,28 @@ local lanes_linda = assert(lanes.linda)
15-- ################################################################################################## 18-- ##################################################################################################
16-- ################################################################################################## 19-- ##################################################################################################
17 20
21-- cancellation of cooperating lanes
22local cooperative = function()
23 local fixture = assert(require "fixture")
24 local which_cancel
25 repeat
26 fixture.block_for(0.2)
27 which_cancel = cancel_test()
28 until which_cancel
29 return which_cancel
30end
31-- soft and hard are behaviorally equivalent when no blocking linda operation is involved
32local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)()
33local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate
34assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b))
35assert(cooperative_lane_soft[1] == "soft") -- return value of the lane body is the value returned by cancel_test()
36local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)()
37local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate
38assert(a == false and b == "timeout", "got " .. tostring(c) .. " " .. tostring(d))
39assert(cooperative_lane_hard[1] == "hard") -- return value of the lane body is the value returned by cancel_test()
40
41-- ##################################################################################################
42
18-- cancellation of lanes waiting on a linda 43-- cancellation of lanes waiting on a linda
19local limited = lanes_linda{name = "limited"} 44local limited = lanes_linda{name = "limited"}
20assert.fails(function() limited:limit("key", -1) end) 45assert.fails(function() limited:limit("key", -1) end)
diff --git a/unit_tests/scripts/lane/tasking_join_test.lua b/unit_tests/scripts/lane/tasking_join_test.lua
index 2fbce6c..495a709 100644
--- a/unit_tests/scripts/lane/tasking_join_test.lua
+++ b/unit_tests/scripts/lane/tasking_join_test.lua
@@ -30,14 +30,19 @@ end
30 30
31PRINT("---=== :join test ===---", "\n\n") 31PRINT("---=== :join test ===---", "\n\n")
32 32
33-- a lane body that returns nothing is successfully joined with true, nil
34local r, ret = lanes_gen(function() end)():join()
35assert(r == true and ret == nil)
36
33-- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil 37-- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil
34-- (unless [1..n] has been read earlier, in which case it would seemingly 38-- (unless [1..n] has been read earlier, in which case it would seemingly
35-- work). 39-- work).
36 40
37local S= lanes_gen("table", { name = 'auto', gc_cb = gc_cb }, 41local S = lanes_gen("table", { name = 'auto', gc_cb = gc_cb },
38 function(arg) 42 function(arg)
39 lane_threadname "join test lane" 43 lane_threadname "join test lane"
40 set_finalizer(function() end) 44 set_finalizer(function() end)
45 -- take arg table, reverse its contents in aux, then return the unpacked result
41 local aux= {} 46 local aux= {}
42 for i, v in ipairs(arg) do 47 for i, v in ipairs(arg) do
43 table.insert(aux, 1, v) 48 table.insert(aux, 1, v)
@@ -46,15 +51,16 @@ local S= lanes_gen("table", { name = 'auto', gc_cb = gc_cb },
46 return (unpack or table.unpack)(aux) 51 return (unpack or table.unpack)(aux)
47end) 52end)
48 53
49h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values 54local h = S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values
50-- wait a bit so that the lane has a chance to set its debug name 55-- wait a bit so that the lane has a chance to set its debug name
51SLEEP(0.5) 56SLEEP(0.5)
52print("joining with '" .. h:get_threadname() .. "'") 57print("joining with '" .. h:get_threadname() .. "'")
53local a,b,c,d= h:join() 58local r, a, b, c, d = h:join()
54if h.status == "error" then 59if h.status == "error" then
55 print(h:get_threadname(), "error: " , a, b, c, d) 60 print(h:get_threadname(), "error: " , r, a, b, c, d)
56else 61else
57 print(h:get_threadname(), a,b,c,d) 62 print(h:get_threadname(), r, a, b, c, d)
63 assert(r==true, "r == " .. tostring(r))
58 assert(a==14, "a == " .. tostring(a)) 64 assert(a==14, "a == " .. tostring(a))
59 assert(b==13, "b == " .. tostring(b)) 65 assert(b==13, "b == " .. tostring(b))
60 assert(c==12, "c == " .. tostring(c)) 66 assert(c==12, "c == " .. tostring(c))
diff --git a/unit_tests/scripts/lane/tasking_send_receive_code.lua b/unit_tests/scripts/lane/tasking_send_receive_code.lua
index cb3663f..fdc2602 100644
--- a/unit_tests/scripts/lane/tasking_send_receive_code.lua
+++ b/unit_tests/scripts/lane/tasking_send_receive_code.lua
@@ -53,28 +53,26 @@ local function chunk2(linda)
53 assert(info.linedefined == 32, "bad linedefined") -- start of 'chunk2' 53 assert(info.linedefined == 32, "bad linedefined") -- start of 'chunk2'
54 assert(config.strip_functions and info.currentline==-1 or info.currentline > info.linedefined, "bad currentline") -- line of 'debug.getinfo' 54 assert(config.strip_functions and info.currentline==-1 or info.currentline > info.linedefined, "bad currentline") -- line of 'debug.getinfo'
55 assert(info.lastlinedefined > info.currentline, "bad lastlinedefined") -- end of 'chunk2' 55 assert(info.lastlinedefined > info.currentline, "bad lastlinedefined") -- end of 'chunk2'
56 local k,func= linda:receive("down") 56 assert(linda:count("down") == 2, "bad linda contents") -- function, "ok"
57 assert(type(func)=="function", "not a function") 57 local k,func,str= linda:receive_batched("down", 2)
58 assert(k=="down") 58 assert(k=="down")
59 assert(type(func)=="function", "not a function")
60 assert(str=="ok", "bad receive result: " .. tostring(k) .. " -> ".. tostring(str))
61 assert(linda:count("down") == 0, "bad linda contents") -- nothing
59 62
60 func(linda) 63 func(linda)
61
62 local k,str= linda:receive("down")
63 assert(str=="ok", "bad receive result")
64
65 linda:send("up", function() return ":)" end, "ok2") 64 linda:send("up", function() return ":)" end, "ok2")
66end 65end
67 66
68local linda = lanes_linda{name = "auto"} 67local linda = lanes_linda{name = "auto"}
69local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch 68local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch
70linda:send("down", function(linda) linda:send("up", "ready!") end, 69linda:send("down", function(linda) linda:send("up", "ready!") end, "ok")
71 "ok")
72-- wait to see if the tiny function gets executed 70-- wait to see if the tiny function gets executed
73-- 71--
74local k,s= linda:receive(1, "up") 72local k,s= linda:receive(1, "up")
75if t2.status == "error" then 73if t2.status == "error" then
76 PRINT("t2 error: " , t2:join()) 74 local n,err,s = t2:join()
77 assert(false) 75 assert(false, "t2 error: " .. err)
78end 76end
79PRINT(s) 77PRINT(s)
80assert(s=="ready!", s .. " is not 'ready!'") 78assert(s=="ready!", s .. " is not 'ready!'")
diff --git a/unit_tests/scripts/lane/uncooperative_shutdown.lua b/unit_tests/scripts/lane/uncooperative_shutdown.lua
index 89e1ff8..eb89ed3 100644
--- a/unit_tests/scripts/lane/uncooperative_shutdown.lua
+++ b/unit_tests/scripts/lane/uncooperative_shutdown.lua
@@ -8,7 +8,7 @@ local lanes = require "lanes".configure{shutdown_timeout = 0.001, on_state_creat
8-- launch lanes that blocks forever 8-- launch lanes that blocks forever
9local lane = function() 9local lane = function()
10 local fixture = require "fixture" 10 local fixture = require "fixture"
11 fixture.sleep_for() 11 fixture.block_for()
12end 12end
13 13
14-- the generator 14-- the generator
diff --git a/unit_tests/scripts/linda/send_receive_func_and_string.lua b/unit_tests/scripts/linda/send_receive_func_and_string.lua
new file mode 100644
index 0000000..188cfcd
--- /dev/null
+++ b/unit_tests/scripts/linda/send_receive_func_and_string.lua
@@ -0,0 +1,13 @@
1local lanes = require "lanes"
2
3-- a newly created linda doesn't contain anything
4local l = lanes.linda()
5
6-- send a function and a string, make sure that's what we read back
7l:send("k", function() end, "str")
8local c = l:count("k")
9assert(c == 2, "got " .. c)
10local k, v1, v2 = l:receive_batched("k", 2)
11local tv1, tv2 = type(v1), type(v2)
12assert(k == "k" and tv1 == "function" and tv2 == "string", "got " .. tv1 .. " " .. tv2)
13assert(l:count("k") == 0)
diff --git a/unit_tests/scripts/linda/wake_period.lua b/unit_tests/scripts/linda/wake_period.lua
index e4a900d..d2dccc3 100644
--- a/unit_tests/scripts/linda/wake_period.lua
+++ b/unit_tests/scripts/linda/wake_period.lua
@@ -6,7 +6,7 @@ local lanes = require_lanes_result_1
6local body = function(linda_) 6local body = function(linda_)
7 -- a blocking read that lasts longer than the tested wake_period values 7 -- a blocking read that lasts longer than the tested wake_period values
8 linda_:receive(2, "empty_slot") 8 linda_:receive(2, "empty_slot")
9 return true 9 return "done"
10end 10end
11 11
12-- if we don't cancel the lane, we should wait the whole duration 12-- if we don't cancel the lane, we should wait the whole duration
@@ -22,7 +22,8 @@ local function check_wake_duration(linda_, expected_, do_cancel_)
22 assert(result == false and reason == 'timeout', "unexpected cancel result") 22 assert(result == false and reason == 'timeout', "unexpected cancel result")
23 end 23 end
24 -- this should wait until the linda wakes by itself before the actual receive timeout and sees the cancel request 24 -- this should wait until the linda wakes by itself before the actual receive timeout and sees the cancel request
25 h:join() 25 local r, ret = h:join()
26 assert(r == true and ret == "done")
26 local t1 = lanes.now_secs() 27 local t1 = lanes.now_secs()
27 local delta = t1 - t0 28 local delta = t1 - t0
28 -- the linda should check for cancellation at about the expected period, not earlier 29 -- the linda should check for cancellation at about the expected period, not earlier
diff --git a/unit_tests/scripts/misc/deeptest.lua b/unit_tests/scripts/misc/deeptest.lua
new file mode 100644
index 0000000..542c35b
--- /dev/null
+++ b/unit_tests/scripts/misc/deeptest.lua
@@ -0,0 +1,161 @@
1local fixture = require "fixture"
2local lanes = require("lanes").configure{on_state_create = fixture.on_state_create}
3local l = lanes.linda{name = "my linda"}
4
5local table_unpack = table.unpack or unpack -- Lua 5.1 support
6
7-- we will transfer userdata created by this module, so we need to make Lanes aware of it
8local dt = lanes.require "deep_userdata_example"
9
10-- set DEEP to any non-false value to run the Deep Userdata tests. "gc" selects a special test for debug purposes
11DEEP = DEEP or true
12-- set CLONABLE to any non-false value to run the Clonable Userdata tests
13CLONABLE = CLONABLE or true
14
15-- lua 5.1->5.2 support a single table uservalue
16-- lua 5.3->5.4 supports an arbitrary type uservalue
17local test_uvtype = (_VERSION == "Lua 5.4") and "function" or (_VERSION == "Lua 5.3") and "string" or "table"
18-- lua 5.4 supports multiple uservalues
19local nupvals = _VERSION == "Lua 5.4" and 3 or 1
20
21local makeUserValue = function( obj_)
22 if test_uvtype == "table" then
23 return {"some uservalue"}
24 elseif test_uvtype == "string" then
25 return "some uservalue"
26 elseif test_uvtype == "function" then
27 -- a function that pull the userdata as upvalue
28 local f = function()
29 return "-> '" .. tostring( obj_) .. "'"
30 end
31 return f
32 end
33end
34
35local printDeep = function( prefix_, obj_, t_)
36 print( prefix_, obj_)
37 for uvi = 1, nupvals do
38 local uservalue = obj_:getuv(uvi)
39 print ("uv #" .. uvi, type( uservalue), uservalue, type(uservalue) == "function" and uservalue() or "")
40 end
41 if t_ then
42 local count = 0
43 for k, v in ipairs( t_) do
44 print( "t["..tostring(k).."]", v)
45 count = count + 1
46 end
47 -- we should have only 2 indexed entries with the same value
48 assert(count == 2 and t_[1] == t_[2])
49 end
50 print()
51end
52
53local performTest = function( obj_)
54 -- setup the userdata with some value and a uservalue
55 obj_:set( 666)
56 obj_:setuv( 1, makeUserValue( obj_))
57 if nupvals > 1 then
58 -- keep uv #2 as nil
59 obj_:setuv( 3, "ENDUV")
60 end
61
62 local t =
63 {
64 -- two indices with an identical value: we should also have identical values on the other side (even if not the same as the original ones when they are clonables)
65 obj_,
66 obj_,
67 -- this one won't transfer because we don't support full uservalue as keys
68 [obj_] = "val"
69 }
70
71 -- read back the contents of the object
72 printDeep( "immediate:", obj_, t)
73
74 -- send the object in a linda, get it back out, read the contents
75 l:set( "key", obj_, t)
76 -- when obj_ is a deep userdata, out is the same userdata as obj_ (not another one pointing on the same deep memory block) because of an internal cache table [deep*] -> proxy)
77 -- when obj_ is a clonable userdata, we get a different clone everytime we cross a linda or lane barrier
78 local _n, _val1, _val2 = l:get( "key", 2)
79 assert(_n == (_val2 and 2 or 1))
80 printDeep( "out of linda:", _val1, _val2)
81
82 -- send the object in a lane through argument passing, the lane body returns it as return value, read the contents
83 local g = lanes.gen(
84 "package"
85 , {
86 name = 'auto',
87 required = { "deep_userdata_example"} -- we will transfer userdata created by this module, so we need to make this lane aware of it
88 }
89 , function( arg_, t_)
90 -- read contents inside lane: arg_ and t_ by argument
91 printDeep( "in lane, as arguments:", arg_, t_)
92 -- read contents inside lane: obj_ and t by upvalue
93 printDeep( "in lane, as upvalues:", obj_, t)
94 -- read contents inside lane: in linda
95 local _n, _val1, _val2 = l:get( "key", 2)
96 assert(_n == (_val2 and 2 or 1))
97 printDeep( "in lane, from linda:", _val1, _val2)
98 return arg_, t_
99 end
100 )
101 h = g( obj_, t)
102 -- when obj_ is a deep userdata, from_lane is the same userdata as obj_ (not another one pointing on the same deep memory block) because of an internal cache table [deep*] -> proxy)
103 -- when obj_ is a clonable userdata, we get a different clone everytime we cross a linda or lane barrier
104 printDeep( "from lane:", h[1], h[2])
105end
106
107if DEEP then
108 print "================================================================"
109 print "DEEP"
110 local d = dt.new_deep(nupvals)
111 if type(DEEP) == "string" then
112 local gc_tests = {
113 thrasher = function(repeat_, size_)
114 print "in thrasher"
115 -- result is a table of repeat_ tables, each containing size_ entries
116 local result = {}
117 for i = 1, repeat_ do
118 local batch_values = {}
119 for j = 1, size_ do
120 table.insert(batch_values, j)
121 end
122 table.insert(result, batch_values)
123 end
124 print "thrasher done"
125 return result
126 end,
127 stack_abuser = function(repeat_, size_)
128 print "in stack_abuser"
129 for i = 1, repeat_ do
130 local batch_values = {}
131 for j = 1, size_ do
132 table.insert(batch_values, j)
133 end
134 -- return size_ values
135 local _ = table_unpack(batch_values)
136 end
137 print "stack_abuser done"
138 return result
139 end
140 }
141 -- have the object call the function from inside one of its functions, to detect if it gets collected from there (while in use!)
142 local testf = gc_tests[DEEP]
143 if testf then
144 local r = d:invoke(gc_tests[DEEP], REPEAT or 10, SIZE or 10)
145 print("invoke -> ", tostring(r))
146 else
147 print("unknown test '" .. DEEP .. "'")
148 end
149 else
150 performTest(d)
151 end
152end
153
154if CLONABLE then
155 print "================================================================"
156 print "CLONABLE"
157 performTest( dt.new_clonable(nupvals))
158end
159
160print "================================================================"
161print "TEST OK" \ No newline at end of file