Merge branch 'master' into s-dash-stdin

This commit is contained in:
ZyX
2017-12-03 16:49:30 +03:00
1530 changed files with 112129 additions and 74550 deletions

View File

@@ -17,4 +17,4 @@ ignore = {
}
-- Ignore whitespace issues in converted Vim legacy tests.
files["functional/legacy"] = {ignore = { "611", "612", "613", "621" }}
--files["functional/legacy"] = {ignore = { "611", "612", "613", "621" }}

View File

@@ -1,34 +1,267 @@
# Tests
Tests
=====
Tests are run by `/cmake/RunTests.cmake` file, using busted.
Tests are run by `/cmake/RunTests.cmake` file, using `busted`.
## Directory structure
For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight.
Directories with tests: `/test/benchmark` for benchmarks, `/test/functional` for
functional tests, `/test/unit` for unit tests. `/test/config` contains `*.in`
files (currently a single one) which are transformed into `*.lua` files using
`configure_file` CMake command: this is for acessing CMake variables in lua
tests. `/test/includes` contains include files for use by luajit `ffi.cdef`
C definitions parser: normally used to make macros not accessible via this
mechanism accessible the other way.
---
Files `/test/*/preload.lua` contain modules which will be preloaded by busted,
via `--helper` option. `/test/**/helpers.lua` contain various “library”
functions, (intended to be) used by a number of tests and not just a single one.
- [Running tests](#running-tests)
- [Unit tests](#unit-tests)
- [Lint](#lint)
- [Environment variables](#environment-variables)
`/test/*/**/*_spec.lua` are files containing actual tests. Files that do not end
with a `_spec.lua` are libraries like `/test/**/helpers.lua`, except that they
have some common topic.
---
Tests inside `/test/unit` and `/test/functional` are normally divided into
groups by the semantic component they are testing.
Running tests
-------------
## Environment variables
Neovim uses third-party tooling to execute tests. So be sure, from the
repository directory, to build the tools before testing:
make cmake
## Executing Tests
To run all _non-legacy_ (unit + functional) tests:
make test
To run only _unit_ tests:
make unittest
To run only _functional_ tests:
make functionaltest
---
## Filter Tests
### Filter by name
Another filter method is by setting a pattern of test name to `TEST_FILTER`.
``` lua
it('foo api',function()
...
end)
it('bar api',function()
...
end)
```
To run only test with filter name:
TEST_TAG='foo.*api' make functionaltest
### Filter by file
To run a *specific* unit test:
TEST_FILE=test/unit/foo.lua make unittest
To run a *specific* functional test:
TEST_FILE=test/functional/foo.lua make functionaltest
To *repeat* a test many times:
.deps/usr/bin/busted --filter 'foo' --repeat 1000 test/functional/ui/foo_spec.lua
### Filter by tag
Tests can be "tagged" by adding `#` before a token in the test description.
``` lua
it('#foo bar baz', function()
...
end)
it('#foo another test', function()
...
end)
```
To run only the tagged tests:
TEST_TAG=foo make functionaltest
**NOTES**:
* Tags are mainly used for testing issues (ex: `#1234`), so use the following
method.
* `TEST_FILE` is not a pattern string like `TEST_TAG` or `TEST_FILTER`. The
given value to `TEST_FILE` must be a path to an existing file.
* Both `TEST_TAG` and `TEST_FILTER` filter tests by the strings from either
`it()` or `describe()` functions.
---
### Legacy
To run all legacy (Vim) integration tests:
make oldtest
To run a *single* legacy test, run `make` with `TEST_FILE=test_name.res`. E.g.
to run `test_syntax.vim`:
TEST_FILE=test_syntax.res make oldtest
- The `.res` extension (instead of `.vim`) is required.
- Specify only the test file name, not the full path.
### Functional tests
`$GDB` can be set to [run tests under
gdbserver](https://github.com/neovim/neovim/pull/1527). If `$VALGRIND` is also
set, it will add the `--vgdb=yes` option to valgrind instead of
starting gdbserver directly.
Unit tests
----------
Tests are broadly divided into *unit tests*
([test/unit](https://github.com/neovim/neovim/tree/master/test/unit) directory)
and *functional tests*
([test/functional](https://github.com/neovim/neovim/tree/master/test/functional)
directory). Use any of the existing tests as a template to start writing new
tests.
- _Unit_ testing is achieved by compiling the tests as a shared library which is
loaded and called by LuaJit [FFI](http://luajit.org/ext_ffi.html).
- _Functional_ tests are driven by RPC, so they do not require LuaJit (as
opposed to Lua).
You can learn the [key concepts of Lua in 15
minutes](http://learnxinyminutes.com/docs/lua/).
## Guidelines for writing tests
- Consider [BDD](http://en.wikipedia.org/wiki/Behavior-driven_development)
guidelines for organization and readability of tests. Describe what you're
testing (and the environment if applicable) and create specs that assert its
behavior.
- For testing static functions or functions that have side effects visible only
in module-global variables, create accessors for the modified variables. For
example, say you are testing a function in misc1.c that modifies a static
variable, create a file `test/c-helpers/misc1.c` and add a function that
retrieves the value after the function call. Files under `test/c-helpers` will
only be compiled when building the test shared library.
- Luajit needs to know about type and constant declarations used in function
prototypes. The
[helpers.lua](https://github.com/neovim/neovim/blob/master/test/unit/helpers.lua)
file automatically parses `types.h`, so types used in the tested functions
must be moved to it to avoid having to rewrite the declarations in the test
files (even though this is how it's currently done currently in the misc1/fs
modules, but contributors are encouraged to refactor the declarations).
- Macro constants must be rewritten as enums so they can be "visible" to the
tests automatically.
- Busted supports various "output providers". The
**[gtest](https://github.com/Olivine-Labs/busted/pull/394) output provider**
shows verbose details that can be useful to diagnose hung tests. Either modify
the Makefile or compile with `make
CMAKE_EXTRA_FLAGS=-DBUSTED_OUTPUT_TYPE=gtest` to enable it.
- **Use busted's `pending()` feature** to skip tests
([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)).
Do not silently skip the test with `if-else`. If a functional test depends on
some external factor (e.g. the existence of `md5sum` on `$PATH`), *and* you
can't mock or fake the dependency, then skip the test via `pending()` if the
external factor is missing. This ensures that the *total* test-count (success
+ fail + error + pending) is the same in all environments.
- *Note:* `pending()` is ignored if it is missing an argument _unless_ it is
[contained in an `it()`
block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11).
Provide empty function argument if the `pending()` call is outside of
`it()`
([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)).
- Use `make testlint` for using the shipped luacheck program ([supported by
syntastic](https://github.com/scrooloose/syntastic/blob/d6b96c079be137c83009827b543a83aa113cc011/doc/syntastic-checkers.txt#L3546))
to lint all tests.
### Where tests go
- _Unit tests_
([test/unit](https://github.com/neovim/neovim/tree/master/test/unit)) should
match 1-to-1 with the structure of `src/nvim/`, because they are testing
functions directly. E.g. unit-tests for `src/nvim/undo.c` should live in
`test/unit/undo_spec.lua`.
- _Functional tests_
([test/functional](https://github.com/neovim/neovim/tree/master/test/functional))
are higher-level (plugins and user input) than unit tests; they are organized
by concept.
- Try to find an existing `test/functional/*/*_spec.lua` group that makes
sense, before creating a new one.
## Checklist for migrating legacy tests
**Note:** Only "old style" (`src/testdir/*.in`) legacy tests should be
converted. Please _do not_ convert "new style" Vim tests (`src/testdir/*.vim`).
The "new style" Vim tests are faster than the old ones, and converting them
takes time and effort better spent elsewhere.
- Remove the test from the Makefile (`src/nvim/testdir/Makefile`).
- Remove the associated `test.in`, `test.out`, and `test.ok` files from
`src/nvim/testdir/`.
- Make sure the lua test ends in `_spec.lua`.
- Make sure the test count increases accordingly in the build log.
- Make sure the new test contains the same control characters (`^]`, ...) as the
old test.
- Instead of the actual control characters, use an equivalent textual
representation (e.g. `<esc>` instead of `^]`). The
`scripts/legacy2luatest.pl` script does some of these conversions
automatically.
## Tips
- Really long `source([=[...]=])` blocks may break syntax highlighting. Try
`:syntax sync fromstart` to fix it.
Lint
----
`make lint` (and `make testlint`) runs [luacheck](https://github.com/mpeterv/luacheck)
on the test code.
If a luacheck warning must be ignored, specify the warning code. Example:
-- luacheck: ignore 621
http://luacheck.readthedocs.io/en/stable/warnings.html
Ignore the smallest applicable scope (e.g. inside a function, not at the top of
the file).
Layout
------
- `/test/benchmark` : benchmarks
- `/test/functional` : functional tests
- `/test/unit` : unit tests
- `/test/config` : contains `*.in` files which are transformed into `*.lua`
files using `configure_file` CMake command: this is for acessing CMake
variables in lua tests.
- `/test/includes` : include-files for use by luajit `ffi.cdef` C definitions
parser: normally used to make macros not accessible via this mechanism
accessible the other way.
- `/test/*/preload.lua` : modules preloaded by busted `--helper` option
- `/test/**/helpers.lua` : common utility functions for test code
- `/test/*/**/*_spec.lua` : actual tests. Files that do not end with
`_spec.lua` are libraries like `/test/**/helpers.lua`, except that they have
some common topic.
Tests in `/test/unit` and `/test/functional` are normally divided into groups
by the semantic component they are testing.
Environment variables
---------------------
Test behaviour is affected by environment variables. Currently supported
(Functional, Unit, Benchmarks) (when Defined; when set to _1_; when defined,
treated as Integer; when defined, treated as String; !must be defined to
function properly):
treated as Integer; when defined, treated as String; when defined, treated as
Number; !must be defined to function properly):
`GDB` (F) (D): makes nvim instances to be run under `gdbserver`. It will be
accessible on `localhost:7777`: use `gdb build/bin/nvim`, type `target remote
@@ -99,3 +332,12 @@ get backtrace from).
approximately 90% of the tests. Should be used when finding cores is too hard
for some reason. Normally (on OS X or when `NVIM_TEST_CORE_GLOB_DIRECTORY` is
defined and this variable is not) cores are checked for after each test.
`NVIM_TEST_RUN_TESTTEST` (U) (1): allows running `test/unit/testtest_spec.lua`
used to check how testing infrastructure works.
`NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level: `0`
disables tracing (the fastest, but you get no data if tests crash and there was
no core dump generated), `1` or empty/undefined leaves only C function cals and
returns in the trace (faster then recording everything), `2` records all
function calls, returns and lua source lines exuecuted.

View File

@@ -1,8 +1,8 @@
-- Test for benchmarking RE engine.
local helpers = require('test.functional.helpers')
local helpers = require('test.functional.helpers')(after_each)
local insert, source = helpers.insert, helpers.source
local clear, execute, wait = helpers.clear, helpers.execute, helpers.wait
local clear, command = helpers.clear, helpers.command
-- Temporary file for gathering benchmarking results for each regexp engine.
local result_file = 'benchmark.out'
@@ -31,7 +31,7 @@ describe('regexp search', function()
clear()
source(measure_script)
insert('" Benchmark_results:')
execute('write! ' .. result_file)
command('write! ' .. result_file)
end)
-- At the end of the test run we just print the contents of the result file
@@ -46,22 +46,19 @@ describe('regexp search', function()
it('is working with regexpengine=0', function()
local regexpengine = 0
execute(string.format(measure_cmd, regexpengine))
execute('write')
wait()
command(string.format(measure_cmd, regexpengine))
command('write')
end)
it('is working with regexpengine=1', function()
local regexpengine = 1
execute(string.format(measure_cmd, regexpengine))
execute('write')
wait()
command(string.format(measure_cmd, regexpengine))
command('write')
end)
it('is working with regexpengine=2', function()
local regexpengine = 2
execute(string.format(measure_cmd, regexpengine))
execute('write')
wait()
command(string.format(measure_cmd, regexpengine))
command('write')
end)
end)

View File

@@ -5,7 +5,8 @@ for p in ("${TEST_INCLUDE_DIRS}" .. ";"):gmatch("[^;]+") do
table.insert(module.include_paths, p)
end
module.test_include_path = "${CMAKE_BINARY_DIR}/test/includes/post"
module.test_build_dir = "${CMAKE_BINARY_DIR}"
module.test_include_path = module.test_build_dir .. "/test/includes/post"
module.test_libnvim_path = "${TEST_LIBNVIM_PATH}"
module.test_source_path = "${CMAKE_SOURCE_DIR}"
module.test_lua_prg = "${LUA_PRG}"

View File

@@ -5,11 +5,12 @@ local curbufmeths, ok = helpers.curbufmeths, helpers.ok
local funcs = helpers.funcs
local request = helpers.request
local exc_exec = helpers.exc_exec
local execute = helpers.execute
local feed_command = helpers.feed_command
local insert = helpers.insert
local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
local command = helpers.command
local bufmeths = helpers.bufmeths
describe('api/buf', function()
before_each(clear)
@@ -121,6 +122,15 @@ describe('api/buf', function()
local get_lines, set_lines = curbufmeths.get_lines, curbufmeths.set_lines
local line_count = curbufmeths.line_count
it('fails correctly when input is not valid', function()
eq(1, curbufmeths.get_number())
local err, emsg = pcall(bufmeths.set_lines, 1, 1, 2, false, {'b\na'})
eq(false, err)
local exp_emsg = 'String cannot contain newlines'
-- Expected {filename}:{lnum}: {exp_emsg}
eq(': ' .. exp_emsg, emsg:sub(-#exp_emsg - 2))
end)
it('has correct line_count when inserting and deleting', function()
eq(1, line_count())
set_lines(-1, -1, true, {'line'})
@@ -246,7 +256,7 @@ describe('api/buf', function()
end)
it("set_line on alternate buffer does not access invalid line (E315)", function()
execute('set hidden')
feed_command('set hidden')
insert('Initial file')
command('enew')
insert([[
@@ -257,7 +267,7 @@ describe('api/buf', function()
The
Other
Buffer]])
execute('$')
feed_command('$')
local retval = exc_exec("call nvim_buf_set_lines(1, 0, 1, v:false, ['test'])")
eq(0, retval)
end)
@@ -271,7 +281,7 @@ describe('api/buf', function()
eq(1, funcs.exists('b:lua'))
curbufmeths.del_var('lua')
eq(0, funcs.exists('b:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curbufmeths.del_var, 'lua'))
eq({false, 'Key does not exist: lua'}, meth_pcall(curbufmeths.del_var, 'lua'))
curbufmeths.set_var('lua', 1)
command('lockvar b:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.del_var, 'lua'))

View File

@@ -0,0 +1,103 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, nvim = helpers.clear, helpers.nvim
local Screen = require('test.functional.ui.screen')
local eq, eval = helpers.eq, helpers.eval
local command = helpers.command
local meths = helpers.meths
describe('highlight api',function()
local expected_rgb = {
background = Screen.colors.Yellow,
foreground = Screen.colors.Red,
special = Screen.colors.Blue,
bold = true,
}
local expected_cterm = {
background = 10,
underline = true,
}
local expected_rgb2 = {
background = Screen.colors.Yellow,
foreground = Screen.colors.Red,
special = Screen.colors.Blue,
bold = true,
italic = true,
reverse = true,
undercurl = true,
underline = true,
}
before_each(function()
clear()
command("hi NewHighlight cterm=underline ctermbg=green guifg=red guibg=yellow guisp=blue gui=bold")
end)
it("nvim_get_hl_by_id", function()
local hl_id = eval("hlID('NewHighlight')")
eq(expected_cterm, nvim("get_hl_by_id", hl_id, false))
hl_id = eval("hlID('NewHighlight')")
-- Test valid id.
eq(expected_rgb, nvim("get_hl_by_id", hl_id, true))
-- Test invalid id.
local err, emsg = pcall(meths.get_hl_by_id, 30000, false)
eq(false, err)
eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*'))
-- Test all highlight properties.
command('hi NewHighlight gui=underline,bold,undercurl,italic,reverse')
eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true))
-- Test nil argument.
err, emsg = pcall(meths.get_hl_by_id, { nil }, false)
eq(false, err)
eq('Wrong type for argument 1, expecting Integer',
string.match(emsg, 'Wrong.*'))
-- Test 0 argument.
err, emsg = pcall(meths.get_hl_by_id, 0, false)
eq(false, err)
eq('Invalid highlight id: 0',
string.match(emsg, 'Invalid.*'))
-- Test -1 argument.
err, emsg = pcall(meths.get_hl_by_id, -1, false)
eq(false, err)
eq('Invalid highlight id: -1',
string.match(emsg, 'Invalid.*'))
end)
it("nvim_get_hl_by_name", function()
local expected_normal = { background = Screen.colors.Yellow,
foreground = Screen.colors.Red }
-- Test `Normal` default values.
eq({}, nvim("get_hl_by_name", 'Normal', true))
eq(expected_cterm, nvim("get_hl_by_name", 'NewHighlight', false))
eq(expected_rgb, nvim("get_hl_by_name", 'NewHighlight', true))
-- Test `Normal` modified values.
command('hi Normal guifg=red guibg=yellow')
eq(expected_normal, nvim("get_hl_by_name", 'Normal', true))
-- Test invalid name.
local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false)
eq(false, err)
eq('Invalid highlight name: unknown_highlight',
string.match(emsg, 'Invalid.*'))
-- Test nil argument.
err, emsg = pcall(meths.get_hl_by_name , { nil }, false)
eq(false, err)
eq('Wrong type for argument 1, expecting String',
string.match(emsg, 'Wrong.*'))
-- Test empty string argument.
err, emsg = pcall(meths.get_hl_by_name , '', false)
eq(false, err)
eq('Invalid highlight name: ',
string.match(emsg, 'Invalid.*'))
end)
end)

View File

@@ -0,0 +1,310 @@
local helpers = require('test.functional.helpers')(after_each)
local global_helpers = require('test.helpers')
local clear = helpers.clear
local command = helpers.command
local curbufmeths = helpers.curbufmeths
local eq = helpers.eq
local funcs = helpers.funcs
local meths = helpers.meths
local source = helpers.source
local shallowcopy = global_helpers.shallowcopy
describe('get_keymap', function()
before_each(clear)
-- Basic mapping and table to be used to describe results
local foo_bar_string = 'nnoremap foo bar'
local foo_bar_map_table = {
lhs='foo',
silent=0,
rhs='bar',
expr=0,
sid=0,
buffer=0,
nowait=0,
mode='n',
noremap=1,
}
it('returns empty list when no map', function()
eq({}, meths.get_keymap('n'))
end)
it('returns list of all applicable mappings', function()
command(foo_bar_string)
-- Only one mapping available
-- Should be the same as the dictionary we supplied earlier
-- and the dictionary you would get from maparg
-- since this is a global map, and not script local
eq({foo_bar_map_table}, meths.get_keymap('n'))
eq({funcs.maparg('foo', 'n', false, true)},
meths.get_keymap('n')
)
-- Add another mapping
command('nnoremap foo_longer bar_longer')
local foolong_bar_map_table = shallowcopy(foo_bar_map_table)
foolong_bar_map_table['lhs'] = 'foo_longer'
foolong_bar_map_table['rhs'] = 'bar_longer'
eq({foolong_bar_map_table, foo_bar_map_table},
meths.get_keymap('n')
)
-- Remove a mapping
command('unmap foo_longer')
eq({foo_bar_map_table},
meths.get_keymap('n')
)
end)
it('works for other modes', function()
-- Add two mappings, one in insert and one normal
-- We'll only check the insert mode one
command('nnoremap not_going to_check')
command('inoremap foo bar')
-- The table will be the same except for the mode
local insert_table = shallowcopy(foo_bar_map_table)
insert_table['mode'] = 'i'
eq({insert_table}, meths.get_keymap('i'))
end)
it('considers scope', function()
-- change the map slightly
command('nnoremap foo_longer bar_longer')
local foolong_bar_map_table = shallowcopy(foo_bar_map_table)
foolong_bar_map_table['lhs'] = 'foo_longer'
foolong_bar_map_table['rhs'] = 'bar_longer'
local buffer_table = shallowcopy(foo_bar_map_table)
buffer_table['buffer'] = 1
command('nnoremap <buffer> foo bar')
-- The buffer mapping should not show up
eq({foolong_bar_map_table}, meths.get_keymap('n'))
eq({buffer_table}, curbufmeths.get_keymap('n'))
end)
it('considers scope for overlapping maps', function()
command('nnoremap foo bar')
local buffer_table = shallowcopy(foo_bar_map_table)
buffer_table['buffer'] = 1
command('nnoremap <buffer> foo bar')
eq({foo_bar_map_table}, meths.get_keymap('n'))
eq({buffer_table}, curbufmeths.get_keymap('n'))
end)
it('can retrieve mapping for different buffers', function()
local original_buffer = curbufmeths.get_number()
-- Place something in each of the buffers to make sure they stick around
-- and set hidden so we can leave them
command('set hidden')
command('new')
command('normal! ihello 2')
command('new')
command('normal! ihello 3')
local final_buffer = curbufmeths.get_number()
command('nnoremap <buffer> foo bar')
-- Final buffer will have buffer mappings
local buffer_table = shallowcopy(foo_bar_map_table)
buffer_table['buffer'] = final_buffer
eq({buffer_table}, meths.buf_get_keymap(final_buffer, 'n'))
eq({buffer_table}, meths.buf_get_keymap(0, 'n'))
command('buffer ' .. original_buffer)
eq(original_buffer, curbufmeths.get_number())
-- Original buffer won't have any mappings
eq({}, meths.get_keymap('n'))
eq({}, curbufmeths.get_keymap('n'))
eq({buffer_table}, meths.buf_get_keymap(final_buffer, 'n'))
end)
-- Test toggle switches for basic options
-- @param option The key represented in the `maparg()` result dict
local function global_and_buffer_test(map,
option,
option_token,
global_on_result,
buffer_on_result,
global_off_result,
buffer_off_result,
new_windows)
local function make_new_windows(number_of_windows)
if new_windows == nil then
return nil
end
for _=1,number_of_windows do
command('new')
end
end
local mode = string.sub(map, 1,1)
-- Don't run this for the <buffer> mapping, since it doesn't make sense
if option_token ~= '<buffer>' then
it(string.format( 'returns %d for the key "%s" when %s is used globally with %s (%s)',
global_on_result, option, option_token, map, mode), function()
make_new_windows(new_windows)
command(map .. ' ' .. option_token .. ' foo bar')
local result = meths.get_keymap(mode)[1][option]
eq(global_on_result, result)
end)
end
it(string.format('returns %d for the key "%s" when %s is used for buffers with %s (%s)',
buffer_on_result, option, option_token, map, mode), function()
make_new_windows(new_windows)
command(map .. ' <buffer> ' .. option_token .. ' foo bar')
local result = curbufmeths.get_keymap(mode)[1][option]
eq(buffer_on_result, result)
end)
-- Don't run these for the <buffer> mapping, since it doesn't make sense
if option_token ~= '<buffer>' then
it(string.format('returns %d for the key "%s" when %s is not used globally with %s (%s)',
global_off_result, option, option_token, map, mode), function()
make_new_windows(new_windows)
command(map .. ' baz bat')
local result = meths.get_keymap(mode)[1][option]
eq(global_off_result, result)
end)
it(string.format('returns %d for the key "%s" when %s is not used for buffers with %s (%s)',
buffer_off_result, option, option_token, map, mode), function()
make_new_windows(new_windows)
command(map .. ' <buffer> foo bar')
local result = curbufmeths.get_keymap(mode)[1][option]
eq(buffer_off_result, result)
end)
end
end
-- Standard modes and returns the same values in the dictionary as maparg()
local mode_list = {'nnoremap', 'nmap', 'imap', 'inoremap', 'cnoremap'}
for mode in pairs(mode_list) do
global_and_buffer_test(mode_list[mode], 'silent', '<silent>', 1, 1, 0, 0)
global_and_buffer_test(mode_list[mode], 'nowait', '<nowait>', 1, 1, 0, 0)
global_and_buffer_test(mode_list[mode], 'expr', '<expr>', 1, 1, 0, 0)
end
-- noremap will now be 2 if script was used, which is not the same as maparg()
global_and_buffer_test('nmap', 'noremap', '<script>', 2, 2, 0, 0)
global_and_buffer_test('nnoremap', 'noremap', '<script>', 2, 2, 1, 1)
-- buffer will return the buffer ID, which is not the same as maparg()
-- Three of these tests won't run
global_and_buffer_test('nnoremap', 'buffer', '<buffer>', nil, 3, nil, nil, 2)
it('returns script numbers for global maps', function()
source([[
function! s:maparg_test_function() abort
return 'testing'
endfunction
nnoremap fizz :call <SID>maparg_test_function()<CR>
]])
local sid_result = meths.get_keymap('n')[1]['sid']
eq(1, sid_result)
eq('testing', meths.call_function('<SNR>' .. sid_result .. '_maparg_test_function', {}))
end)
it('returns script numbers for buffer maps', function()
source([[
function! s:maparg_test_function() abort
return 'testing'
endfunction
nnoremap <buffer> fizz :call <SID>maparg_test_function()<CR>
]])
local sid_result = curbufmeths.get_keymap('n')[1]['sid']
eq(1, sid_result)
eq('testing', meths.call_function('<SNR>' .. sid_result .. '_maparg_test_function', {}))
end)
it('works with <F12> and others', function()
command('nnoremap <F12> :let g:maparg_test_var = 1<CR>')
eq('<F12>', meths.get_keymap('n')[1]['lhs'])
eq(':let g:maparg_test_var = 1<CR>', meths.get_keymap('n')[1]['rhs'])
end)
it('works correctly despite various &cpo settings', function()
local cpo_table = {
silent=0,
expr=0,
sid=0,
buffer=0,
nowait=0,
noremap=1,
}
local function cpomap(lhs, rhs, mode)
local ret = shallowcopy(cpo_table)
ret.lhs = lhs
ret.rhs = rhs
ret.mode = mode
return ret
end
command('set cpo+=B')
command('nnoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
command('nnoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
command('set cpo+=B')
command('xnoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
command('xnoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
command('set cpo-=B')
command('snoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
command('snoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
command('set cpo-=B')
command('onoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\')
command('onoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\')
for _, cmd in ipairs({
'set cpo-=B',
'set cpo+=B',
}) do
command(cmd)
eq({cpomap('\\<C-C><C-C><lt>C-c>\\', '\\<C-D><C-D><lt>C-d>\\', 'n'),
cpomap('\\<C-A><C-A><lt>C-a>\\', '\\<C-B><C-B><lt>C-b>\\', 'n')},
meths.get_keymap('n'))
eq({cpomap('\\<C-C><C-C><lt>C-c>\\', '\\<C-D><C-D><lt>C-d>\\', 'x'),
cpomap('\\<C-A><C-A><lt>C-a>\\', '\\<C-B><C-B><lt>C-b>\\', 'x')},
meths.get_keymap('x'))
eq({cpomap('<lt>C-c><C-C><lt>C-c> ', '<lt>C-d><C-D><lt>C-d>', 's'),
cpomap('<lt>C-a><C-A><lt>C-a> ', '<lt>C-b><C-B><lt>C-b>', 's')},
meths.get_keymap('s'))
eq({cpomap('<lt>C-c><C-C><lt>C-c> ', '<lt>C-d><C-D><lt>C-d>', 'o'),
cpomap('<lt>C-a><C-A><lt>C-a> ', '<lt>C-b><C-B><lt>C-b>', 'o')},
meths.get_keymap('o'))
end
end)
it('always uses space for space and bar for bar', function()
local space_table = {
lhs='| |',
rhs='| |',
mode='n',
silent=0,
expr=0,
sid=0,
buffer=0,
nowait=0,
noremap=1,
}
command('nnoremap \\|<Char-0x20><Char-32><Space><Bar> \\|<Char-0x20><Char-32><Space> <Bar>')
eq({space_table}, meths.get_keymap('n'))
end)
end)

View File

@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local eq, clear, eval, execute, nvim, next_message =
helpers.eq, helpers.clear, helpers.eval, helpers.execute, helpers.nvim,
local eq, clear, eval, command, nvim, next_message =
helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.nvim,
helpers.next_message
local meths = helpers.meths
@@ -16,8 +16,8 @@ describe('notify', function()
it('sends the notification/args to the corresponding channel', function()
eval('rpcnotify('..channel..', "test-event", 1, 2, 3)')
eq({'notification', 'test-event', {1, 2, 3}}, next_message())
execute('au FileType lua call rpcnotify('..channel..', "lua!")')
execute('set filetype=lua')
command('au FileType lua call rpcnotify('..channel..', "lua!")')
command('set filetype=lua')
eq({'notification', 'lua!', {}}, next_message())
end)
end)

View File

@@ -1,12 +1,16 @@
-- Test server -> client RPC scenarios. Note: unlike `rpcnotify`, to evaluate
-- `rpcrequest` calls we need the client event loop to be running.
local helpers = require('test.functional.helpers')(after_each)
local Paths = require('test.config.paths')
local clear, nvim, eval = helpers.clear, helpers.nvim, helpers.eval
local eq, neq, run, stop = helpers.eq, helpers.neq, helpers.run, helpers.stop
local nvim_prog, command, funcs = helpers.nvim_prog, helpers.command, helpers.funcs
local source, next_message = helpers.source, helpers.next_message
local ok = helpers.ok
local meths = helpers.meths
local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv
local set_session = helpers.set_session
describe('server -> client', function()
local cid
@@ -16,6 +20,22 @@ describe('server -> client', function()
cid = nvim('get_api_info')[1]
end)
it('handles unexpected closed stream while preparing RPC response', function()
source([[
let g:_nvim_args = [v:progpath, '--embed', '-n', '-u', 'NONE', '-i', 'NONE', ]
let ch1 = jobstart(g:_nvim_args, {'rpc': v:true})
let child1_ch = rpcrequest(ch1, "nvim_get_api_info")[0]
call rpcnotify(ch1, 'nvim_eval', 'rpcrequest('.child1_ch.', "nvim_get_api_info")')
let ch2 = jobstart(g:_nvim_args, {'rpc': v:true})
let child2_ch = rpcrequest(ch2, "nvim_get_api_info")[0]
call rpcnotify(ch2, 'nvim_eval', 'rpcrequest('.child2_ch.', "nvim_get_api_info")')
call jobstop(ch1)
]])
eq(2, eval("1+1")) -- Still alive?
end)
describe('simple call', function()
it('works', function()
local function on_setup()
@@ -89,7 +109,28 @@ describe('server -> client', function()
end)
describe('requests and notifications interleaved', function()
-- This tests that the following scenario won't happen:
it('does not delay notifications during pending request', function()
local received = false
local function on_setup()
eq("retval", funcs.rpcrequest(cid, "doit"))
stop()
end
local function on_request(method)
if method == "doit" then
funcs.rpcnotify(cid, "headsup")
eq(true,received)
return "retval"
end
end
local function on_notification(method)
if method == "headsup" then
received = true
end
end
run(on_request, on_notification, on_setup)
end)
-- This tests the following scenario:
--
-- server->client [request ] (1)
-- client->server [request ] (2) triggered by (1)
@@ -104,40 +145,42 @@ describe('server -> client', function()
-- only deals with one server->client request at a time. (In other words,
-- the client cannot send a response to a request that is not at the top
-- of nvim's request stack).
--
-- But above scenario shoudn't happen by the way notifications are dealt in
-- Nvim: they are only sent after there are no pending server->client
-- request(the request stack fully unwinds). So (3) is only sent after the
-- client returns (6).
it('works', function()
local expected = 300
local notified = 0
pending('will close connection if not properly synchronized', function()
local function on_setup()
eq('notified!', eval('rpcrequest('..cid..', "notify")'))
end
local function on_request(method)
eq('notify', method)
eq(1, eval('rpcnotify('..cid..', "notification")'))
return 'notified!'
if method == "notify" then
eq(1, eval('rpcnotify('..cid..', "notification")'))
return 'notified!'
elseif method == "nested" then
-- do some busywork, so the first request will return
-- before this one
for _ = 1, 5 do
eq(2, eval("1+1"))
end
eq(1, eval('rpcnotify('..cid..', "nested_done")'))
return 'done!'
end
end
local function on_notification(method)
eq('notification', method)
if notified == expected then
stop()
return
if method == "notification" then
eq('done!', eval('rpcrequest('..cid..', "nested")'))
elseif method == "nested_done" then
-- this should never have been sent
ok(false)
end
notified = notified + 1
eq('notified!', eval('rpcrequest('..cid..', "notify")'))
end
run(on_request, on_notification, on_setup)
eq(expected, notified)
-- ignore disconnect failure, otherwise detected by after_each
clear()
end)
end)
describe('when the client is a recursive vim instance', function()
describe('recursive (child) nvim client', function()
if os.getenv("TRAVIS") and helpers.os_name() == "osx" then
-- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86.
pending("[Hangs on Travis macOS. #5002]", function() end)
@@ -151,7 +194,7 @@ describe('server -> client', function()
after_each(function() command('call rpcstop(vim)') end)
it('can send/recieve notifications and make requests', function()
it('can send/receive notifications and make requests', function()
nvim('command', "call rpcnotify(vim, 'vim_set_current_line', 'SOME TEXT')")
-- Wait for the notification to complete.
@@ -184,7 +227,7 @@ describe('server -> client', function()
end)
end)
describe('when using jobstart', function()
describe('jobstart()', function()
local jobid
before_each(function()
local channel = nvim('get_api_info')[1]
@@ -200,7 +243,7 @@ describe('server -> client', function()
\ 'rpc': v:true
\ }
]])
local lua_prog = arg[-1]
local lua_prog = Paths.test_lua_prg
meths.set_var("args", {lua_prog, 'test/functional/api/rpc_fixture.lua'})
jobid = eval("jobstart(g:args, g:job_opts)")
neq(0, 'jobid')
@@ -219,8 +262,101 @@ describe('server -> client', function()
eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n"))
eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_message())
funcs.rpcrequest(jobid, "exit")
eq({'notification', 'stderr', {0, {''}}}, next_message())
eq({'notification', 'exit', {0, 0}}, next_message())
end)
end)
describe('connecting to another (peer) nvim', function()
local function connect_test(server, mode, address)
local serverpid = funcs.getpid()
local client = spawn(nvim_argv)
set_session(client, true)
local clientpid = funcs.getpid()
neq(serverpid, clientpid)
local id = funcs.sockconnect(mode, address, {rpc=true})
ok(id > 0)
funcs.rpcrequest(id, 'nvim_set_current_line', 'hello')
local client_id = funcs.rpcrequest(id, 'nvim_get_api_info')[1]
set_session(server, true)
eq(serverpid, funcs.getpid())
eq('hello', meths.get_current_line())
-- method calls work both ways
funcs.rpcrequest(client_id, 'nvim_set_current_line', 'howdy!')
eq(id, funcs.rpcrequest(client_id, 'nvim_get_api_info')[1])
set_session(client, true)
eq(clientpid, funcs.getpid())
eq('howdy!', meths.get_current_line())
server:close()
client:close()
end
it('via named pipe', function()
local server = spawn(nvim_argv)
set_session(server)
local address = funcs.serverlist()[1]
local first = string.sub(address,1,1)
ok(first == '/' or first == '\\')
connect_test(server, 'pipe', address)
end)
it('via ipv4 address', function()
local server = spawn(nvim_argv)
set_session(server)
local address = funcs.serverstart("127.0.0.1:")
if #address == 0 then
pending('no ipv4 stack', function() end)
return
end
eq('127.0.0.1:', string.sub(address,1,10))
connect_test(server, 'tcp', address)
end)
it('via ipv6 address', function()
local server = spawn(nvim_argv)
set_session(server)
local address = funcs.serverstart('::1:')
if #address == 0 then
pending('no ipv6 stack', function() end)
return
end
eq('::1:', string.sub(address,1,4))
connect_test(server, 'tcp', address)
end)
it('via hostname', function()
local server = spawn(nvim_argv)
set_session(server)
local address = funcs.serverstart("localhost:")
eq('localhost:', string.sub(address,1,10))
connect_test(server, 'tcp', address)
end)
end)
describe('connecting to its own pipe address', function()
it('does not deadlock', function()
if not os.getenv("TRAVIS") and helpers.os_name() == "osx" then
-- It does, in fact, deadlock on QuickBuild. #6851
pending("deadlocks on QuickBuild", function() end)
return
end
local address = funcs.serverlist()[1]
local first = string.sub(address,1,1)
ok(first == '/' or first == '\\')
local serverpid = funcs.getpid()
local id = funcs.sockconnect('pipe', address, {rpc=true})
funcs.rpcrequest(id, 'nvim_set_current_line', 'hello')
eq('hello', meths.get_current_line())
eq(serverpid, funcs.rpcrequest(id, "nvim_eval", "getpid()"))
eq(id, funcs.rpcrequest(id, 'nvim_get_api_info')[1])
end)
end)
end)

View File

@@ -34,7 +34,7 @@ describe('api/tabpage', function()
eq(1, funcs.exists('t:lua'))
curtabmeths.del_var('lua')
eq(0, funcs.exists('t:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curtabmeths.del_var, 'lua'))
eq({false, 'Key does not exist: lua'}, meth_pcall(curtabmeths.del_var, 'lua'))
curtabmeths.set_var('lua', 1)
command('lockvar t:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.del_var, 'lua'))

View File

@@ -81,6 +81,36 @@ describe('api', function()
end)
end)
describe('nvim_execute_lua', function()
it('works', function()
meths.execute_lua('vim.api.nvim_set_var("test", 3)', {})
eq(3, meths.get_var('test'))
eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7}))
eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{}))
eq("xy", meths.execute_lua('return xx(...)', {'x','y'}))
end)
it('reports errors', function()
eq({false, 'Error loading lua: [string "<nvim>"]:1: '..
"'=' expected near '+'"},
meth_pcall(meths.execute_lua, 'a+*b', {}))
eq({false, 'Error loading lua: [string "<nvim>"]:1: '..
"unexpected symbol near '1'"},
meth_pcall(meths.execute_lua, '1+2', {}))
eq({false, 'Error loading lua: [string "<nvim>"]:1: '..
"unexpected symbol"},
meth_pcall(meths.execute_lua, 'aa=bb\0', {}))
eq({false, 'Error executing lua: [string "<nvim>"]:1: '..
"attempt to call global 'bork' (a nil value)"},
meth_pcall(meths.execute_lua, 'bork()', {}))
end)
end)
describe('nvim_input', function()
it("VimL error: does NOT fail, updates v:errmsg", function()
local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>")
@@ -119,7 +149,7 @@ describe('api', function()
eq(1, funcs.exists('g:lua'))
meths.del_var('lua')
eq(0, funcs.exists('g:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(meths.del_var, 'lua'))
eq({false, 'Key does not exist: lua'}, meth_pcall(meths.del_var, 'lua'))
meths.set_var('lua', 1)
command('lockvar lua')
eq({false, 'Key is locked: lua'}, meth_pcall(meths.del_var, 'lua'))
@@ -153,6 +183,28 @@ describe('api', function()
nvim('set_option', 'equalalways', false)
ok(not nvim('get_option', 'equalalways'))
end)
it('works to get global value of local options', function()
eq(false, nvim('get_option', 'lisp'))
eq(8, nvim('get_option', 'shiftwidth'))
end)
it('works to set global value of local options', function()
nvim('set_option', 'lisp', true)
eq(true, nvim('get_option', 'lisp'))
eq(false, helpers.curbuf('get_option', 'lisp'))
eq(nil, nvim('command_output', 'setglobal lisp?'):match('nolisp'))
eq('nolisp', nvim('command_output', 'setlocal lisp?'):match('nolisp'))
nvim('set_option', 'shiftwidth', 20)
eq('20', nvim('command_output', 'setglobal shiftwidth?'):match('%d+'))
eq('8', nvim('command_output', 'setlocal shiftwidth?'):match('%d+'))
end)
it('most window-local options have no global value', function()
local status, err = pcall(nvim, 'get_option', 'foldcolumn')
eq(false, status)
ok(err:match('Invalid option name') ~= nil)
end)
end)
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
@@ -199,6 +251,170 @@ describe('api', function()
end)
end)
describe('nvim_get_mode', function()
it("during normal-mode `g` returns blocking=true", function()
nvim("input", "o") -- add a line
eq({mode='i', blocking=false}, nvim("get_mode"))
nvim("input", [[<C-\><C-N>]])
eq(2, nvim("eval", "line('.')"))
eq({mode='n', blocking=false}, nvim("get_mode"))
nvim("input", "g")
eq({mode='n', blocking=true}, nvim("get_mode"))
nvim("input", "k") -- complete the operator
eq(1, nvim("eval", "line('.')")) -- verify the completed operator
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
it("returns the correct result multiple consecutive times", function()
for _ = 1,5 do
eq({mode='n', blocking=false}, nvim("get_mode"))
end
nvim("input", "g")
for _ = 1,4 do
eq({mode='n', blocking=true}, nvim("get_mode"))
end
nvim("input", "g")
for _ = 1,7 do
eq({mode='n', blocking=false}, nvim("get_mode"))
end
end)
it("during normal-mode CTRL-W, returns blocking=true", function()
nvim("input", "<C-W>")
eq({mode='n', blocking=true}, nvim("get_mode"))
nvim("input", "s") -- complete the operator
eq(2, nvim("eval", "winnr('$')")) -- verify the completed operator
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
it("during press-enter prompt returns blocking=true", function()
eq({mode='n', blocking=false}, nvim("get_mode"))
command("echom 'msg1'")
command("echom 'msg2'")
command("echom 'msg3'")
command("echom 'msg4'")
command("echom 'msg5'")
eq({mode='n', blocking=false}, nvim("get_mode"))
nvim("input", ":messages<CR>")
eq({mode='r', blocking=true}, nvim("get_mode"))
end)
it("during getchar() returns blocking=false", function()
nvim("input", ":let g:test_input = nr2char(getchar())<CR>")
-- Events are enabled during getchar(), RPC calls are *not* blocked. #5384
eq({mode='n', blocking=false}, nvim("get_mode"))
eq(0, nvim("eval", "exists('g:test_input')"))
nvim("input", "J")
eq("J", nvim("eval", "g:test_input"))
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
-- TODO: bug #6247#issuecomment-286403810
it("batched with input", function()
eq({mode='n', blocking=false}, nvim("get_mode"))
command("echom 'msg1'")
command("echom 'msg2'")
command("echom 'msg3'")
command("echom 'msg4'")
command("echom 'msg5'")
local req = {
{'nvim_get_mode', {}},
{'nvim_input', {':messages<CR>'}},
{'nvim_get_mode', {}},
{'nvim_eval', {'1'}},
}
eq({ { {mode='n', blocking=false},
13,
{mode='n', blocking=false}, -- TODO: should be blocked=true ?
1 },
NIL}, meths.call_atomic(req))
eq({mode='r', blocking=true}, nvim("get_mode"))
end)
it("during insert-mode map-pending, returns blocking=true #6166", function()
command("inoremap xx foo")
nvim("input", "ix")
eq({mode='i', blocking=true}, nvim("get_mode"))
end)
it("during normal-mode gU, returns blocking=false #6166", function()
nvim("input", "gu")
eq({mode='no', blocking=false}, nvim("get_mode"))
end)
end)
describe('RPC (K_EVENT) #6166', function()
it('does not complete ("interrupt") normal-mode operator-pending', function()
helpers.insert([[
FIRST LINE
SECOND LINE]])
nvim('input', 'gg')
nvim('input', 'gu')
-- Make any RPC request (can be non-async: op-pending does not block).
nvim('get_current_buf')
-- Buffer should not change.
helpers.expect([[
FIRST LINE
SECOND LINE]])
-- Now send input to complete the operator.
nvim('input', 'j')
helpers.expect([[
first line
second line]])
end)
it('does not complete ("interrupt") `d` #3732', function()
local screen = Screen.new(20, 4)
screen:attach()
command('set listchars=eol:$')
command('set list')
feed('ia<cr>b<cr>c<cr><Esc>kkk')
feed('d')
-- Make any RPC request (can be non-async: op-pending does not block).
nvim('get_current_buf')
screen:expect([[
^a$ |
b$ |
c$ |
|
]])
end)
it('does not complete ("interrupt") normal-mode map-pending', function()
command("nnoremap dd :let g:foo='it worked...'<CR>")
helpers.insert([[
FIRST LINE
SECOND LINE]])
nvim('input', 'gg')
nvim('input', 'd')
-- Make any RPC request (must be async, because map-pending blocks).
nvim('get_api_info')
-- Send input to complete the mapping.
nvim('input', 'd')
helpers.expect([[
FIRST LINE
SECOND LINE]])
eq('it worked...', helpers.eval('g:foo'))
end)
it('does not complete ("interrupt") insert-mode map-pending', function()
command('inoremap xx foo')
command('set timeoutlen=9999')
helpers.insert([[
FIRST LINE
SECOND LINE]])
nvim('input', 'ix')
-- Make any RPC request (must be async, because map-pending blocks).
nvim('get_api_info')
-- Send input to complete the mapping.
nvim('input', 'x')
helpers.expect([[
FIRST LINE
SECOND LINfooE]])
end)
end)
describe('nvim_replace_termcodes', function()
it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function()
eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true))
@@ -219,6 +435,27 @@ describe('api', function()
eq('\128\253\44', helpers.nvim('replace_termcodes',
'<LeftMouse>', true, true, true))
end)
it('converts keycodes', function()
eq('\nx\27x\rx<x', helpers.nvim('replace_termcodes',
'<NL>x<Esc>x<CR>x<lt>x', true, true, true))
end)
it('does not convert keycodes if special=false', function()
eq('<NL>x<Esc>x<CR>x<lt>x', helpers.nvim('replace_termcodes',
'<NL>x<Esc>x<CR>x<lt>x', true, true, false))
end)
it('does not crash when transforming an empty string', function()
-- Actually does not test anything, because current code will use NULL for
-- an empty string.
--
-- Problem here is that if String argument has .data in allocated memory
-- then `return str` in vim_replace_termcodes body will make Neovim free
-- `str.data` twice: once when freeing arguments, then when freeing return
-- value.
eq('', meths.replace_termcodes('', true, true, true))
end)
end)
describe('nvim_feedkeys', function()
@@ -227,13 +464,13 @@ describe('api', function()
-- notice the special char(…) \xe2\80\xa6
nvim('feedkeys', ':let x1="…"\n', '', true)
-- Both replace_termcodes and feedkeys escape \x80
-- Both nvim_replace_termcodes and nvim_feedkeys escape \x80
local inp = helpers.nvim('replace_termcodes', ':let x2="…"<CR>', true, true, true)
nvim('feedkeys', inp, '', true)
nvim('feedkeys', inp, '', true) -- escape_csi=true
-- Disabling CSI escaping in feedkeys
-- nvim_feedkeys with CSI escaping disabled
inp = helpers.nvim('replace_termcodes', ':let x3="…"<CR>', true, true, true)
nvim('feedkeys', inp, '', false)
nvim('feedkeys', inp, '', false) -- escape_csi=false
helpers.stop()
end
@@ -390,7 +627,7 @@ describe('api', function()
eq(5, meths.get_var('avar'))
end)
it('throws error on malformated arguments', function()
it('throws error on malformed arguments', function()
local req = {
{'nvim_set_var', {'avar', 1}},
{'nvim_set_var'},
@@ -417,20 +654,57 @@ describe('api', function()
}
status, err = pcall(meths.call_atomic, req)
eq(false, status)
ok(err:match('args must be Array') ~= nil)
ok(err:match('Args must be Array') ~= nil)
-- call before was done, but not after
eq(1, meths.get_var('avar'))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
end)
end)
describe('list_runtime_paths', function()
it('returns nothing with empty &runtimepath', function()
meths.set_option('runtimepath', '')
eq({}, meths.list_runtime_paths())
end)
it('returns single runtimepath', function()
meths.set_option('runtimepath', 'a')
eq({'a'}, meths.list_runtime_paths())
end)
it('returns two runtimepaths', function()
meths.set_option('runtimepath', 'a,b')
eq({'a', 'b'}, meths.list_runtime_paths())
end)
it('returns empty strings when appropriate', function()
meths.set_option('runtimepath', 'a,,b')
eq({'a', '', 'b'}, meths.list_runtime_paths())
meths.set_option('runtimepath', ',a,b')
eq({'', 'a', 'b'}, meths.list_runtime_paths())
meths.set_option('runtimepath', 'a,b,')
eq({'a', 'b', ''}, meths.list_runtime_paths())
end)
it('truncates too long paths', function()
local long_path = ('/a'):rep(8192)
meths.set_option('runtimepath', long_path)
local paths_list = meths.list_runtime_paths()
neq({long_path}, paths_list)
eq({long_path:sub(1, #(paths_list[1]))}, paths_list)
end)
end)
it('can throw exceptions', function()
local status, err = pcall(nvim, 'get_option', 'invalid-option')
eq(false, status)
ok(err:match('Invalid option name') ~= nil)
end)
it("doesn't leak memory on incorrect argument types", function()
it('does not truncate error message <1 MB #5984', function()
local very_long_name = 'A'..('x'):rep(10000)..'Z'
local status, err = pcall(nvim, 'get_option', very_long_name)
eq(false, status)
eq(very_long_name, err:match('Ax+Z?'))
end)
it("does not leak memory on incorrect argument types", function()
local status, err = pcall(nvim, 'set_current_dir',{'not', 'a', 'dir'})
eq(false, status)
ok(err:match(': Wrong type for argument 1, expecting String') ~= nil)

View File

@@ -9,6 +9,7 @@ local funcs = helpers.funcs
local request = helpers.request
local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
local meths = helpers.meths
local command = helpers.command
-- check if str is visible at the beginning of some line
@@ -55,6 +56,12 @@ describe('api/win', function()
eq('typing\n some dumb text', curbuf_contents())
end)
it('does not leak memory when using invalid window ID with invalid pos',
function()
eq({false, 'Invalid window id'},
meth_pcall(meths.win_set_cursor, 1, {"b\na"}))
end)
it('updates the screen, and also when the window is unfocused', function()
insert("prologue")
feed('100o<esc>')
@@ -139,7 +146,7 @@ describe('api/win', function()
eq(1, funcs.exists('w:lua'))
curwinmeths.del_var('lua')
eq(0, funcs.exists('w:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curwinmeths.del_var, 'lua'))
eq({false, 'Key does not exist: lua'}, meth_pcall(curwinmeths.del_var, 'lua'))
curwinmeths.set_var('lua', 1)
command('lockvar w:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.del_var, 'lua'))

View File

@@ -1,9 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
local clear = helpers.clear
local meths = helpers.meths
local expect = helpers.expect
local command = helpers.command
local exc_exec = helpers.exc_exec
local curbufmeths = helpers.curbufmeths
describe('autocmds:', function()
before_each(clear)
@@ -33,4 +37,22 @@ describe('autocmds:', function()
it('v:vim_did_enter is 1 after VimEnter', function()
eq(1, eval('v:vim_did_enter'))
end)
describe('BufLeave autocommand', function()
it('can wipe out the buffer created by :edit which triggered autocmd',
function()
meths.set_option('hidden', true)
curbufmeths.set_lines(0, 1, false, {
'start of test file xx',
'end of test file xx'})
command('autocmd BufLeave * bwipeout yy')
eq('Vim(edit):E143: Autocommands unexpectedly deleted new buffer yy',
exc_exec('edit yy'))
expect([[
start of test file xx
end of test file xx]])
end)
end)
end)

View File

@@ -4,7 +4,6 @@ local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
local execute = helpers.execute
local request = helpers.request
local source = helpers.source
@@ -28,7 +27,7 @@ describe('autocmd BufEnter', function()
endtry
endfunction
]])
execute("call Test()")
command("call Test()")
eq(1, eval("exists('g:dir_bufenter')")) -- Did BufEnter for the directory.
eq(2, eval("bufnr('%')")) -- Switched to the dir buffer.
end)

View File

@@ -0,0 +1,117 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local expect = helpers.expect
local next_msg = helpers.next_message
local feed = helpers.feed
local meths = helpers.meths
describe('cmdline autocommands', function()
local channel
before_each(function()
clear()
channel = meths.get_api_info()[1]
meths.set_var("channel",channel)
command("autocmd CmdlineEnter * call rpcnotify(g:channel, 'CmdlineEnter', v:event)")
command("autocmd CmdlineLeave * call rpcnotify(g:channel, 'CmdlineLeave', v:event)")
command("autocmd CmdWinEnter * call rpcnotify(g:channel, 'CmdWinEnter', v:event)")
command("autocmd CmdWinLeave * call rpcnotify(g:channel, 'CmdWinLeave', v:event)")
end)
it('works', function()
feed(':')
eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg())
feed('redraw<cr>')
eq({'notification', 'CmdlineLeave',
{{cmdtype=':', cmdlevel=1, abort=false}}}, next_msg())
feed(':')
eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg())
-- note: feed('bork<c-c>') might not consume 'bork'
-- due to out-of-band interupt handling
feed('bork<esc>')
eq({'notification', 'CmdlineLeave',
{{cmdtype=':', cmdlevel=1, abort=true}}}, next_msg())
end)
it('can abort cmdline', function()
command("autocmd CmdlineLeave * let v:event.abort= len(getcmdline())>15")
feed(":put! ='ok'<cr>")
expect([[
ok
]])
feed(":put! ='blah blah'<cr>")
expect([[
ok
]])
end)
it('handles errors correctly', function()
clear()
local screen = Screen.new(72, 8)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[3] = {bold = true, foreground = Screen.colors.SeaGreen4},
})
command("autocmd CmdlineEnter * echoerr 'FAIL'")
command("autocmd CmdlineLeave * echoerr 'very error'")
feed(':')
screen:expect([[
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
: |
{2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} |
:^ |
]])
feed("put ='lorem ipsum'<cr>")
screen:expect([[
{1:~ }|
{1:~ }|
: |
{2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} |
:put ='lorem ipsum' |
{2:E5500: autocmd has thrown an exception: Vim(echoerr):very error} |
|
{3:Press ENTER or type command to continue}^ |
]])
feed('<cr>')
screen:expect([[
|
^lorem ipsum |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
end)
it('works with nested cmdline', function()
feed(':')
eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg())
feed('<c-r>=')
eq({'notification', 'CmdlineEnter', {{cmdtype='=', cmdlevel=2}}}, next_msg())
feed('<c-f>')
eq({'notification', 'CmdWinEnter', {{}}}, next_msg())
feed(':')
eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=3}}}, next_msg())
feed('<c-c>')
eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=3, abort=true}}}, next_msg())
feed('<c-c>')
eq({'notification', 'CmdWinLeave', {{}}}, next_msg())
feed('1+2<cr>')
eq({'notification', 'CmdlineLeave', {{cmdtype='=', cmdlevel=2, abort=false}}}, next_msg())
end)
end)

View File

@@ -14,17 +14,52 @@ describe('TermClose event', function()
nvim('set_option', 'shellcmdflag', 'EXE')
end)
local function eq_err(expected, actual)
if expected ~= actual then
error('expected: '..tostring(expected)..', actual: '..tostring(actual))
end
end
it('triggers when terminal job ends', function()
it('triggers when fast-exiting terminal job stops', function()
command('autocmd TermClose * let g:test_termclose = 23')
command('terminal')
command('call jobstop(b:terminal_job_id)')
retry(nil, nil, function() eq_err(23, eval('g:test_termclose')) end)
retry(nil, nil, function() eq(23, eval('g:test_termclose')) end)
end)
it('triggers when long-running terminal job gets stopped', function()
nvim('set_option', 'shell', 'sh')
command('autocmd TermClose * let g:test_termclose = 23')
command('terminal')
command('call jobstop(b:terminal_job_id)')
retry(nil, nil, function() eq(23, eval('g:test_termclose')) end)
end)
it('kills job trapping SIGTERM', function()
nvim('set_option', 'shell', 'sh')
nvim('set_option', 'shellcmdflag', '-c')
command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]]
.. [[ 'on_stdout': {-> execute('let g:test_job_started = 1')}, ]]
.. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]])
retry(nil, nil, function() eq(1, eval('get(g:, "test_job_started", 0)')) end)
local start = os.time()
command('call jobstop(g:test_job)')
retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end)
local duration = os.time() - start
eq(2, duration)
end)
it('kills pty job trapping SIGHUP and SIGTERM', function()
nvim('set_option', 'shell', 'sh')
nvim('set_option', 'shellcmdflag', '-c')
command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]]
.. [[ 'pty': 1,]]
.. [[ 'on_stdout': {-> execute('let g:test_job_started = 1')}, ]]
.. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]])
retry(nil, nil, function() eq(1, eval('get(g:, "test_job_started", 0)')) end)
local start = os.time()
command('call jobstop(g:test_job)')
retry(nil, nil, function() eq(1, eval('get(g:, "test_job_exited", 0)')) end)
local duration = os.time() - start
-- nvim starts sending kill after 2*KILL_TIMEOUT_MS
helpers.ok(4 <= duration)
helpers.ok(duration <= 7) -- <= 4 + delta because of slow CI
end)
it('reports the correct <abuf>', function()
@@ -35,12 +70,12 @@ describe('TermClose event', function()
eq(2, eval('bufnr("%")'))
command('terminal')
retry(nil, nil, function() eq_err(3, eval('bufnr("%")')) end)
retry(nil, nil, function() eq(3, eval('bufnr("%")')) end)
command('buffer 1')
retry(nil, nil, function() eq_err(1, eval('bufnr("%")')) end)
retry(nil, nil, function() eq(1, eval('bufnr("%")')) end)
command('3bdelete!')
retry(nil, nil, function() eq_err('3', eval('g:abuf')) end)
retry(nil, nil, function() eq('3', eval('g:abuf')) end)
end)
end)

View File

@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq
local feed, execute, expect, command = helpers.feed, helpers.execute, helpers.expect, helpers.command
local feed, command, expect = helpers.feed, helpers.command, helpers.expect
local curbufmeths, funcs, neq = helpers.curbufmeths, helpers.funcs, helpers.neq
describe('TextYankPost', function()
@@ -8,11 +8,11 @@ describe('TextYankPost', function()
clear()
-- emulate the clipboard so system clipboard isn't affected
execute('let &rtp = "test/functional/fixtures,".&rtp')
command('let &rtp = "test/functional/fixtures,".&rtp')
execute('let g:count = 0')
execute('autocmd TextYankPost * let g:event = copy(v:event)')
execute('autocmd TextYankPost * let g:count += 1')
command('let g:count = 0')
command('autocmd TextYankPost * let g:event = copy(v:event)')
command('autocmd TextYankPost * let g:count += 1')
curbufmeths.set_lines(0, -1, true, {
'foo\0bar',
@@ -61,27 +61,27 @@ describe('TextYankPost', function()
regtype = 'V'
}, eval('g:event'))
execute('set debug=msg')
command('set debug=msg')
-- the regcontents should not be changed without copy.
local status, err = pcall(command,'call extend(g:event.regcontents, ["more text"])')
eq(status,false)
neq(nil, string.find(err, ':E742:'))
-- can't mutate keys inside the autocommand
execute('autocmd! TextYankPost * let v:event.regcontents = 0')
command('autocmd! TextYankPost * let v:event.regcontents = 0')
status, err = pcall(command,'normal yy')
eq(status,false)
neq(nil, string.find(err, ':E46:'))
-- can't add keys inside the autocommand
execute('autocmd! TextYankPost * let v:event.mykey = 0')
command('autocmd! TextYankPost * let v:event.mykey = 0')
status, err = pcall(command,'normal yy')
eq(status,false)
neq(nil, string.find(err, ':E742:'))
end)
it('is not invoked recursively', function()
execute('autocmd TextYankPost * normal "+yy')
command('autocmd TextYankPost * normal "+yy')
feed('yy')
eq({
operator = 'y',
@@ -134,7 +134,7 @@ describe('TextYankPost', function()
feed('"_yy')
eq(0, eval('g:count'))
execute('delete _')
command('delete _')
eq(0, eval('g:count'))
end)
@@ -155,7 +155,7 @@ describe('TextYankPost', function()
regtype = 'V'
}, eval('g:event'))
execute("set clipboard=unnamed")
command("set clipboard=unnamed")
-- regname still shows the name the user requested
feed('yy')
@@ -176,7 +176,7 @@ describe('TextYankPost', function()
end)
it('works with Ex commands', function()
execute('1delete +')
command('1delete +')
eq({
operator = 'd',
regcontents = { 'foo\nbar' },
@@ -185,7 +185,7 @@ describe('TextYankPost', function()
}, eval('g:event'))
eq(1, eval('g:count'))
execute('yank')
command('yank')
eq({
operator = 'y',
regcontents = { 'baz text' },
@@ -194,7 +194,7 @@ describe('TextYankPost', function()
}, eval('g:event'))
eq(2, eval('g:count'))
execute('normal yw')
command('normal yw')
eq({
operator = 'y',
regcontents = { 'baz ' },
@@ -203,7 +203,7 @@ describe('TextYankPost', function()
}, eval('g:event'))
eq(3, eval('g:count'))
execute('normal! dd')
command('normal! dd')
eq({
operator = 'd',
regcontents = { 'baz text' },

View File

@@ -3,7 +3,9 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect, eq, eval = helpers.execute, helpers.expect, helpers.eq, helpers.eval
local feed_command, expect, eq, eval = helpers.feed_command, helpers.expect, helpers.eq, helpers.eval
local command = helpers.command
local meths = helpers.meths
local function basic_register_test(noblock)
insert("some words")
@@ -80,25 +82,112 @@ local function basic_register_test(noblock)
expect("two and three and one")
end
describe('the unnamed register', function()
describe('clipboard', function()
before_each(clear)
it('works without provider', function()
it('unnamed register works without provider', function()
eq('"', eval('v:register'))
basic_register_test()
end)
it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184',
function()
local screen = Screen.new(72, 4)
screen:attach()
command("let g:clipboard = 'bogus'")
feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END')
screen:expect([[
^ |
~ |
~ |
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
]], nil, {{bold = true, foreground = Screen.colors.Blue}})
end)
it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184',
function()
local screen = Screen.new(72, 4)
screen:attach()
command("let g:clipboard = 'bogus'")
feed_command('redir @+> | bogus_cmd | redir END')
screen:expect([[
~ |
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
E492: Not an editor command: bogus_cmd | redir END |
Press ENTER or type command to continue^ |
]], nil, {{bold = true, foreground = Screen.colors.Blue}})
end)
it('invalid g:clipboard shows hint if :redir is not active', function()
command("let g:clipboard = 'bogus'")
eq('', eval('provider#clipboard#Executable()'))
eq('clipboard: invalid g:clipboard', eval('provider#clipboard#Error()'))
local screen = Screen.new(72, 4)
screen:attach()
command("let g:clipboard = 'bogus'")
-- Explicit clipboard attempt, should show a hint message.
feed_command('let @+="foo"')
screen:expect([[
^ |
~ |
~ |
clipboard: No provider. Try ":checkhealth" or ":h clipboard". |
]], nil, {{bold = true, foreground = Screen.colors.Blue}})
end)
it('valid g:clipboard', function()
-- provider#clipboard#Executable() only checks the structure.
meths.set_var('clipboard', {
['name'] = 'clippy!',
['copy'] = { ['+'] = 'any command', ['*'] = 'some other' },
['paste'] = { ['+'] = 'any command', ['*'] = 'some other' },
})
eq('clippy!', eval('provider#clipboard#Executable()'))
eq('', eval('provider#clipboard#Error()'))
end)
end)
describe('clipboard usage', function()
describe('clipboard', function()
local function reset(...)
clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...)
end
before_each(function()
reset()
execute('call getreg("*")') -- force load of provider
feed_command('call getreg("*")') -- force load of provider
end)
it('has independent "* and unnamed registers per default', function()
it('`:redir @+>` invokes clipboard once-per-message', function()
eq(0, eval("g:clip_called_set"))
feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END')
-- Assuming CONTRIBUTING.md has >100 lines.
assert(eval("g:clip_called_set") > 100)
end)
it('`:redir @">` does NOT invoke clipboard', function()
-- :redir to a non-clipboard register, with `:set clipboard=unnamed` does
-- NOT propagate to the clipboard. This is consistent with Vim.
command("set clipboard=unnamedplus")
eq(0, eval("g:clip_called_set"))
feed_command('redir @"> | :silent echo system("cat CONTRIBUTING.md") | redir END')
eq(0, eval("g:clip_called_set"))
end)
it('`:redir @+>|bogus_cmd|redir END` must not recurse #7184',
function()
local screen = Screen.new(72, 4)
screen:attach()
feed_command('redir @+> | bogus_cmd | redir END')
screen:expect([[
^ |
~ |
~ |
E492: Not an editor command: bogus_cmd | redir END |
]], nil, {{bold = true, foreground = Screen.colors.Blue}})
end)
it('has independent "* and unnamed registers by default', function()
insert("some words")
feed('^"*dwdw"*P')
expect('some ')
@@ -139,9 +228,9 @@ describe('clipboard usage', function()
eq({'some\ntext', '\nvery binary\n'}, eval("getreg('*', 1, 1)"))
end)
it('support autodectection of regtype', function()
execute("let g:test_clip['*'] = ['linewise stuff','']")
execute("let g:test_clip['+'] = ['charwise','stuff']")
it('autodetects regtype', function()
feed_command("let g:test_clip['*'] = ['linewise stuff','']")
feed_command("let g:test_clip['+'] = ['charwise','stuff']")
eq("V", eval("getregtype('*')"))
eq("v", eval("getregtype('+')"))
insert("just some text")
@@ -156,7 +245,7 @@ describe('clipboard usage', function()
insert([[
much
text]])
execute("let g:test_clip['*'] = [['very','block'],'b']")
feed_command("let g:test_clip['*'] = [['very','block'],'b']")
feed('gg"*P')
expect([[
very much
@@ -169,16 +258,16 @@ describe('clipboard usage', function()
eq({{' much', 'ktext', ''}, 'b'}, eval("g:test_clip['+']"))
end)
it('supports setreg', function()
execute('call setreg("*", "setted\\ntext", "c")')
execute('call setreg("+", "explicitly\\nlines", "l")')
it('supports setreg()', function()
feed_command('call setreg("*", "setted\\ntext", "c")')
feed_command('call setreg("+", "explicitly\\nlines", "l")')
feed('"+P"*p')
expect([[
esetted
textxplicitly
lines
]])
execute('call setreg("+", "blocky\\nindeed", "b")')
feed_command('call setreg("+", "blocky\\nindeed", "b")')
feed('"+p')
expect([[
esblockyetted
@@ -187,14 +276,14 @@ describe('clipboard usage', function()
]])
end)
it('supports let @+ (issue #1427)', function()
execute("let @+ = 'some'")
execute("let @* = ' other stuff'")
it('supports :let @+ (issue #1427)', function()
feed_command("let @+ = 'some'")
feed_command("let @* = ' other stuff'")
eq({{'some'}, 'v'}, eval("g:test_clip['+']"))
eq({{' other stuff'}, 'v'}, eval("g:test_clip['*']"))
feed('"+p"*p')
expect('some other stuff')
execute("let @+ .= ' more'")
feed_command("let @+ .= ' more'")
feed('dd"+p')
expect('some more')
end)
@@ -202,7 +291,7 @@ describe('clipboard usage', function()
it('pastes unnamed register if the provider fails', function()
insert('the text')
feed('yy')
execute("let g:cliperror = 1")
feed_command("let g:cliperror = 1")
feed('"*p')
expect([[
the text
@@ -214,7 +303,7 @@ describe('clipboard usage', function()
-- the basic behavior of unnamed register should be the same
-- even when handled by clipboard provider
before_each(function()
execute('set clipboard=unnamed')
feed_command('set clipboard=unnamed')
end)
it('works', function()
@@ -222,7 +311,7 @@ describe('clipboard usage', function()
end)
it('works with pure text clipboard', function()
execute("let g:cliplossy = 1")
feed_command("let g:cliplossy = 1")
-- expect failure for block mode
basic_register_test(true)
end)
@@ -237,7 +326,7 @@ describe('clipboard usage', function()
-- "+ shouldn't have changed
eq({''}, eval("g:test_clip['+']"))
execute("let g:test_clip['*'] = ['linewise stuff','']")
feed_command("let g:test_clip['*'] = ['linewise stuff','']")
feed('p')
expect([[
words
@@ -247,7 +336,7 @@ describe('clipboard usage', function()
it('does not clobber "0 when pasting', function()
insert('a line')
feed('yy')
execute("let g:test_clip['*'] = ['b line','']")
feed_command("let g:test_clip['*'] = ['b line','']")
feed('"0pp"0p')
expect([[
a line
@@ -258,20 +347,20 @@ describe('clipboard usage', function()
it('supports v:register and getreg() without parameters', function()
eq('*', eval('v:register'))
execute("let g:test_clip['*'] = [['some block',''], 'b']")
feed_command("let g:test_clip['*'] = [['some block',''], 'b']")
eq('some block', eval('getreg()'))
eq('\02210', eval('getregtype()'))
end)
it('yanks visual selection when pasting', function()
insert("indeed visual")
execute("let g:test_clip['*'] = [['clipboard'], 'c']")
feed_command("let g:test_clip['*'] = [['clipboard'], 'c']")
feed("viwp")
eq({{'visual'}, 'v'}, eval("g:test_clip['*']"))
expect("indeed clipboard")
-- explicit "* should do the same
execute("let g:test_clip['*'] = [['star'], 'c']")
feed_command("let g:test_clip['*'] = [['star'], 'c']")
feed('viw"*p')
eq({{'clipboard'}, 'v'}, eval("g:test_clip['*']"))
expect("indeed star")
@@ -280,7 +369,7 @@ describe('clipboard usage', function()
it('unamed operations work even if the provider fails', function()
insert('the text')
feed('yy')
execute("let g:cliperror = 1")
feed_command("let g:cliperror = 1")
feed('p')
expect([[
the text
@@ -294,20 +383,27 @@ describe('clipboard usage', function()
match
text
]])
execute('g/match/d')
feed_command('g/match/d')
eq('match\n', eval('getreg("*")'))
feed('u')
eval('setreg("*", "---")')
execute('g/test/')
feed_command('g/test/')
feed('<esc>')
eq('---', eval('getreg("*")'))
end)
it('works in the cmdline window', function()
feed('q:itext<esc>yy')
eq({{'text', ''}, 'V'}, eval("g:test_clip['*']"))
command("let g:test_clip['*'] = [['star'], 'c']")
feed('p')
eq('textstar', meths.get_current_line())
end)
end)
describe('with clipboard=unnamedplus', function()
describe('clipboard=unnamedplus', function()
before_each(function()
execute('set clipboard=unnamedplus')
feed_command('set clipboard=unnamedplus')
end)
it('links the "+ and unnamed registers', function()
@@ -320,13 +416,13 @@ describe('clipboard usage', function()
-- "* shouldn't have changed
eq({''}, eval("g:test_clip['*']"))
execute("let g:test_clip['+'] = ['three']")
feed_command("let g:test_clip['+'] = ['three']")
feed('p')
expect('twothree')
end)
it('and unnamed, yanks to both', function()
execute('set clipboard=unnamedplus,unnamed')
feed_command('set clipboard=unnamedplus,unnamed')
insert([[
really unnamed
text]])
@@ -340,8 +436,8 @@ describe('clipboard usage', function()
-- unnamedplus takes predecence when pasting
eq('+', eval('v:register'))
execute("let g:test_clip['+'] = ['the plus','']")
execute("let g:test_clip['*'] = ['the star','']")
feed_command("let g:test_clip['+'] = ['the plus','']")
feed_command("let g:test_clip['*'] = ['the star','']")
feed("p")
expect([[
text
@@ -349,6 +445,7 @@ describe('clipboard usage', function()
really unnamed
the plus]])
end)
it('is updated on global changes', function()
insert([[
text
@@ -356,11 +453,11 @@ describe('clipboard usage', function()
match
text
]])
execute('g/match/d')
feed_command('g/match/d')
eq('match\n', eval('getreg("+")'))
feed('u')
eval('setreg("+", "---")')
execute('g/test/')
feed_command('g/test/')
feed('<esc>')
eq('---', eval('getreg("+")'))
end)
@@ -375,13 +472,13 @@ describe('clipboard usage', function()
it('supports :put', function()
insert("a line")
execute("let g:test_clip['*'] = ['some text']")
execute("let g:test_clip['+'] = ['more', 'text', '']")
execute(":put *")
feed_command("let g:test_clip['*'] = ['some text']")
feed_command("let g:test_clip['+'] = ['more', 'text', '']")
feed_command(":put *")
expect([[
a line
some text]])
execute(":put +")
feed_command(":put +")
expect([[
a line
some text
@@ -392,9 +489,9 @@ describe('clipboard usage', function()
it('supports "+ and "* in registers', function()
local screen = Screen.new(60, 10)
screen:attach()
execute("let g:test_clip['*'] = ['some', 'star data','']")
execute("let g:test_clip['+'] = ['such', 'plus', 'stuff']")
execute("registers")
feed_command("let g:test_clip['*'] = ['some', 'star data','']")
feed_command("let g:test_clip['+'] = ['such', 'plus', 'stuff']")
feed_command("registers")
screen:expect([[
~ |
~ |
@@ -418,17 +515,17 @@ describe('clipboard usage', function()
insert('s/s/t/')
feed('gg"*y$:<c-r>*<cr>')
expect('t/s/t/')
execute("let g:test_clip['*'] = ['s/s/u']")
feed_command("let g:test_clip['*'] = ['s/s/u']")
feed(':<c-r>*<cr>')
expect('t/u/t/')
end)
it('supports :redir @*>', function()
execute("let g:test_clip['*'] = ['stuff']")
execute('redir @*>')
feed_command("let g:test_clip['*'] = ['stuff']")
feed_command('redir @*>')
-- it is made empty
eq({{''}, 'v'}, eval("g:test_clip['*']"))
execute('let g:test = doesnotexist')
feed_command('let g:test = doesnotexist')
feed('<cr>')
eq({{
'',
@@ -436,7 +533,7 @@ describe('clipboard usage', function()
'E121: Undefined variable: doesnotexist',
'E15: Invalid expression: doesnotexist',
}, 'v'}, eval("g:test_clip['*']"))
execute(':echo "Howdy!"')
feed_command(':echo "Howdy!"')
eq({{
'',
'',
@@ -448,7 +545,7 @@ describe('clipboard usage', function()
end)
it('handles middleclick correctly', function()
execute('set mouse=a')
feed_command('set mouse=a')
local screen = Screen.new(30, 5)
screen:attach()
@@ -471,7 +568,7 @@ describe('clipboard usage', function()
the a target]])
-- on error, fall back to unnamed register
execute("let g:cliperror = 1")
feed_command("let g:cliperror = 1")
feed('<MiddleMouse><6,1>')
expect([[
the source

View File

@@ -0,0 +1,266 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq,
helpers.eval, helpers.next_message, helpers.ok, helpers.source
local command, funcs, meths = helpers.command, helpers.funcs, helpers.meths
local sleep = helpers.sleep
local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv
local set_session = helpers.set_session
local nvim_prog = helpers.nvim_prog
local retry = helpers.retry
local expect_twostreams = helpers.expect_twostreams
describe('channels', function()
local init = [[
function! Normalize(data) abort
" Windows: remove ^M
return type([]) == type(a:data)
\ ? map(a:data, 'substitute(v:val, "\r", "", "g")')
\ : a:data
endfunction
function! OnEvent(id, data, event) dict
call rpcnotify(1, a:event, a:id, a:data)
endfunction
]]
before_each(function()
clear()
source(init)
end)
pending('can connect to socket', function()
local server = spawn(nvim_argv)
set_session(server)
local address = funcs.serverlist()[1]
local client = spawn(nvim_argv)
set_session(client, true)
source(init)
meths.set_var('address', address)
command("let g:id = sockconnect('pipe', address, {'on_data':'OnEvent'})")
local id = eval("g:id")
ok(id > 0)
command("call chansend(g:id, msgpackdump([[2,'nvim_set_var',['code',23]]]))")
set_session(server, true)
retry(nil, 1000, function()
eq(23, meths.get_var('code'))
end)
set_session(client, true)
command("call chansend(g:id, msgpackdump([[0,0,'nvim_eval',['2+3']]]))")
local res = eval("msgpackdump([[1,0,v:null,5]])")
eq({"\148\001\n\192\005"}, res)
eq({'notification', 'data', {id, res}}, next_msg())
command("call chansend(g:id, msgpackdump([[2,'nvim_command',['quit']]]))")
eq({'notification', 'data', {id, {''}}}, next_msg())
end)
it('can use stdio channel', function()
source([[
let g:job_opts = {
\ 'on_stdout': function('OnEvent'),
\ 'on_stderr': function('OnEvent'),
\ 'on_exit': function('OnEvent'),
\ }
]])
meths.set_var("nvim_prog", nvim_prog)
meths.set_var("code", [[
function! OnEvent(id, data, event) dict
let text = string([a:id, a:data, a:event])
call chansend(g:x, text)
if a:data == ['']
call chansend(v:stderr, "*dies*")
quit
endif
endfunction
let g:x = stdioopen({'on_stdin':'OnEvent'})
call chansend(x, "hello")
]])
command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)")
local id = eval("g:id")
ok(id > 0)
eq({ "notification", "stdout", {id, { "hello" } } }, next_msg())
command("call chansend(id, 'howdy')")
eq({"notification", "stdout", {id, {"[1, ['howdy'], 'stdin']"}}}, next_msg())
command("call chanclose(id, 'stdin')")
expect_twostreams({{"notification", "stdout", {id, {"[1, [''], 'stdin']"}}},
{'notification', 'stdout', {id, {''}}}},
{{"notification", "stderr", {id, {"*dies*"}}},
{'notification', 'stderr', {id, {''}}}})
eq({"notification", "exit", {3,0}}, next_msg())
end)
local function expect_twoline(id, stream, line1, line2, nobr)
local msg = next_msg()
local joined = nobr and {line1..line2} or {line1, line2}
if not pcall(eq, {"notification", stream, {id, joined}}, msg) then
local sep = (not nobr) and "" or nil
eq({"notification", stream, {id, {line1, sep}}}, msg)
eq({"notification", stream, {id, {line2}}}, next_msg())
end
end
it('can use stdio channel with pty', function()
if helpers.pending_win32(pending) then return end
source([[
let g:job_opts = {
\ 'on_stdout': function('OnEvent'),
\ 'on_exit': function('OnEvent'),
\ 'pty': v:true,
\ }
]])
meths.set_var("nvim_prog", nvim_prog)
meths.set_var("code", [[
function! OnEvent(id, data, event) dict
let text = string([a:id, a:data, a:event])
call chansend(g:x, text)
endfunction
let g:x = stdioopen({'on_stdin':'OnEvent'})
]])
command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)")
local id = eval("g:id")
ok(id > 0)
command("call chansend(id, 'TEXT\n')")
expect_twoline(id, "stdout", "TEXT\r", "[1, ['TEXT', ''], 'stdin']")
command("call chansend(id, 'neovan')")
eq({"notification", "stdout", {id, {"neovan"}}}, next_msg())
command("call chansend(id, '\127\127im\n')")
expect_twoline(id, "stdout", "\b \b\b \bim\r", "[1, ['neovim', ''], 'stdin']")
command("call chansend(id, 'incomplet\004')")
local is_freebsd = eval("system('uname') =~? 'FreeBSD'") == 1
local bsdlike = is_freebsd or (helpers.os_name() == "osx")
print("bsdlike:", bsdlike)
local extra = bsdlike and "^D\008\008" or ""
expect_twoline(id, "stdout",
"incomplet"..extra, "[1, ['incomplet'], 'stdin']", true)
command("call chansend(id, '\004')")
if bsdlike then
expect_twoline(id, "stdout", extra, "[1, [''], 'stdin']", true)
else
eq({"notification", "stdout", {id, {"[1, [''], 'stdin']"}}}, next_msg())
end
-- channel is still open
command("call chansend(id, 'hi again!\n')")
eq({"notification", "stdout", {id, {"hi again!\r", ""}}}, next_msg())
end)
it('stdio channel can use rpc and stderr simultaneously', function()
if helpers.pending_win32(pending) then return end
source([[
let g:job_opts = {
\ 'on_stderr': function('OnEvent'),
\ 'on_exit': function('OnEvent'),
\ 'rpc': v:true,
\ }
]])
meths.set_var("nvim_prog", nvim_prog)
meths.set_var("code", [[
let id = stdioopen({'rpc':v:true})
call rpcnotify(id,"nvim_call_function", "rpcnotify", [1, "message", "hi there!", id])
call chansend(v:stderr, "trouble!")
]])
command("let id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)")
eq({"notification", "message", {"hi there!", 1}}, next_msg())
eq({"notification", "stderr", {3, {"trouble!"}}}, next_msg())
eq(30, eval("rpcrequest(id, 'nvim_eval', '[chansend(v:stderr, \"math??\"), 5*6][1]')"))
eq({"notification", "stderr", {3, {"math??"}}}, next_msg())
local _, err = pcall(command,"call rpcrequest(id, 'nvim_command', 'call chanclose(v:stderr, \"stdin\")')")
ok(string.find(err,"E906: invalid stream for channel") ~= nil)
eq(1, eval("rpcrequest(id, 'nvim_eval', 'chanclose(v:stderr, \"stderr\")')"))
eq({"notification", "stderr", {3, {""}}}, next_msg())
command("call rpcnotify(id, 'nvim_command', 'quit')")
eq({"notification", "exit", {3, 0}}, next_msg())
end)
it('can use buffered output mode', function()
if helpers.pending_win32(pending) then return end
source([[
let g:job_opts = {
\ 'on_stdout': function('OnEvent'),
\ 'on_exit': function('OnEvent'),
\ 'stdout_buffered': v:true,
\ }
]])
command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
local id = eval("g:id")
command([[call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")]])
sleep(10)
command([[call chansend(id, "xx\n20 GOTO 10\nzz\n")]])
command("call chanclose(id, 'stdin')")
eq({"notification", "stdout", {id, {'10 PRINT "NVIM"',
'20 GOTO 10', ''}}}, next_msg())
eq({"notification", "exit", {id, 0}}, next_msg())
command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
id = eval("g:id")
command([[call chansend(id, "is no number\nnot at all")]])
command("call chanclose(id, 'stdin')")
-- works correctly with no output
eq({"notification", "stdout", {id, {''}}}, next_msg())
eq({"notification", "exit", {id, 1}}, next_msg())
end)
it('can use buffered output mode with no stream callback', function()
if helpers.pending_win32(pending) then return end
source([[
function! OnEvent(id, data, event) dict
call rpcnotify(1, a:event, a:id, a:data, self.stdout)
endfunction
let g:job_opts = {
\ 'on_exit': function('OnEvent'),
\ 'stdout_buffered': v:true,
\ }
]])
command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
local id = eval("g:id")
command([[call chansend(id, "stuff\n10 PRINT \"NVIM\"\nxx")]])
sleep(10)
command([[call chansend(id, "xx\n20 GOTO 10\nzz\n")]])
command("call chanclose(id, 'stdin')")
eq({"notification", "exit", {id, 0, {'10 PRINT "NVIM"',
'20 GOTO 10', ''}}}, next_msg())
-- reset dictionary
source([[
let g:job_opts = {
\ 'on_exit': function('OnEvent'),
\ 'stdout_buffered': v:true,
\ }
]])
command("let id = jobstart(['grep', '^[0-9]'], g:job_opts)")
id = eval("g:id")
command([[call chansend(id, "is no number\nnot at all")]])
command("call chanclose(id, 'stdin')")
-- works correctly with no output
eq({"notification", "exit", {id, 1, {''}}}, next_msg())
end)
end)

View File

@@ -2,8 +2,12 @@ local helpers = require('test.functional.helpers')(after_each)
local command = helpers.command
local eval = helpers.eval
local eq, neq = helpers.eq, helpers.neq
local eq = helpers.eq
local run = helpers.run
local funcs = helpers.funcs
local nvim_prog = helpers.nvim_prog
local redir_exec = helpers.redir_exec
local wait = helpers.wait
describe('v:exiting', function()
local cid
@@ -29,18 +33,53 @@ describe('v:exiting', function()
end
run(on_request, nil, on_setup)
end)
end)
it('is non-zero after :cquit', function()
local function on_setup()
command('autocmd VimLeavePre * call rpcrequest('..cid..', "")')
command('autocmd VimLeave * call rpcrequest('..cid..', "")')
command('cquit')
describe(':cquit', function()
local function test_cq(cmdline, exit_code, redir_msg)
if redir_msg then
eq('\n' .. redir_msg, redir_exec(cmdline))
wait()
eq(2, eval("1+1")) -- Still alive?
else
funcs.system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline})
eq(exit_code, eval('v:shell_error'))
end
local function on_request()
neq(0, eval('v:exiting'))
return ''
end
run(on_request, nil, on_setup)
end
before_each(function()
helpers.clear()
end)
it('exits with non-zero after :cquit', function()
test_cq('cquit', 1, nil)
end)
it('exits with non-zero after :cquit 123', function()
test_cq('cquit 123', 123, nil)
end)
it('exits with non-zero after :123 cquit', function()
test_cq('123 cquit', 123, nil)
end)
it('exits with 0 after :cquit 0', function()
test_cq('cquit 0', 0, nil)
end)
it('exits with 0 after :0 cquit', function()
test_cq('0 cquit', 0, nil)
end)
it('exits with redir msg for multiple exit codes after :cquit 1 2', function()
test_cq('cquit 1 2', nil, 'E488: Trailing characters: cquit 1 2')
end)
it('exits with redir msg for non-number exit code after :cquit X', function()
test_cq('cquit X', nil, 'E488: Trailing characters: cquit X')
end)
it('exits with redir msg for negative exit code after :cquit -1', function()
test_cq('cquit -1', nil, 'E488: Trailing characters: cquit -1')
end)
end)

View File

@@ -1,13 +1,16 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, eval, exc_exec, execute, feed, insert, neq, next_msg, nvim,
local clear, eq, eval, exc_exec, feed_command, feed, insert, neq, next_msg, nvim,
nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear,
helpers.eq, helpers.eval, helpers.exc_exec, helpers.execute, helpers.feed,
helpers.eq, helpers.eval, helpers.exc_exec, helpers.feed_command, helpers.feed,
helpers.insert, helpers.neq, helpers.next_message, helpers.nvim,
helpers.nvim_dir, helpers.ok, helpers.source,
helpers.write_file, helpers.mkdir, helpers.rmdir
local command = helpers.command
local wait = helpers.wait
local iswin = helpers.iswin
local get_pathsep = helpers.get_pathsep
local nvim_set = helpers.nvim_set
local expect_twostreams = helpers.expect_twostreams
local Screen = require('test.functional.ui.screen')
describe('jobs', function()
@@ -27,15 +30,14 @@ describe('jobs', function()
\ ? map(a:data, 'substitute(v:val, "\r", "", "g")')
\ : a:data
endfunction
function! s:OnEvent(id, data, event) dict
function! OnEvent(id, data, event) dict
let userdata = get(self, 'user')
let data = Normalize(a:data)
call rpcnotify(g:channel, a:event, userdata, data)
endfunction
let g:job_opts = {
\ 'on_stdout': function('s:OnEvent'),
\ 'on_stderr': function('s:OnEvent'),
\ 'on_exit': function('s:OnEvent'),
\ 'on_stdout': function('OnEvent'),
\ 'on_exit': function('OnEvent'),
\ 'user': 0
\ }
]])
@@ -49,6 +51,7 @@ describe('jobs', function()
nvim('command', "let j = jobstart('echo $VAR', g:job_opts)")
end
eq({'notification', 'stdout', {0, {'abc', ''}}}, next_msg())
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -61,11 +64,12 @@ describe('jobs', function()
end
eq({'notification', 'stdout',
{0, {(iswin() and [[C:\]] or '/'), ''}}}, next_msg())
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
it('changes to given `cwd` directory', function()
local dir = eval('resolve(tempname())')
local dir = eval("resolve(tempname())"):gsub("/", get_pathsep())
mkdir(dir)
nvim('command', "let g:job_opts.cwd = '" .. dir .. "'")
if iswin() then
@@ -74,6 +78,7 @@ describe('jobs', function()
nvim('command', "let j = jobstart('pwd', g:job_opts)")
end
eq({'notification', 'stdout', {0, {dir, ''}}}, next_msg())
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
rmdir(dir)
end)
@@ -93,7 +98,7 @@ describe('jobs', function()
it('returns 0 when it fails to start', function()
eq("", eval("v:errmsg"))
execute("let g:test_jobid = jobstart([])")
feed_command("let g:test_jobid = jobstart([])")
eq(0, eval("g:test_jobid"))
eq("E474:", string.match(eval("v:errmsg"), "E%d*:"))
end)
@@ -116,8 +121,12 @@ describe('jobs', function()
it('invokes callbacks when the job writes and exits', function()
-- TODO: hangs on Windows
if helpers.pending_win32(pending) then return end
nvim('command', "let g:job_opts.on_stderr = function('OnEvent')")
nvim('command', "call jobstart('echo', g:job_opts)")
eq({'notification', 'stdout', {0, {'', ''}}}, next_msg())
expect_twostreams({{'notification', 'stdout', {0, {'', ''}}},
{'notification', 'stdout', {0, {''}}}},
{{'notification', 'stderr', {0, {''}}}})
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -132,6 +141,7 @@ describe('jobs', function()
nvim('command', 'call jobsend(j, [123, "xyz", ""])')
eq({'notification', 'stdout', {0, {'123', 'xyz', ''}}}, next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -143,6 +153,7 @@ describe('jobs', function()
nvim('command', "let j = jobstart(['cat', '"..filename.."'], g:job_opts)")
eq({'notification', 'stdout', {0, {'abc\ndef', ''}}}, next_msg())
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
os.remove(filename)
@@ -166,6 +177,7 @@ describe('jobs', function()
nvim('command', 'call jobsend(j, "abc\\nxyz")')
eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -184,6 +196,7 @@ describe('jobs', function()
eq({'notification', 'stdout', {0, {'\n123\n', 'abc\nxyz\n', ''}}},
next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -194,6 +207,7 @@ describe('jobs', function()
eq({'notification', 'stdout', {0, {'some data', 'without\nfinal nl'}}},
next_msg())
nvim('command', "call jobstop(j)")
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -201,6 +215,7 @@ describe('jobs', function()
if helpers.pending_win32(pending) then return end -- TODO: Need `cat`.
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
nvim('command', 'call jobclose(j, "stdin")')
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
@@ -237,6 +252,7 @@ describe('jobs', function()
local pid = eval('jobpid(j)')
eq(0,os.execute('ps -p '..pid..' > /dev/null'))
nvim('command', 'call jobstop(j)')
eq({'notification', 'stdout', {0, {''}}}, next_msg())
eq({'notification', 'exit', {0, 0}}, next_msg())
neq(0,os.execute('ps -p '..pid..' > /dev/null'))
end)
@@ -268,6 +284,7 @@ describe('jobs', function()
nvim('command', [[call jobstart('echo "foo"', g:job_opts)]])
local data = {n = 5, s = 'str', l = {1}}
eq({'notification', 'stdout', {data, {'foo', ''}}}, next_msg())
eq({'notification', 'stdout', {data, {''}}}, next_msg())
eq({'notification', 'exit', {data, 0}}, next_msg())
end)
@@ -281,7 +298,6 @@ describe('jobs', function()
it('can omit data callbacks', function()
nvim('command', 'unlet g:job_opts.on_stdout')
nvim('command', 'unlet g:job_opts.on_stderr')
nvim('command', 'let g:job_opts.user = 5')
nvim('command', [[call jobstart('echo "foo"', g:job_opts)]])
eq({'notification', 'exit', {5, 0}}, next_msg())
@@ -292,11 +308,13 @@ describe('jobs', function()
nvim('command', 'let g:job_opts.user = 5')
nvim('command', [[call jobstart('echo "foo"', g:job_opts)]])
eq({'notification', 'stdout', {5, {'foo', ''}}}, next_msg())
eq({'notification', 'stdout', {5, {''}}}, next_msg())
end)
it('will pass return code with the exit event', function()
nvim('command', 'let g:job_opts.user = 5')
nvim('command', "call jobstart('exit 55', g:job_opts)")
eq({'notification', 'stdout', {5, {''}}}, next_msg())
eq({'notification', 'exit', {5, 55}}, next_msg())
end)
@@ -339,6 +357,14 @@ describe('jobs', function()
end)
it('requires funcrefs for script-local (s:) functions', function()
local screen = Screen.new(60, 5)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[3] = {bold = true, foreground = Screen.colors.SeaGreen4}
})
-- Pass job callback names _without_ `function(...)`.
source([[
function! s:OnEvent(id, data, event) dict
@@ -348,14 +374,10 @@ describe('jobs', function()
\ 'on_stdout': 's:OnEvent',
\ 'on_stderr': 's:OnEvent',
\ 'on_exit': 's:OnEvent',
\ 'user': 2349
\ })
]])
-- The behavior is asynchronous, retry until a time limit.
helpers.retry(nil, 10000, function()
eq("E120:", string.match(eval("v:errmsg"), "E%d*:"))
end)
screen:expect("{2:E120: Using <SID> not in a script context: s:OnEvent}",nil,nil,nil,true)
end)
it('does not repeat output with slow output handlers', function()
@@ -374,7 +396,7 @@ describe('jobs', function()
call jobwait([jobstart(cmd, d)])
call rpcnotify(g:channel, 'data', d.data)
]])
eq({'notification', 'data', {{{'1', ''}, {'2', ''}, {'3', ''}, {'4', ''}, {'5', ''}}}}, next_msg())
eq({'notification', 'data', {{{'1', ''}, {'2', ''}, {'3', ''}, {'4', ''}, {'5', ''}, {''}}}}, next_msg())
end)
it('jobstart() works with partial functions', function()
@@ -469,7 +491,7 @@ describe('jobs', function()
end)
it('will return -2 when interrupted', function()
execute('call rpcnotify(g:channel, "ready") | '..
feed_command('call rpcnotify(g:channel, "ready") | '..
'call rpcnotify(g:channel, "wait", '..
'jobwait([jobstart("sleep 10; exit 55")]))')
eq({'notification', 'ready', {}}, next_msg())
@@ -495,7 +517,8 @@ describe('jobs', function()
elseif self.state == 2
let self.state = 3
call jobsend(a:id, "line3\n")
else
elseif self.state == 3
let self.state = 4
call rpcnotify(g:channel, 'w', printf('job %d closed', self.counter))
call jobclose(a:id, 'stdin')
endif
@@ -513,7 +536,7 @@ describe('jobs', function()
\ ])
endfunction
]])
execute('call Run()')
feed_command('call Run()')
local r
for i = 10, 1, -1 do
r = next_msg()
@@ -550,6 +573,7 @@ describe('jobs', function()
-- FIXME need to wait until jobsend succeeds before calling jobstop
pending('will only emit the "exit" event after "stdout" and "stderr"', function()
nvim('command', "let g:job_opts.on_stderr = function('s:OnEvent')")
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
local jobid = nvim('eval', 'j')
nvim('eval', 'jobsend(j, "abcdef")')
@@ -636,7 +660,7 @@ describe('jobs', function()
-- there won't be any more messages, and the test would hang.
helpers.sleep(100)
local err = exc_exec('call jobpid(j)')
eq('Vim(call):E900: Invalid job id', err)
eq('Vim(call):E900: Invalid channel id', err)
-- cleanup
eq(other_pid, eval('jobpid(' .. other_jobid .. ')'))
@@ -667,19 +691,20 @@ describe("pty process teardown", function()
it("does not prevent/delay exit. #4798 #4900", function()
if helpers.pending_win32(pending) then return end
-- Use a nested nvim (in :term) to test without --headless.
execute(":terminal '"..helpers.nvim_prog
feed_command(":terminal '"..helpers.nvim_prog
.."' -u NONE -i NONE --cmd '"..nvim_set.."' "
-- Use :term again in the _nested_ nvim to get a PTY process.
-- Use `sleep` to simulate a long-running child of the PTY.
.."' +terminal +'!(sleep 300 &)' +qa")
.."+terminal +'!(sleep 300 &)' +qa")
-- Exiting should terminate all descendants (PTY, its children, ...).
screen:expect([[
|
^ |
[Process exited 0] |
|
|
|
-- TERMINAL -- |
|
]])
end)
end)

View File

@@ -0,0 +1,56 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local command = helpers.command
local iswin = helpers.iswin
describe('path collapse', function()
local targetdir
local expected_path
local function join_path(...)
local pathsep = (iswin() and '\\' or '/')
return table.concat({...}, pathsep)
end
before_each(function()
targetdir = join_path('test', 'functional', 'fixtures')
clear()
command('edit '..join_path(targetdir, 'tty-test.c'))
expected_path = eval('expand("%:p")')
end)
it('with /./ segment #7117', function()
command('edit '..join_path(targetdir, '.', 'tty-test.c'))
eq(expected_path, eval('expand("%:p")'))
end)
it('with ./ prefix #7117', function()
command('edit '..join_path('.', targetdir, 'tty-test.c'))
eq(expected_path, eval('expand("%:p")'))
end)
it('with ./ prefix, after directory change #7117', function()
command('edit '..join_path('.', targetdir, 'tty-test.c'))
command('cd test')
eq(expected_path, eval('expand("%:p")'))
end)
it('with /../ segment #7117', function()
command('edit '..join_path(targetdir, '..', 'fixtures', 'tty-test.c'))
eq(expected_path, eval('expand("%:p")'))
end)
it('with ../ and different starting directory #7117', function()
command('cd test')
command('edit '..join_path('..', targetdir, 'tty-test.c'))
eq(expected_path, eval('expand("%:p")'))
end)
it('with ./../ and different starting directory #7117', function()
command('cd test')
command('edit '..join_path('.', '..', targetdir, 'tty-test.c'))
eq(expected_path, eval('expand("%:p")'))
end)
end)

View File

@@ -0,0 +1,96 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local funcs = helpers.funcs
local nvim_prog = helpers.nvim_prog
local nvim_set = helpers.nvim_set
local read_file = helpers.read_file
local retry = helpers.retry
local iswin = helpers.iswin
describe('startup', function()
before_each(function()
clear()
end)
after_each(function()
os.remove('Xtest_startup_ttyout')
end)
it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
-- system() puts a pipe at both ends.
local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
'--cmd', nvim_set,
'-c', [[echo has('ttyin') has('ttyout')]],
'+q' })
eq('0 0', out)
end)
it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
local screen = Screen.new(25, 3)
-- Remote UI connected by --embed.
screen:attach()
command([[echo has('ttyin') has('ttyout')]])
screen:expect([[
^ |
~ |
0 0 |
]])
end)
it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
local screen = Screen.new(25, 3)
screen:attach()
if iswin() then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
..nvim_set..[[\" ]]
..[[-c \"echo has('ttyin') has('ttyout')\""]]
..[[, shellescape(v:progpath))]])
screen:expect([[
^ |
1 1 |
|
]])
end)
it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
local screen = Screen.new(25, 5)
screen:attach()
if iswin() then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
..nvim_set..[[\" ]]
..[[-c \"call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')\"]]
..[[-c q | cat -v"]] -- Output to a pipe.
..[[, shellescape(v:progpath))]])
retry(nil, 3000, function()
screen:sleep(1)
eq('1\n0\n', -- stdin is a TTY, stdout is a pipe
read_file('Xtest_startup_ttyout'))
end)
end)
it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
local screen = Screen.new(25, 5)
screen:attach()
if iswin() then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
-- Running in :terminal
command([[exe printf("terminal echo foo | ]] -- Input from a pipe.
..[[%s -u NONE -i NONE --cmd \"]]
..nvim_set..[[\" ]]
..[[-c \"call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')\"]]
..[[-c q -- -"]]
..[[, shellescape(v:progpath))]])
retry(nil, 3000, function()
screen:sleep(1)
eq('0\n1\n', -- stdin is a pipe, stdout is a TTY
read_file('Xtest_startup_ttyout'))
end)
end)
end)

View File

@@ -1,7 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local lfs = require('lfs')
local neq, eq, execute = helpers.neq, helpers.eq, helpers.execute
local neq, eq, command = helpers.neq, helpers.eq, helpers.command
local clear, curbufmeths = helpers.clear, helpers.curbufmeths
local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval
local insert = helpers.insert
@@ -10,17 +10,17 @@ describe('api functions', function()
before_each(clear)
it("work", function()
execute("call nvim_command('let g:test = 1')")
command("call nvim_command('let g:test = 1')")
eq(1, eval("nvim_get_var('test')"))
local buf = eval("nvim_get_current_buf()")
execute("call nvim_buf_set_lines("..buf..", 0, -1, v:true, ['aa', 'bb'])")
command("call nvim_buf_set_lines("..buf..", 0, -1, v:true, ['aa', 'bb'])")
expect([[
aa
bb]])
execute("call nvim_win_set_cursor(0, [1, 1])")
execute("call nvim_input('ax<esc>')")
command("call nvim_win_set_cursor(0, [1, 1])")
command("call nvim_input('ax<esc>')")
expect([[
aax
bb]])
@@ -57,7 +57,7 @@ describe('api functions', function()
eq(bnr, bhnd)
eq(wid, whnd)
execute("new") -- creates new buffer and new window
command("new") -- creates new buffer and new window
local bnr2 = eval("bufnr('')")
local bhnd2 = eval("nvim_get_current_buf()")
local wid2 = eval("win_getid()")
@@ -69,7 +69,7 @@ describe('api functions', function()
-- 0 is synonymous to the current buffer
eq(bnr2, eval("nvim_buf_get_number(0)"))
execute("bn") -- show old buffer in new window
command("bn") -- show old buffer in new window
eq(bnr, eval("nvim_get_current_buf()"))
eq(bnr, eval("bufnr('')"))
eq(bnr, eval("nvim_buf_get_number(0)"))
@@ -81,7 +81,7 @@ describe('api functions', function()
curbufmeths.set_lines(0, -1, true, {"aa\0", "b\0b"})
eq({'aa\n', 'b\nb'}, eval("nvim_buf_get_lines(0, 0, -1, 1)"))
execute('call nvim_buf_set_lines(0, 1, 2, v:true, ["xx", "\\nyy"])')
command('call nvim_buf_set_lines(0, 1, 2, v:true, ["xx", "\\nyy"])')
eq({'aa\0', 'xx', '\0yy'}, curbufmeths.get_lines(0, -1, 1))
end)
@@ -106,7 +106,7 @@ describe('api functions', function()
it('have metadata accessible with api_info()', function()
local api_keys = eval("sort(keys(api_info()))")
eq({'error_types', 'functions', 'types', 'version'}, api_keys)
eq({'error_types', 'functions', 'types', 'ui_events', 'version'}, api_keys)
end)
it('are highlighted by vim.vim syntax file', function()
@@ -124,9 +124,9 @@ describe('api functions', function()
[5] = {bold = true, foreground = Screen.colors.Blue},
})
execute("set ft=vim")
execute("let &rtp='build/runtime/,'.&rtp")
execute("syntax on")
command("set ft=vim")
command("let &rtp='build/runtime/,'.&rtp")
command("syntax on")
insert([[
call bufnr('%')
call nvim_input('typing...')

View File

@@ -0,0 +1,302 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local eq = helpers.eq
local clear = helpers.clear
local funcs = helpers.funcs
local meths = helpers.meths
local command = helpers.command
local exc_exec = helpers.exc_exec
local bufmeths = helpers.bufmeths
local winmeths = helpers.winmeths
local curbufmeths = helpers.curbufmeths
local curwinmeths = helpers.curwinmeths
local curtabmeths = helpers.curtabmeths
local get_pathsep = helpers.get_pathsep
local fname = 'Xtest-functional-eval-buf_functions'
local fname2 = fname .. '.2'
local dirname = fname .. '.d'
before_each(clear)
for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
'getbufline(%s, 1)', 'getbufvar(%s, "changedtick")',
'setbufvar(%s, "f", 0)'}) do
local funcname = func:match('%w+')
describe(funcname .. '() function', function()
it('errors out when receives v:true/v:false/v:null', function()
-- Not compatible with Vim: in Vim it always results in buffer not found
-- without any error messages.
for _, var in ipairs({'v:true', 'v:false', 'v:null'}) do
eq('Vim(call):E5300: Expected a Number or a String',
exc_exec('call ' .. func:format(var)))
end
end)
it('errors out when receives invalid argument', function()
eq('Vim(call):E745: Expected a Number or a String, List found',
exc_exec('call ' .. func:format('[]')))
eq('Vim(call):E728: Expected a Number or a String, Dictionary found',
exc_exec('call ' .. func:format('{}')))
eq('Vim(call):E805: Expected a Number or a String, Float found',
exc_exec('call ' .. func:format('0.0')))
eq('Vim(call):E703: Expected a Number or a String, Funcref found',
exc_exec('call ' .. func:format('function("tr")')))
end)
end)
end
describe('bufname() function', function()
it('returns empty string when buffer was not found', function()
command('file ' .. fname)
eq('', funcs.bufname(2))
eq('', funcs.bufname('non-existent-buffer'))
eq('', funcs.bufname('#'))
command('edit ' .. fname2)
eq(2, funcs.bufnr('%'))
eq('', funcs.bufname('X'))
end)
before_each(function()
lfs.mkdir(dirname)
end)
after_each(function()
lfs.rmdir(dirname)
end)
it('returns expected buffer name', function()
eq('', funcs.bufname('%')) -- Buffer has no name yet
command('file ' .. fname)
local wd = lfs.currentdir()
local sep = get_pathsep()
local curdirname = funcs.fnamemodify(wd, ':t')
for _, arg in ipairs({'%', 1, 'X', wd}) do
eq(fname, funcs.bufname(arg))
meths.set_current_dir('..')
eq(curdirname .. sep .. fname, funcs.bufname(arg))
meths.set_current_dir(curdirname)
meths.set_current_dir(dirname)
eq(wd .. sep .. fname, funcs.bufname(arg))
meths.set_current_dir('..')
eq(fname, funcs.bufname(arg))
command('enew')
end
eq('', funcs.bufname('%'))
eq('', funcs.bufname('$'))
eq(2, funcs.bufnr('%'))
end)
end)
describe('bufnr() function', function()
it('returns -1 when buffer was not found', function()
command('file ' .. fname)
eq(-1, funcs.bufnr(2))
eq(-1, funcs.bufnr('non-existent-buffer'))
eq(-1, funcs.bufnr('#'))
command('edit ' .. fname2)
eq(2, funcs.bufnr('%'))
eq(-1, funcs.bufnr('X'))
end)
it('returns expected buffer number', function()
eq(1, funcs.bufnr('%'))
command('file ' .. fname)
local wd = lfs.currentdir()
local curdirname = funcs.fnamemodify(wd, ':t')
eq(1, funcs.bufnr(fname))
eq(1, funcs.bufnr(wd))
eq(1, funcs.bufnr(curdirname))
eq(1, funcs.bufnr('X'))
end)
it('returns number of last buffer with "$"', function()
eq(1, funcs.bufnr('$'))
command('new')
eq(2, funcs.bufnr('$'))
command('new')
eq(3, funcs.bufnr('$'))
command('only')
eq(3, funcs.bufnr('$'))
eq(3, funcs.bufnr('%'))
command('buffer 1')
eq(3, funcs.bufnr('$'))
eq(1, funcs.bufnr('%'))
command('bwipeout 2')
eq(3, funcs.bufnr('$'))
eq(1, funcs.bufnr('%'))
command('bwipeout 3')
eq(1, funcs.bufnr('$'))
eq(1, funcs.bufnr('%'))
command('new')
eq(4, funcs.bufnr('$'))
end)
end)
describe('bufwinnr() function', function()
it('returns -1 when buffer was not found', function()
command('file ' .. fname)
eq(-1, funcs.bufwinnr(2))
eq(-1, funcs.bufwinnr('non-existent-buffer'))
eq(-1, funcs.bufwinnr('#'))
command('split ' .. fname2) -- It would be OK if there was one window
eq(2, funcs.bufnr('%'))
eq(-1, funcs.bufwinnr('X'))
end)
before_each(function()
lfs.mkdir(dirname)
end)
after_each(function()
lfs.rmdir(dirname)
end)
it('returns expected window number', function()
eq(1, funcs.bufwinnr('%'))
command('file ' .. fname)
command('vsplit')
command('split ' .. fname2)
eq(2, funcs.bufwinnr(fname))
eq(1, funcs.bufwinnr(fname2))
eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
meths.set_current_dir(dirname)
eq(2, funcs.bufwinnr(fname))
eq(1, funcs.bufwinnr(fname2))
eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
eq(1, funcs.bufwinnr('%'))
eq(2, funcs.bufwinnr(1))
eq(1, funcs.bufwinnr(2))
eq(-1, funcs.bufwinnr(3))
eq(1, funcs.bufwinnr('$'))
end)
end)
describe('getbufline() function', function()
it('returns empty list when buffer was not found', function()
command('file ' .. fname)
eq({}, funcs.getbufline(2, 1))
eq({}, funcs.getbufline('non-existent-buffer', 1))
eq({}, funcs.getbufline('#', 1))
command('edit ' .. fname2)
eq(2, funcs.bufnr('%'))
eq({}, funcs.getbufline('X', 1))
end)
it('returns empty list when range is invalid', function()
eq({}, funcs.getbufline(1, 0))
curbufmeths.set_lines(0, 1, false, {'foo', 'bar', 'baz'})
eq({}, funcs.getbufline(1, 2, 1))
eq({}, funcs.getbufline(1, -10, -20))
eq({}, funcs.getbufline(1, -2, -1))
eq({}, funcs.getbufline(1, -1, 9999))
end)
it('returns expected lines', function()
meths.set_option('hidden', true)
command('file ' .. fname)
curbufmeths.set_lines(0, 1, false, {'foo\0', '\0bar', 'baz'})
command('edit ' .. fname2)
curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, 9999))
eq({'abc\n', '\ndef', 'ghi'}, funcs.getbufline(2, 1, 9999))
eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, '$'))
eq({'baz'}, funcs.getbufline(1, '$', '$'))
eq({'baz'}, funcs.getbufline(1, '$', 9999))
end)
end)
describe('getbufvar() function', function()
it('returns empty list when buffer was not found', function()
command('file ' .. fname)
eq('', funcs.getbufvar(2, '&autoindent'))
eq('', funcs.getbufvar('non-existent-buffer', '&autoindent'))
eq('', funcs.getbufvar('#', '&autoindent'))
command('edit ' .. fname2)
eq(2, funcs.bufnr('%'))
eq('', funcs.getbufvar('X', '&autoindent'))
end)
it('returns empty list when variable/option/etc was not found', function()
command('file ' .. fname)
eq('', funcs.getbufvar(1, '&autondent'))
eq('', funcs.getbufvar(1, 'changedtic'))
end)
it('returns expected option value', function()
eq(0, funcs.getbufvar(1, '&autoindent'))
eq(0, funcs.getbufvar(1, '&l:autoindent'))
eq(0, funcs.getbufvar(1, '&g:autoindent'))
-- Also works with global-only options
eq(0, funcs.getbufvar(1, '&hidden'))
eq(0, funcs.getbufvar(1, '&l:hidden'))
eq(0, funcs.getbufvar(1, '&g:hidden'))
-- Also works with window-local options
eq(0, funcs.getbufvar(1, '&number'))
eq(0, funcs.getbufvar(1, '&l:number'))
eq(0, funcs.getbufvar(1, '&g:number'))
command('new')
-- But with window-local options it probably does not what you expect
curwinmeths.set_option('number', true)
-- (note that current windows buffer is 2, but getbufvar() receives 1)
eq(2, bufmeths.get_number(curwinmeths.get_buf()))
eq(1, funcs.getbufvar(1, '&number'))
eq(1, funcs.getbufvar(1, '&l:number'))
-- You can get global value though, if you find this useful.
eq(0, funcs.getbufvar(1, '&g:number'))
end)
it('returns expected variable value', function()
eq(2, funcs.getbufvar(1, 'changedtick'))
curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
eq(3, funcs.getbufvar(1, 'changedtick'))
curbufmeths.set_var('test', true)
eq(true, funcs.getbufvar(1, 'test'))
eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
command('new')
eq(3, funcs.getbufvar(1, 'changedtick'))
eq(true, funcs.getbufvar(1, 'test'))
eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
end)
end)
describe('setbufvar() function', function()
it('throws the error or ignores the input when buffer was not found', function()
command('file ' .. fname)
eq(0,
exc_exec('call setbufvar(2, "&autoindent", 0)'))
eq('Vim(call):E94: No matching buffer for non-existent-buffer',
exc_exec('call setbufvar("non-existent-buffer", "&autoindent", 0)'))
eq(0,
exc_exec('call setbufvar("#", "&autoindent", 0)'))
command('edit ' .. fname2)
eq(2, funcs.bufnr('%'))
eq('Vim(call):E93: More than one match for X',
exc_exec('call setbufvar("X", "&autoindent", 0)'))
end)
it('may set options, including window-local and global values', function()
local buf1 = meths.get_current_buf()
eq(false, curwinmeths.get_option('number'))
command('split')
command('new')
eq(2, bufmeths.get_number(curwinmeths.get_buf()))
funcs.setbufvar(1, '&number', true)
local windows = curtabmeths.list_wins()
eq(false, winmeths.get_option(windows[1], 'number'))
eq(true, winmeths.get_option(windows[2], 'number'))
eq(false, winmeths.get_option(windows[3], 'number'))
eq(false, winmeths.get_option(meths.get_current_win(), 'number'))
eq(false, meths.get_option('hidden'))
funcs.setbufvar(1, '&hidden', true)
eq(true, meths.get_option('hidden'))
eq(false, bufmeths.get_option(buf1, 'autoindent'))
funcs.setbufvar(1, '&autoindent', true)
eq(true, bufmeths.get_option(buf1, 'autoindent'))
eq('Vim(call):E355: Unknown option: xxx',
exc_exec('call setbufvar(1, "&xxx", 0)'))
end)
it('may set variables', function()
local buf1 = meths.get_current_buf()
command('split')
command('new')
eq(2, curbufmeths.get_number())
funcs.setbufvar(1, 'number', true)
eq(true, bufmeths.get_var(buf1, 'number'))
eq('Vim(call):E461: Illegal variable name: b:',
exc_exec('call setbufvar(1, "", 0)'))
eq(true, bufmeths.get_var(buf1, 'number'))
funcs.setbufvar(1, 'changedtick', true)
-- eq(true, bufmeths.get_var(buf1, 'changedtick'))
eq(2, funcs.getbufvar(1, 'changedtick'))
end)
end)

View File

@@ -0,0 +1,24 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local eval = helpers.eval
local meths = helpers.meths
local clear = helpers.clear
before_each(clear)
describe('extend()', function()
it('suceeds to extend list with itself', function()
meths.set_var('l', {1, {}})
eq({1, {}, 1, {}}, eval('extend(l, l)'))
eq({1, {}, 1, {}}, meths.get_var('l'))
meths.set_var('l', {1, {}})
eq({1, {}, 1, {}}, eval('extend(l, l, 0)'))
eq({1, {}, 1, {}}, meths.get_var('l'))
meths.set_var('l', {1, {}})
eq({1, 1, {}, {}}, eval('extend(l, l, 1)'))
eq({1, 1, {}, {}}, meths.get_var('l'))
end)
end)

View File

@@ -0,0 +1,29 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local exc_exec = helpers.exc_exec
describe('Up to MAX_FUNC_ARGS arguments are handled by', function()
local max_func_args = 20 -- from eval.h
local range = helpers.funcs.range
before_each(clear)
it('printf()', function()
local printf = helpers.funcs.printf
local rep = helpers.funcs['repeat']
local expected = '2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,'
eq(expected, printf(rep('%d,', max_func_args-1), unpack(range(2, max_func_args))))
local ret = exc_exec('call printf("", 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)')
eq('Vim(call):E740: Too many arguments for function printf', ret)
end)
it('rpcnotify()', function()
local rpcnotify = helpers.funcs.rpcnotify
local ret = rpcnotify(0, 'foo', unpack(range(3, max_func_args)))
eq(1, ret)
ret = exc_exec('call rpcnotify(0, "foo", 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)')
eq('Vim(call):E740: Too many arguments for function rpcnotify', ret)
end)
end)

View File

@@ -1,13 +1,13 @@
local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local clear, execute, eval, eq = helpers.clear, helpers.execute, helpers.eval, helpers.eq
local clear, command, eval, eq = helpers.clear, helpers.command, helpers.eval, helpers.eq
before_each(function()
clear()
lfs.mkdir('test-glob')
-- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
execute('silent cd test-glob')
command('silent cd test-glob')
end)
after_each(function()

View File

@@ -0,0 +1,17 @@
local helpers = require('test.functional.helpers')(after_each)
local ok = helpers.ok
local call = helpers.call
local clear = helpers.clear
describe('hostname()', function()
before_each(clear)
it('returns hostname string', function()
local actual = call('hostname')
ok(string.len(actual) > 0)
if call('executable', 'hostname') == 1 then
local expected = string.gsub(call('system', 'hostname'), '[\n\r]', '')
helpers.eq(expected, actual)
end
end)
end)

View File

@@ -0,0 +1,440 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
local feed = helpers.feed
local meths = helpers.meths
local clear = helpers.clear
local source = helpers.source
local command = helpers.command
local exc_exec = helpers.exc_exec
local screen
before_each(function()
clear()
screen = Screen.new(25, 5)
screen:attach()
source([[
hi Test ctermfg=Red guifg=Red term=bold
function CustomCompl(...)
return 'TEST'
endfunction
function CustomListCompl(...)
return ['FOO']
endfunction
highlight RBP1 guibg=Red
highlight RBP2 guibg=Yellow
highlight RBP3 guibg=Green
highlight RBP4 guibg=Blue
let g:NUM_LVLS = 4
function Redraw()
redraw!
return ''
endfunction
cnoremap <expr> {REDRAW} Redraw()
function RainBowParens(cmdline)
let ret = []
let i = 0
let lvl = 0
while i < len(a:cmdline)
if a:cmdline[i] is# '('
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
let lvl += 1
elseif a:cmdline[i] is# ')'
let lvl -= 1
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
endif
let i += 1
endwhile
return ret
endfunction
]])
screen:set_default_attr_ids({
EOB={bold = true, foreground = Screen.colors.Blue1},
T={foreground=Screen.colors.Red},
RBP1={background=Screen.colors.Red},
RBP2={background=Screen.colors.Yellow},
RBP3={background=Screen.colors.Green},
RBP4={background=Screen.colors.Blue},
})
end)
describe('input()', function()
it('works with multiline prompts', function()
feed([[:call input("Test\nFoo")<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
Test |
Foo^ |
]])
end)
it('works with multiline prompts and :echohl', function()
feed([[:echohl Test | call input("Test\nFoo")<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Test} |
{T:Foo}^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo}^ |
]])
end)
it('allows unequal numeric arguments when using multiple args', function()
command('echohl Test')
feed([[:call input(1, 2)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
end)
it('allows unequal numeric values when using {opts} dictionary', function()
command('echohl Test')
meths.set_var('opts', {prompt=1, default=2, cancelreturn=3})
feed([[:echo input(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
feed('<Esc>')
screen:expect([[
^ |
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:3} |
]])
end)
it('works with redraw', function()
command('echohl Test')
meths.set_var('opts', {prompt='Foo>', default='Bar'})
feed([[:echo inputdialog(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
end)
it('allows omitting everything with dictionary argument', function()
command('echohl Test')
feed([[:call input({})<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
^ |
]])
end)
it('supports completion', function()
feed(':let var = input("", "", "custom,CustomCompl")<CR>')
feed('<Tab><CR>')
eq('TEST', meths.get_var('var'))
feed(':let var = input({"completion": "customlist,CustomListCompl"})<CR>')
feed('<Tab><CR>')
eq('FOO', meths.get_var('var'))
end)
it('supports cancelreturn', function()
feed(':let var = input({"cancelreturn": "BAR"})<CR>')
feed('<Esc>')
eq('BAR', meths.get_var('var'))
end)
it('supports default string', function()
feed(':let var = input("", "DEF1")<CR>')
feed('<CR>')
eq('DEF1', meths.get_var('var'))
feed(':let var = input({"default": "DEF2"})<CR>')
feed('<CR>')
eq('DEF2', meths.get_var('var'))
end)
it('errors out on invalid inputs', function()
eq('Vim(call):E730: using List as a String',
exc_exec('call input([])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input("", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input("", "", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"prompt": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"cancelreturn": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"default": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call input({"completion": []})'))
eq('Vim(call):E5050: {opts} must be the only argument',
exc_exec('call input({}, "default")'))
eq('Vim(call):E118: Too many arguments for function: input',
exc_exec('call input("prompt> ", "default", "file", "extra")'))
end)
it('supports highlighting', function()
command('nnoremap <expr> X input({"highlight": "RainBowParens"})[-1]')
feed([[X]])
feed('(())')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{RBP1:(}{RBP2:()}{RBP1:)}^ |
]])
end)
it('is not hidden by :silent', function()
feed([[:silent call input('Foo: ')<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
Foo: ^ |
|
]])
feed('Bar')
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
Foo: Bar^ |
|
]])
feed('<CR>')
end)
end)
describe('inputdialog()', function()
it('works with multiline prompts', function()
feed([[:call inputdialog("Test\nFoo")<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
Test |
Foo^ |
]])
end)
it('works with multiline prompts and :echohl', function()
feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]])
screen:expect([[
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Test} |
{T:Foo}^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo}^ |
]])
end)
it('allows unequal numeric arguments when using multiple args', function()
command('echohl Test')
feed([[:call inputdialog(1, 2)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
end)
it('allows unequal numeric values when using {opts} dictionary', function()
command('echohl Test')
meths.set_var('opts', {prompt=1, default=2, cancelreturn=3})
feed([[:echo input(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}2^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:1}^ |
]])
feed('<Esc>')
screen:expect([[
^ |
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:3} |
]])
end)
it('works with redraw', function()
command('echohl Test')
meths.set_var('opts', {prompt='Foo>', default='Bar'})
feed([[:echo input(opts)<CR>]])
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Bar^ |
]])
feed('<BS>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
command('redraw!')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{T:Foo>}Ba^ |
]])
end)
it('allows omitting everything with dictionary argument', function()
command('echohl Test')
feed(':echo inputdialog({})<CR>')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
^ |
]])
end)
it('supports completion', function()
feed(':let var = inputdialog({"completion": "customlist,CustomListCompl"})<CR>')
feed('<Tab><CR>')
eq('FOO', meths.get_var('var'))
end)
it('supports cancelreturn', function()
feed(':let var = inputdialog("", "", "CR1")<CR>')
feed('<Esc>')
eq('CR1', meths.get_var('var'))
feed(':let var = inputdialog({"cancelreturn": "BAR"})<CR>')
feed('<Esc>')
eq('BAR', meths.get_var('var'))
end)
it('supports default string', function()
feed(':let var = inputdialog("", "DEF1")<CR>')
feed('<CR>')
eq('DEF1', meths.get_var('var'))
feed(':let var = inputdialog({"default": "DEF2"})<CR>')
feed('<CR>')
eq('DEF2', meths.get_var('var'))
end)
it('errors out on invalid inputs', function()
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog([])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog("", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog("", "", [])'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"prompt": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"cancelreturn": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"default": []})'))
eq('Vim(call):E730: using List as a String',
exc_exec('call inputdialog({"completion": []})'))
eq('Vim(call):E5050: {opts} must be the only argument',
exc_exec('call inputdialog({}, "default")'))
eq('Vim(call):E118: Too many arguments for function: inputdialog',
exc_exec('call inputdialog("prompt> ", "default", "file", "extra")'))
end)
it('supports highlighting', function()
command('nnoremap <expr> X inputdialog({"highlight": "RainBowParens"})[-1]')
feed([[X]])
feed('(())')
screen:expect([[
|
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
{RBP1:(}{RBP2:()}{RBP1:)}^ |
]])
end)
end)

View File

@@ -4,16 +4,17 @@ local funcs = helpers.funcs
local meths = helpers.meths
local eq = helpers.eq
local eval = helpers.eval
local execute = helpers.execute
local command = helpers.command
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local NIL = helpers.NIL
local source = helpers.source
describe('json_decode() function', function()
local restart = function(...)
clear(...)
execute('language C')
execute([[
source([[
language C
function Eq(exp, act)
let act = a:act
let exp = a:exp
@@ -45,8 +46,6 @@ describe('json_decode() function', function()
endif
return 1
endfunction
]])
execute([[
function EvalEq(exp, act_expr)
let act = eval(a:act_expr)
if Eq(a:exp, act)
@@ -441,7 +440,7 @@ describe('json_decode() function', function()
local sp_decode_eq = function(expected, json)
meths.set_var('__json', json)
speq(expected, 'json_decode(g:__json)')
execute('unlet! g:__json')
command('unlet! g:__json')
end
it('parses strings with NUL properly', function()
@@ -527,7 +526,7 @@ end)
describe('json_encode() function', function()
before_each(function()
clear()
execute('language C')
command('language C')
end)
it('dumps strings', function()
@@ -576,94 +575,94 @@ describe('json_encode() function', function()
it('cannot dump generic mapping with generic mapping keys and values',
function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('call add(todump._VAL, [todumpv1, todumpv2])')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('call add(todump._VAL, [todumpv1, todumpv2])')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with ext key', function()
execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with array key', function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with UINT64_MAX key', function()
execute('let todump = {"_TYPE": v:msgpack_types.integer}')
execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('cannot dump generic mapping with floating-point key', function()
execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
command('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)'))
end)
it('can dump generic mapping with STR special key and NUL', function()
execute('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n"]}')
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
command('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n"]}')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('{"\\u0000": 1}', eval('json_encode(todump)'))
end)
it('can dump generic mapping with BIN special key and NUL', function()
execute('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n"]}')
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
command('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n"]}')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}')
eq('{"\\u0000": 1}', eval('json_encode(todump)'))
end)
it('can dump STR special mapping with NUL and NL', function()
execute('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n", ""]}')
command('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n", ""]}')
eq('"\\u0000\\n"', eval('json_encode(todump)'))
end)
it('can dump BIN special mapping with NUL and NL', function()
execute('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n", ""]}')
command('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n", ""]}')
eq('"\\u0000\\n"', eval('json_encode(todump)'))
end)
it('cannot dump special ext mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
eq('Vim(call):E474: Unable to convert EXT string to JSON', exc_exec('call json_encode(todump)'))
end)
it('can dump special array mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
eq('[5, [""]]', eval('json_encode(todump)'))
end)
it('can dump special UINT64_MAX mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.integer}')
execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
eq('18446744073709551615', eval('json_encode(todump)'))
end)
it('can dump special INT64_MIN mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.integer}')
execute('let todump._VAL = [-1, 2, 0, 0]')
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [-1, 2, 0, 0]')
eq('-9223372036854775808', eval('json_encode(todump)'))
end)
it('can dump special BOOLEAN true mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
eq('true', eval('json_encode(todump)'))
end)
it('can dump special BOOLEAN false mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
eq('false', eval('json_encode(todump)'))
end)
it('can dump special NIL mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
eq('null', eval('json_encode(todump)'))
end)
@@ -673,7 +672,7 @@ describe('json_encode() function', function()
end)
it('fails to dump a partial', function()
execute('function T() dict\nendfunction')
command('function T() dict\nendfunction')
eq('Vim(call):E474: Error while dumping encode_tv2json() argument, itself: attempt to dump function reference',
exc_exec('call json_encode(function("T", [1, 2], {}))'))
end)
@@ -684,56 +683,56 @@ describe('json_encode() function', function()
end)
it('fails to dump a recursive list', function()
execute('let todump = [[[]]]')
execute('call add(todump[0][0], todump)')
command('let todump = [[[]]]')
command('call add(todump[0][0], todump)')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
it('fails to dump a recursive dict', function()
execute('let todump = {"d": {"d": {}}}')
execute('call extend(todump.d.d, {"d": todump})')
command('let todump = {"d": {"d": {}}}')
command('call extend(todump.d.d, {"d": todump})')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode([todump])'))
end)
it('can dump dict with two same dicts inside', function()
execute('let inter = {}')
execute('let todump = {"a": inter, "b": inter}')
command('let inter = {}')
command('let todump = {"a": inter, "b": inter}')
eq('{"a": {}, "b": {}}', eval('json_encode(todump)'))
end)
it('can dump list with two same lists inside', function()
execute('let inter = []')
execute('let todump = [inter, inter]')
command('let inter = []')
command('let todump = [inter, inter]')
eq('[[], []]', eval('json_encode(todump)'))
end)
it('fails to dump a recursive list in a special dict', function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
execute('call add(todump._VAL, todump)')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
command('call add(todump._VAL, todump)')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
it('fails to dump a recursive (val) map in a special dict', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('call add(todump._VAL, ["", todump])')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('call add(todump._VAL, ["", todump])')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode([todump])'))
end)
it('fails to dump a recursive (val) map in a special dict, _VAL reference', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [["", []]]}')
execute('call add(todump._VAL[0][1], todump._VAL)')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [["", []]]}')
command('call add(todump._VAL[0][1], todump._VAL)')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
it('fails to dump a recursive (val) special list in a special dict',
function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
execute('call add(todump._VAL, ["", todump._VAL])')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
command('call add(todump._VAL, ["", todump._VAL])')
eq('Vim(call):E724: unable to correctly dump variable with self-referencing container',
exc_exec('call json_encode(todump)'))
end)
@@ -777,4 +776,11 @@ describe('json_encode() function', function()
it('can dump NULL dictionary', function()
eq('{}', eval('json_encode(v:_null_dict)'))
end)
it('fails to parse NULL strings and lists', function()
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode($XXX_UNEXISTENT_VAR_XXX)'))
eq('Vim(call):E474: Attempt to decode a blank string',
exc_exec('call json_decode(v:_null_list)'))
end)
end)

View File

@@ -0,0 +1,159 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local funcs = helpers.funcs
local nvim = helpers.nvim
local source = helpers.source
local command = helpers.command
describe('maparg()', function()
before_each(clear)
local foo_bar_map_table = {
lhs='foo',
silent=0,
rhs='bar',
expr=0,
sid=0,
buffer=0,
nowait=0,
mode='n',
noremap=1,
}
it('returns a dictionary', function()
nvim('command', 'nnoremap foo bar')
eq('bar', funcs.maparg('foo'))
eq(foo_bar_map_table, funcs.maparg('foo', 'n', false, true))
end)
it('returns 1 for silent when <silent> is used', function()
nvim('command', 'nnoremap <silent> foo bar')
eq(1, funcs.maparg('foo', 'n', false, true)['silent'])
nvim('command', 'nnoremap baz bat')
eq(0, funcs.maparg('baz', 'n', false, true)['silent'])
end)
it('returns an empty string when no map is present', function()
eq('', funcs.maparg('not a mapping'))
end)
it('returns an empty dictionary when no map is present and dict is requested', function()
eq({}, funcs.maparg('not a mapping', 'n', false, true))
end)
it('returns the same value for noremap and <script>', function()
nvim('command', 'inoremap <script> hello world')
nvim('command', 'inoremap this that')
eq(
funcs.maparg('hello', 'i', false, true)['noremap'],
funcs.maparg('this', 'i', false, true)['noremap']
)
end)
it('returns a boolean for buffer', function()
-- Open enough windows to know we aren't on buffer number 1
nvim('command', 'new')
nvim('command', 'new')
nvim('command', 'new')
nvim('command', 'cnoremap <buffer> this that')
eq(1, funcs.maparg('this', 'c', false, true)['buffer'])
-- Global will return 0 always
nvim('command', 'nnoremap other another')
eq(0, funcs.maparg('other', 'n', false, true)['buffer'])
end)
it('returns script numbers', function()
source([[
function! s:maparg_test_function() abort
return 'testing'
endfunction
nnoremap fizz :call <SID>maparg_test_function()<CR>
]])
eq(1, funcs.maparg('fizz', 'n', false, true)['sid'])
eq('testing', nvim('call_function', '<SNR>1_maparg_test_function', {}))
end)
it('works with <F12> and others', function()
source([[
let g:maparg_test_var = 0
nnoremap <F12> :let g:maparg_test_var = 1<CR>
]])
eq(0, eval('g:maparg_test_var'))
source([[
call feedkeys("\<F12>")
]])
eq(1, eval('g:maparg_test_var'))
eq(':let g:maparg_test_var = 1<CR>', funcs.maparg('<F12>', 'n', false, true)['rhs'])
end)
it('works with <expr>', function()
source([[
let counter = 0
inoremap <expr> <C-L> ListItem()
inoremap <expr> <C-R> ListReset()
func ListItem()
let g:counter += 1
return g:counter . '. '
endfunc
func ListReset()
let g:counter = 0
return ''
endfunc
call feedkeys("i\<C-L>")
]])
eq(1, eval('g:counter'))
local map_dict = funcs.maparg('<C-L>', 'i', false, true)
eq(1, map_dict['expr'])
eq('i', map_dict['mode'])
end)
it('works with combining characters', function()
-- Using addacutes to make combining character better visible
local function ac(s)
local acute = '\204\129' -- U+0301 COMBINING ACUTE ACCENT
local ret = s:gsub('`', acute)
return ret
end
command(ac([[
nnoremap a b`
nnoremap c` d
nnoremap e` f`
]]))
eq(ac('b`'), funcs.maparg(ac('a')))
eq(ac(''), funcs.maparg(ac('c')))
eq(ac('d'), funcs.maparg(ac('c`')))
eq(ac('f`'), funcs.maparg(ac('e`')))
local function acmap(lhs, rhs)
return {
lhs = ac(lhs),
rhs = ac(rhs),
buffer = 0,
expr = 0,
mode = 'n',
noremap = 1,
nowait = 0,
sid = 0,
silent = 0,
}
end
eq({}, funcs.maparg(ac('c'), 'n', 0, 1))
eq(acmap('a', 'b`'), funcs.maparg(ac('a'), 'n', 0, 1))
eq(acmap('c`', 'd'), funcs.maparg(ac('c`'), 'n', 0, 1))
eq(acmap('e`', 'f`'), funcs.maparg(ac('e`'), 'n', 0, 1))
end)
end)

View File

@@ -0,0 +1,61 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local clear = helpers.clear
local funcs = helpers.funcs
local command = helpers.command
before_each(clear)
describe('setmatches()', function()
it('correctly handles case when both group and pattern entries are numbers',
function()
command('hi def link 1 PreProc')
eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}}))
eq({{
group='1',
pattern='2',
id=3,
priority=4,
}}, funcs.getmatches())
eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4, conceal=5}}))
eq({{
group='1',
pattern='2',
id=3,
priority=4,
conceal='5',
}}, funcs.getmatches())
eq(0, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}))
eq({{
group='1',
pos1={2},
pos2={6},
id=3,
priority=4,
conceal='5',
}}, funcs.getmatches())
end)
it('fails with -1 if highlight group is not defined', function()
eq(-1, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}}))
eq({}, funcs.getmatches())
eq(-1, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}))
eq({}, funcs.getmatches())
end)
end)
describe('matchadd()', function()
it('correctly works when first two arguments and conceal are numbers at once',
function()
command('hi def link 1 PreProc')
eq(4, funcs.matchadd(1, 2, 3, 4, {conceal=5}))
eq({{
group='1',
pattern='2',
priority=3,
id=4,
conceal='5',
}}, funcs.getmatches())
end)
end)

View File

@@ -0,0 +1,51 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local eval = helpers.eval
local clear = helpers.clear
local funcs = helpers.funcs
local redir_exec = helpers.redir_exec
before_each(clear)
for _, func in ipairs({'min', 'max'}) do
describe(func .. '()', function()
it('gives a single error message when multiple values failed conversions',
function()
eq('\nE745: Using a List as a Number\n0',
redir_exec('echo ' .. func .. '([-5, [], [], [], 5])'))
eq('\nE745: Using a List as a Number\n0',
redir_exec('echo ' .. func .. '({1:-5, 2:[], 3:[], 4:[], 5:5})'))
for errmsg, errinput in pairs({
['E745: Using a List as a Number'] = '[]',
['E805: Using a Float as a Number'] = '0.0',
['E703: Using a Funcref as a Number'] = 'function("tr")',
['E728: Using a Dictionary as a Number'] = '{}',
}) do
eq('\n' .. errmsg .. '\n0',
redir_exec('echo ' .. func .. '([' .. errinput .. '])'))
eq('\n' .. errmsg .. '\n0',
redir_exec('echo ' .. func .. '({1:' .. errinput .. '})'))
end
end)
it('works with arrays/dictionaries with zero items', function()
eq(0, funcs[func]({}))
eq(0, eval(func .. '({})'))
end)
it('works with arrays/dictionaries with one item', function()
eq(5, funcs[func]({5}))
eq(5, funcs[func]({test=5}))
end)
it('works with NULL arrays/dictionaries', function()
eq(0, eval(func .. '(v:_null_list)'))
eq(0, eval(func .. '(v:_null_dict)'))
end)
it('errors out for invalid types', function()
for _, errinput in ipairs({'1', 'v:true', 'v:false', 'v:null',
'function("tr")', '""'}) do
eq(('\nE712: Argument of %s() must be a List or Dictionary\n0'):format(
func),
redir_exec('echo ' .. func .. '(' .. errinput .. ')'))
end
end)
end)
end

View File

@@ -1,5 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, execute, write_file = helpers.clear, helpers.execute, helpers.write_file
local clear, command, write_file = helpers.clear, helpers.command, helpers.write_file
local eq, eval = helpers.eq, helpers.eval
describe("modeline", function()
@@ -12,7 +12,7 @@ describe("modeline", function()
it('does not crash with a large version number', function()
write_file(tempfile, 'vim100000000000000000000000')
execute('e! ' .. tempfile)
command('e! ' .. tempfile)
eq(2, eval('1+1')) -- Still alive?
end)

View File

@@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local funcs = helpers.funcs
local eval, eq = helpers.eval, helpers.eq
local execute = helpers.execute
local command = helpers.command
local nvim = helpers.nvim
local exc_exec = helpers.exc_exec
@@ -331,13 +331,14 @@ describe('msgpack*() functions', function()
obj_test('are able to dump and restore floating-point value', {0.125})
it('can restore and dump UINT64_MAX', function()
execute('let dumped = ["\\xCF" . repeat("\\xFF", 8)]')
execute('let parsed = msgpackparse(dumped)')
execute('let dumped2 = msgpackdump(parsed)')
command('let dumped = ["\\xCF" . repeat("\\xFF", 8)]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq(1, eval('type(parsed[0]) == type(0) ' ..
'|| parsed[0]._TYPE is v:msgpack_types.integer'))
if eval('type(parsed[0]) == type(0)') == 1 then
eq(1, eval('0xFFFFFFFFFFFFFFFF == parsed[0]'))
command('call assert_equal(0xFFFFFFFFFFFFFFFF, parsed[0])')
eq({}, eval('v:errors'))
else
eq({_TYPE={}, _VAL={1, 3, 0x7FFFFFFF, 0x7FFFFFFF}}, eval('parsed[0]'))
end
@@ -345,13 +346,14 @@ describe('msgpack*() functions', function()
end)
it('can restore and dump INT64_MIN', function()
execute('let dumped = ["\\xD3\\x80" . repeat("\\n", 7)]')
execute('let parsed = msgpackparse(dumped)')
execute('let dumped2 = msgpackdump(parsed)')
command('let dumped = ["\\xD3\\x80" . repeat("\\n", 7)]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq(1, eval('type(parsed[0]) == type(0) ' ..
'|| parsed[0]._TYPE is v:msgpack_types.integer'))
if eval('type(parsed[0]) == type(0)') == 1 then
eq(1, eval('-0x8000000000000000 == parsed[0]'))
command('call assert_equal(-0x7fffffffffffffff - 1, parsed[0])')
eq({}, eval('v:errors'))
else
eq({_TYPE={}, _VAL={-1, 2, 0, 0}}, eval('parsed[0]'))
end
@@ -359,33 +361,33 @@ describe('msgpack*() functions', function()
end)
it('can restore and dump BIN string with zero byte', function()
execute('let dumped = ["\\xC4\\x01\\n"]')
execute('let parsed = msgpackparse(dumped)')
execute('let dumped2 = msgpackdump(parsed)')
command('let dumped = ["\\xC4\\x01\\n"]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq({{_TYPE={}, _VAL={'\n'}}}, eval('parsed'))
eq(1, eval('parsed[0]._TYPE is v:msgpack_types.binary'))
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump STR string with zero byte', function()
execute('let dumped = ["\\xA1\\n"]')
execute('let parsed = msgpackparse(dumped)')
execute('let dumped2 = msgpackdump(parsed)')
command('let dumped = ["\\xA1\\n"]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq({{_TYPE={}, _VAL={'\n'}}}, eval('parsed'))
eq(1, eval('parsed[0]._TYPE is v:msgpack_types.string'))
eq(1, eval('dumped ==# dumped2'))
end)
it('can restore and dump BIN string with NL', function()
execute('let dumped = ["\\xC4\\x01", ""]')
execute('let parsed = msgpackparse(dumped)')
execute('let dumped2 = msgpackdump(parsed)')
command('let dumped = ["\\xC4\\x01", ""]')
command('let parsed = msgpackparse(dumped)')
command('let dumped2 = msgpackdump(parsed)')
eq({"\n"}, eval('parsed'))
eq(1, eval('dumped ==# dumped2'))
end)
it('dump and restore special mapping with floating-point value', function()
execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
command('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}')
eq({0.125}, eval('msgpackparse(msgpackdump([todump]))'))
end)
end)
@@ -394,52 +396,53 @@ describe('msgpackparse() function', function()
before_each(clear)
it('restores nil as v:null', function()
execute('let dumped = ["\\xC0"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\xC0"]')
command('let parsed = msgpackparse(dumped)')
eq('[v:null]', eval('string(parsed)'))
end)
it('restores boolean false as v:false', function()
execute('let dumped = ["\\xC2"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\xC2"]')
command('let parsed = msgpackparse(dumped)')
eq({false}, eval('parsed'))
end)
it('restores boolean true as v:true', function()
execute('let dumped = ["\\xC3"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\xC3"]')
command('let parsed = msgpackparse(dumped)')
eq({true}, eval('parsed'))
end)
it('restores FIXSTR as special dict', function()
execute('let dumped = ["\\xa2ab"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\xa2ab"]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={'ab'}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.string'))
end)
it('restores BIN 8 as string', function()
execute('let dumped = ["\\xC4\\x02ab"]')
command('let dumped = ["\\xC4\\x02ab"]')
eq({'ab'}, eval('msgpackparse(dumped)'))
end)
it('restores FIXEXT1 as special dictionary', function()
execute('let dumped = ["\\xD4\\x10", ""]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\xD4\\x10", ""]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={0x10, {"", ""}}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.ext'))
end)
it('restores MAP with BIN key as special dictionary', function()
execute('let dumped = ["\\x81\\xC4\\x01a\\xC4\\n"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\x81\\xC4\\x01a\\xC4\\n"]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={{'a', ''}}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
it('restores MAP with duplicate STR keys as special dictionary', function()
execute('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]')
-- FIXME Internal error bug
command('silent! let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={ {{_TYPE={}, _VAL={'a'}}, ''},
{{_TYPE={}, _VAL={'a'}}, ''}}} }, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
@@ -448,8 +451,8 @@ describe('msgpackparse() function', function()
end)
it('restores MAP with MAP key as special dictionary', function()
execute('let dumped = ["\\x81\\x80\\xC4\\n"]')
execute('let parsed = msgpackparse(dumped)')
command('let dumped = ["\\x81\\x80\\xC4\\n"]')
command('let parsed = msgpackparse(dumped)')
eq({{_TYPE={}, _VAL={{{}, ''}}}}, eval('parsed'))
eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map'))
end)
@@ -460,7 +463,7 @@ describe('msgpackparse() function', function()
eval(cmd)
eval(cmd) -- do it again (try to force segfault)
local api_info = eval(cmd) -- do it again
eq({'error_types', 'functions', 'types', 'version'}, api_info)
eq({'error_types', 'functions', 'types', 'ui_events', 'version'}, api_info)
end)
it('fails when called with no arguments', function()
@@ -494,7 +497,7 @@ describe('msgpackparse() function', function()
end)
it('fails to parse a partial', function()
execute('function T() dict\nendfunction')
command('function T() dict\nendfunction')
eq('Vim(call):E686: Argument of msgpackparse() must be a List',
exc_exec('call msgpackparse(function("T", [1, 2], {}))'))
end)
@@ -514,10 +517,10 @@ describe('msgpackdump() function', function()
end)
it('can dump generic mapping with generic mapping keys and values', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('call add(todump._VAL, [todumpv1, todumpv2])')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('call add(todump._VAL, [todumpv1, todumpv2])')
eq({'\129\128\128'}, eval('msgpackdump([todump])'))
end)
@@ -530,130 +533,130 @@ describe('msgpackdump() function', function()
end)
it('can v:null', function()
execute('let todump = v:null')
command('let todump = v:null')
end)
it('can dump special bool mapping (true)', function()
execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}')
eq({'\195'}, eval('msgpackdump([todump])'))
end)
it('can dump special bool mapping (false)', function()
execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
command('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}')
eq({'\194'}, eval('msgpackdump([todump])'))
end)
it('can dump special nil mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
command('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}')
eq({'\192'}, eval('msgpackdump([todump])'))
end)
it('can dump special ext mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}')
eq({'\212\005', ''}, eval('msgpackdump([todump])'))
end)
it('can dump special array mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}')
eq({'\146\005\145\196\n'}, eval('msgpackdump([todump])'))
end)
it('can dump special UINT64_MAX mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.integer}')
execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]')
eq({'\207\255\255\255\255\255\255\255\255'}, eval('msgpackdump([todump])'))
end)
it('can dump special INT64_MIN mapping', function()
execute('let todump = {"_TYPE": v:msgpack_types.integer}')
execute('let todump._VAL = [-1, 2, 0, 0]')
command('let todump = {"_TYPE": v:msgpack_types.integer}')
command('let todump._VAL = [-1, 2, 0, 0]')
eq({'\211\128\n\n\n\n\n\n\n'}, eval('msgpackdump([todump])'))
end)
it('fails to dump a function reference', function()
execute('let Todump = function("tr")')
command('let Todump = function("tr")')
eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference',
exc_exec('call msgpackdump([Todump])'))
end)
it('fails to dump a partial', function()
execute('function T() dict\nendfunction')
execute('let Todump = function("T", [1, 2], {})')
command('function T() dict\nendfunction')
command('let Todump = function("T", [1, 2], {})')
eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference',
exc_exec('call msgpackdump([Todump])'))
end)
it('fails to dump a function reference in a list', function()
execute('let todump = [function("tr")]')
command('let todump = [function("tr")]')
eq('Vim(call):E5004: Error while dumping msgpackdump() argument, index 0, index 0: attempt to dump function reference',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive list', function()
execute('let todump = [[[]]]')
execute('call add(todump[0][0], todump)')
command('let todump = [[[]]]')
command('call add(todump[0][0], todump)')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0, index 0, index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive dict', function()
execute('let todump = {"d": {"d": {}}}')
execute('call extend(todump.d.d, {"d": todump})')
command('let todump = {"d": {"d": {}}}')
command('call extend(todump.d.d, {"d": todump})')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key \'d\', key \'d\', key \'d\'',
exc_exec('call msgpackdump([todump])'))
end)
it('can dump dict with two same dicts inside', function()
execute('let inter = {}')
execute('let todump = {"a": inter, "b": inter}')
command('let inter = {}')
command('let todump = {"a": inter, "b": inter}')
eq({"\130\161a\128\161b\128"}, eval('msgpackdump([todump])'))
end)
it('can dump list with two same lists inside', function()
execute('let inter = []')
execute('let todump = [inter, inter]')
command('let inter = []')
command('let todump = [inter, inter]')
eq({"\146\144\144"}, eval('msgpackdump([todump])'))
end)
it('fails to dump a recursive list in a special dict', function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
execute('call add(todump._VAL, todump)')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
command('call add(todump._VAL, todump)')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (key) map in a special dict', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('call add(todump._VAL, [todump, 0])')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('call add(todump._VAL, [todump, 0])')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 1',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (val) map in a special dict', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
execute('call add(todump._VAL, [0, todump])')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}')
command('call add(todump._VAL, [0, todump])')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key 0 at index 0 from special map',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (key) map in a special dict, _VAL reference', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
execute('call add(todump._VAL[0][0], todump._VAL)')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
command('call add(todump._VAL[0][0], todump._VAL)')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key [[[[...@0], []]]] at index 0 from special map, index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (val) map in a special dict, _VAL reference', function()
execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
execute('call add(todump._VAL[0][1], todump._VAL)')
command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[[], []]]}')
command('call add(todump._VAL[0][1], todump._VAL)')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in key [] at index 0 from special map, index 0',
exc_exec('call msgpackdump([todump])'))
end)
it('fails to dump a recursive (val) special list in a special dict',
function()
execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
execute('call add(todump._VAL, [0, todump._VAL])')
command('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}')
command('call add(todump._VAL, [0, todump._VAL])')
eq('Vim(call):E5005: Unable to dump msgpackdump() argument, index 0: container references itself in index 0, index 1',
exc_exec('call msgpackdump([todump])'))
end)
@@ -689,7 +692,7 @@ describe('msgpackdump() function', function()
end)
it('fails to dump a partial', function()
execute('function T() dict\nendfunction')
command('function T() dict\nendfunction')
eq('Vim(call):E686: Argument of msgpackdump() must be a List',
exc_exec('call msgpackdump(function("T", [1, 2], {}))'))
end)

View File

@@ -0,0 +1,138 @@
local helpers = require('test.functional.helpers')(after_each)
local curbufmeths = helpers.curbufmeths
local redir_exec = helpers.redir_exec
local exc_exec = helpers.exc_exec
local command = helpers.command
local clear = helpers.clear
local meths = helpers.meths
local funcs = helpers.funcs
local eq = helpers.eq
describe('NULL', function()
before_each(function()
clear()
command('let L = v:_null_list')
command('let D = v:_null_dict')
command('let S = $XXX_NONEXISTENT_VAR_XXX')
end)
local tmpfname = 'Xtest-functional-viml-null'
after_each(function()
os.remove(tmpfname)
end)
local null_test = function(name, cmd, err)
it(name, function()
eq(err, exc_exec(cmd))
end)
end
local null_expr_test = function(name, expr, err, val, after)
it(name, function()
eq((err == 0) and ('') or ('\n' .. err),
redir_exec('let g:_var = ' .. expr))
if val == nil then
eq(0, funcs.exists('g:_var'))
else
eq(val, meths.get_var('_var'))
end
if after ~= nil then
after()
end
end)
end
describe('list', function()
-- Incorrect behaviour
-- FIXME map() should not return 0 without error
null_expr_test('does not crash map()', 'map(L, "v:val")', 0, 0)
-- FIXME map() should not return 0 without error
null_expr_test('does not crash filter()', 'filter(L, "1")', 0, 0)
-- FIXME map() should at least return L
null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 0)
-- FIXME filter() should at least return L
null_expr_test('makes filter() return v:_null_list', 'map(L, "1") is# L', 0, 0)
-- FIXME add() should not return 1 at all
null_expr_test('does not crash add()', 'add(L, 0)', 0, 1)
null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0)
null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1})
-- FIXME should be accepted by inputlist()
null_expr_test('is accepted as an empty list by inputlist()',
'[feedkeys("\\n"), inputlist(L)]', 'E686: Argument of inputlist() must be a List', {0, 0})
-- FIXME should be accepted by writefile(), return {0, {}}
null_expr_test('is accepted as an empty list by writefile()',
('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname),
'E484: Can\'t open file ' .. tmpfname, {0, {}})
-- FIXME should give error message
null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0)
-- FIXME should return 0
null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1)
-- FIXME should return 0
null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, -1)
-- FIXME should return 0
null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, -1)
-- FIXME should return empty list or error out
null_expr_test('is accepted by sort()', 'sort(L)', 0, 0)
-- FIXME Should return 1
null_expr_test('is accepted by sort()', 'sort(L) is L', 0, 0)
-- FIXME should not error out
null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected')
-- FIXME should not error out
null_test('is accepted by :lexpr', 'lexpr L', 'Vim(lexpr):E777: String or List expected')
null_test('is accepted by :for', 'for x in L|throw x|endfor', 0)
-- Subjectable behaviour
-- FIXME Should return 1
null_expr_test('is equal to empty list', 'L == []', 0, 0)
-- FIXME Should return 1
null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0)
-- FIXME Should return 1
null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0)
-- Crashes
-- null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0)
-- null_expr_test('does not crash setline', 'setline(1, L)', 0, 0)
-- null_expr_test('does not crash system()', 'system("cat", L)', 0, '')
-- null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {})
-- Correct behaviour
null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function()
eq({''}, curbufmeths.get_lines(0, -1, false))
end)
null_expr_test('is identical to itself', 'L is L', 0, 1)
null_expr_test('can be sliced', 'L[:]', 0, {})
null_expr_test('can be copied', 'copy(L)', 0, {})
null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {})
null_expr_test('does not crash when indexed', 'L[1]',
'E684: list index out of range: 1\nE15: Invalid expression: L[1]', nil)
null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0)
null_expr_test('does not crash col()', 'col(L)', 0, 0)
null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0)
null_expr_test('does not crash line()', 'line(L)', 0, 0)
null_expr_test('does not crash count()', 'count(L, 1)', 0, 0)
null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1)
null_expr_test('is empty', 'empty(L)', 0, 1)
null_expr_test('does not crash get()', 'get(L, 1, 10)', 0, 10)
null_expr_test('has zero length', 'len(L)', 0, 0)
null_expr_test('is accepted as an empty list by max()', 'max(L)', 0, 0)
null_expr_test('is accepted as an empty list by min()', 'min(L)', 0, 0)
null_expr_test('is stringified correctly', 'string(L)', 0, '[]')
null_expr_test('is JSON encoded correctly', 'json_encode(L)', 0, '[]')
null_test('does not crash lockvar', 'lockvar! L', 0)
null_expr_test('can be added to itself', '(L + L)', 0, {})
null_expr_test('can be added to itself', '(L + L) is L', 0, 1)
null_expr_test('can be added to non-empty list', '([1] + L)', 0, {1})
null_expr_test('can be added to non-empty list (reversed)', '(L + [1])', 0, {1})
null_expr_test('is equal to itself', 'L == L', 0, 1)
null_expr_test('is not not equal to itself', 'L != L', 0, 0)
null_expr_test('counts correctly', 'count([L], L)', 0, 1)
end)
describe('dict', function()
it('does not crash when indexing NULL dict', function()
eq('\nE716: Key not present in Dictionary: test\nE15: Invalid expression: v:_null_dict.test',
redir_exec('echo v:_null_dict.test'))
end)
null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0)
null_expr_test('makes extend do nothing', 'extend({1: 2}, D)', 0, {['1']=2})
end)
end)

View File

@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, ok = helpers.clear, helpers.eq, helpers.ok
local neq, execute, funcs = helpers.neq, helpers.execute, helpers.funcs
local neq, command, funcs = helpers.neq, helpers.command, helpers.funcs
local reltime, reltimestr, reltimefloat = funcs.reltime, funcs.reltimestr, funcs.reltimefloat
describe('reltimestr(), reltimefloat()', function()
@@ -8,7 +8,7 @@ describe('reltimestr(), reltimefloat()', function()
it('Checks', function()
local now = reltime()
execute('sleep 10m')
command('sleep 10m')
local later = reltime()
local elapsed = reltime(now)

View File

@@ -1,20 +1,27 @@
local helpers = require('test.functional.helpers')(after_each)
local nvim, eq, neq, eval = helpers.nvim, helpers.eq, helpers.neq, helpers.eval
local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval
local command = helpers.command
local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths
local os_name = helpers.os_name
local function clear_serverlist()
for _, server in pairs(funcs.serverlist()) do
funcs.serverstop(server)
end
end
describe('serverstart(), serverstop()', function()
before_each(clear)
it('sets $NVIM_LISTEN_ADDRESS on first invocation', function()
-- Unset $NVIM_LISTEN_ADDRESS
nvim('command', 'let $NVIM_LISTEN_ADDRESS = ""')
command('let $NVIM_LISTEN_ADDRESS = ""')
local s = eval('serverstart()')
assert(s ~= nil and s:len() > 0, "serverstart() returned empty")
eq(s, eval('$NVIM_LISTEN_ADDRESS'))
nvim('command', "call serverstop('"..s.."')")
command("call serverstop('"..s.."')")
eq('', eval('$NVIM_LISTEN_ADDRESS'))
end)
@@ -47,10 +54,48 @@ describe('serverstart(), serverstop()', function()
end)
it('serverstop() ignores invalid input', function()
nvim('command', "call serverstop('')")
nvim('command', "call serverstop('bogus-socket-name')")
command("call serverstop('')")
command("call serverstop('bogus-socket-name')")
end)
it('parses endpoints correctly', function()
clear_serverlist()
eq({}, funcs.serverlist())
local s = funcs.serverstart('127.0.0.1:0') -- assign random port
if #s > 0 then
assert(string.match(s, '127.0.0.1:%d+'))
eq(s, funcs.serverlist()[1])
clear_serverlist()
end
s = funcs.serverstart('127.0.0.1:') -- assign random port
if #s > 0 then
assert(string.match(s, '127.0.0.1:%d+'))
eq(s, funcs.serverlist()[1])
clear_serverlist()
end
local expected = {}
local v4 = '127.0.0.1:12345'
s = funcs.serverstart(v4)
if #s > 0 then
table.insert(expected, v4)
funcs.serverstart(v4) -- exists already; ignore
end
local v6 = '::1:12345'
s = funcs.serverstart(v6)
if #s > 0 then
table.insert(expected, v6)
funcs.serverstart(v6) -- exists already; ignore
end
eq(expected, funcs.serverlist())
clear_serverlist()
funcs.serverstart('127.0.0.1:65536') -- invalid port
eq({}, funcs.serverlist())
end)
end)
describe('serverlist()', function()
@@ -75,7 +120,7 @@ describe('serverlist()', function()
-- The new servers should be at the end of the list.
for i = 1, #servs do
eq(servs[i], new_servs[i + n])
nvim('command', "call serverstop('"..servs[i].."')")
command("call serverstop('"..servs[i].."')")
end
-- After serverstop() the servers should NOT be in the list.
eq(n, eval('len(serverlist())'))

View File

@@ -3,7 +3,7 @@ local setpos = helpers.funcs.setpos
local getpos = helpers.funcs.getpos
local insert = helpers.insert
local clear = helpers.clear
local execute = helpers.execute
local command = helpers.command
local eval = helpers.eval
local eq = helpers.eq
local exc_exec = helpers.exc_exec
@@ -16,7 +16,7 @@ describe('setpos() function', function()
First line of text
Second line of text
Third line of text]])
execute('new')
command('new')
insert([[
Line of text 1
Line of text 2
@@ -34,7 +34,8 @@ describe('setpos() function', function()
it('can set lowercase marks in the current buffer', function()
setpos("'d", {0, 2, 1, 0})
eq(getpos("'d"), {0, 2, 1, 0})
execute('undo', 'call setpos("\'d", [2, 3, 1, 0])')
command('undo')
command('call setpos("\'d", [2, 3, 1, 0])')
eq(getpos("'d"), {0, 3, 1, 0})
end)
it('can set lowercase marks in other buffers', function()
@@ -42,7 +43,7 @@ describe('setpos() function', function()
eq(0, retval)
setpos("'d", {1, 2, 1, 0})
eq(getpos("'d"), {0, 0, 0, 0})
execute('wincmd w')
command('wincmd w')
eq(eval('bufnr("%")'), 1)
eq(getpos("'d"), {0, 2, 1, 0})
end)

View File

@@ -0,0 +1,41 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local NIL = helpers.NIL
local eval = helpers.eval
local clear = helpers.clear
local meths = helpers.meths
local funcs = helpers.funcs
local command = helpers.command
local exc_exec = helpers.exc_exec
before_each(clear)
describe('sort()', function()
it('errors out when sorting special values', function()
eq('Vim(call):E907: Using a special value as a Float',
exc_exec('call sort([v:true, v:false], "f")'))
end)
it('sorts “wrong” values between -0.0001 and 0.0001, preserving order',
function()
meths.set_var('list', {true, false, NIL, {}, {a=42}, 'check',
0.0001, -0.0001})
command('call insert(g:list, function("tr"))')
local error_lines = funcs.split(
funcs.execute('silent! call sort(g:list, "f")'), '\n')
local errors = {}
for _, err in ipairs(error_lines) do
errors[err] = true
end
eq({
['E891: Using a Funcref as a Float']=true,
['E892: Using a String as a Float']=true,
['E893: Using a List as a Float']=true,
['E894: Using a Dictionary as a Float']=true,
['E907: Using a special value as a Float']=true,
}, errors)
eq('[-1.0e-4, function(\'tr\'), v:true, v:false, v:null, [], {\'a\': 42}, \'check\', 1.0e-4]',
eval('string(g:list)'))
end)
end)

View File

@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local exc_exec = helpers.exc_exec
local execute = helpers.execute
local command = helpers.command
local funcs = helpers.funcs
local clear = helpers.clear
local eval = helpers.eval
@@ -12,7 +12,7 @@ describe('Special values', function()
before_each(clear)
it('do not cause error when freed', function()
execute([[
command([[
function Test()
try
return v:true
@@ -109,7 +109,7 @@ describe('Special values', function()
it('does not work with +=/-=/.=', function()
meths.set_var('true', true)
meths.set_var('false', false)
execute('let null = v:null')
command('let null = v:null')
eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let true += 1'))
eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let false += 1'))
@@ -168,4 +168,11 @@ describe('Special values', function()
'Expected True but got v:null',
}, meths.get_vvar('errors'))
end)
describe('compat', function()
it('v:count is distinct from count', function()
command('let count = []') -- v:count is readonly
eq(1, eval('count is# g:["count"]'))
end)
end)
end)

View File

@@ -7,7 +7,6 @@ local eval = helpers.eval
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local funcs = helpers.funcs
local write_file = helpers.write_file
local NIL = helpers.NIL
local source = helpers.source
local dedent = helpers.dedent
@@ -105,10 +104,8 @@ describe('string() function', function()
end)
describe('used to represent funcrefs', function()
local fname = 'Xtest-functional-eval-string_spec-fref-script.vim'
before_each(function()
write_file(fname, [[
source([[
function Test1()
endfunction
@@ -120,11 +117,6 @@ describe('string() function', function()
let g:Test2_f = function('s:Test2')
]])
command('source ' .. fname)
end)
after_each(function()
os.remove(fname)
end)
it('dumps references to built-in functions', function()

View File

@@ -1,7 +1,11 @@
local helpers = require('test.functional.helpers')(after_each)
local eq, call, clear, eval, execute, feed, nvim =
helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.execute,
local nvim_dir = helpers.nvim_dir
local eq, call, clear, eval, feed_command, feed, nvim =
helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.feed_command,
helpers.feed, helpers.nvim
local command = helpers.command
local iswin = helpers.iswin
local Screen = require('test.functional.ui.screen')
@@ -31,8 +35,7 @@ describe('system()', function()
describe('command passed as a List', function()
local function printargs_path()
return helpers.nvim_dir..'/printargs-test'
.. (helpers.os_name() == 'windows' and '.exe' or '')
return nvim_dir..'/printargs-test' .. (iswin() and '.exe' or '')
end
it('sets v:shell_error if cmd[0] is not executable', function()
@@ -43,10 +46,10 @@ describe('system()', function()
it('parameter validation does NOT modify v:shell_error', function()
-- 1. Call system() with invalid parameters.
-- 2. Assert that v:shell_error was NOT set.
execute('call system({})')
feed_command('call system({})')
eq('E475: Invalid argument: expected String or List', eval('v:errmsg'))
eq(0, eval('v:shell_error'))
execute('call system([])')
feed_command('call system([])')
eq('E474: Invalid argument', eval('v:errmsg'))
eq(0, eval('v:shell_error'))
@@ -57,9 +60,9 @@ describe('system()', function()
-- 1. Call system() with invalid parameters.
-- 2. Assert that v:shell_error was NOT modified.
execute('call system({})')
feed_command('call system({})')
eq(old_val, eval('v:shell_error'))
execute('call system([])')
feed_command('call system([])')
eq(old_val, eval('v:shell_error'))
end)
@@ -86,23 +89,32 @@ describe('system()', function()
end)
it('does NOT run in shell', function()
if helpers.os_name() ~= 'windows' then
if not iswin() then
eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])"))
end
end)
end)
if helpers.pending_win32(pending) then return end
it('sets v:shell_error', function()
eval([[system("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[system("sh -c 'exit 1'")]])
eq(1, eval('v:shell_error'))
eval([[system("sh -c 'exit 5'")]])
eq(5, eval('v:shell_error'))
eval([[system('this-should-not-exist')]])
eq(127, eval('v:shell_error'))
if iswin() then
eval([[system("cmd.exe /c exit")]])
eq(0, eval('v:shell_error'))
eval([[system("cmd.exe /c exit 1")]])
eq(1, eval('v:shell_error'))
eval([[system("cmd.exe /c exit 5")]])
eq(5, eval('v:shell_error'))
eval([[system('this-should-not-exist')]])
eq(1, eval('v:shell_error'))
else
eval([[system("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[system("sh -c 'exit 1'")]])
eq(1, eval('v:shell_error'))
eval([[system("sh -c 'exit 5'")]])
eq(5, eval('v:shell_error'))
eval([[system('this-should-not-exist')]])
eq(127, eval('v:shell_error'))
end
end)
describe('executes shell function if passed a string', function()
@@ -118,6 +130,40 @@ describe('system()', function()
screen:detach()
end)
if iswin() then
it('with shell=cmd.exe', function()
command('set shell=cmd.exe')
eq('""\n', eval([[system('echo ""')]]))
eq('"a b"\n', eval([[system('echo "a b"')]]))
eq('a \nb\n', eval([[system('echo a & echo b')]]))
eq('a \n', eval([[system('echo a 2>&1')]]))
eval([[system('cd "C:\Program Files"')]])
eq(0, eval('v:shell_error'))
end)
it('with shell=cmd', function()
command('set shell=cmd')
eq('"a b"\n', eval([[system('echo "a b"')]]))
end)
it('with shell=$COMSPEC', function()
local comspecshell = eval("fnamemodify($COMSPEC, ':t')")
if comspecshell == 'cmd.exe' then
command('set shell=$COMSPEC')
eq('"a b"\n', eval([[system('echo "a b"')]]))
else
pending('$COMSPEC is not cmd.exe: ' .. comspecshell)
end
end)
it('works with powershell', function()
helpers.set_shell_powershell()
eq('a\nb\n', eval([[system('echo a b')]]))
eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]]))
eq('a b\n', eval([[system('echo "a b"')]]))
end)
end
it('`echo` and waits for its return', function()
feed(':call system("echo")<cr>')
screen:expect([[
@@ -178,11 +224,15 @@ describe('system()', function()
describe('passing no input', function()
it('returns the program output', function()
eq("echoed", eval('system("echo -n echoed")'))
if iswin() then
eq("echoed\n", eval('system("echo echoed")'))
else
eq("echoed", eval('system("echo -n echoed")'))
end
end)
it('to backgrounded command does not crash', function()
-- This is indeterminate, just exercise the codepath. May get E5677.
execute('call system("echo -n echoed &")')
feed_command('call system("echo -n echoed &")')
local v_errnum = string.match(eval("v:errmsg"), "^E%d*:")
if v_errnum then
eq("E5677:", v_errnum)
@@ -197,7 +247,7 @@ describe('system()', function()
end)
it('to backgrounded command does not crash', function()
-- This is indeterminate, just exercise the codepath. May get E5677.
execute('call system("cat - &")')
feed_command('call system("cat - &")')
local v_errnum = string.match(eval("v:errmsg"), "^E%d*:")
if v_errnum then
eq("E5677:", v_errnum)
@@ -275,21 +325,30 @@ describe('system()', function()
end)
end)
if helpers.pending_win32(pending) then return end
describe('systemlist()', function()
-- Similar to `system()`, but returns List instead of String.
before_each(clear)
it('sets the v:shell_error variable', function()
eval([[systemlist("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[systemlist("sh -c 'exit 1'")]])
eq(1, eval('v:shell_error'))
eval([[systemlist("sh -c 'exit 5'")]])
eq(5, eval('v:shell_error'))
eval([[systemlist('this-should-not-exist')]])
eq(127, eval('v:shell_error'))
it('sets v:shell_error', function()
if iswin() then
eval([[systemlist("cmd.exe /c exit")]])
eq(0, eval('v:shell_error'))
eval([[systemlist("cmd.exe /c exit 1")]])
eq(1, eval('v:shell_error'))
eval([[systemlist("cmd.exe /c exit 5")]])
eq(5, eval('v:shell_error'))
eval([[systemlist('this-should-not-exist')]])
eq(1, eval('v:shell_error'))
else
eval([[systemlist("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[systemlist("sh -c 'exit 1'")]])
eq(1, eval('v:shell_error'))
eval([[systemlist("sh -c 'exit 5'")]])
eq(5, eval('v:shell_error'))
eval([[systemlist('this-should-not-exist')]])
eq(127, eval('v:shell_error'))
end
end)
describe('exectues shell function', function()
@@ -387,6 +446,7 @@ describe('systemlist()', function()
after_each(delete_file(fname))
it('replaces NULs by newline characters', function()
if helpers.pending_win32(pending) then return end
eq({'part1\npart2\npart3'}, eval('systemlist("cat '..fname..'")'))
end)
end)

View File

@@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local ok, feed, eq, eval = helpers.ok, helpers.feed, helpers.eq, helpers.eval
local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run
local clear, execute, funcs = helpers.clear, helpers.execute, helpers.funcs
local clear, command, funcs = helpers.clear, helpers.command, helpers.funcs
local curbufmeths = helpers.curbufmeths
describe('timers', function()
@@ -17,14 +17,14 @@ describe('timers', function()
end)
it('works one-shot', function()
execute("call timer_start(50, 'MyHandler')")
command("call timer_start(50, 'MyHandler')")
eq(0,eval("g:val"))
run(nil, nil, nil, 200)
eq(1,eval("g:val"))
end)
it('works one-shot when repeat=0', function()
execute("call timer_start(50, 'MyHandler', {'repeat': 0})")
command("call timer_start(50, 'MyHandler', {'repeat': 0})")
eq(0,eval("g:val"))
run(nil, nil, nil, 200)
eq(1,eval("g:val"))
@@ -32,14 +32,14 @@ describe('timers', function()
it('works with repeat two', function()
execute("call timer_start(50, 'MyHandler', {'repeat': 2})")
command("call timer_start(50, 'MyHandler', {'repeat': 2})")
eq(0,eval("g:val"))
run(nil, nil, nil, 300)
eq(2,eval("g:val"))
end)
it('are triggered during sleep', function()
execute("call timer_start(50, 'MyHandler', {'repeat': 2})")
command("call timer_start(50, 'MyHandler', {'repeat': 2})")
nvim_async("command", "sleep 10")
eq(0,eval("g:val"))
run(nil, nil, nil, 300)
@@ -49,7 +49,7 @@ describe('timers', function()
it('works with zero timeout', function()
-- timer_start does still not invoke the callback immediately
eq(0,eval("[timer_start(0, 'MyHandler', {'repeat': 1000}), g:val][1]"))
run(nil, nil, nil, 300)
run(nil, nil, nil, 400)
eq(1000,eval("g:val"))
end)
@@ -63,12 +63,12 @@ describe('timers', function()
end)
it('are paused when event processing is disabled', function()
execute("call timer_start(50, 'MyHandler', {'repeat': -1})")
command("call timer_start(50, 'MyHandler', {'repeat': -1})")
run(nil, nil, nil, 100)
local count = eval("g:val")
-- shows two line error message and thus invokes the return prompt.
-- if we start to allow event processing here, we need to change this test.
execute("throw 'fatal error'")
feed(':throw "fatal error"<CR>')
run(nil, nil, nil, 300)
feed("<cr>")
local diff = eval("g:val") - count
@@ -76,7 +76,7 @@ describe('timers', function()
end)
it('are triggered in blocking getchar() call', function()
execute("call timer_start(50, 'MyHandler', {'repeat': -1})")
command("call timer_start(50, 'MyHandler', {'repeat': -1})")
nvim_async("command", "let g:c = getchar()")
run(nil, nil, nil, 300)
feed("c")
@@ -157,7 +157,7 @@ describe('timers', function()
endif
endfunc
]])
execute("call timer_start(50, 'MyHandler', {'repeat': -1})")
command("call timer_start(50, 'MyHandler', {'repeat': -1})")
eq(0,eval("g:val"))
run(nil, nil, nil, 300)
eq(3,eval("g:val"))
@@ -170,8 +170,8 @@ describe('timers', function()
let g:val2 += 1
endfunc
]])
execute("call timer_start(50, 'MyHandler', {'repeat': 3})")
execute("call timer_start(100, 'MyHandler2', {'repeat': 2})")
command("call timer_start(50, 'MyHandler', {'repeat': 3})")
command("call timer_start(100, 'MyHandler2', {'repeat': 2})")
run(nil, nil, nil, 300)
eq(3,eval("g:val"))
eq(2,eval("g:val2"))
@@ -186,7 +186,7 @@ describe('timers', function()
let g:val += 1
endfunc
]])
execute("call timer_start(5, 'MyHandler', {'repeat': 1})")
command("call timer_start(5, 'MyHandler', {'repeat': 1})")
run(nil, nil, nil, 300)
eq(1,eval("g:val"))
end)
@@ -201,7 +201,7 @@ describe('timers', function()
echo "evil"
endfunc
]])
execute("call timer_start(100, 'MyHandler', {'repeat': 1})")
command("call timer_start(100, 'MyHandler', {'repeat': 1})")
feed(":good")
screen:sleep(200)
screen:expect([[

View File

@@ -80,6 +80,13 @@ describe('writefile()', function()
eq('a\0\0\0b', read_file(fname))
end)
it('writes with s and S', function()
eq(0, funcs.writefile({'\na\nb\n'}, fname, 'bs'))
eq('\0a\0b\0', read_file(fname))
eq(0, funcs.writefile({'a\n\n\nb'}, fname, 'bS'))
eq('a\0\0\0b', read_file(fname))
end)
it('correctly overwrites file', function()
eq(0, funcs.writefile({'\na\nb\n'}, fname, 'b'))
eq('\0a\0b\0', read_file(fname))
@@ -115,6 +122,8 @@ describe('writefile()', function()
eq('\nE729: using Funcref as a String',
redir_exec(('call writefile(%s)'):format(args:format('function("tr")'))))
end
eq('\nE5060: Unknown flag: «»',
redir_exec(('call writefile([], "%s", "bs«»")'):format(fname)))
eq('TEST', read_file(fname))
end)

View File

@@ -1,5 +1,5 @@
local helpers = require("test.functional.helpers")(after_each)
local eq, execute, funcs = helpers.eq, helpers.execute, helpers.funcs
local eq, command, funcs = helpers.eq, helpers.command, helpers.funcs
local ok = helpers.ok
local clear = helpers.clear
@@ -9,15 +9,15 @@ describe(":argument", function()
end)
it("does not restart :terminal buffer", function()
execute("terminal")
command("terminal")
helpers.feed([[<C-\><C-N>]])
execute("argadd")
command("argadd")
helpers.feed([[<C-\><C-N>]])
local bufname_before = funcs.bufname("%")
local bufnr_before = funcs.bufnr("%")
helpers.ok(nil ~= string.find(bufname_before, "^term://")) -- sanity
execute("argument 1")
command("argument 1")
helpers.feed([[<C-\><C-N>]])
local bufname_after = funcs.bufname("%")

View File

@@ -1,7 +1,7 @@
-- Specs for bang/filter commands
local helpers = require('test.functional.helpers')(after_each)
local feed, execute, clear = helpers.feed, helpers.execute, helpers.clear
local feed, command, clear = helpers.feed, helpers.command, helpers.clear
local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdir
if helpers.pending_win32(pending) then return end
@@ -28,7 +28,7 @@ describe('issues', function()
end)
it('#3269 Last line of shell output is not truncated', function()
execute([[nnoremap <silent>\l :!ls bang_filter_spec<cr>]])
command([[nnoremap <silent>\l :!ls bang_filter_spec<cr>]])
feed([[\l]])
screen:expect([[
~ |

View File

@@ -6,7 +6,7 @@ local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local call = helpers.call
local clear = helpers.clear
local execute = helpers.execute
local command = helpers.command
local exc_exec = helpers.exc_exec
if helpers.pending_win32(pending) then return end
@@ -58,7 +58,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(0, lwd(globalwin))
eq(0, lwd(globalwin, tabnr))
execute('bot split')
command('bot split')
local localwin = call('winnr')
-- Initial window is still using globalDir
eq(globalDir, cwd(localwin))
@@ -66,7 +66,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(0, lwd(globalwin))
eq(0, lwd(globalwin, tabnr))
execute('silent l' .. cmd .. ' ' .. directories.window)
command('silent l' .. cmd .. ' ' .. directories.window)
-- From window with local dir, the original window
-- is still reporting the global dir
eq(globalDir, cwd(globalwin))
@@ -80,7 +80,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(1, lwd(localwin))
eq(1, lwd(localwin, tabnr))
execute('tabnew')
command('tabnew')
-- From new tab page, original window reports global dir
eq(globalDir, cwd(globalwin, tabnr))
eq(0, lwd(globalwin, tabnr))
@@ -100,8 +100,8 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(0, lwd(-1, 0))
eq(0, lwd(-1, globaltab))
execute('tabnew')
execute('silent t' .. cmd .. ' ' .. directories.tab)
command('tabnew')
command('silent t' .. cmd .. ' ' .. directories.tab)
local localtab = call('tabpagenr')
-- From local tab page, original tab reports globalDir
@@ -114,7 +114,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(1, lwd(-1, 0))
eq(1, lwd(-1, localtab))
execute('tabnext')
command('tabnext')
-- From original tab page, local reports as such
eq(globalDir .. '/' .. directories.tab, cwd(-1, localtab))
eq(1, lwd(-1, localtab))
@@ -128,13 +128,13 @@ for _, cmd in ipairs {'cd', 'chdir'} do
end)
it('works with tab-local pwd', function()
execute('silent t' .. cmd .. ' ' .. directories.tab)
command('silent t' .. cmd .. ' ' .. directories.tab)
eq(directories.start, cwd(-1, -1))
eq(0, lwd(-1, -1))
end)
it('works with window-local pwd', function()
execute('silent l' .. cmd .. ' ' .. directories.window)
command('silent l' .. cmd .. ' ' .. directories.window)
eq(directories.start, cwd(-1, -1))
eq(0, lwd(-1, -1))
end)
@@ -145,18 +145,18 @@ for _, cmd in ipairs {'cd', 'chdir'} do
local globalDir = directories.start
-- Create a new tab and change directory
execute('tabnew')
execute('silent t' .. cmd .. ' ' .. directories.tab)
command('tabnew')
command('silent t' .. cmd .. ' ' .. directories.tab)
eq(globalDir .. '/' .. directories.tab, tcwd())
-- Create a new tab and verify it has inherited the directory
execute('tabnew')
command('tabnew')
eq(globalDir .. '/' .. directories.tab, tcwd())
-- Change tab and change back, verify that directories are correct
execute('tabnext')
command('tabnext')
eq(globalDir, tcwd())
execute('tabprevious')
command('tabprevious')
eq(globalDir .. '/' .. directories.tab, tcwd())
end)
end)
@@ -164,7 +164,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do
it('works', function()
local globalDir = directories.start
-- Create a new tab first and verify that is has the same working dir
execute('tabnew')
command('tabnew')
eq(globalDir, cwd())
eq(globalDir, tcwd()) -- has no tab-local directory
eq(0, tlwd())
@@ -172,7 +172,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(0, wlwd())
-- Change tab-local working directory and verify it is different
execute('silent t' .. cmd .. ' ' .. directories.tab)
command('silent t' .. cmd .. ' ' .. directories.tab)
eq(globalDir .. '/' .. directories.tab, cwd())
eq(cwd(), tcwd()) -- working directory maches tab directory
eq(1, tlwd())
@@ -180,46 +180,46 @@ for _, cmd in ipairs {'cd', 'chdir'} do
eq(0, wlwd())
-- Create a new window in this tab to test `:lcd`
execute('new')
command('new')
eq(1, tlwd()) -- Still tab-local working directory
eq(0, wlwd()) -- Still no window-local working directory
eq(globalDir .. '/' .. directories.tab, cwd())
execute('silent l' .. cmd .. ' ../' .. directories.window)
command('silent l' .. cmd .. ' ../' .. directories.window)
eq(globalDir .. '/' .. directories.window, cwd())
eq(globalDir .. '/' .. directories.tab, tcwd())
eq(1, wlwd())
-- Verify the first window still has the tab local directory
execute('wincmd w')
command('wincmd w')
eq(globalDir .. '/' .. directories.tab, cwd())
eq(globalDir .. '/' .. directories.tab, tcwd())
eq(0, wlwd()) -- No window-local directory
-- Change back to initial tab and verify working directory has stayed
execute('tabnext')
command('tabnext')
eq(globalDir, cwd() )
eq(0, tlwd())
eq(0, wlwd())
-- Verify global changes don't affect local ones
execute('silent ' .. cmd .. ' ' .. directories.global)
command('silent ' .. cmd .. ' ' .. directories.global)
eq(globalDir .. '/' .. directories.global, cwd())
execute('tabnext')
command('tabnext')
eq(globalDir .. '/' .. directories.tab, cwd())
eq(globalDir .. '/' .. directories.tab, tcwd())
eq(0, wlwd()) -- Still no window-local directory in this window
-- Unless the global change happened in a tab with local directory
execute('silent ' .. cmd .. ' ..')
command('silent ' .. cmd .. ' ..')
eq(globalDir, cwd() )
eq(0 , tlwd())
eq(0 , wlwd())
-- Which also affects the first tab
execute('tabnext')
command('tabnext')
eq(globalDir, cwd())
-- But not in a window with its own local directory
execute('tabnext | wincmd w')
command('tabnext | wincmd w')
eq(globalDir .. '/' .. directories.window, cwd() )
eq(0 , tlwd())
eq(globalDir .. '/' .. directories.window, wcwd())
@@ -280,8 +280,8 @@ describe("getcwd()", function ()
end)
it("returns empty string if working directory does not exist", function()
execute("cd "..directories.global)
execute("call delete('../"..directories.global.."', 'd')")
command("cd "..directories.global)
command("call delete('../"..directories.global.."', 'd')")
eq("", helpers.eval("getcwd()"))
end)
end)

View File

@@ -1,7 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, source = helpers.clear, helpers.feed, helpers.source
local execute = helpers.execute
local command = helpers.command
describe("CTRL-C (mapped)", function()
before_each(function()
@@ -20,7 +20,7 @@ describe("CTRL-C (mapped)", function()
nnoremap <C-C> <NOP>
]])
execute("silent edit! test/functional/fixtures/bigfile.txt")
command("silent edit! test/functional/fixtures/bigfile.txt")
local screen = Screen.new(52, 6)
screen:attach()
screen:set_default_attr_ids({
@@ -41,13 +41,13 @@ describe("CTRL-C (mapped)", function()
local function test_ctrl_c(ms)
feed(":global/^/p<CR>")
helpers.sleep(ms)
screen:sleep(ms)
feed("<C-C>")
screen:expect([[Interrupt]], nil, nil, nil, true)
end
-- The test is time-sensitive. Try different sleep values.
local ms_values = {1, 10, 100}
local ms_values = {100, 1000, 10000}
for i, ms in ipairs(ms_values) do
if i < #ms_values then
local status, _ = pcall(test_ctrl_c, ms)

View File

@@ -9,7 +9,7 @@ local eval = helpers.eval
describe('dictionary change notifications', function()
local channel
setup(function()
before_each(function()
clear()
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
@@ -18,19 +18,15 @@ describe('dictionary change notifications', function()
-- the same set of tests are applied to top-level dictionaries(g:, b:, w: and
-- t:) and a dictionary variable, so we generate them in the following
-- function.
local function gentests(dict_expr, dict_expr_suffix, dict_init)
if not dict_expr_suffix then
dict_expr_suffix = ''
end
local function gentests(dict_expr, dict_init)
local function update(opval, key)
if not key then
key = 'watched'
end
if opval == '' then
nvim('command', "unlet "..dict_expr..dict_expr_suffix..key)
command(('unlet %s[\'%s\']'):format(dict_expr, key))
else
nvim('command', "let "..dict_expr..dict_expr_suffix..key.." "..opval)
command(('let %s[\'%s\'] %s'):format(dict_expr, key, opval))
end
end
@@ -48,9 +44,9 @@ describe('dictionary change notifications', function()
eq({'notification', 'values', {key, vals}}, next_msg())
end
describe('watcher', function()
describe(dict_expr .. ' watcher', function()
if dict_init then
setup(function()
before_each(function()
source(dict_init)
end)
end
@@ -58,7 +54,7 @@ describe('dictionary change notifications', function()
before_each(function()
source([[
function! g:Changed(dict, key, value)
if a:dict != ]]..dict_expr..[[ |
if a:dict isnot ]]..dict_expr..[[ |
throw 'invalid dict'
endif
call rpcnotify(g:channel, 'values', a:key, a:value)
@@ -143,6 +139,32 @@ describe('dictionary change notifications', function()
]])
end)
it('is triggered for empty keys', function()
command([[
call dictwatcheradd(]]..dict_expr..[[, "", "g:Changed")
]])
update('= 1', '')
verify_value({new = 1}, '')
update('= 2', '')
verify_value({old = 1, new = 2}, '')
command([[
call dictwatcherdel(]]..dict_expr..[[, "", "g:Changed")
]])
end)
it('is triggered for empty keys when using catch-all *', function()
command([[
call dictwatcheradd(]]..dict_expr..[[, "*", "g:Changed")
]])
update('= 1', '')
verify_value({new = 1}, '')
update('= 2', '')
verify_value({old = 1, new = 2}, '')
command([[
call dictwatcherdel(]]..dict_expr..[[, "*", "g:Changed")
]])
end)
-- test a sequence of updates of different types to ensure proper memory
-- management(with ASAN)
local function test_updates(tests)
@@ -190,10 +212,10 @@ describe('dictionary change notifications', function()
gentests('b:')
gentests('w:')
gentests('t:')
gentests('g:dict_var', '.', 'let g:dict_var = {}')
gentests('g:dict_var', 'let g:dict_var = {}')
describe('multiple watchers on the same dict/key', function()
setup(function()
before_each(function()
source([[
function! g:Watcher1(dict, key, value)
call rpcnotify(g:channel, '1', a:key, a:value)
@@ -213,13 +235,37 @@ describe('dictionary change notifications', function()
end)
it('only removes watchers that fully match dict, key and callback', function()
nvim('command', 'let g:key = "value"')
eq({'notification', '1', {'key', {new = 'value'}}}, next_msg())
eq({'notification', '2', {'key', {new = 'value'}}}, next_msg())
nvim('command', 'call dictwatcherdel(g:, "key", "g:Watcher1")')
nvim('command', 'let g:key = "v2"')
eq({'notification', '2', {'key', {old = 'value', new = 'v2'}}}, next_msg())
end)
end)
it('errors out when adding to v:_null_dict', function()
command([[
function! g:Watcher1(dict, key, value)
call rpcnotify(g:channel, '1', a:key, a:value)
endfunction
]])
eq('Vim(call):E46: Cannot change read-only variable "dictwatcheradd() argument"',
exc_exec('call dictwatcheradd(v:_null_dict, "x", "g:Watcher1")'))
end)
describe('errors', function()
before_each(function()
source([[
function! g:Watcher1(dict, key, value)
call rpcnotify(g:channel, '1', a:key, a:value)
endfunction
function! g:Watcher2(dict, key, value)
call rpcnotify(g:channel, '2', a:key, a:value)
endfunction
]])
end)
-- WARNING: This suite depends on the above tests
it('fails to remove if no watcher with matching callback is found', function()
eq("Vim(call):Couldn't find a watcher matching key and callback",
@@ -236,15 +282,24 @@ describe('dictionary change notifications', function()
command('call dictwatcherdel(g:, "key", "g:InvalidCb")')
end)
it('fails with empty keys', function()
eq("Vim(call):E713: Cannot use empty key for Dictionary",
exc_exec('call dictwatcheradd(g:, "", "g:Watcher1")'))
eq("Vim(call):E713: Cannot use empty key for Dictionary",
exc_exec('call dictwatcherdel(g:, "", "g:Watcher1")'))
it('fails to remove watcher from v:_null_dict', function()
eq("Vim(call):Couldn't find a watcher matching key and callback",
exc_exec('call dictwatcherdel(v:_null_dict, "x", "g:Watcher2")'))
end)
--[[
[ it("fails to add/remove if the callback doesn't exist", function()
[ eq("Vim(call):Function g:InvalidCb doesn't exist",
[ exc_exec('call dictwatcheradd(g:, "key", "g:InvalidCb")'))
[ eq("Vim(call):Function g:InvalidCb doesn't exist",
[ exc_exec('call dictwatcherdel(g:, "key", "g:InvalidCb")'))
[ end)
]]
it('does not fail to replace a watcher function', function()
source([[
let g:key = 'v2'
call dictwatcheradd(g:, "key", "g:Watcher2")
function! g:ReplaceWatcher2()
function! g:Watcher2(dict, key, value)
call rpcnotify(g:channel, '2b', a:key, a:value)

View File

@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute
local clear, feed, feed_command = helpers.clear, helpers.feed, helpers.feed_command
describe(":drop", function()
local screen
@@ -15,7 +15,7 @@ describe(":drop", function()
[2] = {reverse = true},
[3] = {bold = true},
})
execute("set laststatus=2")
feed_command("set laststatus=2")
end)
after_each(function()
@@ -23,7 +23,7 @@ describe(":drop", function()
end)
it("works like :e when called with only one window open", function()
execute("drop tmp1.vim")
feed_command("drop tmp1.vim")
screen:expect([[
^ |
{0:~ }|
@@ -39,10 +39,10 @@ describe(":drop", function()
end)
it("switches to an open window showing the buffer", function()
execute("edit tmp1")
execute("vsplit")
execute("edit tmp2")
execute("drop tmp1")
feed_command("edit tmp1")
feed_command("vsplit")
feed_command("edit tmp2")
feed_command("drop tmp1")
screen:expect([[
{2:|}^ |
{0:~ }{2:|}{0:~ }|
@@ -58,11 +58,11 @@ describe(":drop", function()
end)
it("splits off a new window when a buffer can't be abandoned", function()
execute("edit tmp1")
execute("vsplit")
execute("edit tmp2")
feed_command("edit tmp1")
feed_command("vsplit")
feed_command("edit tmp2")
feed("iABC<esc>")
execute("drop tmp3")
feed_command("drop tmp3")
screen:expect([[
^ {2:|} |
{0:~ }{2:|}{0:~ }|

View File

@@ -0,0 +1,321 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local NIL = helpers.NIL
local eval = helpers.eval
local clear = helpers.clear
local meths = helpers.meths
local funcs = helpers.funcs
local source = helpers.source
local dedent = helpers.dedent
local command = helpers.command
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
describe(':echo', function()
before_each(function()
clear()
source([[
function String(s)
return execute('echo a:s')[1:]
endfunction
]])
end)
describe('used to represent floating-point values', function()
it('dumps NaN values', function()
eq('str2float(\'nan\')', eval('String(str2float(\'nan\'))'))
end)
it('dumps infinite values', function()
eq('str2float(\'inf\')', eval('String(str2float(\'inf\'))'))
eq('-str2float(\'inf\')', eval('String(str2float(\'-inf\'))'))
end)
it('dumps regular values', function()
eq('1.5', funcs.String(1.5))
eq('1.56e-20', funcs.String(1.56000e-020))
eq('0.0', eval('String(0.0)'))
end)
it('dumps special v: values', function()
eq('v:true', eval('String(v:true)'))
eq('v:false', eval('String(v:false)'))
eq('v:null', eval('String(v:null)'))
eq('v:true', funcs.String(true))
eq('v:false', funcs.String(false))
eq('v:null', funcs.String(NIL))
end)
it('dumps values with at most six digits after the decimal point',
function()
eq('1.234568e-20', funcs.String(1.23456789123456789123456789e-020))
eq('1.234568', funcs.String(1.23456789123456789123456789))
end)
it('dumps values with at most seven digits before the decimal point',
function()
eq('1234567.891235', funcs.String(1234567.89123456789123456789))
eq('1.234568e7', funcs.String(12345678.9123456789123456789))
end)
it('dumps negative values', function()
eq('-1.5', funcs.String(-1.5))
eq('-1.56e-20', funcs.String(-1.56000e-020))
eq('-1.234568e-20', funcs.String(-1.23456789123456789123456789e-020))
eq('-1.234568', funcs.String(-1.23456789123456789123456789))
eq('-1234567.891235', funcs.String(-1234567.89123456789123456789))
eq('-1.234568e7', funcs.String(-12345678.9123456789123456789))
end)
end)
describe('used to represent numbers', function()
it('dumps regular values', function()
eq('0', funcs.String(0))
eq('-1', funcs.String(-1))
eq('1', funcs.String(1))
end)
it('dumps large values', function()
eq('2147483647', funcs.String(2^31-1))
eq('-2147483648', funcs.String(-2^31))
end)
end)
describe('used to represent strings', function()
it('dumps regular strings', function()
eq('test', funcs.String('test'))
end)
it('dumps empty strings', function()
eq('', funcs.String(''))
end)
it('dumps strings with \' inside', function()
eq('\'\'\'', funcs.String('\'\'\''))
eq('a\'b\'\'', funcs.String('a\'b\'\''))
eq('\'b\'\'d', funcs.String('\'b\'\'d'))
eq('a\'b\'c\'d', funcs.String('a\'b\'c\'d'))
end)
it('dumps NULL strings', function()
eq('', eval('String($XXX_UNEXISTENT_VAR_XXX)'))
end)
it('dumps NULL lists', function()
eq('[]', eval('String(v:_null_list)'))
end)
it('dumps NULL dictionaries', function()
eq('{}', eval('String(v:_null_dict)'))
end)
end)
describe('used to represent funcrefs', function()
before_each(function()
source([[
function Test1()
endfunction
function s:Test2() dict
endfunction
function g:Test3() dict
endfunction
let g:Test2_f = function('s:Test2')
]])
end)
it('dumps references to built-in functions', function()
eq('function', eval('String(function("function"))'))
end)
it('dumps references to user functions', function()
eq('Test1', eval('String(function("Test1"))'))
eq('g:Test3', eval('String(function("g:Test3"))'))
end)
it('dumps references to script functions', function()
eq('<SNR>2_Test2', eval('String(Test2_f)'))
end)
it('dumps partials with self referencing a partial', function()
source([[
function TestDict() dict
endfunction
let d = {}
let TestDictRef = function('TestDict', d)
let d.tdr = TestDictRef
]])
eq(dedent([[
function('TestDict', {'tdr': function('TestDict', {...@1})})
function('TestDict', {'tdr': function('TestDict', {...@1})})]]),
redir_exec('echo String(d.tdr)'))
end)
it('dumps automatically created partials', function()
eq('function(\'<SNR>2_Test2\', {\'f\': function(\'<SNR>2_Test2\')})',
eval('String({"f": Test2_f}.f)'))
eq('function(\'<SNR>2_Test2\', [1], {\'f\': function(\'<SNR>2_Test2\', [1])})',
eval('String({"f": function(Test2_f, [1])}.f)'))
end)
it('dumps manually created partials', function()
eq('function(\'Test3\', [1, 2], {})',
eval('String(function("Test3", [1, 2], {}))'))
eq('function(\'Test3\', {})',
eval('String(function("Test3", {}))'))
eq('function(\'Test3\', [1, 2])',
eval('String(function("Test3", [1, 2]))'))
end)
it('does not crash or halt when dumping partials with reference cycles in self',
function()
meths.set_var('d', {v=true})
eq(dedent([[
{'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}
{'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]]),
redir_exec('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))'))
end)
it('does not show errors when dumping partials referencing the same dictionary',
function()
command('let d = {}')
-- Regression for “eval/typval_encode: Dump empty dictionary before
-- checking for refcycle”, results in error.
eq('[function(\'tr\', {}), function(\'tr\', {})]', eval('String([function("tr", d), function("tr", d)])'))
-- Regression for “eval: Work with reference cycles in partials (self)
-- properly”, results in crash.
eval('extend(d, {"a": 1})')
eq('[function(\'tr\', {\'a\': 1}), function(\'tr\', {\'a\': 1})]', eval('String([function("tr", d), function("tr", d)])'))
end)
it('does not crash or halt when dumping partials with reference cycles in arguments',
function()
meths.set_var('l', {})
eval('add(l, l)')
-- Regression: the below line used to crash (add returns original list and
-- there was error in dumping partials). Tested explicitly in
-- test/unit/api/private_helpers_spec.lua.
eval('add(l, function("Test1", l))')
eq(dedent([=[
function('Test1', [[[...@2], function('Test1', [[...@2]])], function('Test1', [[[...@4], function('Test1', [[...@4]])]])])
function('Test1', [[[...@2], function('Test1', [[...@2]])], function('Test1', [[[...@4], function('Test1', [[...@4]])]])])]=]),
redir_exec('echo String(function("Test1", l))'))
end)
it('does not crash or halt when dumping partials with reference cycles in self and arguments',
function()
meths.set_var('d', {v=true})
meths.set_var('l', {})
eval('add(l, l)')
eval('add(l, function("Test1", l))')
eval('add(l, function("Test1", d))')
eq(dedent([=[
{'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}
{'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]=]),
redir_exec('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))'))
end)
end)
describe('used to represent lists', function()
it('dumps empty list', function()
eq('[]', funcs.String({}))
end)
it('dumps nested lists', function()
eq('[[[[[]]]]]', funcs.String({{{{{}}}}}))
end)
it('dumps nested non-empty lists', function()
eq('[1, [[3, [[5], 4]], 2]]', funcs.String({1, {{3, {{5}, 4}}, 2}}))
end)
it('does not error when dumping recursive lists', function()
meths.set_var('l', {})
eval('add(l, l)')
eq(0, exc_exec('echo String(l)'))
end)
it('dumps recursive lists without error', function()
meths.set_var('l', {})
eval('add(l, l)')
eq('\n[[...@0]]\n[[...@0]]', redir_exec('echo String(l)'))
eq('\n[[[...@1]]]\n[[[...@1]]]', redir_exec('echo String([l])'))
end)
end)
describe('used to represent dictionaries', function()
it('dumps empty dictionary', function()
eq('{}', eval('String({})'))
end)
it('dumps list with two same empty dictionaries, also in partials', function()
command('let d = {}')
eq('[{}, {}]', eval('String([d, d])'))
eq('[function(\'tr\', {}), {}]', eval('String([function("tr", d), d])'))
eq('[{}, function(\'tr\', {})]', eval('String([d, function("tr", d)])'))
end)
it('dumps non-empty dictionary', function()
eq('{\'t\'\'est\': 1}', funcs.String({['t\'est']=1}))
end)
it('does not error when dumping recursive dictionaries', function()
meths.set_var('d', {d=1})
eval('extend(d, {"d": d})')
eq(0, exc_exec('echo String(d)'))
end)
it('dumps recursive dictionaries without the error', function()
meths.set_var('d', {d=1})
eval('extend(d, {"d": d})')
eq('\n{\'d\': {...@0}}\n{\'d\': {...@0}}',
redir_exec('echo String(d)'))
eq('\n{\'out\': {\'d\': {...@1}}}\n{\'out\': {\'d\': {...@1}}}',
redir_exec('echo String({"out": d})'))
end)
end)
describe('used to represent special values', function()
local function chr(n)
return ('%c'):format(n)
end
local function ctrl(c)
return ('%c'):format(c:upper():byte() - 0x40)
end
it('displays hex as hex', function()
-- Regression: due to missing (uint8_t) cast \x80 was represented as
-- ~@<80>.
eq('<80>', funcs.String(chr(0x80)))
eq('<81>', funcs.String(chr(0x81)))
eq('<8e>', funcs.String(chr(0x8e)))
eq('<c2>', funcs.String(('«'):sub(1, 1)))
eq('«', funcs.String(('«'):sub(1, 2)))
end)
it('displays ASCII control characters using ^X notation', function()
eq('^C', funcs.String(ctrl('c')))
eq('^A', funcs.String(ctrl('a')))
eq('^F', funcs.String(ctrl('f')))
end)
it('prints CR, NL and tab as-is', function()
eq('\n', funcs.String('\n'))
eq('\r', funcs.String('\r'))
eq('\t', funcs.String('\t'))
end)
it('prints non-printable UTF-8 in <> notation', function()
-- SINGLE SHIFT TWO, unicode control
eq('<8e>', funcs.String(funcs.nr2char(0x8E)))
-- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as
-- 0xD83C 0xDCA0. This is not valid in UTF-8.
eq('<d83c>', funcs.String(funcs.nr2char(0xD83C)))
eq('<dca0>', funcs.String(funcs.nr2char(0xDCA0)))
eq('<d83c><dca0>', funcs.String(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0)))
end)
end)
end)

View File

@@ -1,7 +1,8 @@
local helpers = require("test.functional.helpers")(after_each)
local eq, execute, funcs = helpers.eq, helpers.execute, helpers.funcs
local eq, command, funcs = helpers.eq, helpers.command, helpers.funcs
local ok = helpers.ok
local clear = helpers.clear
local feed = helpers.feed
describe(":edit", function()
before_each(function()
@@ -9,13 +10,13 @@ describe(":edit", function()
end)
it("without arguments does not restart :terminal buffer", function()
execute("terminal")
helpers.feed([[<C-\><C-N>]])
command("terminal")
feed([[<C-\><C-N>]])
local bufname_before = funcs.bufname("%")
local bufnr_before = funcs.bufnr("%")
helpers.ok(nil ~= string.find(bufname_before, "^term://")) -- sanity
execute("edit")
command("edit")
local bufname_after = funcs.bufname("%")
local bufnr_after = funcs.bufnr("%")

View File

@@ -1,5 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, execute, feed = helpers.clear, helpers.execute, helpers.feed
local clear, feed_command, feed = helpers.clear, helpers.feed_command, helpers.feed
local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval
describe('&encoding', function()
@@ -12,10 +12,10 @@ describe('&encoding', function()
end)
it('cannot be changed after setup', function()
execute('set encoding=latin1')
feed_command('set encoding=latin1')
-- error message expected
feed('<cr>')
neq(nil, string.find(eval('v:errmsg'), '^E474:'))
neq(nil, string.find(eval('v:errmsg'), '^E519:'))
eq('utf-8', eval('&encoding'))
-- check nvim is still in utf-8 mode
eq(3, eval('strwidth("Bär")'))
@@ -25,13 +25,13 @@ describe('&encoding', function()
clear('--cmd', 'set enc=latin1')
-- error message expected
feed('<cr>')
neq(nil, string.find(eval('v:errmsg'), '^E474:'))
neq(nil, string.find(eval('v:errmsg'), '^E519:'))
eq('utf-8', eval('&encoding'))
eq(3, eval('strwidth("Bär")'))
end)
it('can be set to utf-8 without error', function()
execute('set encoding=utf-8')
feed_command('set encoding=utf-8')
eq("", eval('v:errmsg'))
clear('--cmd', 'set enc=utf-8')

View File

@@ -0,0 +1,35 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local funcs = helpers.funcs
local rmdir = helpers.rmdir
describe(':file', function()
local swapdir = lfs.currentdir()..'/Xtest-file_spec'
before_each(function()
clear()
rmdir(swapdir)
lfs.mkdir(swapdir)
end)
after_each(function()
command('%bwipeout!')
rmdir(swapdir)
end)
it("rename does not lose swapfile #6487", function()
local testfile = 'test-file_spec'
local testfile_renamed = testfile..'-renamed'
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
-- attempt to create a swapfile in different directory.
command('set directory^='..swapdir..'//')
command('set swapfile fileformat=unix undolevels=-1')
command('edit! '..testfile)
-- Before #6487 this gave "E301: Oops, lost the swap file !!!" on Windows.
command('file '..testfile_renamed)
eq(testfile_renamed..'.swp',
string.match(funcs.execute('swapname'), '[^%%]+$'))
end)
end)

View File

@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, execute, feed, ok, eval =
helpers.clear, helpers.execute, helpers.feed, helpers.ok, helpers.eval
local clear, feed_command, feed, ok, eval =
helpers.clear, helpers.feed_command, helpers.feed, helpers.ok, helpers.eval
describe(':grep', function()
before_each(clear)
@@ -11,10 +11,10 @@ describe(':grep', function()
return
end
execute([[set grepprg=grep\ -r]])
feed_command([[set grepprg=grep\ -r]])
-- Change to test directory so that the test does not run too long.
execute('cd test')
execute('grep a **/*')
feed_command('cd test')
feed_command('grep a **/*')
feed('<cr>') -- Press ENTER
ok(eval('len(getqflist())') > 9000) -- IT'S OVER 9000!!1
end)

View File

@@ -0,0 +1,43 @@
local Screen = require('test.functional.ui.screen')
local helpers = require("test.functional.helpers")(after_each)
local eq, command = helpers.eq, helpers.command
local clear = helpers.clear
local eval, exc_exec = helpers.eval, helpers.exc_exec
describe(':highlight', function()
local screen
before_each(function()
clear()
screen = Screen.new()
screen:attach()
end)
after_each(function()
screen:detach()
end)
it('invalid color name', function()
eq('Vim(highlight):E421: Color name or number not recognized: ctermfg=#181818',
exc_exec("highlight normal ctermfg=#181818"))
eq('Vim(highlight):E421: Color name or number not recognized: ctermbg=#181818',
exc_exec("highlight normal ctermbg=#181818"))
end)
it('invalid group name', function()
eq('Vim(highlight):E411: highlight group not found: foo',
exc_exec("highlight foo"))
end)
it('"Normal" foreground with red', function()
eq('', eval('synIDattr(hlID("Normal"), "fg", "cterm")'))
command('highlight normal ctermfg=red')
eq('9', eval('synIDattr(hlID("Normal"), "fg", "cterm")'))
end)
it('"Normal" background with red', function()
eq('', eval('synIDattr(hlID("Normal"), "bg", "cterm")'))
command('highlight normal ctermbg=red')
eq('9', eval('synIDattr(hlID("Normal"), "bg", "cterm")'))
end)
end)

View File

@@ -1,23 +1,25 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, execute, nvim = helpers.clear, helpers.execute, helpers.nvim
local expect, feed, command = helpers.expect, helpers.feed, helpers.command
local clear, command, nvim = helpers.clear, helpers.command, helpers.nvim
local expect, feed = helpers.expect, helpers.feed
local eq, eval = helpers.eq, helpers.eval
local funcs = helpers.funcs
describe(':emenu', function()
before_each(function()
clear()
execute('nnoremenu Test.Test inormal<ESC>')
execute('inoremenu Test.Test insert')
execute('vnoremenu Test.Test x')
execute('cnoremenu Test.Test cmdmode')
command('nnoremenu Test.Test inormal<ESC>')
command('inoremenu Test.Test insert')
command('vnoremenu Test.Test x')
command('cnoremenu Test.Test cmdmode')
execute('nnoremenu Edit.Paste p')
execute('cnoremenu Edit.Paste <C-R>"')
command('nnoremenu Edit.Paste p')
command('cnoremenu Edit.Paste <C-R>"')
end)
it('executes correct bindings in normal mode without using API', function()
execute('emenu Test.Test')
command('emenu Test.Test')
expect('normal')
end)
@@ -56,3 +58,572 @@ describe(':emenu', function()
eq('thiscmdmode', eval('getcmdline()'))
end)
end)
describe('menu_get', function()
before_each(function()
clear()
command('nnoremenu &Test.Test inormal<ESC>')
command('inoremenu Test.Test insert')
command('vnoremenu Test.Test x')
command('cnoremenu Test.Test cmdmode')
command('menu Test.Nested.test level1')
command('menu Test.Nested.Nested2 level2')
command('nnoremenu <script> Export.Script p')
command('tmenu Export.Script This is the tooltip')
command('menu ]Export.hidden thisoneshouldbehidden')
command('nnoremenu Edit.Paste p')
command('cnoremenu Edit.Paste <C-R>"')
end)
it("path='', modes='a'", function()
local m = funcs.menu_get("","a");
-- HINT: To print the expected table and regenerate the tests:
-- print(require('pl.pretty').dump(m))
local expected = {
{
shortcut = "T",
hidden = 0,
submenus = {
{
mappings = {
i = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "insert",
silent = 0
},
s = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "x",
silent = 0
},
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "inormal<Esc>",
silent = 0
},
v = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "x",
silent = 0
},
c = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "cmdmode",
silent = 0
}
},
priority = 500,
name = "Test",
hidden = 0
},
{
priority = 500,
name = "Nested",
submenus = {
{
mappings = {
o = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level1",
silent = 0
},
v = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level1",
silent = 0
},
s = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level1",
silent = 0
},
n = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level1",
silent = 0
}
},
priority = 500,
name = "test",
hidden = 0
},
{
mappings = {
o = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level2",
silent = 0
},
v = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level2",
silent = 0
},
s = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level2",
silent = 0
},
n = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "level2",
silent = 0
}
},
priority = 500,
name = "Nested2",
hidden = 0
}
},
hidden = 0
}
},
priority = 500,
name = "Test"
},
{
priority = 500,
name = "Export",
submenus = {
{
tooltip = "This is the tooltip",
hidden = 0,
name = "Script",
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "p",
silent = 0
}
}
}
},
hidden = 0
},
{
priority = 500,
name = "Edit",
submenus = {
{
mappings = {
c = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "<C-R>\"",
silent = 0
},
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "p",
silent = 0
}
},
priority = 500,
name = "Paste",
hidden = 0
}
},
hidden = 0
},
{
priority = 500,
name = "]Export",
submenus = {
{
mappings = {
o = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "thisoneshouldbehidden",
silent = 0
},
v = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "thisoneshouldbehidden",
silent = 0
},
s = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "thisoneshouldbehidden",
silent = 0
},
n = {
sid = 0,
noremap = 0,
enabled = 1,
rhs = "thisoneshouldbehidden",
silent = 0
}
},
priority = 500,
name = "hidden",
hidden = 0
}
},
hidden = 1
}
}
eq(expected, m)
end)
it('matching path, default modes', function()
local m = funcs.menu_get("Export", "a")
local expected = {
{
tooltip = "This is the tooltip",
hidden = 0,
name = "Script",
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "p",
silent = 0
}
}
}
}
eq(expected, m)
end)
it('no path, matching modes', function()
local m = funcs.menu_get("","i")
local expected = {
{
shortcut = "T",
hidden = 0,
submenus = {
{
mappings = {
i = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "insert",
silent = 0
}
},
priority = 500,
name = "Test",
hidden = 0
},
{
}
},
priority = 500,
name = "Test"
}
}
eq(expected, m)
end)
it('matching path and modes', function()
local m = funcs.menu_get("Test","i")
local expected = {
{
mappings = {
i = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "insert",
silent = 0
}
},
priority = 500,
name = "Test",
hidden = 0
}
}
eq(expected, m)
end)
end)
describe('menu_get', function()
before_each(function()
clear()
end)
it('returns <keycode> representation of special keys', function()
command('nnoremenu &Test.Test inormal<ESC>')
command('inoremenu &Test.Test2 <Tab><Esc>')
command('vnoremenu &Test.Test3 yA<C-R>0<Tab>xyz<Esc>')
command('inoremenu &Test.Test4 <c-r>*')
command('inoremenu &Test.Test5 <c-R>+')
command('nnoremenu &Test.Test6 <Nop>')
command('nnoremenu &Test.Test7 <NOP>')
command('nnoremenu &Test.Test8 <NoP>')
command('nnoremenu &Test.Test9 ""')
local m = funcs.menu_get("");
local expected = {
{
shortcut = "T",
hidden = 0,
submenus = {
{
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "inormal<Esc>",
silent = 0
}
},
name = "Test",
hidden = 0
},
{
priority = 500,
mappings = {
i = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "<Tab><Esc>",
silent = 0
}
},
name = "Test2",
hidden = 0
},
{
priority = 500,
mappings = {
s = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "yA<C-R>0<Tab>xyz<Esc>",
silent = 0
},
v = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "yA<C-R>0<Tab>xyz<Esc>",
silent = 0
}
},
name = "Test3",
hidden = 0
},
{
priority = 500,
mappings = {
i = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "<C-R>*",
silent = 0
}
},
name = "Test4",
hidden = 0
},
{
priority = 500,
mappings = {
i = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "<C-R>+",
silent = 0
}
},
name = "Test5",
hidden = 0
},
{
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "",
silent = 0
}
},
name = "Test6",
hidden = 0
},
{
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "",
silent = 0
}
},
name = "Test7",
hidden = 0
},
{
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "",
silent = 0
}
},
name = "Test8",
hidden = 0
},
{
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "\"\"",
silent = 0
}
},
name = "Test9",
hidden = 0
}
},
priority = 500,
name = "Test"
}
}
eq(m, expected)
end)
it('works with right-aligned text and spaces', function()
command('nnoremenu &Test<Tab>Y.Test<Tab>X\\ x inormal<Alt-j>')
command('nnoremenu &Test\\ 1.Test\\ 2 Wargl')
command('nnoremenu &Test4.Test<Tab>3 i space<Esc>')
local m = funcs.menu_get("");
local expected = {
{
shortcut = "T",
hidden = 0,
actext = "Y",
submenus = {
{
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "inormal<Alt-j>",
silent = 0
}
},
hidden = 0,
actext = "X x",
priority = 500,
name = "Test"
}
},
priority = 500,
name = "Test"
},
{
shortcut = "T",
hidden = 0,
submenus = {
{
priority = 500,
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "Wargl",
silent = 0
}
},
name = "Test 2",
hidden = 0
}
},
priority = 500,
name = "Test 1"
},
{
shortcut = "T",
hidden = 0,
submenus = {
{
mappings = {
n = {
sid = 1,
noremap = 1,
enabled = 1,
rhs = "i space<Esc>",
silent = 0
}
},
hidden = 0,
actext = "3",
priority = 500,
name = "Test"
}
},
priority = 500,
name = "Test4"
}
}
eq(m, expected)
end)
end)

View File

@@ -0,0 +1,49 @@
local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local command = helpers.command
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local funcs = helpers.funcs
local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec'
describe(':mksession', function()
local session_file = file_prefix .. '.vim'
local tab_dir = file_prefix .. '.d'
before_each(function()
clear()
lfs.mkdir(tab_dir)
end)
after_each(function()
os.remove(session_file)
lfs.rmdir(tab_dir)
end)
it('restores tab-local working directories', function()
local tmpfile_base = file_prefix .. '-tmpfile'
local cwd_dir = funcs.getcwd()
-- :mksession does not save empty tabs, so create some buffers.
command('edit ' .. tmpfile_base .. '1')
command('tabnew')
command('edit ' .. tmpfile_base .. '2')
command('tcd ' .. tab_dir)
command('tabfirst')
command('mksession ' .. session_file)
-- Create a new test instance of Nvim.
clear()
command('source ' .. session_file)
-- First tab should have the original working directory.
command('tabnext 1')
eq(cwd_dir, funcs.getcwd())
-- Second tab should have the tab-local working directory.
command('tabnext 2')
eq(cwd_dir .. get_pathsep() .. tab_dir, funcs.getcwd())
end)
end)

View File

@@ -0,0 +1,67 @@
local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local command = helpers.command
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local funcs = helpers.funcs
local rmdir = helpers.rmdir
local file_prefix = 'Xtest-functional-ex_cmds-mkview_spec'
describe(':mkview', function()
local tmp_file_base = file_prefix .. '-tmpfile'
local local_dir = file_prefix .. '.d'
local view_dir = file_prefix .. '.view.d'
before_each(function()
clear()
lfs.mkdir(view_dir)
lfs.mkdir(local_dir)
end)
after_each(function()
-- Remove any views created in the view directory
rmdir(view_dir)
lfs.rmdir(local_dir)
end)
it('viewoption curdir restores local current directory', function()
local cwd_dir = funcs.getcwd()
local set_view_dir_command = 'set viewdir=' .. cwd_dir ..
get_pathsep() .. view_dir
-- By default the local current directory should save
command(set_view_dir_command)
command('edit ' .. tmp_file_base .. '1')
command('lcd ' .. local_dir)
command('mkview')
-- Create a new instance of Nvim to remove the 'lcd'
clear()
-- Disable saving the local current directory for the second view
command(set_view_dir_command)
command('set viewoptions-=curdir')
command('edit ' .. tmp_file_base .. '2')
command('lcd ' .. local_dir)
command('mkview')
-- Create a new instance of Nvim to test saved 'lcd' option
clear()
command(set_view_dir_command)
-- Load the view without a saved local current directory
command('edit ' .. tmp_file_base .. '2')
command('loadview')
-- The view's current directory should not have changed
eq(cwd_dir, funcs.getcwd())
-- Load the view with a saved local current directory
command('edit ' .. tmp_file_base .. '1')
command('loadview')
-- The view's local directory should have been saved
eq(cwd_dir .. get_pathsep() .. local_dir, funcs.getcwd())
end)
end)

View File

@@ -1,16 +1,18 @@
local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)
local buf, eq, execute = helpers.curbufmeths, helpers.eq, helpers.execute
local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command
local feed, nvim_prog, wait = helpers.feed, helpers.nvim_prog, helpers.wait
local ok, set_session, spawn = helpers.ok, helpers.set_session, helpers.spawn
local eval = helpers.eval
local shada_file = 'test.shada'
local shada_file = 'Xtest.shada'
local function _clear()
set_session(spawn({nvim_prog, '--embed', '-u', 'NONE', '--cmd',
set_session(spawn({nvim_prog, '--embed', '-u', 'NONE',
-- Need shada for these tests.
'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}))
'-i', shada_file,
'--cmd', 'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}))
end
describe(':oldfiles', function()
@@ -27,12 +29,12 @@ describe(':oldfiles', function()
it('shows most recently used files', function()
local screen = Screen.new(100, 5)
screen:attach()
execute('edit testfile1')
execute('edit testfile2')
execute('wshada ' .. shada_file)
execute('rshada! ' .. shada_file)
feed_command('edit testfile1')
feed_command('edit testfile2')
feed_command('wshada')
feed_command('rshada!')
local oldfiles = helpers.meths.get_vvar('oldfiles')
execute('oldfiles')
feed_command('oldfiles')
screen:expect([[
testfile2 |
1: ]].. add_padding(oldfiles[1]) ..[[ |
@@ -41,6 +43,38 @@ describe(':oldfiles', function()
Press ENTER or type command to continue^ |
]])
end)
it('can be filtered with :filter', function()
feed_command('edit file_one.txt')
local file1 = buf.get_name()
feed_command('edit file_two.txt')
local file2 = buf.get_name()
feed_command('edit another.txt')
local another = buf.get_name()
feed_command('wshada')
feed_command('rshada!')
local function get_oldfiles(cmd)
local t = eval([[split(execute(']]..cmd..[['), "\n")]])
for i, _ in ipairs(t) do
t[i] = t[i]:gsub('^%d+:%s+', '')
end
table.sort(t)
return t
end
local oldfiles = get_oldfiles('oldfiles')
eq({another, file1, file2}, oldfiles)
oldfiles = get_oldfiles('filter file_ oldfiles')
eq({file1, file2}, oldfiles)
oldfiles = get_oldfiles('filter /another/ oldfiles')
eq({another}, oldfiles)
oldfiles = get_oldfiles('filter! file_ oldfiles')
eq({another}, oldfiles)
end)
end)
describe(':browse oldfiles', function()
@@ -50,14 +84,13 @@ describe(':browse oldfiles', function()
before_each(function()
_clear()
execute('edit testfile1')
feed_command('edit testfile1')
filename = buf.get_name()
execute('edit testfile2')
feed_command('edit testfile2')
filename2 = buf.get_name()
execute('wshada ' .. shada_file)
feed_command('wshada')
wait()
_clear()
execute('rshada! ' .. shada_file)
-- Ensure nvim is out of "Press ENTER..." prompt.
feed('<cr>')
@@ -70,7 +103,7 @@ describe(':browse oldfiles', function()
ok(filename == oldfiles[1] or filename == oldfiles[2])
ok(filename2 == oldfiles[1] or filename2 == oldfiles[2])
execute('browse oldfiles')
feed_command('browse oldfiles')
end)
after_each(function()

View File

@@ -0,0 +1,12 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, command, funcs =
helpers.clear, helpers.eq, helpers.command, helpers.funcs
describe(':z^', function()
before_each(clear)
it('correctly sets the cursor after :z^', function()
command('z^')
eq(1, funcs.line('.'))
end)
end)

View File

@@ -0,0 +1,111 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local clear = helpers.clear
local funcs = helpers.funcs
local command = helpers.command
local exc_exec = helpers.exc_exec
local write_file = helpers.write_file
local curbufmeths = helpers.curbufmeths
local source = helpers.source
local file_base = 'Xtest-functional-ex_cmds-quickfix_commands'
before_each(clear)
for _, c in ipairs({'l', 'c'}) do
local file = ('%s.%s'):format(file_base, c)
local filecmd = c .. 'file'
local getfcmd = c .. 'getfile'
local addfcmd = c .. 'addfile'
local getlist = (c == 'c') and funcs.getqflist or (
function() return funcs.getloclist(0) end)
describe((':%s*file commands'):format(c), function()
before_each(function()
write_file(file, ([[
%s-1.res:700:10:Line 700
%s-2.res:800:15:Line 800
]]):format(file, file))
end)
after_each(function()
os.remove(file)
end)
it('work', function()
command(('%s %s'):format(filecmd, file))
-- Second line of each entry (i.e. `nr=-1, …`) was obtained from actual
-- results. First line (i.e. `{lnum=…`) was obtained from legacy test.
local list = {
{lnum=700, col=10, text='Line 700',
nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''},
{lnum=800, col=15, text='Line 800',
nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''},
}
eq(list, getlist())
eq(('%s-1.res'):format(file), funcs.bufname(list[1].bufnr))
eq(('%s-2.res'):format(file), funcs.bufname(list[2].bufnr))
-- Run cfile/lfile from a modified buffer
command('enew!')
curbufmeths.set_lines(1, 1, true, {'Quickfix'})
eq(('Vim(%s):E37: No write since last change (add ! to override)'):format(
filecmd),
exc_exec(('%s %s'):format(filecmd, file)))
write_file(file, ([[
%s-3.res:900:30:Line 900
]]):format(file))
command(('%s %s'):format(addfcmd, file))
list[#list + 1] = {
lnum=900, col=30, text='Line 900',
nr=-1, bufnr=5, valid=1, pattern='', vcol=0, ['type']='',
}
eq(list, getlist())
eq(('%s-3.res'):format(file), funcs.bufname(list[3].bufnr))
write_file(file, ([[
%s-1.res:222:77:Line 222
%s-2.res:333:88:Line 333
]]):format(file, file))
command('enew!')
command(('%s %s'):format(getfcmd, file))
list = {
{lnum=222, col=77, text='Line 222',
nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''},
{lnum=333, col=88, text='Line 333',
nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''},
}
eq(list, getlist())
eq(('%s-1.res'):format(file), funcs.bufname(list[1].bufnr))
eq(('%s-2.res'):format(file), funcs.bufname(list[2].bufnr))
end)
end)
end
describe('quickfix', function()
it('location-list update on buffer modification', function()
source([[
new
setl bt=nofile
let lines = ['Line 1', 'Line 2', 'Line 3', 'Line 4', 'Line 5']
call append(0, lines)
new
setl bt=nofile
call append(0, lines)
let qf_item = {
\ 'lnum': 4,
\ 'text': "This is the error line.",
\ }
let qf_item['bufnr'] = bufnr('%')
call setloclist(0, [qf_item])
wincmd p
let qf_item['bufnr'] = bufnr('%')
call setloclist(0, [qf_item])
1del _
call append(0, ['New line 1', 'New line 2', 'New line 3'])
silent ll
]])
eq({0, 6, 1, 0, 1}, funcs.getcurpos())
end)
end)

View File

@@ -1,19 +1,18 @@
-- Tests for :recover
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local execute, eq, clear, eval, feed, expect, source =
helpers.execute, helpers.eq, helpers.clear, helpers.eval, helpers.feed,
local feed_command, eq, clear, eval, feed, expect, source =
helpers.feed_command, helpers.eq, helpers.clear, helpers.eval, helpers.feed,
helpers.expect, helpers.source
if helpers.pending_win32(pending) then return end
local command = helpers.command
local ok = helpers.ok
local rmdir = helpers.rmdir
describe(':recover', function()
before_each(clear)
it('fails if given a non-existent swapfile', function()
local swapname = 'bogus-swapfile'
execute('recover '..swapname) -- This should not segfault. #2117
feed_command('recover '..swapname) -- This should not segfault. #2117
eq('E305: No swap file found for '..swapname, eval('v:errmsg'))
end)
@@ -23,30 +22,29 @@ describe(':preserve', function()
local swapdir = lfs.currentdir()..'/testdir_recover_spec'
before_each(function()
clear()
helpers.rmdir(swapdir)
rmdir(swapdir)
lfs.mkdir(swapdir)
end)
after_each(function()
helpers.rmdir(swapdir)
command('%bwipeout!')
rmdir(swapdir)
end)
it("saves to custom 'directory' and (R)ecovers (issue #1836)", function()
local testfile = 'testfile_recover_spec'
-- Put swapdir at the start of the 'directory' list. #1836
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
-- attempt to create a swapfile in different directory.
local init = [[
set directory^=]]..swapdir..[[//
set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
set swapfile fileformat=unix undolevels=-1
]]
source(init)
execute('set swapfile fileformat=unix undolevels=-1')
-- Put swapdir at the start of the 'directory' list. #1836
execute('set directory^='..swapdir..'//')
execute('edit '..testfile)
command('edit! '..testfile)
feed('isometext<esc>')
execute('preserve')
source('redir => g:swapname | swapname | redir END')
command('preserve')
source('redir => g:swapname | silent swapname | redir END')
local swappath1 = eval('g:swapname')
@@ -59,19 +57,20 @@ describe(':preserve', function()
source(init)
-- Use the "SwapExists" event to choose the (R)ecover choice at the dialog.
execute('autocmd SwapExists * let v:swapchoice = "r"')
execute('silent edit '..testfile)
source('redir => g:swapname | swapname | redir END')
command('autocmd SwapExists * let v:swapchoice = "r"')
command('silent edit! '..testfile)
source('redir => g:swapname | silent swapname | redir END')
local swappath2 = eval('g:swapname')
-- swapfile from session 1 should end in .swp
assert(testfile..'.swp' == string.match(swappath1, '[^%%]+$'))
-- swapfile from session 2 should end in .swo
assert(testfile..'.swo' == string.match(swappath2, '[^%%]+$'))
expect('sometext')
-- swapfile from session 1 should end in .swp
eq(testfile..'.swp', string.match(swappath1, '[^%%]+$'))
-- swapfile from session 2 should end in .swo
eq(testfile..'.swo', string.match(swappath2, '[^%%]+$'))
-- Verify that :swapname was not truncated (:help 'shortmess').
ok(nil == string.find(swappath1, '%.%.%.'))
ok(nil == string.find(swappath2, '%.%.%.'))
end)
end)

View File

@@ -0,0 +1,75 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local neq = helpers.neq
local meths = helpers.meths
local clear = helpers.clear
local dedent = helpers.dedent
local source = helpers.source
local exc_exec = helpers.exc_exec
local missing_provider = helpers.missing_provider
before_each(clear)
describe('script_get-based command', function()
local garbage = ')}{+*({}]*[;(+}{&[]}{*])('
local function test_garbage_exec(cmd, check_neq)
describe(cmd, function()
it('works correctly when skipping oneline variant', function()
eq(true, pcall(source, (dedent([[
if 0
%s %s
endif
]])):format(cmd, garbage)))
eq('', meths.command_output('messages'))
if check_neq then
neq(0, exc_exec(dedent([[
%s %s
]])):format(cmd, garbage))
end
end)
it('works correctly when skipping HEREdoc variant', function()
eq(true, pcall(source, (dedent([[
if 0
%s << EOF
%s
EOF
endif
]])):format(cmd, garbage)))
eq('', meths.command_output('messages'))
if check_neq then
eq(true, pcall(source, (dedent([[
let g:exc = 0
try
%s << EOF
%s
EOF
catch
let g:exc = v:exception
endtry
]])):format(cmd, garbage)))
neq(0, meths.get_var('exc'))
end
end)
end)
end
clear()
-- Built-in scripts
test_garbage_exec('lua', true)
-- Provider-based scripts
test_garbage_exec('ruby', not missing_provider('ruby'))
test_garbage_exec('python', not missing_provider('python'))
test_garbage_exec('python3', not missing_provider('python3'))
-- Missing scripts
test_garbage_exec('tcl', false)
test_garbage_exec('mzscheme', false)
test_garbage_exec('perl', false)
-- Not really a script
test_garbage_exec('xxxinvalidlanguagexxx', true)
end)

View File

@@ -0,0 +1,17 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local clear = helpers.clear
local exc_exec = helpers.exc_exec
describe(':syntax', function()
before_each(clear)
describe('keyword', function()
it('does not crash when group name contains unprintable characters',
function()
eq('Vim(syntax):E669: Unprintable character in group name',
exc_exec('syntax keyword \024 foo bar'))
end)
end)
end)

View File

@@ -5,7 +5,7 @@ local clear = helpers.clear
local insert = helpers.insert
local feed = helpers.feed
local expect = helpers.expect
local execute = helpers.execute
local feed_command = helpers.feed_command
local exc_exec = helpers.exc_exec
describe(':undojoin command', function()
@@ -14,10 +14,10 @@ describe(':undojoin command', function()
insert([[
Line of text 1
Line of text 2]])
execute('goto 1')
feed_command('goto 1')
end)
it('joins changes in a buffer', function()
execute('undojoin | delete')
feed_command('undojoin | delete')
expect([[
Line of text 2]])
feed('u')
@@ -26,7 +26,7 @@ describe(':undojoin command', function()
end)
it('does not corrupt undolist when connected with redo', function()
feed('ixx<esc>')
execute('undojoin | redo')
feed_command('undojoin | redo')
expect([[
xxLine of text 1
Line of text 2]])

View File

@@ -1,15 +1,29 @@
local helpers = require('test.functional.helpers')(after_each)
local eq, eval, clear, write_file, execute, source, insert =
local lfs = require('lfs')
local eq, eval, clear, write_file, source, insert =
helpers.eq, helpers.eval, helpers.clear, helpers.write_file,
helpers.execute, helpers.source, helpers.insert
helpers.source, helpers.insert
local redir_exec = helpers.redir_exec
local exc_exec = helpers.exc_exec
local command = helpers.command
local feed_command = helpers.feed_command
local funcs = helpers.funcs
local meths = helpers.meths
if helpers.pending_win32(pending) then return end
local fname = 'Xtest-functional-ex_cmds-write'
local fname_bak = fname .. '~'
local fname_broken = fname_bak .. 'broken'
describe(':write', function()
local function cleanup()
os.remove('test_bkc_file.txt')
os.remove('test_bkc_link.txt')
os.remove('test_fifo')
os.remove(fname)
os.remove(fname_bak)
os.remove(fname_broken)
end
before_each(function()
clear()
@@ -20,9 +34,9 @@ describe(':write', function()
end)
it('&backupcopy=auto preserves symlinks', function()
execute('set backupcopy=auto')
command('set backupcopy=auto')
write_file('test_bkc_file.txt', 'content0')
execute("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
command("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
source([[
edit test_bkc_link.txt
call setline(1, ['content1'])
@@ -33,9 +47,9 @@ describe(':write', function()
end)
it('&backupcopy=no replaces symlink with new file', function()
execute('set backupcopy=no')
command('set backupcopy=no')
write_file('test_bkc_file.txt', 'content0')
execute("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
command("silent !ln -s test_bkc_file.txt test_bkc_link.txt")
source([[
edit test_bkc_link.txt
call setline(1, ['content1'])
@@ -56,11 +70,41 @@ describe(':write', function()
insert(text)
-- Blocks until a consumer reads the FIFO.
execute("write >> test_fifo")
feed_command("write >> test_fifo")
-- Read the FIFO, this will unblock the :write above.
local fifo = assert(io.open("test_fifo"))
eq(text.."\n", fifo:read("*all"))
fifo:close()
end)
it('errors out correctly', function()
command('let $HOME=""')
eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~'))
-- Message from check_overwrite
eq(('\nE17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'),
redir_exec('write .'))
meths.set_option('writeany', true)
-- Message from buf_write
eq(('\nE502: "." is a directory'),
redir_exec('write .'))
funcs.mkdir(fname_bak)
meths.set_option('backupdir', '.')
meths.set_option('backup', true)
write_file(fname, 'content0')
eq(0, exc_exec('edit ' .. fname))
funcs.setline(1, 'TTY')
eq('Vim(write):E510: Can\'t make backup file (add ! to override)',
exc_exec('write'))
meths.set_option('backup', false)
funcs.setfperm(fname, 'r--------')
eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)',
exc_exec('write'))
os.remove(fname)
os.remove(fname_bak)
write_file(fname_bak, 'TTYX')
lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true)
eq('Vim(write):E166: Can\'t open linked file for writing',
exc_exec('write!'))
end)
end)

View File

@@ -1,20 +1,21 @@
-- Specs for :wundo and underlying functions
local helpers = require('test.functional.helpers')(after_each)
local execute, clear, eval, feed, spawn, nvim_prog, set_session =
helpers.execute, helpers.clear, helpers.eval, helpers.feed, helpers.spawn,
local command, clear, eval, spawn, nvim_prog, set_session =
helpers.command, helpers.clear, helpers.eval, helpers.spawn,
helpers.nvim_prog, helpers.set_session
describe(':wundo', function()
before_each(clear)
after_each(function()
os.remove(eval('getcwd()') .. '/foo')
end)
it('safely fails on new, non-empty buffer', function()
feed('iabc<esc>')
execute('wundo foo') -- This should not segfault. #1027
command('normal! iabc')
command('wundo foo') -- This should not segfault. #1027
--TODO: check messages for error message
os.remove(eval('getcwd()') .. '/foo') --cleanup
end)
end)
@@ -23,7 +24,7 @@ describe('u_* functions', function()
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
'-c', 'set undodir=. undofile'})
set_session(session)
execute('echo "True"') -- Should not error out due to crashed Neovim
command('echo "True"') -- Should not error out due to crashed Neovim
session:close()
end)
end)

View File

@@ -1,8 +1,8 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local execute, eq, neq, spawn, nvim_prog, set_session, wait, write_file
= helpers.execute, helpers.eq, helpers.neq, helpers.spawn,
helpers.nvim_prog, helpers.set_session, helpers.wait, helpers.write_file
local command, eq, neq, spawn, nvim_prog, set_session, write_file =
helpers.command, helpers.eq, helpers.neq, helpers.spawn,
helpers.nvim_prog, helpers.set_session, helpers.write_file
describe(':wshada', function()
local shada_file = 'wshada_test'
@@ -24,8 +24,7 @@ describe(':wshada', function()
it('creates a shada file', function()
-- file should _not_ exist
eq(nil, lfs.attributes(shada_file))
execute('wsh! '..shada_file)
wait()
command('wsh! '..shada_file)
-- file _should_ exist
neq(nil, lfs.attributes(shada_file))
end)
@@ -40,8 +39,7 @@ describe(':wshada', function()
eq(text, io.open(shada_file):read())
neq(nil, lfs.attributes(shada_file))
execute('wsh! '..shada_file)
wait()
command('wsh! '..shada_file)
-- File should have been overwritten with a shada file.
local fp = io.open(shada_file, 'r')

Binary file not shown.

Binary file not shown.

View File

@@ -5,7 +5,13 @@ let s:methods = {}
let g:cliplossy = 0
let g:cliperror = 0
" Count how many times the clipboard was invoked.
let g:clip_called_get = 0
let g:clip_called_set = 0
function! s:methods.get(reg)
let g:clip_called_get += 1
if g:cliperror
return 0
end
@@ -19,6 +25,8 @@ function! s:methods.get(reg)
endfunction
function! s:methods.set(lines, regtype, reg)
let g:clip_called_set += 1
if a:reg == '"'
call s:methods.set(a:lines,a:regtype,'+')
call s:methods.set(a:lines,a:regtype,'*')

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,6 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <stdio.h>
int main(int argc, char **argv)

View File

@@ -1,3 +1,6 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <stdio.h>
#include <string.h>
#include <stdint.h>

View File

@@ -1,40 +1,49 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
uv_tty_t tty;
#ifdef _WIN32
#include <windows.h>
bool owns_tty(void)
{
HWND consoleWnd = GetConsoleWindow();
DWORD dwProcessId;
GetWindowThreadProcessId(consoleWnd, &dwProcessId);
return GetCurrentProcessId() == dwProcessId;
}
# include <windows.h>
#else
#include <unistd.h>
bool owns_tty(void)
{
return getsid(0) == getpid();
}
# include <unistd.h>
#endif
// -V:STRUCT_CAST:641
#define STRUCT_CAST(Type, obj) ((Type *)(obj))
#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY)
#define BUF_SIZE 0xfff
#define CTRL_C 0x03
static void walk_cb(uv_handle_t *handle, void *arg) {
uv_tty_t tty;
uv_tty_t tty_out;
bool owns_tty(void)
{
#ifdef _WIN32
// XXX: We need to make proper detect owns tty
// HWND consoleWnd = GetConsoleWindow();
// DWORD dwProcessId;
// GetWindowThreadProcessId(consoleWnd, &dwProcessId);
// return GetCurrentProcessId() == dwProcessId;
return true;
#else
return getsid(0) == getpid();
#endif
}
static void walk_cb(uv_handle_t *handle, void *arg)
{
if (!uv_is_closing(handle)) {
uv_close(handle, NULL);
}
}
#ifndef WIN32
static void sig_handler(int signum)
{
switch(signum) {
switch (signum) {
case SIGWINCH: {
int width, height;
uv_tty_get_winsize(&tty, &width, &height);
@@ -48,15 +57,15 @@ static void sig_handler(int signum)
return;
}
}
#endif
// 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, "rows: %d, cols: %d\n", height, width);
// }
#ifdef WIN32
static void sigwinch_cb(uv_signal_t *handle, int signum)
{
int width, height;
uv_tty_get_winsize(&tty_out, &width, &height);
fprintf(stderr, "rows: %d, cols: %d\n", height, width);
}
#endif
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
{
@@ -74,7 +83,7 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
int *interrupted = stream->data;
for (int i = 0; i < cnt; i++) {
if (buf->base[i] == 3) {
if (buf->base[i] == CTRL_C) {
(*interrupted)++;
}
}
@@ -82,12 +91,14 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
uv_loop_t write_loop;
uv_loop_init(&write_loop);
uv_tty_t out;
uv_tty_init(&write_loop, &out, 1, 0);
uv_tty_init(&write_loop, &out, fileno(stdout), 0);
uv_write_t req;
uv_buf_t b = {.base = buf->base, .len = (size_t)cnt};
uv_write(&req, (uv_stream_t *)&out, &b, 1, NULL);
uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL);
uv_run(&write_loop, UV_RUN_DEFAULT);
uv_close((uv_handle_t *)&out, NULL);
uv_close(STRUCT_CAST(uv_handle_t, &out), NULL);
uv_run(&write_loop, UV_RUN_DEFAULT);
if (uv_loop_close(&write_loop)) {
abort();
@@ -131,7 +142,7 @@ int main(int argc, char **argv)
if (argc > 1) {
int count = atoi(argv[1]);
for (int i = 0; i < count; ++i) {
for (int i = 0; i < count; i++) {
printf("line%d\n", i);
}
fflush(stdout);
@@ -142,11 +153,17 @@ int main(int argc, char **argv)
uv_prepare_t prepare;
uv_prepare_init(uv_default_loop(), &prepare);
uv_prepare_start(&prepare, prepare_cb);
// uv_tty_t tty;
#ifndef WIN32
uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1);
#else
uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1);
uv_tty_init(uv_default_loop(), &tty_out, fileno(stdout), 0);
int width, height;
uv_tty_get_winsize(&tty_out, &width, &height);
#endif
uv_tty_set_mode(&tty, UV_TTY_MODE_RAW);
tty.data = &interrupted;
uv_read_start((uv_stream_t *)&tty, alloc_cb, read_cb);
uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb);
#ifndef WIN32
struct sigaction sa;
sigemptyset(&sa.sa_mask);
@@ -154,15 +171,17 @@ int main(int argc, char **argv)
sa.sa_handler = sig_handler;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGWINCH, &sa, NULL);
// uv_signal_t sigwinch_watcher;
// uv_signal_init(uv_default_loop(), &sigwinch_watcher);
// sigwinch_watcher.data = &tty;
// uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
#else
uv_signal_t sigwinch_watcher;
uv_signal_init(uv_default_loop(), &sigwinch_watcher);
uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
#endif
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
#ifndef WIN32
// XXX: Without this the SIGHUP handler is skipped on some systems.
sleep(100);
#endif
return 0;
}

View File

@@ -8,6 +8,7 @@ local Session = require('nvim.session')
local TcpStream = require('nvim.tcp_stream')
local SocketStream = require('nvim.socket_stream')
local ChildProcessStream = require('nvim.child_process_stream')
local Paths = require('test.config.paths')
local check_cores = global_helpers.check_cores
local check_logs = global_helpers.check_logs
@@ -16,30 +17,30 @@ local eq = global_helpers.eq
local ok = global_helpers.ok
local map = global_helpers.map
local filter = global_helpers.filter
local dedent = global_helpers.dedent
local start_dir = lfs.currentdir()
-- XXX: NVIM_PROG takes precedence, QuickBuild sets it.
local nvim_prog = os.getenv('NVIM_PROG') or os.getenv('NVIM_PRG') or 'build/bin/nvim'
local nvim_prog = (
os.getenv('NVIM_PROG')
or os.getenv('NVIM_PRG')
or Paths.test_build_dir .. '/bin/nvim'
)
-- Default settings for the test session.
local nvim_set = 'set shortmess+=I background=light noswapfile noautoindent'
..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
..' belloff= noshowcmd noruler'
..' belloff= noshowcmd noruler nomore'
local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
'--cmd', nvim_set, '--embed'}
local mpack = require('mpack')
local tmpname = global_helpers.tmpname
local uname = global_helpers.uname
-- Formulate a path to the directory containing nvim. We use this to
-- help run test executables. It helps to keep the tests working, even
-- when the build is not in the default location.
-- Directory containing nvim.
local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "")
if nvim_dir == nvim_prog then
nvim_dir = "."
end
local mpack = require('mpack')
local tmpname = global_helpers.tmpname
local uname = global_helpers.uname
local prepend_argv
if os.getenv('VALGRIND') then
@@ -75,8 +76,8 @@ end
local session, loop_running, last_error
local function set_session(s)
if session then
local function set_session(s, keep)
if session and not keep then
session:close()
end
session = s
@@ -99,6 +100,22 @@ local function next_message()
return session:next_message()
end
local function expect_twostreams(msgs1, msgs2)
local pos1, pos2 = 1, 1
while pos1 <= #msgs1 or pos2 <= #msgs2 do
local msg = next_message()
if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then
pos1 = pos1 + 1
elseif pos2 <= #msgs2 then
eq(msgs2[pos2], msg)
pos2 = pos2 + 1
else
-- already failed, but show the right error message
eq(msgs1[pos1], msg)
end
end
end
local function call_and_stop_on_error(...)
local status, result = copcall(...) -- luacheck: ignore
if not status then
@@ -173,7 +190,7 @@ local os_name = (function()
end)()
local function iswin()
return os_name() == 'windows'
return package.config:sub(1,1) == '\\'
end
-- Executes a VimL function.
@@ -191,28 +208,6 @@ local function nvim_feed(input)
end
end
local function dedent(str)
-- find minimum common indent across lines
local indent = nil
for line in str:gmatch('[^\n]+') do
local line_indent = line:match('^%s+') or ''
if indent == nil or #line_indent < #indent then
indent = line_indent
end
end
if indent == nil or #indent == 0 then
-- no minimum common indent
return str
end
-- create a pattern for the indent
indent = indent:gsub('%s', '[ \t]')
-- strip it from the first line
str = str:gsub('^'..indent, '')
-- strip it from the remaining lines
str = str:gsub('[\n]'..indent, '\n')
return str
end
local function feed(...)
for _, v in ipairs({...}) do
nvim_feed(dedent(v))
@@ -267,12 +262,13 @@ local function retry(max, max_ms, fn)
return result
end
if (max and tries >= max) or (luv.now() - start_time > timeout) then
break
if type(result) == "string" then
result = "\nretry() attempts: "..tostring(tries).."\n"..result
end
error(result)
end
tries = tries + 1
end
-- Do not use pcall() for the final attempt, let the failure bubble up.
return fn()
end
local function clear(...)
@@ -325,7 +321,7 @@ end
-- Executes an ex-command by user input. Because nvim_input() is used, VimL
-- errors will not manifest as client (lua) errors. Use command() for that.
local function execute(...)
local function feed_command(...)
for _, v in ipairs({...}) do
if v:sub(1, 1) ~= '/' then
-- not a search command, prefix with colon
@@ -339,7 +335,14 @@ end
-- Dedent the given text and write it to the file name.
local function write_file(name, text, dont_dedent)
local file = io.open(name, 'w')
if not dont_dedent then
if type(text) == 'table' then
-- Byte blob
local bytes = text
text = ''
for _, char in ipairs(bytes) do
text = ('%s%c'):format(text, char)
end
elseif not dont_dedent then
text = dedent(text)
end
file:write(text)
@@ -357,18 +360,30 @@ local function read_file(name)
return ret
end
local sourced_fnames = {}
local function source(code)
local fname = tmpname()
write_file(fname, code)
nvim_command('source '..fname)
os.remove(fname)
-- DO NOT REMOVE FILE HERE.
-- do_source() has a habit of checking whether files are “same” by using inode
-- and device IDs. If you run two source() calls in quick succession there is
-- a good chance that underlying filesystem will reuse the inode, making files
-- appear as “symlinks” to do_source when it checks FileIDs. With current
-- setup linux machines (both QB, travis and mine(ZyX-I) with XFS) do reuse
-- inodes, Mac OS machines (again, both QB and travis) do not.
--
-- Files appearing as “symlinks” mean that both the first and the second
-- source() calls will use same SID, which may fail some tests which check for
-- exact numbers after `<SNR>` in e.g. function names.
sourced_fnames[#sourced_fnames + 1] = fname
return fname
end
local function set_shell_powershell()
source([[
set shell=powershell shellquote=\" shellpipe=\| shellredir=>
set shellcmdflag=\ -ExecutionPolicy\ RemoteSigned\ -Command
set shellcmdflag=\ -NoLogo\ -NoProfile\ -ExecutionPolicy\ RemoteSigned\ -Command
let &shellxquote=' '
]])
end
@@ -405,14 +420,21 @@ local function curbuf(method, ...)
end
local function wait()
-- Execute 'vim_eval' (a deferred function) to block
-- Execute 'nvim_eval' (a deferred function) to block
-- until all pending input is processed.
session:request('vim_eval', '1')
session:request('nvim_eval', '1')
end
-- sleeps the test runner (_not_ the nvim instance)
local function sleep(ms)
run(nil, nil, nil, ms)
local function notification_cb(method, _)
if method == "redraw" then
error("Screen is attached; use screen:sleep() instead.")
end
return true
end
run(nil, notification_cb, nil, ms)
end
local function curbuf_contents()
@@ -445,21 +467,27 @@ end
local function do_rmdir(path)
if lfs.attributes(path, 'mode') ~= 'directory' then
return nil
return -- Don't complain.
end
for file in lfs.dir(path) do
if file ~= '.' and file ~= '..' then
local abspath = path..'/'..file
if lfs.attributes(abspath, 'mode') == 'directory' then
local ret = do_rmdir(abspath) -- recurse
if not ret then
return nil
end
do_rmdir(abspath) -- recurse
else
local ret, err = os.remove(abspath)
if not ret then
error('os.remove: '..err)
return nil
if not session then
error('os.remove: '..err)
else
-- Try Nvim delete(): it handles `readonly` attribute on Windows,
-- and avoids Lua cross-version/platform incompatibilities.
if -1 == nvim_call('delete', abspath) then
local hint = (os_name() == 'windows'
and ' (hint: try :%bwipeout! before rmdir())' or '')
error('delete() failed'..hint..': '..abspath)
end
end
end
end
end
@@ -468,7 +496,6 @@ local function do_rmdir(path)
if not ret then
error('lfs.rmdir('..path..'): '..err)
end
return ret
end
local function rmdir(path)
@@ -500,17 +527,6 @@ local exc_exec = function(cmd)
return ret
end
local function redir_exec(cmd)
nvim_command(([[
redir => g:__output
silent! execute "%s"
redir END
]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0')))
local ret = nvim_eval('get(g:, "__output", 0)')
nvim_command('unlet! g:__output')
return ret
end
local function create_callindex(func)
local table = {}
setmetatable(table, {
@@ -570,7 +586,91 @@ local curbufmeths = create_callindex(curbuf)
local curwinmeths = create_callindex(curwin)
local curtabmeths = create_callindex(curtab)
local M = {
local function redir_exec(cmd)
meths.set_var('__redir_exec_cmd', cmd)
nvim_command([[
redir => g:__redir_exec_output
silent! execute g:__redir_exec_cmd
redir END
]])
local ret = meths.get_var('__redir_exec_output')
meths.del_var('__redir_exec_output')
meths.del_var('__redir_exec_cmd')
return ret
end
local function get_pathsep()
return funcs.fnamemodify('.', ':p'):sub(-1)
end
-- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS.
-- Useful for communicating with child instances.
local function new_pipename()
-- HACK: Start a server temporarily, get the name, then stop it.
local pipename = nvim_eval('serverstart()')
funcs.serverstop(pipename)
return pipename
end
local function missing_provider(provider)
if provider == 'ruby' then
local prog = funcs['provider#' .. provider .. '#Detect']()
return prog == '' and (provider .. ' not detected') or false
elseif provider == 'python' or provider == 'python3' then
local py_major_version = (provider == 'python3' and 3 or 2)
local errors = funcs['provider#pythonx#Detect'](py_major_version)[2]
return errors ~= '' and errors or false
else
assert(false, 'Unknown provider: ' .. provider)
end
end
local function alter_slashes(obj)
if not iswin() then
return obj
end
if type(obj) == 'string' then
local ret = obj:gsub('/', '\\')
return ret
elseif type(obj) == 'table' then
local ret = {}
for k, v in pairs(obj) do
ret[k] = alter_slashes(v)
end
return ret
else
assert(false, 'Could only alter slashes for tables of strings and strings')
end
end
local function hexdump(str)
local len = string.len( str )
local dump = ""
local hex = ""
local asc = ""
for i = 1, len do
if 1 == i % 8 then
dump = dump .. hex .. asc .. "\n"
hex = string.format( "%04x: ", i - 1 )
asc = ""
end
local ord = string.byte( str, i )
hex = hex .. string.format( "%02x ", ord )
if ord >= 32 and ord <= 126 then
asc = asc .. string.char( ord )
else
asc = asc .. "."
end
end
return dump .. hex
.. string.rep( " ", 8 - len % 8 ) .. asc
end
local module = {
prepend_argv = prepend_argv,
clear = clear,
connect = connect,
@@ -582,12 +682,13 @@ local M = {
insert = insert,
iswin = iswin,
feed = feed,
execute = execute,
feed_command = feed_command,
eval = nvim_eval,
call = nvim_call,
command = nvim_command,
request = request,
next_message = next_message,
expect_twostreams = expect_twostreams,
run = run,
stop = stop,
eq = eq,
@@ -600,6 +701,7 @@ local M = {
nvim = nvim,
nvim_async = nvim_async,
nvim_prog = nvim_prog,
nvim_argv = nvim_argv,
nvim_set = nvim_set,
nvim_dir = nvim_dir,
buffer = buffer,
@@ -635,14 +737,22 @@ local M = {
tmpname = tmpname,
meth_pcall = meth_pcall,
NIL = mpack.NIL,
get_pathsep = get_pathsep,
missing_provider = missing_provider,
alter_slashes = alter_slashes,
hexdump = hexdump,
new_pipename = new_pipename,
}
return function(after_each)
if after_each then
after_each(function()
for _, fname in ipairs(sourced_fnames) do
os.remove(fname)
end
check_logs()
check_cores('build/bin/nvim')
end)
end
return M
return module
end

View File

@@ -0,0 +1,43 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
local expect = helpers.expect
local feed = helpers.feed
local insert = helpers.insert
describe('insert-mode Ctrl-O', function()
before_each(clear)
it('enters command mode for one command', function()
feed('ihello world<C-o>')
feed(':let ctrlo = "test"<CR>')
feed('iii')
expect('hello worldiii')
eq(1, eval('ctrlo ==# "test"'))
end)
it('re-enters insert mode at the end of the line when running startinsert', function()
-- #6962
feed('ihello world<C-o>')
feed(':startinsert<CR>')
feed('iii')
expect('hello worldiii')
end)
it('re-enters insert mode at the beginning of the line when running startinsert', function()
insert('hello world')
feed('0<C-o>')
feed(':startinsert<CR>')
feed('aaa')
expect('aaahello world')
end)
it('re-enters insert mode in the middle of the line when running startinsert', function()
insert('hello world')
feed('bi<C-o>')
feed(':startinsert<CR>')
feed('ooo')
expect('hello oooworld')
end)
end)

View File

@@ -0,0 +1,19 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed = helpers.clear, helpers.feed
local expect, command = helpers.expect, helpers.command
describe('insert-mode Ctrl-R', function()
before_each(clear)
it('works', function()
command("let @@ = 'test'")
feed('i<C-r>"')
expect('test')
end)
it('works with multi-byte text', function()
command("let @@ = 'påskägg'")
feed('i<C-r>"')
expect('påskägg')
end)
end)

View File

@@ -3,7 +3,7 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
describe('filename recognition', function()
setup(clear)
@@ -17,17 +17,17 @@ describe('filename recognition', function()
fourth test for URL:\\machine.name\tmp\vimtest2d, and other text]])
-- Go to the first URL and append it to the beginning
execute('/^first', '/tmp', 'call append(0, expand("<cfile>"))')
feed_command('/^first', '/tmp', 'call append(0, expand("<cfile>"))')
-- Repeat for the second URL
-- this time, navigate to the word "URL" instead of "tmp"
execute('/^second', '/URL', 'call append(1, expand("<cfile>"))')
feed_command('/^second', '/URL', 'call append(1, expand("<cfile>"))')
-- Repeat for the remaining URLs. This time, the 'isfname' option must be
-- set to allow '\' in filenames
execute('set isf=@,48-57,/,.,-,_,+,,,$,:,~,\\')
execute('/^third', '/name', 'call append(2, expand("<cfile>"))')
execute('/^fourth', '/URL', 'call append(3, expand("<cfile>"))')
feed_command('set isf=@,48-57,/,.,-,_,+,,,$,:,~,\\')
feed_command('/^third', '/name', 'call append(2, expand("<cfile>"))')
feed_command('/^fourth', '/URL', 'call append(3, expand("<cfile>"))')
-- Delete the initial text, which now starts at line 5
feed('5GdG')

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,9 @@
-- vim: set foldmethod=marker foldmarker=[[,]] :
-- Test for autocommand that changes current buffer on BufEnter event.
-- Check if modelines are interpreted for the correct buffer.
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
describe('BufEnter with modelines', function()
setup(clear)
@@ -20,34 +19,34 @@ describe('BufEnter with modelines', function()
this is a test
end of test file Xxx]])
execute('au BufEnter Xxx brew')
feed_command('au BufEnter Xxx brew')
-- Write test file Xxx
execute('/start of')
execute('.,/end of/w! Xxx')
execute('set ai modeline modelines=3')
feed_command('/start of')
feed_command('.,/end of/w! Xxx')
feed_command('set ai modeline modelines=3')
-- Split to Xxx, autocmd will do :brew
execute('sp Xxx')
feed_command('sp Xxx')
-- Append text with autoindent to this file
feed('G?this is a<CR>')
feed('othis should be auto-indented<Esc>')
-- Go to Xxx, no autocmd anymore
execute('au! BufEnter Xxx')
execute('buf Xxx')
feed_command('au! BufEnter Xxx')
feed_command('buf Xxx')
-- Append text without autoindent to Xxx
feed('G?this is a<CR>')
feed('othis should be in column 1<Esc>')
execute('wq')
feed_command('wq')
-- Include Xxx in the current file
feed('G:r Xxx<CR>')
-- Vim issue #57 do not move cursor on <c-o> when autoindent is set
execute('set fo+=r')
feed_command('set fo+=r')
feed('G')
feed('o# abcdef<Esc>2hi<CR><c-o>d0<Esc>')
feed('o# abcdef<Esc>2hi<c-o>d0<Esc>')

View File

@@ -3,11 +3,13 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local command, expect = helpers.command, helpers.expect
local wait = helpers.wait
describe('test5', function()
setup(clear)
-- luacheck: ignore 621 (Indentation)
it('is working', function()
insert([[
start of test file Xxx
@@ -18,35 +20,37 @@ describe('test5', function()
this is a test
end of test file Xxx]])
execute('w! Xxx0')
execute('au BufLeave Xxx bwipe')
execute('/start of')
command('w! Xxx0')
command('au BufLeave Xxx bwipe')
command('/start of')
-- Write test file Xxx.
execute('.,/end of/w! Xxx')
command('.,/end of/w! Xxx')
-- Split to Xxx.
execute('sp Xxx')
command('sp Xxx')
-- Delete buffer Xxx, now we're back here.
execute('bwipe')
command('bwipe')
feed('G?this is a<cr>')
feed('othis is some more text<esc>')
wait()
-- Append some text to this file.
-- Write current file contents.
execute('?start?,$yank A')
command('?start?,$yank A')
-- Delete current buffer, get an empty one.
execute('bwipe!')
command('bwipe!')
-- Append an extra line to the output register.
feed('ithis is another test line<esc>:yank A<cr>')
wait()
-- Output results
execute('%d')
execute('0put a')
execute('$d')
command('%d')
command('0put a')
command('$d')
-- Assert buffer contents.
expect([[

View File

@@ -2,8 +2,9 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, dedent, eq = helpers.execute, helpers.dedent, helpers.eq
local command, dedent, eq = helpers.command, helpers.dedent, helpers.eq
local curbuf_contents = helpers.curbuf_contents
local wait = helpers.wait
describe('argument list', function()
setup(clear)
@@ -16,10 +17,11 @@ describe('argument list', function()
this is a test
this is a test
end of test file Xxx]])
wait()
command('au BufReadPost Xxx2 next Xxx2 Xxx1')
command('/^start of')
execute('au BufReadPost Xxx2 next Xxx2 Xxx1')
execute('/^start of')
-- Write test file Xxx1
feed('A1<Esc>:.,/end of/w! Xxx1<cr>')
@@ -28,29 +30,31 @@ describe('argument list', function()
-- Write test file Xxx3
feed('$r3:.,/end of/w! Xxx3<cr>')
wait()
-- Redefine arglist; go to Xxx1
execute('next! Xxx1 Xxx2 Xxx3')
command('next! Xxx1 Xxx2 Xxx3')
-- Open window for all args
execute('all')
command('all')
-- Write contents of Xxx1
execute('%yank A')
command('%yank A')
-- Append contents of last window (Xxx1)
feed('')
execute('%yank A')
-- should now be in Xxx2
execute('rew')
-- Append contents of Xxx2
execute('%yank A')
wait()
command('%yank A')
execute('%d')
execute('0put=@a')
execute('$d')
-- should now be in Xxx2
command('rew')
-- Append contents of Xxx2
command('%yank A')
command('%d')
command('0put=@a')
command('$d')
eq(dedent([[
start of test file Xxx1
@@ -74,8 +78,8 @@ describe('argument list', function()
end)
teardown(function()
os.remove('Xxx1')
os.remove('Xxx2')
os.remove('Xxx3')
os.remove('Xxx1')
os.remove('Xxx2')
os.remove('Xxx3')
end)
end)

View File

@@ -2,7 +2,7 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
describe(':ball', function()
setup(clear)
@@ -14,44 +14,44 @@ describe(':ball', function()
this is a test
end of test file Xxx]])
execute('w! Xxx0')
feed_command('w! Xxx0')
feed('gg')
-- Write test file Xxx1
feed('A1:.,/end of/w! Xxx1<cr>')
execute('sp Xxx1')
execute('close')
feed_command('sp Xxx1')
feed_command('close')
-- Write test file Xxx2
feed('$r2:.,/end of/w! Xxx2<cr>')
execute('sp Xxx2')
execute('close')
feed_command('sp Xxx2')
feed_command('close')
-- Write test file Xxx3
feed('$r3:.,/end of/w! Xxx3<cr>')
execute('sp Xxx3')
execute('close')
feed_command('sp Xxx3')
feed_command('close')
execute('au BufReadPost Xxx2 bwipe')
feed_command('au BufReadPost Xxx2 bwipe')
-- Open window for all args, close Xxx2
feed('$r4:ball<cr>')
-- Write contents of this file
execute('%yank A')
feed_command('%yank A')
-- Append contents of second window (Xxx1)
feed('')
execute('%yank A')
feed_command('%yank A')
-- Append contents of last window (this file)
feed('')
execute('%yank A')
feed_command('%yank A')
execute('bf')
execute('%d')
execute('0put=@a')
execute('$d')
feed_command('bf')
feed_command('%d')
feed_command('0put=@a')
feed_command('$d')
expect([[
start of test file Xxx4

View File

@@ -3,7 +3,7 @@
local helpers = require('test.functional.helpers')(after_each)
local feed, source = helpers.feed, helpers.source
local clear, execute, expect, eq, eval = helpers.clear, helpers.execute, helpers.expect, helpers.eq, helpers.eval
local clear, feed_command, expect, eq, eval = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.eval
local write_file, wait, dedent = helpers.write_file, helpers.wait, helpers.dedent
local io = require('io')
@@ -25,15 +25,15 @@ describe('autocommands that delete and unload buffers:', function()
before_each(clear)
it('BufWritePre, BufUnload', function()
execute('au BufWritePre Xxx1 bunload')
execute('au BufWritePre Xxx2 bwipe')
execute('e Xxx2')
feed_command('au BufWritePre Xxx1 bunload')
feed_command('au BufWritePre Xxx2 bwipe')
feed_command('e Xxx2')
eq('Xxx2', eval('bufname("%")'))
execute('e Xxx1')
feed_command('e Xxx1')
eq('Xxx1', eval('bufname("%")'))
-- The legacy test file did not check the error message.
execute('let v:errmsg = "no error"')
execute('write')
feed_command('let v:errmsg = "no error"')
feed_command('write')
-- Discard all "hit enter" prompts and messages.
feed('<C-L>')
eq('E203: Autocommands deleted or unloaded buffer to be written',
@@ -41,11 +41,11 @@ describe('autocommands that delete and unload buffers:', function()
eq('Xxx2', eval('bufname("%")'))
expect(text2)
-- Start editing Xxx2.
execute('e! Xxx2')
feed_command('e! Xxx2')
-- The legacy test file did not check the error message.
execute('let v:errmsg = "no error"')
feed_command('let v:errmsg = "no error"')
-- Write Xxx2, will delete the buffer and give an error msg.
execute('w')
feed_command('w')
-- Discard all "hit enter" prompts and messages.
feed('<C-L>')
eq('E203: Autocommands deleted or unloaded buffer to be written',
@@ -73,17 +73,17 @@ describe('autocommands that delete and unload buffers:', function()
au BufUnload * call CloseAll()
au VimLeave * call WriteToOut()
]])
execute('e Xxx2')
feed_command('e Xxx2')
-- Discard all "hit enter" prompts and messages.
feed('<C-L>')
execute('e Xxx1')
feed_command('e Xxx1')
-- Discard all "hit enter" prompts and messages.
feed('<C-L>')
execute('e Makefile') -- an existing file
feed_command('e Makefile') -- an existing file
feed('<C-L>')
execute('sp new2')
feed_command('sp new2')
feed('<C-L>')
execute('q')
feed_command('q')
wait()
eq('VimLeave done',
string.match(io.open('test.out', 'r'):read('*all'), "^%s*(.-)%s*$"))

View File

@@ -1,22 +0,0 @@
-- Test for Bufleave autocommand that deletes the buffer we are about to edit.
local helpers = require('test.functional.helpers')(after_each)
local clear, insert = helpers.clear, helpers.insert
local execute, expect = helpers.execute, helpers.expect
describe('BufLeave autocommand', function()
setup(clear)
it('is working', function()
insert([[
start of test file xx
end of test file xx]])
execute('au BufLeave * bwipe yy')
execute('e yy')
expect([[
start of test file xx
end of test file xx]])
end)
end)

View File

@@ -14,8 +14,8 @@
local helpers= require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local clear, execute, expect, eq, neq, dedent, write_file, feed =
helpers.clear, helpers.execute, helpers.expect, helpers.eq, helpers.neq,
local clear, feed_command, expect, eq, neq, dedent, write_file, feed =
helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq,
helpers.dedent, helpers.write_file, helpers.feed
if helpers.pending_win32(pending) then return end
@@ -66,26 +66,26 @@ describe('file reading, writing and bufnew and filter autocommands', function()
it('FileReadPost (using gzip)', function()
prepare_gz_file('Xtestfile', text1)
execute('let $GZIP = ""')
feed_command('let $GZIP = ""')
--execute('au FileChangedShell * echo "caught FileChangedShell"')
execute('set bin')
execute("au FileReadPost *.gz '[,']!gzip -d")
feed_command('set bin')
feed_command("au FileReadPost *.gz '[,']!gzip -d")
-- Read and decompress the testfile.
execute('$r Xtestfile.gz')
feed_command('$r Xtestfile.gz')
expect('\n'..text1)
end)
it('BufReadPre, BufReadPost (using gzip)', function()
prepare_gz_file('Xtestfile', text1)
local gzip_data = io.open('Xtestfile.gz'):read('*all')
execute('let $GZIP = ""')
feed_command('let $GZIP = ""')
-- Setup autocommands to decompress before reading and re-compress afterwards.
execute("au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand('<afile>'))")
execute("au BufReadPre *.gz call rename(expand('<afile>:r'), expand('<afile>'))")
execute("au BufReadPost *.gz call rename(expand('<afile>'), expand('<afile>:r'))")
execute("au BufReadPost *.gz exe '!gzip ' . shellescape(expand('<afile>:r'))")
feed_command("au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand('<afile>'))")
feed_command("au BufReadPre *.gz call rename(expand('<afile>:r'), expand('<afile>'))")
feed_command("au BufReadPost *.gz call rename(expand('<afile>'), expand('<afile>:r'))")
feed_command("au BufReadPost *.gz exe '!gzip ' . shellescape(expand('<afile>:r'))")
-- Edit compressed file.
execute('e! Xtestfile.gz')
feed_command('e! Xtestfile.gz')
-- Discard all prompts and messages.
feed('<C-L>')
-- Expect the decompressed file in the buffer.
@@ -94,13 +94,15 @@ describe('file reading, writing and bufnew and filter autocommands', function()
eq(gzip_data, io.open('Xtestfile.gz'):read('*all'))
end)
-- luacheck: ignore 621 (Indentation)
-- luacheck: ignore 611 (Line contains only whitespaces)
it('FileReadPre, FileReadPost', function()
prepare_gz_file('Xtestfile', text1)
execute('au! FileReadPre *.gz exe "silent !gzip -d " . shellescape(expand("<afile>"))')
execute('au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))')
execute("au! FileReadPost *.gz '[,']s/l/L/")
feed_command('au! FileReadPre *.gz exe "silent !gzip -d " . shellescape(expand("<afile>"))')
feed_command('au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))')
feed_command("au! FileReadPost *.gz '[,']s/l/L/")
-- Read compressed file.
execute('$r Xtestfile.gz')
feed_command('$r Xtestfile.gz')
-- Discard all prompts and messages.
feed('<C-L>')
expect([[
@@ -121,17 +123,17 @@ describe('file reading, writing and bufnew and filter autocommands', function()
end
it('FileAppendPre, FileAppendPost', function()
execute('au BufNewFile *.c read Xtest.c')
feed_command('au BufNewFile *.c read Xtest.c')
-- Will load Xtest.c.
execute('e! foo.c')
execute("au FileAppendPre *.out '[,']s/new/NEW/")
execute('au FileAppendPost *.out !cat Xtest.c >>test.out')
feed_command('e! foo.c')
feed_command("au FileAppendPre *.out '[,']s/new/NEW/")
feed_command('au FileAppendPost *.out !cat Xtest.c >>test.out')
-- Append it to the output file.
execute('w>>test.out')
feed_command('w>>test.out')
-- Discard all prompts and messages.
feed('<C-L>')
-- Expect the decompressed file in the buffer.
execute('e test.out')
feed_command('e test.out')
expect([[
/*
@@ -166,18 +168,18 @@ describe('file reading, writing and bufnew and filter autocommands', function()
* Here is a new .c file
*/]]))
-- Need temp files here.
execute('set shelltemp')
execute('au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")')
execute('au FilterReadPre *.out exe "silent !sed s/e/E/ " . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))')
execute('au FilterReadPre *.out exe "silent !rm " . shellescape(expand("<afile>")) . ".t"')
execute("au FilterReadPost *.out '[,']s/x/X/g")
feed_command('set shelltemp')
feed_command('au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")')
feed_command('au FilterReadPre *.out exe "silent !sed s/e/E/ " . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))')
feed_command('au FilterReadPre *.out exe "silent !rm " . shellescape(expand("<afile>")) . ".t"')
feed_command("au FilterReadPost *.out '[,']s/x/X/g")
-- Edit the output file.
execute('e! test.out')
execute('23,$!cat')
feed_command('e! test.out')
feed_command('23,$!cat')
-- Discard all prompts and messages.
feed('<C-L>')
-- Remove CR for when sed adds them.
execute([[23,$s/\r$//]])
feed_command([[23,$s/\r$//]])
expect([[
startstart
start of testfile

View File

@@ -3,12 +3,19 @@
-- - "./dir", in directory relative to file
-- - "dir", in directory relative to current dir
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local insert, eq = helpers.insert, helpers.eq
local neq, eval = helpers.neq, helpers.eval
local clear, execute = helpers.clear, helpers.execute
local wait, write_file = helpers.wait, helpers.write_file
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local eq = helpers.eq
local neq = helpers.neq
local wait = helpers.wait
local funcs = helpers.funcs
local meths = helpers.meths
local clear = helpers.clear
local insert = helpers.insert
local command = helpers.command
local write_file = helpers.write_file
local curbufmeths = helpers.curbufmeths
local function ls_dir_sorted(dirname)
local files = {}
@@ -36,7 +43,7 @@ describe("'directory' option", function()
clear()
end)
teardown(function()
execute('qall!')
command('qall!')
helpers.rmdir('Xtest.je')
helpers.rmdir('Xtest2')
os.remove('Xtest1')
@@ -49,21 +56,22 @@ describe("'directory' option", function()
line 3 Abcdefghij
end of testfile]])
execute('set swapfile')
execute('set dir=.,~')
meths.set_option('swapfile', true)
curbufmeths.set_option('swapfile', true)
meths.set_option('directory', '.')
-- sanity check: files should not exist yet.
eq(nil, lfs.attributes('.Xtest1.swp'))
execute('e! Xtest1')
command('edit! Xtest1')
wait()
eq('Xtest1', eval('buffer_name("%")'))
eq('Xtest1', funcs.buffer_name('%'))
-- Verify that the swapfile exists. In the legacy test this was done by
-- reading the output from :!ls.
neq(nil, lfs.attributes('.Xtest1.swp'))
execute('set dir=./Xtest2,.,~')
execute('e Xtest1')
meths.set_option('directory', './Xtest2,.')
command('edit Xtest1')
wait()
-- swapfile should no longer exist in CWD.
@@ -71,9 +79,9 @@ describe("'directory' option", function()
eq({ "Xtest1.swp", "Xtest3" }, ls_dir_sorted("Xtest2"))
execute('set dir=Xtest.je,~')
execute('e Xtest2/Xtest3')
eq(1, eval('&swapfile'))
meths.set_option('directory', 'Xtest.je')
command('edit Xtest2/Xtest3')
eq(true, curbufmeths.get_option('swapfile'))
wait()
eq({ "Xtest3" }, ls_dir_sorted("Xtest2"))

View File

@@ -4,11 +4,12 @@
local helpers = require('test.functional.helpers')(after_each)
local feed, insert = helpers.feed, helpers.insert
local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect
local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect
describe('alignment', function()
setup(clear)
-- luacheck: ignore 621 (Indentation)
it('is working', function()
insert([[
test for :left
@@ -19,7 +20,7 @@ describe('alignment', function()
asdfa a
xasdfa a
asxxdfa a
test for :center
a a
fa afd asdf
@@ -28,7 +29,7 @@ describe('alignment', function()
asdfa a
xasdfa asdfasdfasdfasdfasdf
asxxdfa a
test for :right
a a
fa a
@@ -111,34 +112,34 @@ describe('alignment', function()
asxxdfa axxxoikey
asxa;ofa axxxoikey
asdfaqwer axxxoikey
xxxxx xx xxxxxx
xxxxx xx xxxxxx
xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx
xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx
xx xxxxxxx. xxxx xxxx.
> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx
> xxxxxx xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx
aa aa aa aa
bb bb bb bb
cc cc cc cc]])
execute('set tw=65')
feed_command('set tw=65')
feed([[:/^\s*test for :left/,/^\s*test for :center/ left<cr>]])
feed([[:/^\s*test for :center/,/^\s*test for :right/ center<cr>]])
feed([[:/^\s*test for :right/,/^xxx/-1 right<cr>]])
execute('set fo+=tcroql tw=72')
feed_command('set fo+=tcroql tw=72')
feed('/xxxxxxxx$<cr>')
feed('0gq6kk<cr>')
-- Undo/redo here to make the next undo only work on the following changes.
feed('u<cr>')
execute('map gg :.,.+2s/^/x/<CR>kk:set tw=3<CR>gqq')
execute('/^aa')
feed_command('map gg :.,.+2s/^/x/<CR>kk:set tw=3<CR>gqq')
feed_command('/^aa')
feed('ggu<cr>')
-- Assert buffer contents.
@@ -151,7 +152,7 @@ describe('alignment', function()
asdfa a
xasdfa a
asxxdfa a
test for :center
a a
fa afd asdf
@@ -160,7 +161,7 @@ describe('alignment', function()
asdfa a
xasdfa asdfasdfasdfasdfasdf
asxxdfa a
test for :right
a a
fa a
@@ -243,14 +244,14 @@ describe('alignment', function()
asxxdfa axxxoikey
asxa;ofa axxxoikey
asdfaqwer axxxoikey
xxxxx xx xxxxxx xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx
xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx xx xxxxxxx.
xxxx xxxx.
> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx xxxxxx
> xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx
aa aa aa aa
bb bb bb bb
cc cc cc cc]])

View File

@@ -1,11 +1,15 @@
-- Tests for not doing smart indenting when it isn't set.
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed = helpers.feed
local clear = helpers.clear
local insert = helpers.insert
local expect = helpers.expect
local feed_command = helpers.feed_command
describe('unset smart indenting', function()
setup(clear)
before_each(clear)
it('is working', function()
insert([[
@@ -15,8 +19,8 @@ describe('unset smart indenting', function()
test text
test text]])
execute('set nocin nosi ai')
execute('/some')
feed_command('set nocin nosi ai')
feed_command('/some')
feed('2cc#test<Esc>')
expect([[

View File

@@ -3,11 +3,12 @@
local helpers = require('test.functional.helpers')(after_each)
local feed, insert = helpers.feed, helpers.insert
local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect
local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect
describe([[performing "r<Tab>" with 'smarttab' and 'expandtab' set/not set, and "dv_"]], function()
setup(clear)
-- luacheck: ignore 621 (Indentation)
it('is working', function()
insert([[
start text
@@ -19,24 +20,24 @@ describe([[performing "r<Tab>" with 'smarttab' and 'expandtab' set/not set, and
test text
Second line beginning with whitespace]])
execute('set smarttab expandtab ts=8 sw=4')
feed_command('set smarttab expandtab ts=8 sw=4')
-- Make sure that backspace works, no matter what termcap is used.
execute('set t_kD=x7f t_kb=x08')
feed_command('set t_kD=x7f t_kb=x08')
execute('/some')
feed_command('/some')
feed('r ')
execute('set noexpandtab')
execute('/other')
feed_command('set noexpandtab')
feed_command('/other')
feed('r <cr>')
-- Test replacing with Tabs and then backspacing to undo it.
feed('0wR <bs><bs><bs><esc><cr>')
-- Test replacing with Tabs.
feed('0wR <esc><cr>')
-- Test that copyindent works with expandtab set.
execute('set expandtab smartindent copyindent ts=8 sw=8 sts=8')
feed_command('set expandtab smartindent copyindent ts=8 sw=8 sts=8')
feed('o{<cr>x<esc>')
execute('set nosol')
execute('/Second line/')
feed_command('set nosol')
feed_command('/Second line/')
-- Test "dv_"
feed('fwdv_')

View File

@@ -1,11 +1,10 @@
-- vim: set foldmethod=marker foldmarker=[[,]] :
-- Tests Blockwise Visual when there are TABs before the text.
-- First test for undo working properly when executing commands from a register.
-- Also test this in an empty buffer.
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
describe('blockwise visual', function()
setup(clear)
@@ -26,15 +25,15 @@ Ox jAy kdd]])
feed(":let @a = 'Ox<C-v><Esc>jAy<C-v><Esc>kdd'<cr>")
feed('G0k@au')
execute('new')
feed_command('new')
feed('@auY')
execute('quit')
feed_command('quit')
feed('GP')
execute('/start here')
feed_command('/start here')
feed('"by$<C-v>jjlld')
execute('/456')
feed_command('/456')
feed('<C-v>jj"bP')
execute('$-3,$d')
feed_command('$-3,$d')
expect([[
123start here56

View File

@@ -1,9 +1,8 @@
-- vim: set foldmethod=marker foldmarker=[[,]] :
-- Tests for [ CTRL-I with a count and CTRL-W CTRL-I with a count
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
describe('CTRL-W CTRL-I', function()
setup(clear)
@@ -20,18 +19,18 @@ describe('CTRL-W CTRL-I', function()
test text]])
-- Search for the second occurence of start and append to register
execute('/start')
feed_command('/start')
feed('2[<C-i>')
execute('yank A')
feed_command('yank A')
-- Same as above but using different keystrokes.
feed('?start<cr>')
feed('2<C-w><Tab>')
execute('yank A')
feed_command('yank A')
-- Clean buffer and put register
feed('ggdG"ap')
execute('1d')
feed_command('1d')
-- The buffer should now contain:
expect([[

View File

@@ -2,7 +2,7 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed = helpers.clear, helpers.feed
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
describe('line ending', function()
setup(clear)
@@ -14,8 +14,8 @@ describe('line ending', function()
this one does<C-V><C-M>
and the last one doesn't]], '<ESC>')
execute('set ta tx')
execute('e!')
feed_command('set ta tx')
feed_command('e!')
expect("this lines ends in a\r\n"..
"this one doesn't\n"..

View File

@@ -2,7 +2,8 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, insert = helpers.clear, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local command, expect = helpers.command, helpers.expect
local wait = helpers.wait
describe(':edit', function()
setup(clear)
@@ -12,31 +13,32 @@ describe(':edit', function()
The result should be in Xfile1: "fooPIPEbar", in Xfile2: "fooSLASHbar"
foo|bar
foo/bar]])
wait()
-- Prepare some test files
execute('$-1w! Xfile1')
execute('$w! Xfile2')
execute('w! Xfile0')
command('$-1w! Xfile1')
command('$w! Xfile2')
command('w! Xfile0')
-- Open Xfile using '+' range
execute('edit +1 Xfile1')
execute('s/|/PIPE/')
execute('yank A')
execute('w! Xfile1')
command('edit +1 Xfile1')
command('s/|/PIPE/')
command('yank A')
command('w! Xfile1')
-- Open Xfile2 using '|' range
execute('edit Xfile2|1')
execute("s/\\//SLASH/")
execute('yank A')
execute('w! Xfile2')
command('edit Xfile2|1')
command("s/\\//SLASH/")
command('yank A')
command('w! Xfile2')
-- Clean first buffer and put @a
execute('bf')
execute('%d')
execute('0put a')
command('bf')
command('%d')
command('0put a')
-- Remove empty line
execute('$d')
command('$d')
-- The buffer should now contain
expect([[

View File

@@ -3,7 +3,7 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local execute, expect = helpers.execute, helpers.expect
local feed_command, expect = helpers.feed_command, helpers.expect
if helpers.pending_win32(pending) then return end
@@ -21,30 +21,30 @@ describe('jump to a tag with hidden set', function()
SECTION_OFF]])
execute('w! Xxx')
execute('set hidden')
feed_command('w! Xxx')
feed_command('set hidden')
-- Create a link from test25.dir to the current directory.
execute('!rm -f test25.dir')
execute('!ln -s . test25.dir')
feed_command('!rm -f test25.dir')
feed_command('!ln -s . test25.dir')
-- Create tags.text, with the current directory name inserted.
execute('/tags line')
execute('r !pwd')
feed_command('/tags line')
feed_command('r !pwd')
feed('d$/test<cr>')
feed('hP:.w! tags.test<cr>')
-- Try jumping to a tag in the current file, but with a path that contains a
-- symbolic link. When wrong, this will give the ATTENTION message. The next
-- space will then be eaten by hit-return, instead of moving the cursor to 'd'.
execute('set tags=tags.test')
feed_command('set tags=tags.test')
feed('G<C-]> x:yank a<cr>')
execute('!rm -f Xxx test25.dir tags.test')
feed_command('!rm -f Xxx test25.dir tags.test')
-- Put @a and remove empty line
execute('%d')
execute('0put a')
execute('$d')
feed_command('%d')
feed_command('0put a')
feed_command('$d')
-- Assert buffer contents.
expect("#efine SECTION_OFF 3")

View File

@@ -1,9 +1,11 @@
-- Test for :execute, :while and :if
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local execute, expect = helpers.execute, helpers.expect
local expect = helpers.expect
local source = helpers.source
local command = helpers.command
describe(':execute, :while and :if', function()
setup(clear)
@@ -37,7 +39,7 @@ describe(':execute, :while and :if', function()
]])
-- Remove empty line
execute('1d')
command('1d')
-- Assert buffer contents.
expect([[

Some files were not shown because too many files have changed in this diff Show More