From b87867e69e94d9784468a126f21c721446f080de Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 11 Sep 2021 11:48:58 +0900 Subject: feat(lua): add proper support of luv threads --- test/functional/lua/thread_spec.lua | 403 ++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 test/functional/lua/thread_spec.lua (limited to 'test/functional/lua/thread_spec.lua') diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua new file mode 100644 index 0000000000..a60f57a860 --- /dev/null +++ b/test/functional/lua/thread_spec.lua @@ -0,0 +1,403 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local assert_alive = helpers.assert_alive +local clear = helpers.clear +local feed = helpers.feed +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local next_msg = helpers.next_msg +local NIL = helpers.NIL +local pcall_err = helpers.pcall_err + +describe('thread', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [5] = {bold = true}, + }) + end) + + it('entry func is executed in protected mode', function() + local code = [[ + local thread = vim.loop.new_thread(function() + error('Error in thread entry func') + end) + vim.loop.thread_join(thread) + ]] + exec_lua(code) + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv thread:} | + {3:[string ""]:2: Error in thread entry func} | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('') + assert_alive() + end) + + it('callback is executed in protected mode', function() + local code = [[ + local thread = vim.loop.new_thread(function() + local timer = vim.loop.new_timer() + local function ontimeout() + timer:stop() + timer:close() + error('Error in thread callback') + end + timer:start(10, 0, ontimeout) + vim.loop.run() + end) + vim.loop.thread_join(thread) + ]] + exec_lua(code) + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv callback, thread:} | + {3:[string ""]:6: Error in thread callback} | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('') + assert_alive() + end) + + describe('print', function() + it('work', function() + local code = [[ + local thread = vim.loop.new_thread(function() + print('print in thread') + end) + vim.loop.thread_join(thread) + ]] + exec_lua(code) + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + print in thread | + ]]) + + end) + end) + + describe('vim.*', function() + before_each(function() + clear() + local code = [[ + Thread_Test = {} + + Thread_Test.entry_func = function(async, entry_str, args) + local decoded_args = vim.mpack.decode(args) + assert(loadstring(entry_str))(async, decoded_args) + end + + function Thread_Test:do_test() + local async + local on_async = self.on_async + async = vim.loop.new_async(function(ret) + on_async(ret) + async:close() + end) + local thread = + vim.loop.new_thread(self.entry_func, async, self.entry_str, self.args) + vim.loop.thread_join(thread) + end + + Thread_Test.new = function(entry, on_async, ...) + self = {} + setmetatable(self, {__index = Thread_Test}) + self.args = vim.mpack.encode({...}) + self.entry_str = string.dump(entry) + self.on_async = on_async + return self + end + ]] + exec_lua(code) + end) + + it('is_thread', function() + local code = [[ + local entry = function(async) + async:send(vim.is_thread()) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', {true}}, next_msg()) + end) + + it('loop', function() + local code = [[ + local entry = function(async) + async:send(vim.loop.version()) + end + local on_async = function(ret) + vim.rpcnotify(1, ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + exec_lua(code) + + local msg = next_msg() + eq(msg[1], 'notification') + assert(tonumber(msg[2]) >= 72961) + end) + + it('mpack', function() + local code = [[ + local entry = function(async) + async:send(vim.mpack.encode({33, vim.NIL, 'text'})) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', vim.mpack.decode(ret)) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('json', function() + local code = [[ + local entry = function(async) + async:send(vim.json.encode({33, vim.NIL, 'text'})) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', vim.json.decode(ret)) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('diff', function() + local code = [[ + local entry = function(async) + async:send(vim.diff('Hello\n', 'Helli\n')) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', + {table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n')}}, + next_msg()) + end) + end) +end) + +describe('threadpool', function() + before_each(clear) + + it('is_thread', function() + eq(false, exec_lua('return vim.is_thread()')) + + local code = [[ + local work_fn = function() + return vim.is_thread() + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local work = vim.loop.new_work(work_fn, after_work_fn) + work:queue() + ]] + exec_lua(code) + + eq({'notification', 'result', {true}}, next_msg()) + end) + + it('with invalid argument', function() + local code = [[ + local work = vim.loop.new_thread(function() end, function() end) + work:queue({}) + ]] + + eq([[Error executing lua: [string ""]:0: Error: thread arg not support type 'function' at 1]], + pcall_err(exec_lua, code)) + end) + + it('with invalid return value', function() + local screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [5] = {bold = true}, + }) + + local code = [[ + local work = vim.loop.new_work(function() return {} end, function() end) + work:queue() + ]] + exec_lua(code) + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv thread:} | + {3:Error: thread arg not support type 'table' at 1} | + {4:Press ENTER or type command to continue}^ | + ]]) + end) + + describe('vim.*', function() + before_each(function() + clear() + local code = [[ + Threadpool_Test = {} + + Threadpool_Test.work_fn = function(work_fn_str, args) + local decoded_args = vim.mpack.decode(args) + return assert(loadstring(work_fn_str))(decoded_args) + end + + function Threadpool_Test:do_test() + local work = + vim.loop.new_work(self.work_fn, self.after_work) + work:queue(self.work_fn_str, self.args) + end + + Threadpool_Test.new = function(work_fn, after_work, ...) + self = {} + setmetatable(self, {__index = Threadpool_Test}) + self.args = vim.mpack.encode({...}) + self.work_fn_str = string.dump(work_fn) + self.after_work = after_work + return self + end + ]] + exec_lua(code) + end) + + it('loop', function() + local code = [[ + local work_fn = function() + return vim.loop.version() + end + local after_work_fn = function(ret) + vim.rpcnotify(1, ret) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + exec_lua(code) + + local msg = next_msg() + eq(msg[1], 'notification') + assert(tonumber(msg[2]) >= 72961) + end) + + it('mpack', function() + local code = [[ + local work_fn = function() + local var = vim.mpack.encode({33, vim.NIL, 'text'}) + return var + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', vim.mpack.decode(ret)) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('json', function() + local code = [[ + local work_fn = function() + local var = vim.json.encode({33, vim.NIL, 'text'}) + return var + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', vim.json.decode(ret)) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('work', function() + local code = [[ + local work_fn = function() + return vim.diff('Hello\n', 'Helli\n') + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + exec_lua(code) + + eq({'notification', 'result', + {table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n')}}, + next_msg()) + end) + end) +end) -- cgit From 850b3e19c9fc8d84d960e6932a9ad4f0bcad2a8e Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 26 Feb 2022 11:03:39 +0100 Subject: refactor(lua): cleanup and docs for threads --- test/functional/lua/thread_spec.lua | 93 +++++++++++++++---------------------- 1 file changed, 38 insertions(+), 55 deletions(-) (limited to 'test/functional/lua/thread_spec.lua') diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index a60f57a860..2e0ab7bdff 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -26,13 +26,12 @@ describe('thread', function() end) it('entry func is executed in protected mode', function() - local code = [[ + exec_lua [[ local thread = vim.loop.new_thread(function() error('Error in thread entry func') end) vim.loop.thread_join(thread) ]] - exec_lua(code) screen:expect([[ | @@ -51,7 +50,7 @@ describe('thread', function() end) it('callback is executed in protected mode', function() - local code = [[ + exec_lua [[ local thread = vim.loop.new_thread(function() local timer = vim.loop.new_timer() local function ontimeout() @@ -64,7 +63,6 @@ describe('thread', function() end) vim.loop.thread_join(thread) ]] - exec_lua(code) screen:expect([[ | @@ -83,35 +81,33 @@ describe('thread', function() end) describe('print', function() - it('work', function() - local code = [[ - local thread = vim.loop.new_thread(function() - print('print in thread') - end) - vim.loop.thread_join(thread) - ]] - exec_lua(code) - - screen:expect([[ - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - print in thread | - ]]) + it('works', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + print('print in thread') + end) + vim.loop.thread_join(thread) + ]] + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + print in thread | + ]]) end) end) describe('vim.*', function() before_each(function() clear() - local code = [[ + exec_lua [[ Thread_Test = {} Thread_Test.entry_func = function(async, entry_str, args) @@ -140,11 +136,10 @@ describe('thread', function() return self end ]] - exec_lua(code) end) it('is_thread', function() - local code = [[ + exec_lua [[ local entry = function(async) async:send(vim.is_thread()) end @@ -154,13 +149,12 @@ describe('thread', function() local thread_test = Thread_Test.new(entry, on_async) thread_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {true}}, next_msg()) end) it('loop', function() - local code = [[ + exec_lua [[ local entry = function(async) async:send(vim.loop.version()) end @@ -170,7 +164,6 @@ describe('thread', function() local thread_test = Thread_Test.new(entry, on_async) thread_test:do_test() ]] - exec_lua(code) local msg = next_msg() eq(msg[1], 'notification') @@ -178,7 +171,7 @@ describe('thread', function() end) it('mpack', function() - local code = [[ + exec_lua [[ local entry = function(async) async:send(vim.mpack.encode({33, vim.NIL, 'text'})) end @@ -188,13 +181,12 @@ describe('thread', function() local thread_test = Thread_Test.new(entry, on_async) thread_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) end) it('json', function() - local code = [[ + exec_lua [[ local entry = function(async) async:send(vim.json.encode({33, vim.NIL, 'text'})) end @@ -204,13 +196,12 @@ describe('thread', function() local thread_test = Thread_Test.new(entry, on_async) thread_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) end) it('diff', function() - local code = [[ + exec_lua [[ local entry = function(async) async:send(vim.diff('Hello\n', 'Helli\n')) end @@ -220,7 +211,6 @@ describe('thread', function() local thread_test = Thread_Test.new(entry, on_async) thread_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {table.concat({ @@ -238,9 +228,9 @@ describe('threadpool', function() before_each(clear) it('is_thread', function() - eq(false, exec_lua('return vim.is_thread()')) + eq(false, exec_lua [[return vim.is_thread()]]) - local code = [[ + exec_lua [[ local work_fn = function() return vim.is_thread() end @@ -250,19 +240,18 @@ describe('threadpool', function() local work = vim.loop.new_work(work_fn, after_work_fn) work:queue() ]] - exec_lua(code) eq({'notification', 'result', {true}}, next_msg()) end) it('with invalid argument', function() - local code = [[ + local status = pcall_err(exec_lua, [[ local work = vim.loop.new_thread(function() end, function() end) work:queue({}) - ]] + ]]) eq([[Error executing lua: [string ""]:0: Error: thread arg not support type 'function' at 1]], - pcall_err(exec_lua, code)) + status) end) it('with invalid return value', function() @@ -276,11 +265,10 @@ describe('threadpool', function() [5] = {bold = true}, }) - local code = [[ + exec_lua [[ local work = vim.loop.new_work(function() return {} end, function() end) work:queue() ]] - exec_lua(code) screen:expect([[ | @@ -299,7 +287,7 @@ describe('threadpool', function() describe('vim.*', function() before_each(function() clear() - local code = [[ + exec_lua [[ Threadpool_Test = {} Threadpool_Test.work_fn = function(work_fn_str, args) @@ -322,11 +310,10 @@ describe('threadpool', function() return self end ]] - exec_lua(code) end) it('loop', function() - local code = [[ + exec_lua [[ local work_fn = function() return vim.loop.version() end @@ -336,7 +323,6 @@ describe('threadpool', function() local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) threadpool_test:do_test() ]] - exec_lua(code) local msg = next_msg() eq(msg[1], 'notification') @@ -344,7 +330,7 @@ describe('threadpool', function() end) it('mpack', function() - local code = [[ + exec_lua [[ local work_fn = function() local var = vim.mpack.encode({33, vim.NIL, 'text'}) return var @@ -355,13 +341,12 @@ describe('threadpool', function() local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) threadpool_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) end) it('json', function() - local code = [[ + exec_lua [[ local work_fn = function() local var = vim.json.encode({33, vim.NIL, 'text'}) return var @@ -372,13 +357,12 @@ describe('threadpool', function() local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) threadpool_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) end) it('work', function() - local code = [[ + exec_lua [[ local work_fn = function() return vim.diff('Hello\n', 'Helli\n') end @@ -388,7 +372,6 @@ describe('threadpool', function() local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) threadpool_test:do_test() ]] - exec_lua(code) eq({'notification', 'result', {table.concat({ -- cgit