mirror of
https://github.com/neovim/neovim.git
synced 2025-10-26 12:27:24 +00:00
docs: dev-quickstart, dev-test #36304
This commit is contained in:
@@ -4,6 +4,9 @@ Contributing to Neovim
|
|||||||
Getting started
|
Getting started
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
If you are new to the codebase, read [:help dev-quickstart](https://neovim.io/doc/user/dev_tools.html#dev-quickstart)
|
||||||
|
to see how to run tests and start hacking on the codebase.
|
||||||
|
|
||||||
If you want to help but don't know where to start, here are some
|
If you want to help but don't know where to start, here are some
|
||||||
low-risk/isolated tasks:
|
low-risk/isolated tasks:
|
||||||
|
|
||||||
@@ -11,9 +14,9 @@ low-risk/isolated tasks:
|
|||||||
- Fix bugs found by [Coverity](#coverity).
|
- Fix bugs found by [Coverity](#coverity).
|
||||||
- [Merge a Vim patch] (requires strong familiarity with Vim)
|
- [Merge a Vim patch] (requires strong familiarity with Vim)
|
||||||
- NOTE: read the above link before sending improvements to "runtime files" (anything in `runtime/`).
|
- NOTE: read the above link before sending improvements to "runtime files" (anything in `runtime/`).
|
||||||
- Vimscript and documentation files are (mostly) maintained by [Vim], not Nvim.
|
- *Vimscript* files are (mostly) maintained by [Vim], not Nvim.
|
||||||
|
- *Lua* files are maintained by *Nvim*.
|
||||||
- Nvim's [filetype detection](https://github.com/neovim/neovim/blob/master/runtime/lua/vim/filetype.lua) behavior matches Vim, so changes to filetype detection should be submitted to [Vim] first.
|
- Nvim's [filetype detection](https://github.com/neovim/neovim/blob/master/runtime/lua/vim/filetype.lua) behavior matches Vim, so changes to filetype detection should be submitted to [Vim] first.
|
||||||
- Lua files are maintained by Nvim.
|
|
||||||
|
|
||||||
Reporting problems
|
Reporting problems
|
||||||
------------------
|
------------------
|
||||||
@@ -33,9 +36,10 @@ Reporting problems
|
|||||||
Developer guidelines
|
Developer guidelines
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
- Read [:help dev](https://neovim.io/doc/user/develop.html#dev) and [:help dev-doc][dev-doc-guide] if you are working on Nvim core.
|
- Read [:help dev-quickstart](https://neovim.io/doc/user/dev_tools.html#dev-quickstart) to see how to run tests and start hacking on the codebase.
|
||||||
- Read [:help dev-ui](https://neovim.io/doc/user/develop.html#dev-ui) if you are developing a UI.
|
- Read [:help dev](https://neovim.io/doc/user/dev.html#dev) and [:help dev-doc][dev-doc-guide] if you are working on Nvim core.
|
||||||
- Read [:help dev-api-client](https://neovim.io/doc/user/develop.html#dev-api-client) if you are developing an API client.
|
- Read [:help dev-ui](https://neovim.io/doc/user/dev.html#dev-ui) if you are developing a UI.
|
||||||
|
- Read [:help dev-api-client](https://neovim.io/doc/user/dev.html#dev-api-client) if you are developing an API client.
|
||||||
- Install `ninja` for faster builds of Nvim.
|
- Install `ninja` for faster builds of Nvim.
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install ninja-build
|
sudo apt-get install ninja-build
|
||||||
@@ -353,8 +357,8 @@ as context, use the `-W` argument as well.
|
|||||||
[Merge a Vim patch]: https://neovim.io/doc/user/dev_vimpatch.html
|
[Merge a Vim patch]: https://neovim.io/doc/user/dev_vimpatch.html
|
||||||
[complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow
|
[complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow
|
||||||
[conventional_commits]: https://www.conventionalcommits.org
|
[conventional_commits]: https://www.conventionalcommits.org
|
||||||
[dev-doc-guide]: https://neovim.io/doc/user/develop.html#dev-doc
|
[dev-doc-guide]: https://neovim.io/doc/user/dev.html#dev-doc
|
||||||
[dev-lua-doc]: https://neovim.io/doc/user/develop.html#dev-lua-doc
|
[dev-lua-doc]: https://neovim.io/doc/user/dev.html#dev-lua-doc
|
||||||
[LuaLS]: https://luals.github.io/wiki/annotations/
|
[LuaLS]: https://luals.github.io/wiki/annotations/
|
||||||
[gcc-warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
|
[gcc-warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
|
||||||
[gh]: https://cli.github.com/
|
[gh]: https://cli.github.com/
|
||||||
|
|||||||
519
runtime/doc/dev_test.txt
Normal file
519
runtime/doc/dev_test.txt
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
*dev_test* Nvim
|
||||||
|
|
||||||
|
|
||||||
|
NVIM REFERENCE MANUAL
|
||||||
|
|
||||||
|
|
||||||
|
Writing tests for Nvim *dev-test*
|
||||||
|
|
||||||
|
Type |gO| to see the table of contents.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Writing tests for Nvim
|
||||||
|
|
||||||
|
Nvim has a powerful yet simple test framework. It's approximately 7x better
|
||||||
|
than whatever you use at work.
|
||||||
|
|
||||||
|
Each test starts a new Nvim process (10-30ms) which is discarded after the
|
||||||
|
test finishes. You assert stuff using `t.eq()` and `screen:expect()` (which
|
||||||
|
automatically waits as needed). That's pretty much it.
|
||||||
|
|
||||||
|
TODO: Expose the test framework as a public interface, for use in 3P plugins:
|
||||||
|
https://github.com/neovim/neovim/issues/34592
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Test framework
|
||||||
|
|
||||||
|
Tests are broadly divided into unit tests (`test/unit/`), functional tests
|
||||||
|
(`test/functional/`) and old tests (`test/old/testdir/`).
|
||||||
|
|
||||||
|
- Unit testing is achieved by compiling the tests as a shared library which is
|
||||||
|
loaded and called by [LuaJit FFI](https://luajit.org/ext_ffi.html).
|
||||||
|
- Functional tests are driven by RPC, so they do not require LuaJit (as
|
||||||
|
opposed to Lua). They are essentially "integration" tests, they test the
|
||||||
|
full system. But they are fast.
|
||||||
|
|
||||||
|
You can learn the [Lua concepts 15 minutes](https://learnxinyminutes.com/docs/lua/),
|
||||||
|
see also |lua-guide|. Use any existing test as a template to start writing new
|
||||||
|
tests, or see |dev-quickstart|.
|
||||||
|
|
||||||
|
Tests are run by the `/cmake/RunTests.cmake` script using `busted` (a Lua test-runner).
|
||||||
|
For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight.
|
||||||
|
|
||||||
|
Depending on the presence of binaries (e.g., `xclip`) some tests will be
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Test Layout
|
||||||
|
|
||||||
|
- `/test/benchmark` : benchmarks
|
||||||
|
- `/test/functional` : functional tests
|
||||||
|
- `/test/unit` : unit tests
|
||||||
|
- `/test/old/testdir` : old tests (from Vim)
|
||||||
|
- `/test/config` : contains `*.in` files which are transformed into `*.lua`
|
||||||
|
files using `configure_file` CMake command: this is for accessing 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/**/testutil.lua` : common utility functions in the context of the test
|
||||||
|
runner
|
||||||
|
- `/test/**/testnvim.lua` : common utility functions in the context of the
|
||||||
|
test session (RPC channel to the Nvim child process created by clear() for each test)
|
||||||
|
- `/test/*/**/*_spec.lua` : actual tests. Files that do not end with
|
||||||
|
`_spec.lua` are libraries like `/test/**/testutil.lua`, except that they have
|
||||||
|
some common topic.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Running tests *dev-run-test*
|
||||||
|
|
||||||
|
EXECUTING TESTS
|
||||||
|
|
||||||
|
To run all tests (except "old" tests): >
|
||||||
|
make test
|
||||||
|
|
||||||
|
To run only _unit_ tests: >
|
||||||
|
make unittest
|
||||||
|
|
||||||
|
To run only _functional_ tests: >
|
||||||
|
make functionaltest
|
||||||
|
|
||||||
|
|
||||||
|
LEGACY TESTS
|
||||||
|
|
||||||
|
To run all legacy Vim tests: >
|
||||||
|
make oldtest
|
||||||
|
|
||||||
|
To run a _single_ legacy test file you can use either: >
|
||||||
|
# Specify only the test file name, not the full path.
|
||||||
|
make oldtest TEST_FILE=test_syntax.vim
|
||||||
|
or: >
|
||||||
|
make test/old/testdir/test_syntax.vim
|
||||||
|
|
||||||
|
|
||||||
|
DEBUGGING TESTS
|
||||||
|
|
||||||
|
- Each test gets a test id which looks like "T123". This also appears in the
|
||||||
|
log file. Child processes spawned from a test appear in the logs with the
|
||||||
|
_parent_ name followed by "/c". Example: >
|
||||||
|
|
||||||
|
DBG 2022-06-15T18:37:45.226 T57.58016.0 UI: flush
|
||||||
|
DBG 2022-06-15T18:37:45.226 T57.58016.0 inbuf_poll:442: blocking... events_enabled=0 events_pending=0
|
||||||
|
DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop
|
||||||
|
INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0
|
||||||
|
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
|
||||||
|
INF 2022-06-15T18:37:45.229 T57.58016.0 on_proc_exit:400: exited: pid=58017 status=0 stoptime=0
|
||||||
|
|
||||||
|
- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527): >sh
|
||||||
|
GDB=1 TEST_FILE=test/functional/api/buffer_spec.lua TEST_FILTER='nvim_buf_set_text works$' make functionaltest
|
||||||
|
<
|
||||||
|
Read more about |dev-filter-test|.
|
||||||
|
|
||||||
|
Then, in another terminal: >sh
|
||||||
|
gdb -ex 'target remote localhost:7777' build/bin/nvim
|
||||||
|
<
|
||||||
|
If `$VALGRIND` is also set it will pass `--vgdb=yes` to valgrind instead of
|
||||||
|
starting gdbserver directly.
|
||||||
|
|
||||||
|
See `nvim_argv` in `test/functional/testnvim.lua`.
|
||||||
|
|
||||||
|
- Hanging tests can happen due to unexpected "press-enter" prompts. The
|
||||||
|
default screen width is 50 columns. Commands that try to print lines longer
|
||||||
|
than 50 columns in the command-line, e.g. `:edit very...long...path`, will
|
||||||
|
trigger the prompt. Try using a shorter path, or `:silent edit`.
|
||||||
|
- If you can't figure out what is going on, try to visualize the screen. Put
|
||||||
|
this at the beginning of your test: >
|
||||||
|
local Screen = require('test.functional.ui.screen')
|
||||||
|
local screen = Screen.new()
|
||||||
|
screen:attach()
|
||||||
|
< Then put `screen:snapshot_util()` anywhere in your test. See the comments in
|
||||||
|
`test/functional/ui/screen.lua` for more info.
|
||||||
|
|
||||||
|
DEBUGGING LUA TEST CODE
|
||||||
|
|
||||||
|
Debugging Lua test code is a bit involved. Get your shopping list ready, you'll
|
||||||
|
need to install and configure:
|
||||||
|
|
||||||
|
1. [nvim-dap](https://github.com/mfussenegger/nvim-dap)
|
||||||
|
2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode)
|
||||||
|
3. [nlua](https://github.com/mfussenegger/nlua)
|
||||||
|
4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`)
|
||||||
|
5. A `nbusted` command in `$PATH`. This command can be a copy of `busted` with
|
||||||
|
`exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the
|
||||||
|
path to your `nlua`)
|
||||||
|
|
||||||
|
|
||||||
|
The setup roughly looks like this: >
|
||||||
|
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ nvim used for debugging │◄────┐
|
||||||
|
└─────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ local-lua-debug │ │
|
||||||
|
└─────────────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────┐ │
|
||||||
|
│ nbusted │ │
|
||||||
|
└─────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌───────────┐ │
|
||||||
|
│ test-case │ │
|
||||||
|
└───────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌────────────────────┐ │
|
||||||
|
│ nvim test-instance │ │
|
||||||
|
└────────────────────┘ │
|
||||||
|
│ ┌─────┐ │
|
||||||
|
└──►│ osv │─────────────────┘
|
||||||
|
└─────┘
|
||||||
|
|
||||||
|
|
||||||
|
With these installed you can use a configuration like this: >
|
||||||
|
|
||||||
|
local dap = require("dap")
|
||||||
|
|
||||||
|
|
||||||
|
local function free_port()
|
||||||
|
local tcp = vim.loop.new_tcp()
|
||||||
|
assert(tcp)
|
||||||
|
tcp:bind('127.0.0.1', 0)
|
||||||
|
local port = tcp:getsockname().port
|
||||||
|
tcp:shutdown()
|
||||||
|
tcp:close()
|
||||||
|
return port
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local name = "nvim-test-case" -- arbitrary name
|
||||||
|
local config = {
|
||||||
|
name = name,
|
||||||
|
|
||||||
|
-- value of type must match the key used in `dap.adapters["local-lua"] = ...` from step 2)
|
||||||
|
type = "local-lua",
|
||||||
|
|
||||||
|
request = "launch",
|
||||||
|
cwd = "${workspaceFolder}",
|
||||||
|
program = {
|
||||||
|
command = "nbusted",
|
||||||
|
},
|
||||||
|
args = {
|
||||||
|
"--ignore-lua",
|
||||||
|
"--lazy",
|
||||||
|
"--helper=test/functional/preload.lua",
|
||||||
|
"--lpath=build/?.lua",
|
||||||
|
"--lpath=?.lua",
|
||||||
|
|
||||||
|
-- path to file to debug, could be replaced with a hardcoded string
|
||||||
|
function()
|
||||||
|
return vim.api.nvim_buf_get_name(0)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- You can filter to specific test-case by adding:
|
||||||
|
-- '--filter="' .. test_case_name .. '"',
|
||||||
|
},
|
||||||
|
env = {
|
||||||
|
OSV_PORT = free_port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Whenever the config is used it needs to launch a second debug session that attaches to `osv`
|
||||||
|
-- This makes it possible to step into `exec_lua` code blocks
|
||||||
|
setmetatable(config, {
|
||||||
|
|
||||||
|
__call = function(c)
|
||||||
|
---@param session dap.Session
|
||||||
|
dap.listeners.after.event_initialized["nvim_debug"] = function(session)
|
||||||
|
if session.config.name ~= name then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
dap.listeners.after.event_initialized["nvim_debug"] = nil
|
||||||
|
vim.defer_fn(function()
|
||||||
|
dap.run({
|
||||||
|
name = "attach-osv",
|
||||||
|
type = "nlua", -- value must match the `dap.adapters` definition key for osv
|
||||||
|
request = "attach",
|
||||||
|
port = session.config.env.OSV_PORT,
|
||||||
|
})
|
||||||
|
end, 500)
|
||||||
|
end
|
||||||
|
|
||||||
|
return c
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
You can either add this configuration to your `dap.configurations.lua` list as
|
||||||
|
described in `:help dap-configuration` or create it dynamically in a
|
||||||
|
user-command or function and call it directly via `dap.run(config)`. The latter
|
||||||
|
is useful if you use treesitter to find the test case around a cursor location
|
||||||
|
with a query like the following and set the `--filter` property to it. >query
|
||||||
|
|
||||||
|
(function_call
|
||||||
|
name: (identifier) @name (#any-of? @name "describe" "it")
|
||||||
|
arguments: (arguments
|
||||||
|
(string) @str
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
- You need to add the following boilerplate to each spec file where you want to
|
||||||
|
be able to stop at breakpoints within the test-case code: >
|
||||||
|
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
|
||||||
|
require("lldebugger").start()
|
||||||
|
end
|
||||||
|
< This is a [local-lua-debugger limitation](https://github.com/tomblind/local-lua-debugger-vscode?tab=readme-ov-file#busted)
|
||||||
|
- You cannot step into code of files which get baked into the nvim binary like
|
||||||
|
the `shared.lua`.
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Filtering tests *dev-filter-test*
|
||||||
|
|
||||||
|
FILTER BY NAME
|
||||||
|
|
||||||
|
Tests can be filtered by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`. >
|
||||||
|
|
||||||
|
it('foo api',function()
|
||||||
|
...
|
||||||
|
end)
|
||||||
|
it('bar api',function()
|
||||||
|
...
|
||||||
|
end)
|
||||||
|
|
||||||
|
To run only test with filter name: >
|
||||||
|
TEST_FILTER='foo.*api' make functionaltest
|
||||||
|
|
||||||
|
To run all tests except ones matching a filter: >
|
||||||
|
TEST_FILTER_OUT='foo.*api' make functionaltest
|
||||||
|
|
||||||
|
FILTER BY FILE
|
||||||
|
|
||||||
|
To run a _specific_ unit test: >
|
||||||
|
TEST_FILE=test/unit/foo.lua make unittest
|
||||||
|
|
||||||
|
or >
|
||||||
|
cmake -E env "TEST_FILE=test/unit/foo.lua" cmake --build build --target unittest
|
||||||
|
|
||||||
|
To run a _specific_ functional test: >
|
||||||
|
TEST_FILE=test/functional/foo.lua make functionaltest
|
||||||
|
|
||||||
|
or >
|
||||||
|
cmake -E env "TEST_FILE=test/functional/foo.lua" cmake --build build --target functionaltest
|
||||||
|
|
||||||
|
To _repeat_ a test: >
|
||||||
|
BUSTED_ARGS="--repeat=100 --no-keep-going" TEST_FILE=test/functional/foo_spec.lua make functionaltest
|
||||||
|
|
||||||
|
or >
|
||||||
|
cmake -E env "TEST_FILE=test/functional/foo_spec.lua" cmake -E env BUSTED_ARGS="--repeat=100 --no-keep-going" cmake --build build --target functionaltest
|
||||||
|
|
||||||
|
FILTER BY TAG
|
||||||
|
|
||||||
|
Tests can be "tagged" by adding `#` before a token in the test description. >
|
||||||
|
|
||||||
|
it('#foo bar baz', function()
|
||||||
|
...
|
||||||
|
end)
|
||||||
|
it('#foo another test', function()
|
||||||
|
...
|
||||||
|
end)
|
||||||
|
|
||||||
|
To run only the tagged tests: >
|
||||||
|
TEST_TAG=foo make functionaltest
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
|
||||||
|
- `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 string descriptions
|
||||||
|
found in `it()` and `describe()`.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Writing tests *dev-write-test*
|
||||||
|
|
||||||
|
GUIDELINES
|
||||||
|
|
||||||
|
- Luajit needs to know about type and constant declarations used in function
|
||||||
|
prototypes. The
|
||||||
|
[testutil.lua](https://github.com/neovim/neovim/blob/master/test/unit/testutil.lua)
|
||||||
|
file automatically parses `types.h`, so types used in the tested functions
|
||||||
|
could be moved to it to avoid having to rewrite the declarations in the test
|
||||||
|
files.
|
||||||
|
- `#define` constants must be rewritten `const` or `enum` so they can be
|
||||||
|
"visible" to the tests.
|
||||||
|
- Use `pending()` 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 `it()`
|
||||||
|
([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)).
|
||||||
|
|
||||||
|
WHERE TESTS GO
|
||||||
|
|
||||||
|
Tests in `/test/unit` and `/test/functional` are divided into groups by the
|
||||||
|
semantic component they are testing.
|
||||||
|
|
||||||
|
- Unit tests (`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/`) are higher-level (plugins, UI, 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.
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Fixing tests *dev-fix-test*
|
||||||
|
|
||||||
|
FIXING HARNESS WARNINGS
|
||||||
|
|
||||||
|
> Nvim session T123 took 2000 milliseconds to exit
|
||||||
|
> This indicates a likely problem with the test even if it passed!
|
||||||
|
|
||||||
|
This may indicate a leak, because Nvim waits on uv handles before exiting.
|
||||||
|
Example: https://github.com/neovim/neovim/pull/35768
|
||||||
|
|
||||||
|
|
||||||
|
FIXING LINT FAILURES
|
||||||
|
|
||||||
|
`make lint` (and `make lintlua`) runs [LuaLS](https://github.com/LuaLS/lua-language-server/wiki/Annotations)
|
||||||
|
on the test code.
|
||||||
|
|
||||||
|
If a LuaLS/EmmyLS warning must be ignored, specify the warning code. Example: >lua
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: unused-vararg
|
||||||
|
|
||||||
|
https://github.com/LuaLS/lua-language-server/wiki/Annotations#diagnostic
|
||||||
|
|
||||||
|
Ignore the smallest applicable scope (e.g. inside a function, not at the top of
|
||||||
|
the file).
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Configuration *dev-test-config*
|
||||||
|
|
||||||
|
(TODO: clean this up, too many variables and some of them are not used anymore.)
|
||||||
|
|
||||||
|
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; when defined, treated as
|
||||||
|
Number; !must be defined to function properly):
|
||||||
|
|
||||||
|
- `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`.
|
||||||
|
|
||||||
|
- `CC` (U) (S): specifies which C compiler to use to preprocess files.
|
||||||
|
Currently only compilers with gcc-compatible arguments are supported.
|
||||||
|
|
||||||
|
- `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
|
||||||
|
:7777` inside.
|
||||||
|
|
||||||
|
- `GDBSERVER_PORT` (F) (I): overrides port used for `GDB`.
|
||||||
|
|
||||||
|
- `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files.
|
||||||
|
|
||||||
|
- `VALGRIND` (F) (D): makes nvim instances to be run under `valgrind`. Log
|
||||||
|
files are named `valgrind-%p.log` in this case. Note that non-empty valgrind
|
||||||
|
log may fail tests. Valgrind arguments may be seen in
|
||||||
|
`/test/functional/testnvim.lua`. May be used in conjunction with `GDB`.
|
||||||
|
|
||||||
|
- `VALGRIND_LOG` (F) (S): overrides valgrind log file name used for `VALGRIND`.
|
||||||
|
|
||||||
|
- `TEST_COLORS` (F) (U) (D): enable pretty colors in test runner. Set to true by default.
|
||||||
|
|
||||||
|
- `TEST_SKIP_FRAGILE` (F) (D): makes test suite skip some fragile tests.
|
||||||
|
|
||||||
|
- `TEST_TIMEOUT` (FU) (I): specifies maximum time, in seconds, before the test
|
||||||
|
suite run is killed
|
||||||
|
|
||||||
|
- `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects
|
||||||
|
|
||||||
|
- `NVIM_PRG` (F) (S): path to Nvim executable (default: `build/bin/nvim`).
|
||||||
|
|
||||||
|
- `NVIM_TEST_MAIN_CDEFS` (U) (1): makes `ffi.cdef` run in main process. This
|
||||||
|
raises a possibility of bugs due to conflicts in header definitions, despite
|
||||||
|
the counters, but greatly speeds up unit tests by not requiring `ffi.cdef` to
|
||||||
|
do parsing of big strings with C definitions.
|
||||||
|
|
||||||
|
- `NVIM_TEST_PRINT_I` (U) (1): makes `cimport` print preprocessed, but not yet
|
||||||
|
filtered through `formatc` headers. Used to debug `formatc`. Printing is done
|
||||||
|
with the line numbers.
|
||||||
|
|
||||||
|
- `NVIM_TEST_PRINT_CDEF` (U) (1): makes `cimport` print final lines which will
|
||||||
|
be then passed to `ffi.cdef`. Used to debug errors `ffi.cdef` happens to
|
||||||
|
throw sometimes.
|
||||||
|
|
||||||
|
- `NVIM_TEST_PRINT_SYSCALLS` (U) (1): makes it print to stderr when syscall
|
||||||
|
wrappers are called and what they returned. Used to debug code which makes
|
||||||
|
unit tests be executed in separate processes.
|
||||||
|
|
||||||
|
- `NVIM_TEST_RUN_FAILING_TESTS` (U) (1): makes `itp` run tests which are known
|
||||||
|
to fail (marked by setting third argument to `true`).
|
||||||
|
|
||||||
|
- `NVIM_TEST_CORE_*` (FU) (S): a set of environment variables which specify
|
||||||
|
where to search for core files. Are supposed to be defined all at once.
|
||||||
|
|
||||||
|
- `NVIM_TEST_CORE_GLOB_DIRECTORY` (FU) (S): directory where core files are
|
||||||
|
located. May be `.`. This directory is then recursively searched for core
|
||||||
|
files. Note: this variable must be defined for any of the following to have
|
||||||
|
any effect.
|
||||||
|
|
||||||
|
- `NVIM_TEST_CORE_GLOB_RE` (FU) (S): regular expression which must be matched
|
||||||
|
by core files. E.g. `/core[^/]*$`. May be absent, in which case any file is
|
||||||
|
considered to be matched.
|
||||||
|
|
||||||
|
- `NVIM_TEST_CORE_EXC_RE` (FU) (S): regular expression which excludes certain
|
||||||
|
directories from searching for core files inside. E.g. use `^/%.deps$` to not
|
||||||
|
search inside `/.deps`. If absent, nothing is excluded.
|
||||||
|
|
||||||
|
- `NVIM_TEST_CORE_DB_CMD` (FU) (S): command to get backtrace out of the
|
||||||
|
debugger. E.g. `gdb -n -batch -ex "thread apply all bt full"
|
||||||
|
"$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"`. Defaults to the example command.
|
||||||
|
This debug command may use environment variables `_NVIM_TEST_APP` (path to
|
||||||
|
application which is being debugged: normally either nvim or luajit) and
|
||||||
|
`_NVIM_TEST_CORE` (core file to get backtrace from).
|
||||||
|
|
||||||
|
- `NVIM_TEST_CORE_RANDOM_SKIP` (FU) (D): makes `check_cores` not check cores
|
||||||
|
after 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_INTEG` (F) (D): enables integration tests that makes real network
|
||||||
|
calls. By default these tests are skipped. When set to `1`, tests requiring external
|
||||||
|
HTTP requests (e.g `vim.net.request()`) will be run.
|
||||||
|
|
||||||
|
- `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 no core dump was generated),
|
||||||
|
- `1` leaves only C function calls and returns in the trace (faster than
|
||||||
|
recording everything),
|
||||||
|
- `2` records all function calls, returns and executed Lua source lines.
|
||||||
|
|
||||||
|
- `NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in
|
||||||
|
addition to regular error message.
|
||||||
|
|
||||||
|
- `NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to
|
||||||
|
keep. Default is 1024.
|
||||||
|
|
||||||
|
- `OSV_PORT`: (F): launches `osv` listening on the given port within nvim test
|
||||||
|
instances.
|
||||||
|
|
||||||
|
|
||||||
|
vim:tw=78:ts=8:sw=4:et:ft=help:norl:
|
||||||
@@ -10,6 +10,50 @@ This is for developing or debugging Nvim itself.
|
|||||||
|
|
||||||
Type |gO| to see the table of contents.
|
Type |gO| to see the table of contents.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Quickstart guide to developing Nvim *dev-quickstart*
|
||||||
|
|
||||||
|
You can start hacking on Nvim in less than 5 minutes:
|
||||||
|
|
||||||
|
1. Ensure you have the build prerequisites from `BUILD.md`.
|
||||||
|
2. Clone the source repo and "cd" into it: >
|
||||||
|
git clone https://github.com/neovim/neovim
|
||||||
|
cd neovim
|
||||||
|
# (Optional) Build and run Nvim:
|
||||||
|
make
|
||||||
|
./build/bin/nvim
|
||||||
|
3. Run a single test. We will start with "example_spec.lua", which is a real
|
||||||
|
test that shows how tests are written: >
|
||||||
|
make functionaltest TEST_FILE=test/functional/example_spec.lua
|
||||||
|
4. Notice the `before_each` block in the test file. Because it calls
|
||||||
|
`clear()`, each `it()` test will start a new Nvim instance.
|
||||||
|
5. Tests will do stuff in the Nvim instance and make assertions using `eq()`.
|
||||||
|
Tests that want to check the UI can also use `screen:expect()`.
|
||||||
|
6. Now make a code change in Nvim itself, then you can see the effects. The
|
||||||
|
example test does `feed('iline1…')`, so let's make a change to the
|
||||||
|
insert-mode code, which lives in `src/nvim/edit.c`. In the
|
||||||
|
`insert_handle_key` function, just after the `normalchar` label, add this
|
||||||
|
code: >
|
||||||
|
s->c = 'x';
|
||||||
|
7. Then run the "example_spec.lua" test again, and it should fail with
|
||||||
|
something like this: >
|
||||||
|
test/functional/example_spec.lua:31: Row 1 did not match.
|
||||||
|
Expected:
|
||||||
|
|*line1 |
|
||||||
|
|*line^2 |
|
||||||
|
|{0:~ }|
|
||||||
|
|{0:~ }|
|
||||||
|
| |
|
||||||
|
Actual:
|
||||||
|
|*xine1 |
|
||||||
|
|*xine^2 |
|
||||||
|
|{0:~ }|
|
||||||
|
|{0:~ }|
|
||||||
|
| |
|
||||||
|
|
||||||
|
You now understand how to modify the codebase, write tests, and run tests. See
|
||||||
|
|dev-arch| for details about the internal architecture.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Logs *dev-tools-logs*
|
Logs *dev-tools-logs*
|
||||||
|
|
||||||
@@ -485,4 +529,4 @@ For more information: https://github.com/google/sanitizers/wiki/SanitizerCommonF
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
vim:tw=78:ts=8:et:ft=help:norl:
|
vim:tw=78:ts=8:sw=4:et:ft=help:norl:
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ local new_layout = {
|
|||||||
local redirects = {
|
local redirects = {
|
||||||
['api-ui-events'] = 'ui',
|
['api-ui-events'] = 'ui',
|
||||||
['credits'] = 'backers',
|
['credits'] = 'backers',
|
||||||
['dev_tools'] = 'debug',
|
['dev'] = 'develop',
|
||||||
|
['dev-tools'] = 'debug',
|
||||||
['plugins'] = 'editorconfig',
|
['plugins'] = 'editorconfig',
|
||||||
['terminal'] = 'nvim_terminal_emulator',
|
['terminal'] = 'nvim_terminal_emulator',
|
||||||
['tui'] = 'term',
|
['tui'] = 'term',
|
||||||
|
|||||||
556
test/README.md
556
test/README.md
@@ -1,555 +1,3 @@
|
|||||||
Tests
|
## Moved to:
|
||||||
=====
|
|
||||||
|
|
||||||
Tests are broadly divided into *unit tests* ([test/unit](https://github.com/neovim/neovim/tree/master/test/unit/)),
|
- [dev_test.txt](../runtime/doc/dev_test.txt)
|
||||||
*functional tests* ([test/functional](https://github.com/neovim/neovim/tree/master/test/functional/)),
|
|
||||||
and *old tests* ([test/old/testdir/](https://github.com/neovim/neovim/tree/master/test/old/testdir/)).
|
|
||||||
|
|
||||||
- _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/).
|
|
||||||
Use any existing test as a template to start writing new tests.
|
|
||||||
|
|
||||||
Tests are run by `/cmake/RunTests.cmake` file, using `busted` (a Lua test-runner).
|
|
||||||
For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight.
|
|
||||||
|
|
||||||
Depending on the presence of binaries (e.g., `xclip`) some tests will be
|
|
||||||
ignored. You must compile with libintl to prevent `E319: The command is not
|
|
||||||
available in this version` errors.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- [Running tests](#running-tests)
|
|
||||||
- [Writing tests](#writing-tests)
|
|
||||||
- [Lint](#lint)
|
|
||||||
- [Configuration](#configuration)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
Layout
|
|
||||||
======
|
|
||||||
|
|
||||||
- `/test/benchmark` : benchmarks
|
|
||||||
- `/test/functional` : functional tests
|
|
||||||
- `/test/unit` : unit tests
|
|
||||||
- `/test/old/testdir` : old tests (from Vim)
|
|
||||||
- `/test/config` : contains `*.in` files which are transformed into `*.lua`
|
|
||||||
files using `configure_file` CMake command: this is for accessing 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/**/testutil.lua` : common utility functions in the context of the test
|
|
||||||
runner
|
|
||||||
- `/test/**/testnvim.lua` : common utility functions in the context of the
|
|
||||||
test session (RPC channel to the Nvim child process created by clear() for each test)
|
|
||||||
- `/test/*/**/*_spec.lua` : actual tests. Files that do not end with
|
|
||||||
`_spec.lua` are libraries like `/test/**/testutil.lua`, except that they have
|
|
||||||
some common topic.
|
|
||||||
|
|
||||||
|
|
||||||
Running tests
|
|
||||||
=============
|
|
||||||
|
|
||||||
Executing Tests
|
|
||||||
---------------
|
|
||||||
|
|
||||||
To run all tests (except "old" tests):
|
|
||||||
|
|
||||||
make test
|
|
||||||
|
|
||||||
To run only _unit_ tests:
|
|
||||||
|
|
||||||
make unittest
|
|
||||||
|
|
||||||
To run only _functional_ tests:
|
|
||||||
|
|
||||||
make functionaltest
|
|
||||||
|
|
||||||
|
|
||||||
Legacy tests
|
|
||||||
------------
|
|
||||||
|
|
||||||
To run all legacy Vim tests:
|
|
||||||
|
|
||||||
make oldtest
|
|
||||||
|
|
||||||
To run a *single* legacy test file you can use either:
|
|
||||||
|
|
||||||
make oldtest TEST_FILE=test_syntax.vim
|
|
||||||
|
|
||||||
or:
|
|
||||||
|
|
||||||
make test/old/testdir/test_syntax.vim
|
|
||||||
|
|
||||||
- Specify only the test file name, not the full path.
|
|
||||||
|
|
||||||
|
|
||||||
Debugging tests
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- Each test gets a test id which looks like "T123". This also appears in the
|
|
||||||
log file. Child processes spawned from a test appear in the logs with the
|
|
||||||
*parent* name followed by "/c". Example:
|
|
||||||
```
|
|
||||||
DBG 2022-06-15T18:37:45.226 T57.58016.0 UI: flush
|
|
||||||
DBG 2022-06-15T18:37:45.226 T57.58016.0 inbuf_poll:442: blocking... events_enabled=0 events_pending=0
|
|
||||||
DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop
|
|
||||||
INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0
|
|
||||||
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
|
|
||||||
INF 2022-06-15T18:37:45.229 T57.58016.0 on_proc_exit:400: exited: pid=58017 status=0 stoptime=0
|
|
||||||
```
|
|
||||||
- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GDB=1 TEST_FILE=test/functional/api/buffer_spec.lua TEST_FILTER='nvim_buf_set_text works$' make functionaltest
|
|
||||||
```
|
|
||||||
|
|
||||||
Read more about [filtering tests](#filtering-tests).
|
|
||||||
|
|
||||||
Then, in another terminal:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
gdb -ex 'target remote localhost:7777' build/bin/nvim
|
|
||||||
```
|
|
||||||
|
|
||||||
If `$VALGRIND` is also set it will pass `--vgdb=yes` to valgrind instead of
|
|
||||||
starting gdbserver directly.
|
|
||||||
|
|
||||||
See `nvim_argv` in https://github.com/neovim/neovim/blob/master/test/functional/testnvim.lua.
|
|
||||||
|
|
||||||
- Hanging tests can happen due to unexpected "press-enter" prompts. The
|
|
||||||
default screen width is 50 columns. Commands that try to print lines longer
|
|
||||||
than 50 columns in the command-line, e.g. `:edit very...long...path`, will
|
|
||||||
trigger the prompt. Try using a shorter path, or `:silent edit`.
|
|
||||||
- If you can't figure out what is going on, try to visualize the screen. Put
|
|
||||||
this at the beginning of your test:
|
|
||||||
```lua
|
|
||||||
local Screen = require('test.functional.ui.screen')
|
|
||||||
local screen = Screen.new()
|
|
||||||
screen:attach()
|
|
||||||
```
|
|
||||||
Then put `screen:snapshot_util()` anywhere in your test. See the comments in
|
|
||||||
`test/functional/ui/screen.lua` for more info.
|
|
||||||
|
|
||||||
Debugging Lua test code
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Debugging Lua test code is a bit involved. Get your shopping list ready, you'll
|
|
||||||
need to install and configure:
|
|
||||||
|
|
||||||
1. [nvim-dap](https://github.com/mfussenegger/nvim-dap)
|
|
||||||
2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode)
|
|
||||||
3. [nlua](https://github.com/mfussenegger/nlua)
|
|
||||||
4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`)
|
|
||||||
5. A `nbusted` command in `$PATH`. This command can be a copy of `busted` with
|
|
||||||
`exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the
|
|
||||||
path to your `nlua`)
|
|
||||||
|
|
||||||
|
|
||||||
The setup roughly looks like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────┐
|
|
||||||
│ nvim used for debugging │◄────┐
|
|
||||||
└─────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
▼ │
|
|
||||||
┌─────────────────┐ │
|
|
||||||
│ local-lua-debug │ │
|
|
||||||
└─────────────────┘ │
|
|
||||||
│ │
|
|
||||||
▼ │
|
|
||||||
┌─────────┐ │
|
|
||||||
│ nbusted │ │
|
|
||||||
└─────────┘ │
|
|
||||||
│ │
|
|
||||||
▼ │
|
|
||||||
┌───────────┐ │
|
|
||||||
│ test-case │ │
|
|
||||||
└───────────┘ │
|
|
||||||
│ │
|
|
||||||
▼ │
|
|
||||||
┌────────────────────┐ │
|
|
||||||
│ nvim test-instance │ │
|
|
||||||
└────────────────────┘ │
|
|
||||||
│ ┌─────┐ │
|
|
||||||
└──►│ osv │─────────────────┘
|
|
||||||
└─────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
With these installed you can use a configuration like this:
|
|
||||||
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local dap = require("dap")
|
|
||||||
|
|
||||||
|
|
||||||
local function free_port()
|
|
||||||
local tcp = vim.loop.new_tcp()
|
|
||||||
assert(tcp)
|
|
||||||
tcp:bind('127.0.0.1', 0)
|
|
||||||
local port = tcp:getsockname().port
|
|
||||||
tcp:shutdown()
|
|
||||||
tcp:close()
|
|
||||||
return port
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local name = "nvim-test-case" -- arbitrary name
|
|
||||||
local config = {
|
|
||||||
name = name,
|
|
||||||
|
|
||||||
-- value of type must match the key used in `dap.adapters["local-lua"] = ...` from step 2)
|
|
||||||
type = "local-lua",
|
|
||||||
|
|
||||||
request = "launch",
|
|
||||||
cwd = "${workspaceFolder}",
|
|
||||||
program = {
|
|
||||||
command = "nbusted",
|
|
||||||
},
|
|
||||||
args = {
|
|
||||||
"--ignore-lua",
|
|
||||||
"--lazy",
|
|
||||||
"--helper=test/functional/preload.lua",
|
|
||||||
"--lpath=build/?.lua",
|
|
||||||
"--lpath=?.lua",
|
|
||||||
|
|
||||||
-- path to file to debug, could be replaced with a hardcoded string
|
|
||||||
function()
|
|
||||||
return vim.api.nvim_buf_get_name(0)
|
|
||||||
end,
|
|
||||||
|
|
||||||
-- You can filter to specific test-case by adding:
|
|
||||||
-- '--filter="' .. test_case_name .. '"',
|
|
||||||
},
|
|
||||||
env = {
|
|
||||||
OSV_PORT = free_port
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Whenever the config is used it needs to launch a second debug session that attaches to `osv`
|
|
||||||
-- This makes it possible to step into `exec_lua` code blocks
|
|
||||||
setmetatable(config, {
|
|
||||||
|
|
||||||
__call = function(c)
|
|
||||||
---@param session dap.Session
|
|
||||||
dap.listeners.after.event_initialized["nvim_debug"] = function(session)
|
|
||||||
if session.config.name ~= name then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
dap.listeners.after.event_initialized["nvim_debug"] = nil
|
|
||||||
vim.defer_fn(function()
|
|
||||||
dap.run({
|
|
||||||
name = "attach-osv",
|
|
||||||
type = "nlua", -- value must match the `dap.adapters` definition key for osv
|
|
||||||
request = "attach",
|
|
||||||
port = session.config.env.OSV_PORT,
|
|
||||||
})
|
|
||||||
end, 500)
|
|
||||||
end
|
|
||||||
|
|
||||||
return c
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
You can either add this configuration to your `dap.configurations.lua` list as
|
|
||||||
described in `:help dap-configuration` or create it dynamically in a
|
|
||||||
user-command or function and call it directly via `dap.run(config)`. The latter
|
|
||||||
is useful if you use tree-sitter to find the test case around a cursor location
|
|
||||||
with a query like the following and set the `--filter` property to it.
|
|
||||||
|
|
||||||
```query
|
|
||||||
(function_call
|
|
||||||
name: (identifier) @name (#any-of? @name "describe" "it")
|
|
||||||
arguments: (arguments
|
|
||||||
(string) @str
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Limitations:
|
|
||||||
|
|
||||||
- You need to add the following boilerplate to each spec file where you want to
|
|
||||||
be able to stop at breakpoints within the test-case code:
|
|
||||||
|
|
||||||
```
|
|
||||||
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
|
|
||||||
require("lldebugger").start()
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a [local-lua-debugger
|
|
||||||
limitation](https://github.com/tomblind/local-lua-debugger-vscode?tab=readme-ov-file#busted)
|
|
||||||
|
|
||||||
- You cannot step into code of files which get baked into the nvim binary like
|
|
||||||
the `shared.lua`.
|
|
||||||
|
|
||||||
|
|
||||||
Filtering Tests
|
|
||||||
---------------
|
|
||||||
|
|
||||||
### Filter by name
|
|
||||||
|
|
||||||
Tests can be filtered by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`.
|
|
||||||
|
|
||||||
``` lua
|
|
||||||
it('foo api',function()
|
|
||||||
...
|
|
||||||
end)
|
|
||||||
it('bar api',function()
|
|
||||||
...
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
To run only test with filter name:
|
|
||||||
|
|
||||||
TEST_FILTER='foo.*api' make functionaltest
|
|
||||||
|
|
||||||
To run all tests except ones matching a filter:
|
|
||||||
|
|
||||||
TEST_FILTER_OUT='foo.*api' make functionaltest
|
|
||||||
|
|
||||||
### Filter by file
|
|
||||||
|
|
||||||
To run a *specific* unit test:
|
|
||||||
|
|
||||||
TEST_FILE=test/unit/foo.lua make unittest
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
cmake -E env "TEST_FILE=test/unit/foo.lua" cmake --build build --target unittest
|
|
||||||
|
|
||||||
To run a *specific* functional test:
|
|
||||||
|
|
||||||
TEST_FILE=test/functional/foo.lua make functionaltest
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
cmake -E env "TEST_FILE=test/functional/foo.lua" cmake --build build --target functionaltest
|
|
||||||
|
|
||||||
To *repeat* a test:
|
|
||||||
|
|
||||||
BUSTED_ARGS="--repeat=100 --no-keep-going" TEST_FILE=test/functional/foo_spec.lua make functionaltest
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
cmake -E env "TEST_FILE=test/functional/foo_spec.lua" cmake -E env BUSTED_ARGS="--repeat=100 --no-keep-going" cmake --build build --target functionaltest
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
**NOTE:**
|
|
||||||
|
|
||||||
* `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 string descriptions
|
|
||||||
found in `it()` and `describe()`.
|
|
||||||
|
|
||||||
|
|
||||||
Writing tests
|
|
||||||
=============
|
|
||||||
|
|
||||||
Guidelines
|
|
||||||
----------
|
|
||||||
|
|
||||||
- Luajit needs to know about type and constant declarations used in function
|
|
||||||
prototypes. The
|
|
||||||
[testutil.lua](https://github.com/neovim/neovim/blob/master/test/unit/testutil.lua)
|
|
||||||
file automatically parses `types.h`, so types used in the tested functions
|
|
||||||
could be moved to it to avoid having to rewrite the declarations in the test
|
|
||||||
files.
|
|
||||||
- `#define` constants must be rewritten `const` or `enum` so they can be
|
|
||||||
"visible" to the tests.
|
|
||||||
- Use **pending()** 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 `it()`
|
|
||||||
([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)).
|
|
||||||
- Really long `source([=[...]=])` blocks may break Vim's Lua syntax
|
|
||||||
highlighting. Try `:syntax sync fromstart` to fix it.
|
|
||||||
|
|
||||||
Where tests go
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Tests in `/test/unit` and `/test/functional` are divided into groups
|
|
||||||
by the semantic component they are testing.
|
|
||||||
|
|
||||||
- _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.
|
|
||||||
|
|
||||||
|
|
||||||
Fixing tests
|
|
||||||
============
|
|
||||||
|
|
||||||
> Nvim session T123 took 2000 milliseconds to exit
|
|
||||||
> This indicates a likely problem with the test even if it passed!
|
|
||||||
|
|
||||||
This may indicate a leak, because Nvim waits on uv handles before exiting.
|
|
||||||
Example: https://github.com/neovim/neovim/pull/35768
|
|
||||||
|
|
||||||
|
|
||||||
Lint
|
|
||||||
====
|
|
||||||
|
|
||||||
`make lint` (and `make lintlua`) 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).
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
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; when defined, treated as
|
|
||||||
Number; !must be defined to function properly):
|
|
||||||
|
|
||||||
- `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`.
|
|
||||||
|
|
||||||
- `CC` (U) (S): specifies which C compiler to use to preprocess files.
|
|
||||||
Currently only compilers with gcc-compatible arguments are supported.
|
|
||||||
|
|
||||||
- `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
|
|
||||||
:7777` inside.
|
|
||||||
|
|
||||||
- `GDBSERVER_PORT` (F) (I): overrides port used for `GDB`.
|
|
||||||
|
|
||||||
- `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files.
|
|
||||||
|
|
||||||
- `VALGRIND` (F) (D): makes nvim instances to be run under `valgrind`. Log
|
|
||||||
files are named `valgrind-%p.log` in this case. Note that non-empty valgrind
|
|
||||||
log may fail tests. Valgrind arguments may be seen in
|
|
||||||
`/test/functional/testnvim.lua`. May be used in conjunction with `GDB`.
|
|
||||||
|
|
||||||
- `VALGRIND_LOG` (F) (S): overrides valgrind log file name used for `VALGRIND`.
|
|
||||||
|
|
||||||
- `TEST_COLORS` (F) (U) (D): enable pretty colors in test runner. Set to true by default.
|
|
||||||
|
|
||||||
- `TEST_SKIP_FRAGILE` (F) (D): makes test suite skip some fragile tests.
|
|
||||||
|
|
||||||
- `TEST_TIMEOUT` (FU) (I): specifies maximum time, in seconds, before the test
|
|
||||||
suite run is killed
|
|
||||||
|
|
||||||
- `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects
|
|
||||||
|
|
||||||
- `NVIM_PRG` (F) (S): path to Nvim executable (default: `build/bin/nvim`).
|
|
||||||
|
|
||||||
- `NVIM_TEST_MAIN_CDEFS` (U) (1): makes `ffi.cdef` run in main process. This
|
|
||||||
raises a possibility of bugs due to conflicts in header definitions, despite
|
|
||||||
the counters, but greatly speeds up unit tests by not requiring `ffi.cdef` to
|
|
||||||
do parsing of big strings with C definitions.
|
|
||||||
|
|
||||||
- `NVIM_TEST_PRINT_I` (U) (1): makes `cimport` print preprocessed, but not yet
|
|
||||||
filtered through `formatc` headers. Used to debug `formatc`. Printing is done
|
|
||||||
with the line numbers.
|
|
||||||
|
|
||||||
- `NVIM_TEST_PRINT_CDEF` (U) (1): makes `cimport` print final lines which will
|
|
||||||
be then passed to `ffi.cdef`. Used to debug errors `ffi.cdef` happens to
|
|
||||||
throw sometimes.
|
|
||||||
|
|
||||||
- `NVIM_TEST_PRINT_SYSCALLS` (U) (1): makes it print to stderr when syscall
|
|
||||||
wrappers are called and what they returned. Used to debug code which makes
|
|
||||||
unit tests be executed in separate processes.
|
|
||||||
|
|
||||||
- `NVIM_TEST_RUN_FAILING_TESTS` (U) (1): makes `itp` run tests which are known
|
|
||||||
to fail (marked by setting third argument to `true`).
|
|
||||||
|
|
||||||
- `NVIM_TEST_CORE_*` (FU) (S): a set of environment variables which specify
|
|
||||||
where to search for core files. Are supposed to be defined all at once.
|
|
||||||
|
|
||||||
- `NVIM_TEST_CORE_GLOB_DIRECTORY` (FU) (S): directory where core files are
|
|
||||||
located. May be `.`. This directory is then recursively searched for core
|
|
||||||
files. Note: this variable must be defined for any of the following to have
|
|
||||||
any effect.
|
|
||||||
|
|
||||||
- `NVIM_TEST_CORE_GLOB_RE` (FU) (S): regular expression which must be matched
|
|
||||||
by core files. E.g. `/core[^/]*$`. May be absent, in which case any file is
|
|
||||||
considered to be matched.
|
|
||||||
|
|
||||||
- `NVIM_TEST_CORE_EXC_RE` (FU) (S): regular expression which excludes certain
|
|
||||||
directories from searching for core files inside. E.g. use `^/%.deps$` to not
|
|
||||||
search inside `/.deps`. If absent, nothing is excluded.
|
|
||||||
|
|
||||||
- `NVIM_TEST_CORE_DB_CMD` (FU) (S): command to get backtrace out of the
|
|
||||||
debugger. E.g. `gdb -n -batch -ex "thread apply all bt full"
|
|
||||||
"$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"`. Defaults to the example command.
|
|
||||||
This debug command may use environment variables `_NVIM_TEST_APP` (path to
|
|
||||||
application which is being debugged: normally either nvim or luajit) and
|
|
||||||
`_NVIM_TEST_CORE` (core file to get backtrace from).
|
|
||||||
|
|
||||||
- `NVIM_TEST_CORE_RANDOM_SKIP` (FU) (D): makes `check_cores` not check cores
|
|
||||||
after 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_INTEG` (F) (D): enables integration tests that makes real network
|
|
||||||
calls. By default these tests are skipped. When set to `1`, tests requiring external
|
|
||||||
HTTP requests (e.g `vim.net.request()`) will be run.
|
|
||||||
|
|
||||||
- `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 no core dump was generated),
|
|
||||||
- `1` leaves only C function calls and returns in the trace (faster than
|
|
||||||
recording everything),
|
|
||||||
- `2` records all function calls, returns and executed Lua source lines.
|
|
||||||
|
|
||||||
- `NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in
|
|
||||||
addition to regular error message.
|
|
||||||
|
|
||||||
- `NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to
|
|
||||||
keep. Default is 1024.
|
|
||||||
|
|
||||||
- `OSV_PORT`: (F): launches `osv` listening on the given port within nvim test
|
|
||||||
instances.
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
-- To run this test:
|
-- To run this test:
|
||||||
-- TEST_FILE=test/functional/example_spec.lua make functionaltest
|
-- make functionaltest TEST_FILE=test/functional/example_spec.lua
|
||||||
|
|
||||||
local t = require('test.testutil')
|
local t = require('test.testutil')
|
||||||
local n = require('test.functional.testnvim')()
|
local n = require('test.functional.testnvim')()
|
||||||
|
|||||||
Reference in New Issue
Block a user