neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

dev_test.txt (21594B)


      1 *dev_test.txt*          Nvim
      2 
      3 
      4                            NVIM REFERENCE MANUAL
      5 
      6 
      7 Writing tests for Nvim                                              *dev-test*
      8 
      9                                  Type |gO| to see the table of contents.
     10 
     11 ==============================================================================
     12 Writing tests for Nvim
     13 
     14 Nvim has a powerful yet simple test framework. It's approximately 7x better
     15 than whatever you use at work.
     16 
     17 Each test starts a new Nvim process (10-30ms) which is discarded after the
     18 test finishes. You assert stuff using `t.eq()` and `screen:expect()` (which
     19 automatically waits as needed). That's pretty much it.
     20 
     21 TODO: Expose the test framework as a public interface, for use in 3P plugins:
     22 https://github.com/neovim/neovim/issues/34592
     23 
     24 ==============================================================================
     25 Test framework
     26 
     27 Tests are broadly divided into unit tests (`test/unit/`), functional tests
     28 (`test/functional/`) and old tests (`test/old/testdir/`).
     29 
     30 - Unit testing is achieved by compiling the tests as a shared library which is
     31  loaded and called by [LuaJit FFI](https://luajit.org/ext_ffi.html).
     32 - Functional tests are driven by RPC, so they do not require LuaJit (as
     33  opposed to Lua). They are essentially "integration" tests, they test the
     34  full system. But they are fast.
     35 
     36 You can learn [Lua concepts 15 minutes](https://learnxinyminutes.com/docs/lua/),
     37 see also |lua-guide|. Use any existing test as a template to start writing new
     38 tests, or see |dev-quickstart|.
     39 
     40 Tests are run by the `/cmake/RunTests.cmake` script using `busted` (a Lua test-runner).
     41 For some failures, `./build/nvim.log` (or `$NVIM_LOG_FILE`) may provide insight.
     42 
     43 Depending on the presence of binaries (e.g., `xclip`) some tests will be
     44 skipped.
     45 
     46 ==============================================================================
     47 Test Layout
     48 
     49 - `/test/benchmark` : benchmarks
     50 - `/test/functional` : functional tests
     51 - `/test/unit` : unit tests
     52 - `/test/old/testdir` : old tests (from Vim)
     53 - `/test/config` : contains `*.in` files which are transformed into `*.lua`
     54  files using `configure_file` CMake command: this is for accessing CMake
     55  variables in Lua tests.
     56 - `/test/includes` : include-files for use by luajit `ffi.cdef` C definitions
     57  parser: normally used to make macros not accessible via this mechanism
     58  accessible the other way.
     59 - `/test/*/preload.lua` : modules preloaded by busted `--helper` option
     60 - `/test/**/testutil.lua` : common utility functions in the context of the test
     61  runner
     62 - `/test/**/testnvim.lua` : common utility functions in the context of the
     63  test session (RPC channel to the Nvim child process created by clear() for each test)
     64 - `/test/*/**/*_spec.lua` : actual tests. Files that do not end with
     65  `_spec.lua` are libraries like `/test/**/testutil.lua`, except that they have
     66  some common topic.
     67 
     68 
     69 ==============================================================================
     70 Running tests                                                   *dev-run-test*
     71 
     72 EXECUTING TESTS
     73 
     74 To run all tests (except "old" tests): >
     75    make test
     76 
     77 To run only _unit_ tests: >
     78    make unittest
     79 
     80 To run only _functional_ tests: >
     81    make functionaltest
     82 
     83 To run functional tests in parallel (used in CI): >
     84    cmake --build build --target functionaltest_parallel -j2
     85    cmake --build build --target functionaltest_summary
     86 
     87 
     88 LEGACY TESTS
     89 
     90 To run all legacy Vim tests: >
     91    make oldtest
     92 
     93 To run a _single_ legacy test file you can use either: >
     94    # Specify only the test file name, not the full path.
     95    make oldtest TEST_FILE=test_syntax.vim
     96 or: >
     97    make test/old/testdir/test_syntax.vim
     98 
     99 
    100 DEBUGGING TESTS
    101 
    102 - Each test gets a test id which looks like "T123". This also appears in the
    103  log file. Child processes spawned from a test appear in the logs with the
    104  _parent_ name followed by "/c". Example: >
    105 
    106    DBG 2022-06-15T18:37:45.226 T57.58016.0   UI: flush
    107    DBG 2022-06-15T18:37:45.226 T57.58016.0   inbuf_poll:442: blocking... events_enabled=0 events_pending=0
    108    DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop
    109    INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0
    110    DBG 2022-06-15T18:37:45.229 T57.58016.0   read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
    111    INF 2022-06-15T18:37:45.229 T57.58016.0   on_proc_exit:400: exited: pid=58017 status=0 stoptime=0
    112 
    113 - You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527): >sh
    114    GDB=1 TEST_FILE=test/functional/api/buffer_spec.lua TEST_FILTER='nvim_buf_set_text works$' make functionaltest
    115 <
    116  Read more about |dev-filter-test|.
    117 
    118  Then, in another terminal: >sh
    119    gdb -ex 'target remote localhost:7777' build/bin/nvim
    120 <
    121  If `$VALGRIND` is also set it will pass `--vgdb=yes` to valgrind instead of
    122  starting gdbserver directly.
    123 
    124  See `nvim_argv` in `test/functional/testnvim.lua`.
    125 
    126 - Hanging tests can happen due to unexpected "press-enter" prompts. The
    127  default screen width is 50 columns. Commands that try to print lines longer
    128  than 50 columns in the command-line, e.g. `:edit very...long...path`, will
    129  trigger the prompt. Try using a shorter path, or `:silent edit`.
    130 - If you can't figure out what is going on, try to visualize the screen. Put
    131  this at the beginning of your test: >
    132    local Screen = require('test.functional.ui.screen')
    133    local screen = Screen.new()
    134    screen:attach()
    135 <  Then put `screen:snapshot_util()` anywhere in your test. See the comments in
    136  `test/functional/ui/screen.lua` for more info.
    137 
    138 DEBUGGING LUA TEST CODE
    139 
    140 Debugging Lua test code is a bit involved. Get your shopping list ready, you'll
    141 need to install and configure:
    142 
    143 1. [nvim-dap](https://github.com/mfussenegger/nvim-dap)
    144 2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode)
    145 3. [nlua](https://github.com/mfussenegger/nlua)
    146 4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`)
    147 5. A `nbusted` command in `$PATH`. This command can be a copy of `busted` with
    148   `exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the
    149   path to your `nlua`)
    150 
    151 
    152 The setup roughly looks like this: >
    153 
    154    ┌─────────────────────────┐
    155    │ nvim used for debugging │◄────┐
    156    └─────────────────────────┘     │
    157               │                    │
    158               ▼                    │
    159      ┌─────────────────┐           │
    160      │ local-lua-debug │           │
    161      └─────────────────┘           │
    162              │                     │
    163              ▼                     │
    164         ┌─────────┐                │
    165         │ nbusted │                │
    166         └─────────┘                │
    167              │                     │
    168              ▼                     │
    169         ┌───────────┐              │
    170         │ test-case │              │
    171         └───────────┘              │
    172              │                     │
    173              ▼                     │
    174      ┌────────────────────┐        │
    175      │ nvim test-instance │        │
    176      └────────────────────┘        │
    177        │   ┌─────┐                 │
    178        └──►│ osv │─────────────────┘
    179            └─────┘
    180 
    181 
    182 With these installed you can use a configuration like this: >
    183 
    184    local dap = require("dap")
    185 
    186 
    187    local function free_port()
    188      local tcp = vim.loop.new_tcp()
    189      assert(tcp)
    190      tcp:bind('127.0.0.1', 0)
    191      local port = tcp:getsockname().port
    192      tcp:shutdown()
    193      tcp:close()
    194      return port
    195    end
    196 
    197 
    198    local name = "nvim-test-case" -- arbitrary name
    199    local config = {
    200      name = name,
    201 
    202      -- value of type must match the key used in `dap.adapters["local-lua"] = ...` from step 2)
    203      type = "local-lua",
    204 
    205      request = "launch",
    206      cwd = "${workspaceFolder}",
    207      program = {
    208        command = "nbusted",
    209      },
    210      args = {
    211        "--ignore-lua",
    212        "--lazy",
    213        "--helper=test/functional/preload.lua",
    214        "--lpath=build/?.lua",
    215        "--lpath=?.lua",
    216 
    217        -- path to file to debug, could be replaced with a hardcoded string
    218        function()
    219          return vim.api.nvim_buf_get_name(0)
    220        end,
    221 
    222        -- You can filter to specific test-case by adding:
    223        -- '--filter="' .. test_case_name .. '"',
    224      },
    225      env = {
    226        OSV_PORT = free_port
    227      }
    228    }
    229 
    230    -- Whenever the config is used it needs to launch a second debug session that attaches to `osv`
    231    -- This makes it possible to step into `exec_lua` code blocks
    232    setmetatable(config, {
    233 
    234      __call = function(c)
    235        ---@param session dap.Session
    236        dap.listeners.after.event_initialized["nvim_debug"] = function(session)
    237          if session.config.name ~= name then
    238            return
    239          end
    240          dap.listeners.after.event_initialized["nvim_debug"] = nil
    241          vim.defer_fn(function()
    242            dap.run({
    243              name = "attach-osv",
    244              type = "nlua", -- value must match the `dap.adapters` definition key for osv
    245              request = "attach",
    246              port = session.config.env.OSV_PORT,
    247            })
    248          end, 500)
    249        end
    250 
    251        return c
    252      end,
    253    })
    254 
    255 
    256 You can either add this configuration to your `dap.configurations.lua` list as
    257 described in `:help dap-configuration` or create it dynamically in a
    258 user-command or function and call it directly via `dap.run(config)`. The latter
    259 is useful if you use treesitter to find the test case around a cursor location
    260 with a query like the following and set the `--filter` property to it. >query
    261 
    262    (function_call
    263      name: (identifier) @name (#any-of? @name "describe" "it")
    264      arguments: (arguments
    265        (string) @str
    266      )
    267    )
    268 
    269 Limitations:
    270 
    271 - You need to add the following boilerplate to each spec file where you want to
    272  be able to stop at breakpoints within the test-case code: >
    273    if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
    274      require("lldebugger").start()
    275    end
    276 <  This is a [local-lua-debugger limitation](https://github.com/tomblind/local-lua-debugger-vscode?tab=readme-ov-file#busted)
    277 - You cannot step into code of files which get baked into the nvim binary
    278  (such as `_core/*.lua` and `inspect.lua`).
    279 
    280 
    281 ------------------------------------------------------------------------------
    282 Filtering tests                                              *dev-filter-test*
    283 
    284 FILTER BY NAME
    285 
    286 Tests can be filtered by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`. >
    287 
    288    it('foo api',function()
    289      ...
    290    end)
    291    it('bar api',function()
    292      ...
    293    end)
    294 
    295 To run only test with filter name: >
    296    TEST_FILTER='foo.*api' make functionaltest
    297 
    298 To run all tests except ones matching a filter: >
    299    TEST_FILTER_OUT='foo.*api' make functionaltest
    300 
    301 FILTER BY FILE
    302 
    303 To run a _specific_ unit test: >
    304    TEST_FILE=test/unit/foo.lua make unittest
    305 
    306 or >
    307    cmake -E env "TEST_FILE=test/unit/foo.lua" cmake --build build --target unittest
    308 
    309 To run a _specific_ functional test: >
    310    TEST_FILE=test/functional/foo.lua make functionaltest
    311 
    312 or >
    313    cmake -E env "TEST_FILE=test/functional/foo.lua" cmake --build build --target functionaltest
    314 
    315 To _repeat_ a test: >
    316    BUSTED_ARGS="--repeat=100 --no-keep-going" TEST_FILE=test/functional/foo_spec.lua make functionaltest
    317 
    318 or >
    319    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
    320 
    321 FILTER BY TAG
    322 
    323 Tests can be "tagged" by adding `#` before a token in the test description. >
    324 
    325    it('#foo bar baz', function()
    326      ...
    327    end)
    328    it('#foo another test', function()
    329      ...
    330    end)
    331 
    332 To run only the tagged tests: >
    333    TEST_TAG=foo make functionaltest
    334 
    335 NOTE:
    336 
    337 - `TEST_FILE` is not a pattern string like `TEST_TAG` or `TEST_FILTER`. The
    338  given value to `TEST_FILE` must be a path to an existing file.
    339 - Both `TEST_TAG` and `TEST_FILTER` filter tests by the string descriptions
    340  found in `it()` and `describe()`.
    341 
    342 
    343 ==============================================================================
    344 Writing tests                                                 *dev-write-test*
    345 
    346 GUIDELINES
    347 
    348 - Luajit needs to know about type and constant declarations used in function
    349  prototypes. The
    350  [testutil.lua](https://github.com/neovim/neovim/blob/master/test/unit/testutil.lua)
    351  file automatically parses `types.h`, so types used in the tested functions
    352  could be moved to it to avoid having to rewrite the declarations in the test
    353  files.
    354  - `#define` constants must be rewritten `const` or `enum` so they can be
    355    "visible" to the tests.
    356 - Use `pending()` or `t.skip()` to skip tests.
    357  - Example: https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18
    358  - Note: If a test is skipped because of a non-actionable reason, we don't
    359    want it to appear in the "pending" list. Include "N/A" in the skip
    360    description, then it won't be added to the "pending" list. For example, if
    361    a test is skipped because it's running on a non-LuaJit system, including
    362    it in the "pending" list is just noise. Thus, its pending reason should
    363    say "N/A": >
    364    pending('N/A: missing LuaJIT FFI')
    365 <
    366  - Do not silently skip the test with `if-else`. If a functional test depends
    367    on some external factor (e.g. the existence of `md5sum` on `$PATH`), _and_
    368    you can't mock or fake the dependency, then skip the test via `pending()`
    369    if the external factor is missing. This ensures that the _total_
    370    test-count (success + fail + error + pending) is the same in all
    371    environments.
    372    - Note: `pending()` is ignored if it is missing an argument, unless it is
    373      [contained in an `it()` block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11).
    374      Provide empty function argument if the `pending()` call is outside `it()`
    375      Example: https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18
    376 
    377 WHERE TESTS GO
    378 
    379 Tests in `/test/unit` and `/test/functional` are divided into groups by the
    380 semantic component they are testing.
    381 
    382 - Unit tests (`test/unit/`) should match 1-to-1 with the structure of
    383  `src/nvim/`, because they are testing functions directly. E.g. unit-tests
    384  for `src/nvim/undo.c` should live in `test/unit/undo_spec.lua`.
    385 - Functional tests (`test/functional/`) are higher-level (plugins, UI, user
    386  input) than unit tests; they are organized by concept.
    387    - Try to find an existing `test/functional/*/*_spec.lua` group that makes
    388      sense, before creating a new one.
    389 
    390 
    391 ------------------------------------------------------------------------------
    392 Fixing tests                                                    *dev-fix-test*
    393 
    394 FIXING HARNESS WARNINGS
    395 
    396 > Nvim session T123 took 2000 milliseconds to exit
    397 > This indicates a likely problem with the test even if it passed!
    398 
    399 This may indicate a leak, because Nvim waits on uv handles before exiting.
    400 Example: https://github.com/neovim/neovim/pull/35768
    401 
    402 
    403 FIXING LINT FAILURES
    404 
    405 `make lint` (and `make lintlua`) runs [LuaLS](https://github.com/LuaLS/lua-language-server/wiki/Annotations)
    406 on the test code.
    407 
    408 If a LuaLS/EmmyLS warning must be ignored, specify the warning code. Example: >lua
    409 
    410    ---@diagnostic disable-next-line: unused-vararg
    411 
    412 https://github.com/LuaLS/lua-language-server/wiki/Annotations#diagnostic
    413 
    414 Ignore the smallest applicable scope (e.g. inside a function, not at the top of
    415 the file).
    416 
    417 
    418 ==============================================================================
    419 Configuration                                                *dev-test-config*
    420 
    421 (TODO: clean this up, too many variables and some of them are not used anymore.)
    422 
    423 Test behaviour is affected by environment variables. Currently supported
    424 (Functional, Unit, Benchmarks) (when Defined; when set to _1_; when defined,
    425 treated as Integer; when defined, treated as String; when defined, treated as
    426 Number; !must be defined to function properly):
    427 
    428 - `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`.
    429 
    430 - `CC` (U) (S): specifies which C compiler to use to preprocess files.
    431  Currently only compilers with gcc-compatible arguments are supported.
    432 
    433 - `GDB` (F) (D): makes nvim instances to be run under `gdbserver`. It will be
    434  accessible on `localhost:7777`: use `gdb build/bin/nvim`, type `target remote :7777` inside.
    435 
    436 - `GDBSERVER_PORT` (F) (I): overrides port used for `GDB`.
    437 
    438 - `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files.
    439 
    440 - `VALGRIND` (F) (D): makes nvim instances to be run under `valgrind`. Log
    441  files are named `valgrind-%p.log` in this case. Note that non-empty valgrind
    442  log may fail tests. Valgrind arguments may be seen in
    443  `/test/functional/testnvim.lua`. May be used in conjunction with `GDB`.
    444 
    445 - `VALGRIND_LOG` (F) (S): overrides valgrind log file name used for `VALGRIND`.
    446 
    447 - `TEST_COLORS` (F) (U) (D): enable pretty colors in test runner. Set to true by default.
    448 
    449 - `TEST_SKIP_FRAGILE` (F) (D): makes test suite skip some fragile tests.
    450 
    451 - `TEST_TIMEOUT` (FU) (I): specifies maximum time, in seconds, before the test
    452  suite run is killed
    453 
    454 - `NVIM_TEST` (FU) (D): lets Nvim process detect that it is running in a test.
    455 
    456 - `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects
    457 
    458 - `NVIM_PRG` (F) (S): path to Nvim executable (default: `build/bin/nvim`).
    459 
    460 - `NVIM_TEST_MAIN_CDEFS` (U) (1): makes `ffi.cdef` run in main process. This
    461  raises a possibility of bugs due to conflicts in header definitions, despite
    462  the counters, but greatly speeds up unit tests by not requiring `ffi.cdef` to
    463  do parsing of big strings with C definitions.
    464 
    465 - `NVIM_TEST_PRINT_I` (U) (1): makes `cimport` print preprocessed, but not yet
    466  filtered through `formatc` headers. Used to debug `formatc`. Printing is done
    467  with the line numbers.
    468 
    469 - `NVIM_TEST_PRINT_CDEF` (U) (1): makes `cimport` print final lines which will
    470  be then passed to `ffi.cdef`. Used to debug errors `ffi.cdef` happens to
    471  throw sometimes.
    472 
    473 - `NVIM_TEST_PRINT_SYSCALLS` (U) (1): makes it print to stderr when syscall
    474  wrappers are called and what they returned. Used to debug code which makes
    475  unit tests be executed in separate processes.
    476 
    477 - `NVIM_TEST_RUN_FAILING_TESTS` (U) (1): makes `itp` run tests which are known
    478  to fail (marked by setting third argument to `true`).
    479 
    480 - `NVIM_TEST_CORE_*` (FU) (S): a set of environment variables which specify
    481  where to search for core files. Are supposed to be defined all at once.
    482 
    483 - `NVIM_TEST_CORE_GLOB_DIRECTORY` (FU) (S): directory where core files are
    484  located. May be `.`. This directory is then recursively searched for core
    485  files. Note: this variable must be defined for any of the following to have
    486  any effect.
    487 
    488 - `NVIM_TEST_CORE_GLOB_RE` (FU) (S): regular expression which must be matched
    489  by core files. E.g. `/core[^/]*$`. May be absent, in which case any file is
    490  considered to be matched.
    491 
    492 - `NVIM_TEST_CORE_EXC_RE` (FU) (S): regular expression which excludes certain
    493  directories from searching for core files inside. E.g. use `^/%.deps$` to not
    494  search inside `/.deps`. If absent, nothing is excluded.
    495 
    496 - `NVIM_TEST_CORE_DB_CMD` (FU) (S): command to get backtrace out of the
    497  debugger. E.g. `gdb -n -batch -ex "thread apply all bt full"
    498  "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"`. Defaults to the example command.
    499  This debug command may use environment variables `_NVIM_TEST_APP` (path to
    500  application which is being debugged: normally either nvim or luajit) and
    501  `_NVIM_TEST_CORE` (core file to get backtrace from).
    502 
    503 - `NVIM_TEST_CORE_RANDOM_SKIP` (FU) (D): makes `check_cores` not check cores
    504  after approximately 90% of the tests. Should be used when finding cores is
    505  too hard for some reason. Normally (on OS X or when
    506  `NVIM_TEST_CORE_GLOB_DIRECTORY` is defined and this variable is not) cores
    507  are checked for after each test.
    508 
    509 - `NVIM_TEST_INTEG` (F) (D): enables integration tests that makes real network
    510  calls. By default these tests are skipped. When set to `1`, tests requiring external
    511  HTTP requests (e.g `vim.net.request()`) will be run.
    512 
    513 - `NVIM_TEST_RUN_TESTTEST` (U) (1): allows running
    514  `test/unit/testtest_spec.lua` used to check how testing infrastructure works.
    515 
    516 - `NVIM_TEST_TRACE_LEVEL` (U) (N): specifies unit tests tracing level:
    517  - `0` disables tracing (the fastest, but you get no data if tests crash and
    518    no core dump was generated),
    519  - `1` leaves only C function calls and returns in the trace (faster than
    520    recording everything),
    521  - `2` records all function calls, returns and executed Lua source lines.
    522 
    523 - `NVIM_TEST_TRACE_ON_ERROR` (U) (1): makes unit tests yield trace on error in
    524  addition to regular error message.
    525 
    526 - `NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to
    527  keep. Default is 1024.
    528 
    529 - `OSV_PORT`: (F): launches `osv` listening on the given port within nvim test
    530  instances.
    531 
    532 
    533 vim:tw=78:ts=8:sw=4:et:ft=help:norl: