diff options
Diffstat (limited to 'unit_tests/scripts')
26 files changed, 745 insertions, 196 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 |
69 | end | 69 | end |
70 | 70 | ||
71 | -- a function that yields back what got in, one element at a time | ||
72 | local 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!" | ||
86 | end | ||
87 | |||
71 | return { | 88 | return { |
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 @@ | |||
1 | local utils = require "_utils" | ||
2 | |||
3 | -- expand _utils module with Lua5.4 specific stuff | ||
4 | |||
5 | -- a lane body that yields stuff | ||
6 | utils.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 | ||
28 | end | ||
29 | |||
30 | return 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 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local PRINT = utils.MAKE_PRINT() | ||
8 | |||
9 | if 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") | ||
27 | end | ||
28 | |||
29 | -- a lane coroutine that yields back what got in, one element at a time | ||
30 | local 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!" | ||
41 | end | ||
42 | |||
43 | if 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) | ||
61 | end | ||
62 | |||
63 | -- the generator | ||
64 | local coro_g = lanes.coro("*", {name = "auto"}, yielder) | ||
65 | |||
66 | if 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!") | ||
76 | end | ||
77 | |||
78 | if 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") | ||
97 | end | ||
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 @@ | |||
1 | local fixture = require "fixture" | ||
2 | local lanes = require "lanes".configure{on_state_create = fixture.on_state_create} | ||
3 | |||
4 | local fixture = require "fixture" | ||
5 | lanes.finally(fixture.throwing_finalizer) | ||
6 | |||
7 | local utils = lanes.require "_utils" | ||
8 | local PRINT = utils.MAKE_PRINT() | ||
9 | |||
10 | -------------------------------------------------- | ||
11 | -- TEST: cancelling a suspended Lane should end it | ||
12 | -------------------------------------------------- | ||
13 | if 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) | ||
31 | end | ||
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 @@ | |||
1 | local fixture = require "fixture" | ||
2 | local lanes = require "lanes".configure{on_state_create = fixture.on_state_create} | ||
3 | |||
4 | local fixture = require "fixture" | ||
5 | lanes.finally(fixture.throwing_finalizer) | ||
6 | |||
7 | -- this test is only for Lua 5.4+ | ||
8 | local utils = lanes.require "_utils54" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local out_linda = lanes.linda() | ||
12 | |||
13 | ------------------------------------------------------------------------------ | ||
14 | -- TEST: to-be-closed variables are properly closed when the lane is collected | ||
15 | ------------------------------------------------------------------------------ | ||
16 | if 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 | ||
35 | end | ||
36 | |||
37 | --------------------------------------------------------------------------------------------------- | ||
38 | -- TEST: if a to-be-closed handler takes longer than the join timeout, everything works as expected | ||
39 | --------------------------------------------------------------------------------------------------- | ||
40 | if 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 | ||
64 | end | ||
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) |
39 | end | 39 | end |
40 | 40 | ||
41 | if false then | 41 | if true then |
42 | force_error_test("minimal") | 42 | force_error_test("minimal") |
43 | end | 43 | end |
44 | 44 | ||
45 | if false then | 45 | if true then |
46 | force_error_test("basic") | 46 | force_error_test("basic") |
47 | end | 47 | end |
48 | 48 | ||
49 | if false then | 49 | if true then |
50 | force_error_test("extended") | 50 | force_error_test("extended") |
51 | end | 51 | end |
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 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local PRINT = utils.MAKE_PRINT() | ||
8 | |||
9 | -- the coroutine generator | ||
10 | local 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 | ------------------------------------------------------------------------- | ||
15 | if 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") | ||
28 | end | ||
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 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local PRINT = utils.MAKE_PRINT() | ||
8 | |||
9 | -- the coroutine generator | ||
10 | local 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 | --------------------------------------------------- | ||
15 | if 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)) | ||
24 | end | ||
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 @@ | |||
1 | local fixture = require "fixture" | ||
2 | local lanes = require "lanes".configure{on_state_create = fixture.on_state_create} | ||
3 | |||
4 | local fixture = require "fixture" | ||
5 | lanes.finally(fixture.throwing_finalizer) | ||
6 | |||
7 | -- this test is only for Lua 5.4+ | ||
8 | local utils = lanes.require "_utils54" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local out_linda = lanes.linda() | ||
12 | |||
13 | local 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)) | ||
26 | end | ||
27 | |||
28 | --------------------------------------------------------- | ||
29 | -- TEST: first, try the close mechanism outside of a lane | ||
30 | --------------------------------------------------------- | ||
31 | if true then | ||
32 | assert(type(utils.yielder_with_to_be_closed) == "function") | ||
33 | test_close("base", utils.yielder_with_to_be_closed) | ||
34 | end | ||
35 | |||
36 | --------------------------------------------------------------- | ||
37 | -- TEST: try again with a function obtained through dump/undump | ||
38 | --------------------------------------------------------------- | ||
39 | if 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))) | ||
42 | end | ||
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 @@ | |||
1 | local lanes = require "lanes".configure() | ||
2 | |||
3 | local utils = lanes.require "_utils" | ||
4 | local PRINT = utils.MAKE_PRINT() | ||
5 | |||
6 | -- a lane body that just returns some value | ||
7 | local 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" | ||
13 | end | ||
14 | |||
15 | -- a function that returns some value can run in a coroutine | ||
16 | if 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") | ||
25 | end | ||
26 | |||
27 | -- can't resume a coro after the lane body has returned | ||
28 | if 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") | ||
38 | end | ||
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 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local PRINT = utils.MAKE_PRINT() | ||
8 | |||
9 | -- the coroutine generator | ||
10 | local 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 | ------------------------------------------------------------------------------------------------- | ||
15 | if 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!") | ||
25 | end | ||
26 | |||
27 | --------------------------------------------------------------------------------------------- | ||
28 | -- TEST: we can resume as many times as the lane yields, then read the returned value on join | ||
29 | --------------------------------------------------------------------------------------------- | ||
30 | if 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!") | ||
40 | end | ||
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 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local 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 | -------------------------------------------------------------------------------------------------- | ||
12 | local fun_g = lanes.gen("*", { name = 'auto' }, utils.yield_one_by_one) | ||
13 | local h = fun_g("hello", "world", "!") | ||
14 | local err, status, stack = h:join() | ||
15 | PRINT(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. | ||
19 | local 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 | } | ||
26 | local expected_msg = msgs[_VERSION] | ||
27 | PRINT("expected_msg = " .. expected_msg) | ||
28 | assert(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 @@ | |||
1 | local lanes = require "lanes".configure() | ||
2 | |||
3 | -- ################################################################################################## | ||
4 | -- ################################################################################################## | ||
5 | -- ################################################################################################## | ||
6 | |||
7 | -- we can create a generator where the lane body is a C function | ||
8 | do | ||
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") | ||
16 | end | ||
17 | |||
18 | -- we can create a generator where the lane body is a C function that raises an error | ||
19 | do | ||
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) | ||
28 | end | ||
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 @@ | |||
1 | local lanes = require "lanes" | 1 | local 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 | ||
5 | local lane1 = function() | 5 | local 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 | |||
23 | local lane3 = function() | 23 | local 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) |
29 | end | 28 | end |
@@ -43,7 +42,14 @@ local h2 = g2(linda) | |||
43 | 42 | ||
44 | local h3 = g3() | 43 | local h3 = g3() |
45 | 44 | ||
46 | -- wait until they are both started | 45 | lanes.sleep(0.1) |
47 | repeat until h1.status == "running" and h2.status == "waiting" and h3.status == "running" | 46 | |
47 | local is_running = function(lane_h) | ||
48 | local status = lane_h.status | ||
49 | return status == "running" or status == "waiting" | ||
50 | end | ||
51 | |||
52 | -- wait until they are all started | ||
53 | repeat 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 85600ab..d153ffa 100644 --- a/unit_tests/scripts/lane/tasking_cancelling.lua +++ b/unit_tests/scripts/lane/tasking_cancelling.lua | |||
@@ -1,4 +1,7 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | 1 | local require_fixture_result_1, require_fixture_result_2 = require "fixture" |
2 | local fixture = assert(require_fixture_result_1) | ||
3 | |||
4 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{on_state_create = fixture.on_state_create}.configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | 5 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) |
3 | local lanes = require_lanes_result_1 | 6 | local lanes = require_lanes_result_1 |
4 | 7 | ||
@@ -15,69 +18,34 @@ local lanes_linda = assert(lanes.linda) | |||
15 | -- ################################################################################################## | 18 | -- ################################################################################################## |
16 | -- ################################################################################################## | 19 | -- ################################################################################################## |
17 | 20 | ||
18 | local function task(a, b, c) | 21 | -- cancellation of cooperating lanes |
19 | lane_threadname("task("..a..","..b..","..c..")") | 22 | local cooperative = function() |
20 | --error "111" -- testing error messages | 23 | local fixture = assert(require "fixture") |
21 | assert(hey) | 24 | local which_cancel |
22 | local v=0 | 25 | repeat |
23 | for i=a,b,c do | 26 | fixture.block_for(0.2) |
24 | v= v+i | 27 | which_cancel = cancel_test() |
25 | end | 28 | until which_cancel |
26 | return v, hey | 29 | return which_cancel |
27 | end | ||
28 | |||
29 | local gc_cb = function(name_, status_) | ||
30 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
31 | end | 30 | end |
31 | -- soft and hard are behaviorally equivalent when no blocking linda operation is involved | ||
32 | local cooperative_lane_soft = lanes_gen("*", { name = 'auto' }, cooperative)() | ||
33 | local a, b = cooperative_lane_soft:cancel("soft", 0) -- issue request, do not wait for lane to terminate | ||
34 | assert(a == false and b == "timeout", "got " .. tostring(a) .. " " .. tostring(b)) | ||
35 | assert(cooperative_lane_soft[1] == "soft") -- return value of the lane body is the value returned by cancel_test() | ||
36 | local cooperative_lane_hard = lanes_gen("*", { name = 'auto' }, cooperative)() | ||
37 | local c, d = cooperative_lane_hard:cancel("hard", 0) -- issue request, do not wait for lane to terminate | ||
38 | assert(a == false and b == "timeout", "got " .. tostring(c) .. " " .. tostring(d)) | ||
39 | assert(cooperative_lane_hard[1] == "hard") -- return value of the lane body is the value returned by cancel_test() | ||
32 | 40 | ||
33 | -- ################################################################################################## | 41 | -- ################################################################################################## |
34 | -- ################################################################################################## | ||
35 | -- ################################################################################################## | ||
36 | |||
37 | PRINT("\n\n", "---=== Tasking (cancelling) ===---", "\n\n") | ||
38 | |||
39 | local task_launch2 = lanes_gen("", { name = 'auto', globals={hey=true}, gc_cb = gc_cb }, task) | ||
40 | |||
41 | local N=999999999 | ||
42 | local lane9= task_launch2(1,N,1) -- huuuuuuge... | ||
43 | |||
44 | -- Wait until state changes "pending"->"running" | ||
45 | -- | ||
46 | local st | ||
47 | local t0= os.time() | ||
48 | while os.time()-t0 < 5 do | ||
49 | st= lane9.status | ||
50 | io.stderr:write((i==1) and st.." " or '.') | ||
51 | if st~="pending" then break end | ||
52 | end | ||
53 | PRINT(" "..st) | ||
54 | |||
55 | if st=="error" then | ||
56 | local _= lane9[0] -- propagate the error here | ||
57 | end | ||
58 | if st=="done" then | ||
59 | error("Looping to "..N.." was not long enough (cannot test cancellation)") | ||
60 | end | ||
61 | assert(st=="running", "st == " .. st) | ||
62 | |||
63 | -- when running under luajit, the function is JIT-ed, and the instruction count isn't hit, so we need a different hook | ||
64 | lane9:cancel(jit and "line" or "count", 100) -- 0 timeout, hook triggers cancelslation when reaching the specified count | ||
65 | |||
66 | local t0= os.time() | ||
67 | while os.time()-t0 < 5 do | ||
68 | st= lane9.status | ||
69 | io.stderr:write((i==1) and st.." " or '.') | ||
70 | if st~="running" then break end | ||
71 | end | ||
72 | PRINT(" "..st) | ||
73 | assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'") | ||
74 | 42 | ||
75 | -- cancellation of lanes waiting on a linda | 43 | -- cancellation of lanes waiting on a linda |
76 | local limited = lanes_linda("limited") | 44 | local limited = lanes_linda{name = "limited"} |
77 | assert.fails(function() limited:limit("key", -1) end) | 45 | assert.fails(function() limited:limit("key", -1) end) |
78 | assert.failsnot(function() limited:limit("key", 1) end) | 46 | assert.failsnot(function() limited:limit("key", 1) end) |
79 | -- [[################################################ | 47 | -- [[################################################ |
80 | limited:send("key", "hello") -- saturate linda | 48 | limited:send("key", "hello") -- saturate linda, so that subsequent sends will block |
81 | for k, v in pairs(limited:dump()) do | 49 | for k, v in pairs(limited:dump()) do |
82 | PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v)) | 50 | PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v)) |
83 | end | 51 | end |
@@ -88,11 +56,15 @@ local wait_send = function() | |||
88 | end | 56 | end |
89 | 57 | ||
90 | local wait_send_lane = lanes_gen("*", { name = 'auto' }, wait_send)() | 58 | local wait_send_lane = lanes_gen("*", { name = 'auto' }, wait_send)() |
91 | repeat until wait_send_lane.status == "waiting" | 59 | repeat |
92 | print "wait_send_lane is waiting" | 60 | io.stderr:write('!') |
61 | -- currently mingw64 builds can deadlock if we cancel the lane too early (before the linda blocks, at it causes the linda condvar not to be signalled) | ||
62 | lanes.sleep(0.1) | ||
63 | until wait_send_lane.status == "waiting" | ||
64 | PRINT "wait_send_lane is waiting" | ||
93 | wait_send_lane:cancel() -- hard cancel, 0 timeout | 65 | wait_send_lane:cancel() -- hard cancel, 0 timeout |
94 | repeat until wait_send_lane.status == "cancelled" | 66 | repeat until wait_send_lane.status == "cancelled" |
95 | print "wait_send_lane is cancelled" | 67 | PRINT "wait_send_lane is cancelled" |
96 | --################################################]] | 68 | --################################################]] |
97 | local wait_receive = function() | 69 | local wait_receive = function() |
98 | local k, v | 70 | local k, v |
@@ -101,22 +73,30 @@ local wait_receive = function() | |||
101 | end | 73 | end |
102 | 74 | ||
103 | local wait_receive_lane = lanes_gen("*", { name = 'auto' }, wait_receive)() | 75 | local wait_receive_lane = lanes_gen("*", { name = 'auto' }, wait_receive)() |
104 | repeat until wait_receive_lane.status == "waiting" | 76 | repeat |
105 | print "wait_receive_lane is waiting" | 77 | io.stderr:write('!') |
78 | -- currently mingw64 builds can deadlock if we cancel the lane too early (before the linda blocks, at it causes the linda condvar not to be signalled) | ||
79 | lanes.sleep(0.1) | ||
80 | until wait_receive_lane.status == "waiting" | ||
81 | PRINT "wait_receive_lane is waiting" | ||
106 | wait_receive_lane:cancel() -- hard cancel, 0 timeout | 82 | wait_receive_lane:cancel() -- hard cancel, 0 timeout |
107 | repeat until wait_receive_lane.status == "cancelled" | 83 | repeat until wait_receive_lane.status == "cancelled" |
108 | print "wait_receive_lane is cancelled" | 84 | PRINT "wait_receive_lane is cancelled" |
109 | --################################################]] | 85 | --################################################]] |
110 | local wait_receive_batched = function() | 86 | local wait_receive_batched = function() |
111 | local k, v1, v2 | 87 | local k, v1, v2 |
112 | set_finalizer(function() print("wait_receive_batched", k, v1, v2) end) | 88 | set_finalizer(function() print("wait_receive_batched", k, v1, v2) end) |
113 | k, v1, v2 = limited:receive(limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled | 89 | k, v1, v2 = limited:receive_batched("dummy", 2) -- infinite timeout, returns only when lane is cancelled |
114 | end | 90 | end |
115 | 91 | ||
116 | local wait_receive_batched_lane = lanes_gen("*", { name = 'auto' }, wait_receive_batched)() | 92 | local wait_receive_batched_lane = lanes_gen("*", { name = 'auto' }, wait_receive_batched)() |
117 | repeat until wait_receive_batched_lane.status == "waiting" | 93 | repeat |
118 | print "wait_receive_batched_lane is waiting" | 94 | io.stderr:write('!') |
95 | -- currently mingw64 builds can deadlock if we cancel the lane too early (before the linda blocks, at it causes the linda condvar not to be signalled) | ||
96 | lanes.sleep(0.1) | ||
97 | until wait_receive_batched_lane.status == "waiting" | ||
98 | PRINT "wait_receive_batched_lane is waiting" | ||
119 | wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout | 99 | wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout |
120 | repeat until wait_receive_batched_lane.status == "cancelled" | 100 | repeat until wait_receive_batched_lane.status == "cancelled" |
121 | print "wait_receive_batched_lane is cancelled" | 101 | PRINT "wait_receive_batched_lane is cancelled" |
122 | --################################################]] | 102 | --################################################]] |
diff --git a/unit_tests/scripts/lane/tasking_cancelling_with_hook.lua b/unit_tests/scripts/lane/tasking_cancelling_with_hook.lua new file mode 100644 index 0000000..56b934f --- /dev/null +++ b/unit_tests/scripts/lane/tasking_cancelling_with_hook.lua | |||
@@ -0,0 +1,68 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | -- ################################################################################################## | ||
12 | -- ################################################################################################## | ||
13 | -- ################################################################################################## | ||
14 | |||
15 | local function task(a, b, c) | ||
16 | lane_threadname("task("..a..","..b..","..c..")") | ||
17 | --error "111" -- testing error messages | ||
18 | assert(hey) | ||
19 | local v=0 | ||
20 | for i=a,b,c do | ||
21 | v= v+i | ||
22 | end | ||
23 | return v, hey | ||
24 | end | ||
25 | |||
26 | local gc_cb = function(name_, status_) | ||
27 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
28 | end | ||
29 | |||
30 | -- ################################################################################################## | ||
31 | -- ################################################################################################## | ||
32 | -- ################################################################################################## | ||
33 | |||
34 | local generator = lanes.gen("", { name = 'auto', globals={hey=true}, gc_cb = gc_cb }, task) | ||
35 | |||
36 | local N = 999999999 | ||
37 | local lane_h = generator(1,N,1) -- huuuuuuge... | ||
38 | |||
39 | -- Wait until state changes "pending"->"running" | ||
40 | -- | ||
41 | local st | ||
42 | local t0 = os.time() | ||
43 | while os.time()-t0 < 5 do | ||
44 | st = lane_h.status | ||
45 | io.stderr:write((i==1) and st.." " or '.') | ||
46 | if st~="pending" then break end | ||
47 | end | ||
48 | PRINT(" "..st) | ||
49 | |||
50 | if st == "error" then | ||
51 | local _ = lane_h[0] -- propagate the error here | ||
52 | end | ||
53 | if st == "done" then | ||
54 | error("Looping to "..N.." was not long enough (cannot test cancellation)") | ||
55 | end | ||
56 | assert(st == "running", "st == " .. st) | ||
57 | |||
58 | -- when running under luajit, the function is JIT-ed, and the instruction count isn't hit, so we need a different hook | ||
59 | lane_h:cancel(jit and "line" or "count", 100) -- 0 timeout, hook triggers cancelslation when reaching the specified count | ||
60 | |||
61 | local t0 = os.time() | ||
62 | while os.time()-t0 < 5 do | ||
63 | st = lane_h.status | ||
64 | io.stderr:write((i==1) and st.." " or '.') | ||
65 | if st~="running" then break end | ||
66 | end | ||
67 | PRINT(" "..st) | ||
68 | assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'") | ||
diff --git a/unit_tests/scripts/lane/tasking_comms_criss_cross.lua b/unit_tests/scripts/lane/tasking_comms_criss_cross.lua index 497e81d..610da8b 100644 --- a/unit_tests/scripts/lane/tasking_comms_criss_cross.lua +++ b/unit_tests/scripts/lane/tasking_comms_criss_cross.lua | |||
@@ -42,7 +42,7 @@ local tc = lanes_gen("io", { name = 'auto', gc_cb = gc_cb }, | |||
42 | end | 42 | end |
43 | ) | 43 | ) |
44 | 44 | ||
45 | local linda= lanes_linda("criss cross") | 45 | local linda= lanes_linda{name = "criss cross"} |
46 | 46 | ||
47 | local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms | 47 | local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms |
48 | 48 | ||
diff --git a/unit_tests/scripts/lane/tasking_communications.lua b/unit_tests/scripts/lane/tasking_communications.lua index 1fd43b0..01842b4 100644 --- a/unit_tests/scripts/lane/tasking_communications.lua +++ b/unit_tests/scripts/lane/tasking_communications.lua | |||
@@ -72,7 +72,7 @@ local chunk= function(linda) | |||
72 | WR("chunk ", "Lane ends!\n") | 72 | WR("chunk ", "Lane ends!\n") |
73 | end | 73 | end |
74 | 74 | ||
75 | local linda = lanes_linda("communications") | 75 | local linda = lanes_linda{name = "communications"} |
76 | assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications") | 76 | assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications") |
77 | -- | 77 | -- |
78 | -- ["->"] master -> slave | 78 | -- ["->"] master -> slave |
@@ -90,7 +90,7 @@ local b,x,y,z,w = linda:get("<->", 4) | |||
90 | assert(b == 3 and x == "x" and y == "y" and z == "z" and w == nil) | 90 | assert(b == 3 and x == "x" and y == "y" and z == "z" and w == nil) |
91 | local k, x = linda:receive("<->") | 91 | local k, x = linda:receive("<->") |
92 | assert(k == "<->" and x == "x") | 92 | assert(k == "<->" and x == "x") |
93 | local k,y,z = linda:receive(linda.batched, "<->", 2) | 93 | local k,y,z = linda:receive_batched("<->", 2) |
94 | assert(k == "<->" and y == "y" and z == "z") | 94 | assert(k == "<->" and y == "y" and z == "z") |
95 | linda:set("<->") | 95 | linda:set("<->") |
96 | local b,x,y,z,w = linda:get("<->", 4) | 96 | local b,x,y,z,w = linda:get("<->", 4) |
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 | ||
31 | PRINT("---=== :join test ===---", "\n\n") | 31 | PRINT("---=== :join test ===---", "\n\n") |
32 | 32 | ||
33 | -- a lane body that returns nothing is successfully joined with true, nil | ||
34 | local r, ret = lanes_gen(function() end)():join() | ||
35 | assert(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 | ||
37 | local S= lanes_gen("table", { name = 'auto', gc_cb = gc_cb }, | 41 | local 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) |
47 | end) | 52 | end) |
48 | 53 | ||
49 | h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values | 54 | local 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 |
51 | SLEEP(0.5) | 56 | SLEEP(0.5) |
52 | print("joining with '" .. h:get_threadname() .. "'") | 57 | print("joining with '" .. h:get_threadname() .. "'") |
53 | local a,b,c,d= h:join() | 58 | local r, a, b, c, d = h:join() |
54 | if h.status == "error" then | 59 | if 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) |
56 | else | 61 | else |
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 e329a88..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") |
66 | end | 65 | end |
67 | 66 | ||
68 | local linda = lanes_linda("auto") | 67 | local linda = lanes_linda{name = "auto"} |
69 | local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch | 68 | local t2= lanes_gen("debug,package,string,io", { name = 'auto', gc_cb = gc_cb }, chunk2)(linda) -- prepare & launch |
70 | linda:send("down", function(linda) linda:send("up", "ready!") end, | 69 | linda: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 | -- |
74 | local k,s= linda:receive(1, "up") | 72 | local k,s= linda:receive(1, "up") |
75 | if t2.status == "error" then | 73 | if 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) |
78 | end | 76 | end |
79 | PRINT(s) | 77 | PRINT(s) |
80 | assert(s=="ready!", s .. " is not 'ready!'") | 78 | assert(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 |
9 | local lane = function() | 9 | local lane = function() |
10 | local fixture = require "fixture" | 10 | local fixture = require "fixture" |
11 | fixture.sleep_for() | 11 | fixture.block_for() |
12 | end | 12 | end |
13 | 13 | ||
14 | -- the generator | 14 | -- the generator |
diff --git a/unit_tests/scripts/linda/multiple_keepers.lua b/unit_tests/scripts/linda/multiple_keepers.lua index 8733087..267d874 100644 --- a/unit_tests/scripts/linda/multiple_keepers.lua +++ b/unit_tests/scripts/linda/multiple_keepers.lua | |||
@@ -2,9 +2,9 @@ | |||
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500} | 2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500} |
3 | local lanes = require_lanes_result_1 | 3 | local lanes = require_lanes_result_1 |
4 | 4 | ||
5 | local a = lanes.linda("A", 1) | 5 | local a = lanes.linda{name = "A", group = 1} |
6 | local b = lanes.linda("B", 2) | 6 | local b = lanes.linda{name = "B", group = 2} |
7 | local c = lanes.linda("C", 3) | 7 | local c = lanes.linda{name = "C", group = 3} |
8 | 8 | ||
9 | -- store each linda in the other 2 | 9 | -- store each linda in the other 2 |
10 | do | 10 | do |
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 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | -- a newly created linda doesn't contain anything | ||
4 | local l = lanes.linda() | ||
5 | |||
6 | -- send a function and a string, make sure that's what we read back | ||
7 | l:send("k", function() end, "str") | ||
8 | local c = l:count("k") | ||
9 | assert(c == 2, "got " .. c) | ||
10 | local k, v1, v2 = l:receive_batched("k", 2) | ||
11 | local tv1, tv2 = type(v1), type(v2) | ||
12 | assert(k == "k" and tv1 == "function" and tv2 == "string", "got " .. tv1 .. " " .. tv2) | ||
13 | assert(l:count("k") == 0) | ||
diff --git a/unit_tests/scripts/linda/send_registered_userdata.lua b/unit_tests/scripts/linda/send_registered_userdata.lua index 2c0195a..90c05c9 100644 --- a/unit_tests/scripts/linda/send_registered_userdata.lua +++ b/unit_tests/scripts/linda/send_registered_userdata.lua | |||
@@ -1,5 +1,5 @@ | |||
1 | local lanes = require 'lanes'.configure{with_timers = false} | 1 | local lanes = require 'lanes'.configure{with_timers = false} |
2 | local l = lanes.linda'gleh' | 2 | local l = lanes.linda{name = 'gleh'} |
3 | l:set('yo', io.stdin) | 3 | l:set('yo', io.stdin) |
4 | local n, stdin_out = l:get('yo') | 4 | local n, stdin_out = l:get('yo') |
5 | assert(n == 1 and stdin_out == io.stdin, tostring(stdin_out) .. " ~= " .. tostring(io.stdin)) | 5 | assert(n == 1 and stdin_out == io.stdin, tostring(stdin_out) .. " ~= " .. tostring(io.stdin)) |
diff --git a/unit_tests/scripts/linda/wake_period.lua b/unit_tests/scripts/linda/wake_period.lua new file mode 100644 index 0000000..d2dccc3 --- /dev/null +++ b/unit_tests/scripts/linda/wake_period.lua | |||
@@ -0,0 +1,42 @@ | |||
1 | -- default wake period is 0.5 seconds | ||
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{linda_wake_period = 0.5} | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | -- a lane that performs a blocking operation for 2 seconds | ||
6 | local body = function(linda_) | ||
7 | -- a blocking read that lasts longer than the tested wake_period values | ||
8 | linda_:receive(2, "empty_slot") | ||
9 | return "done" | ||
10 | end | ||
11 | |||
12 | -- if we don't cancel the lane, we should wait the whole duration | ||
13 | local function check_wake_duration(linda_, expected_, do_cancel_) | ||
14 | local h = lanes.gen(body)(linda_) | ||
15 | -- wait until the linda is blocked | ||
16 | repeat until h.status == "waiting" | ||
17 | local t0 = lanes.now_secs() | ||
18 | -- soft cancel, no timeout, no waking | ||
19 | if do_cancel_ then | ||
20 | local result, reason = h:cancel('soft', 0, false) | ||
21 | -- should say there was a timeout, since the lane didn't actually cancel (normal since we did not wake it) | ||
22 | assert(result == false and reason == 'timeout', "unexpected cancel result") | ||
23 | end | ||
24 | -- this should wait until the linda wakes by itself before the actual receive timeout and sees the cancel request | ||
25 | local r, ret = h:join() | ||
26 | assert(r == true and ret == "done") | ||
27 | local t1 = lanes.now_secs() | ||
28 | local delta = t1 - t0 | ||
29 | -- the linda should check for cancellation at about the expected period, not earlier | ||
30 | assert(delta >= expected_, tostring(linda_) .. " woke too early:" .. delta) | ||
31 | -- the lane shouldn't terminate too long after cancellation was processed | ||
32 | assert(delta <= expected_ * 1.1, tostring(linda_) .. " woke too late: " .. delta) | ||
33 | end | ||
34 | |||
35 | -- legacy behavior: linda waits until operation timeout | ||
36 | check_wake_duration(lanes.linda{name = "A", wake_period = 'never'}, 2, true) | ||
37 | -- early wake behavior: linda wakes after the expected time, sees a cancellation requests, and aborts the operation early | ||
38 | check_wake_duration(lanes.linda{name = "B", wake_period = 0.25}, 0.25, true) | ||
39 | check_wake_duration(lanes.linda{name = "C"}, 0.5, true) -- wake_period defaults to 0.5 (see above) | ||
40 | check_wake_duration(lanes.linda{name = "D", wake_period = 1}, 1, true) | ||
41 | -- even when there is a wake_period, the operation should reach full timeout if not cancelled early | ||
42 | check_wake_duration(lanes.linda{name = "E", wake_period = 0.1}, 2, false) | ||
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 @@ | |||
1 | local fixture = require "fixture" | ||
2 | local lanes = require("lanes").configure{on_state_create = fixture.on_state_create} | ||
3 | local l = lanes.linda{name = "my linda"} | ||
4 | |||
5 | local 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 | ||
8 | local 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 | ||
11 | DEEP = DEEP or true | ||
12 | -- set CLONABLE to any non-false value to run the Clonable Userdata tests | ||
13 | CLONABLE = 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 | ||
17 | local test_uvtype = (_VERSION == "Lua 5.4") and "function" or (_VERSION == "Lua 5.3") and "string" or "table" | ||
18 | -- lua 5.4 supports multiple uservalues | ||
19 | local nupvals = _VERSION == "Lua 5.4" and 3 or 1 | ||
20 | |||
21 | local 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 | ||
33 | end | ||
34 | |||
35 | local 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() | ||
51 | end | ||
52 | |||
53 | local 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]) | ||
105 | end | ||
106 | |||
107 | if 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 | ||
152 | end | ||
153 | |||
154 | if CLONABLE then | ||
155 | print "================================================================" | ||
156 | print "CLONABLE" | ||
157 | performTest( dt.new_clonable(nupvals)) | ||
158 | end | ||
159 | |||
160 | print "================================================================" | ||
161 | print "TEST OK" \ No newline at end of file | ||