diff options
Diffstat (limited to 'test/functional/job')
-rw-r--r-- | test/functional/job/CMakeLists.txt | 2 | ||||
-rw-r--r-- | test/functional/job/job_spec.lua | 87 | ||||
-rw-r--r-- | test/functional/job/tty-test.c | 114 |
3 files changed, 195 insertions, 8 deletions
diff --git a/test/functional/job/CMakeLists.txt b/test/functional/job/CMakeLists.txt new file mode 100644 index 0000000000..14ec287816 --- /dev/null +++ b/test/functional/job/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(tty-test tty-test.c) +target_link_libraries(tty-test ${LIBUV_LIBRARIES}) diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua index 5304c9e9d3..0051de09e1 100644 --- a/test/functional/job/job_spec.lua +++ b/test/functional/job/job_spec.lua @@ -4,6 +4,7 @@ local clear, nvim, eq, neq, ok, expect, eval, next_message, run, stop, session = helpers.clear, helpers.nvim, helpers.eq, helpers.neq, helpers.ok, helpers.expect, helpers.eval, helpers.next_message, helpers.run, helpers.stop, helpers.session +local insert = helpers.insert local channel = nvim('get_api_info')[1] @@ -41,11 +42,11 @@ describe('jobs', function() nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") neq(0, eval('j')) nvim('command', 'call jobsend(j, "abc\\n")') - eq({'notification', 'stdout', {{'abc'}}}, next_message()) + eq({'notification', 'stdout', {{'abc', ''}}}, next_message()) nvim('command', 'call jobsend(j, "123\\nxyz\\n")') - eq({'notification', 'stdout', {{'123', 'xyz'}}}, next_message()) + eq({'notification', 'stdout', {{'123', 'xyz', ''}}}, next_message()) nvim('command', 'call jobsend(j, [123, "xyz"])') - eq({'notification', 'stdout', {{'123', 'xyz'}}}, next_message()) + eq({'notification', 'stdout', {{'123', 'xyz', ''}}}, next_message()) nvim('command', "call jobstop(j)") eq({'notification', 'exit', {0}}, next_message()) end) @@ -60,24 +61,41 @@ describe('jobs', function() -- v:job_data preserves NULs. nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) nvim('command', "let j = jobstart('xxx', 'cat', ['"..filename.."'])") - eq({'notification', 'stdout', {{'abc\ndef'}}}, next_message()) + eq({'notification', 'stdout', {{'abc\ndef', ''}}}, next_message()) eq({'notification', 'exit', {0}}, next_message()) os.remove(filename) -- jobsend() preserves NULs. nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") nvim('command', [[call jobsend(j, ["123\n456"])]]) - eq({'notification', 'stdout', {{'123\n456'}}}, next_message()) + eq({'notification', 'stdout', {{'123\n456', ''}}}, next_message()) nvim('command', "call jobstop(j)") end) - it('will hold data if it does not end in a newline', function() + it('will not buffer data if it doesnt end in newlines', function() nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") nvim('command', 'call jobsend(j, "abc\\nxyz")') - eq({'notification', 'stdout', {{'abc'}}}, next_message()) + eq({'notification', 'stdout', {{'abc', 'xyz'}}}, next_message()) + nvim('command', "call jobstop(j)") + eq({'notification', 'exit', {0}}, next_message()) + end) + + it('can preserve newlines', function() + nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) + nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") + nvim('command', 'call jobsend(j, "a\\n\\nc\\n\\n\\n\\nb\\n\\n")') + eq({'notification', 'stdout', {{'a', '', 'c', '', '', '', 'b', '', ''}}}, + next_message()) + end) + + it('can preserve nuls', function() + nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) + nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") + nvim('command', 'call jobsend(j, ["\n123\n", "abc\\nxyz\n"])') + eq({'notification', 'stdout', {{'\n123\n', 'abc\nxyz\n', ''}}}, + next_message()) nvim('command', "call jobstop(j)") - eq({'notification', 'stdout', {{'xyz'}}}, next_message()) eq({'notification', 'exit', {0}}, next_message()) end) @@ -107,4 +125,57 @@ describe('jobs', function() eq({'notification', 'j', {{jobid, 'stdout', {'abcdef'}}}}, next_message()) eq({'notification', 'j', {{jobid, 'exit'}}}, next_message()) end) + + describe('running tty-test program', function() + local function next_chunk() + local rv = '' + while true do + local msg = next_message() + local data = msg[3][1] + for i = 1, #data do + data[i] = data[i]:gsub('\n', '\000') + end + rv = table.concat(data, '\n') + rv = rv:gsub('\r\n$', '') + if rv ~= '' then + break + end + end + return rv + end + + local function send(str) + nvim('command', 'call jobsend(j, "'..str..'")') + end + + before_each(function() + -- the full path to tty-test seems to be required when running on travis. + insert('build/bin/tty-test') + nvim('command', 'let exec = expand("<cfile>:p")') + nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) + nvim('command', "let j = jobstart('xxx', exec, [], {})") + eq('tty ready', next_chunk()) + end) + + it('echoing input', function() + send('test') + -- the tty driver will echo input by default + eq('test', next_chunk()) + end) + + it('resizing window', function() + nvim('command', 'call jobresize(j, 40, 10)') + eq('screen resized. rows: 10, columns: 40', next_chunk()) + nvim('command', 'call jobresize(j, 10, 40)') + eq('screen resized. rows: 40, columns: 10', next_chunk()) + end) + + it('preprocessing ctrl+c with terminal driver', function() + send('\\<c-c>') + eq('^Cinterrupt received, press again to exit', next_chunk()) + send('\\<c-c>') + eq('^Ctty done', next_chunk()) + eq({'notification', 'exit', {0}}, next_message()) + end) + end) end) diff --git a/test/functional/job/tty-test.c b/test/functional/job/tty-test.c new file mode 100644 index 0000000000..25e76840aa --- /dev/null +++ b/test/functional/job/tty-test.c @@ -0,0 +1,114 @@ +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <uv.h> + +#ifdef _WIN32 +#include <windows.h> +bool owns_tty(void) +{ + HWND consoleWnd = GetConsoleWindow(); + DWORD dwProcessId; + GetWindowThreadProcessId(consoleWnd, &dwProcessId); + return GetCurrentProcessId() == dwProcessId; +} +#else +bool owns_tty(void) +{ + // TODO: Check if the process is the session leader + return true; +} +#endif + +#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) +#define BUF_SIZE 0xfff + +static void walk_cb(uv_handle_t *handle, void *arg) { + if (!uv_is_closing(handle)) { + uv_close(handle, NULL); + } +} + +static void sigwinch_cb(uv_signal_t *handle, int signum) +{ + int width, height; + uv_tty_t *tty = handle->data; + uv_tty_get_winsize(tty, &width, &height); + fprintf(stderr, "screen resized. rows: %d, columns: %d\n", height, width); +} + +static void sigint_cb(uv_signal_t *handle, int signum) +{ + bool *interrupted = handle->data; + + if (*interrupted) { + uv_walk(uv_default_loop(), walk_cb, NULL); + return; + } + + *interrupted = true; + fprintf(stderr, "interrupt received, press again to exit\n"); +} + +static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) +{ + buf->len = BUF_SIZE; + buf->base = malloc(BUF_SIZE); +} + +static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) +{ + if (cnt <= 0) { + uv_read_stop(stream); + return; + } + + fprintf(stderr, "received data: "); + uv_loop_t write_loop; + uv_loop_init(&write_loop); + uv_tty_t out; + uv_tty_init(&write_loop, &out, 1, 0); + uv_write_t req; + uv_buf_t b = {.base = buf->base, .len = buf->len}; + uv_write(&req, (uv_stream_t *)&out, &b, 1, NULL); + uv_run(&write_loop, UV_RUN_DEFAULT); + uv_close((uv_handle_t *)&out, NULL); + uv_run(&write_loop, UV_RUN_DEFAULT); + if (uv_loop_close(&write_loop)) { + abort(); + } + free(buf->base); +} + +int main(int argc, char **argv) +{ + if (!is_terminal(stdin)) { + fprintf(stderr, "stdin is not a terminal\n"); + exit(2); + } + + if (!is_terminal(stdout)) { + fprintf(stderr, "stdout is not a terminal\n"); + exit(2); + } + + if (!is_terminal(stderr)) { + fprintf(stderr, "stderr is not a terminal\n"); + exit(2); + } + + bool interrupted = false; + fprintf(stderr, "tty ready\n"); + uv_tty_t tty; + uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); + uv_read_start((uv_stream_t *)&tty, alloc_cb, read_cb); + uv_signal_t sigwinch_watcher, sigint_watcher; + uv_signal_init(uv_default_loop(), &sigwinch_watcher); + sigwinch_watcher.data = &tty; + uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); + uv_signal_init(uv_default_loop(), &sigint_watcher); + sigint_watcher.data = &interrupted; + uv_signal_start(&sigint_watcher, sigint_cb, SIGINT); + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + fprintf(stderr, "tty done\n"); +} |