Open-Source Wikis

/

Neovim

/

How to contribute

/

Testing

neovim/neovim

Testing

Neovim has three test suites and they all live under test/. The full developer-facing description is test/README.md and runtime/doc/dev_test.txt; this page is the cheat sheet.

The three suites

Suite Path Speed What it does
Unit test/unit/ Fast Calls C functions directly via FFI. Best for marktree, path, typval encoder, etc.
Functional test/functional/ Medium Spawns a real nvim --embed child and drives it over msgpack-rpc. Most editor behavior tests live here.
Old (vim) test/old/ Slow Vim's original test framework, ported. Used mainly for vim-patch: porting.

There is also a benchmark suite (test/functional/benchmark/, run with make benchmark) for performance regressions.

The Lua suites are written against busted. The runner is test/runner.lua and the harness — the helpers that spawn the child editor and wrap RPC — is test/testutil.lua (25k lines) and test/harness.lua (48k lines).

Running

make test                  # everything
make functionaltest
make unittest
make oldtest

# Run a single file
TEST_FILE=test/functional/lua/diagnostic_spec.lua make functionaltest

# Filter by test name (busted --filter)
TEST_FILTER='vim.diagnostic.set' make functionaltest

# Run with valgrind
VALGRIND=1 TEST_FILE=... make functionaltest

# Run the build under ASAN/UBSAN
rm -rf build
CMAKE_EXTRA_FLAGS="-DCMAKE_C_COMPILER=clang -DENABLE_ASAN_UBSAN=1" make
ASAN_OPTIONS=log_path=/tmp/nvim_asan make functionaltest

CI also runs make lint, the docs lint, the windows test matrix, and the vim-patch lint. Locally you usually only need the suite that covers the area you touched.

Anatomy of a functional test

local n = require('test.functional.testnvim')()
local clear, eq, exec_lua = n.clear, t.eq, n.exec_lua

describe('vim.diagnostic.set()', function()
  before_each(function()
    clear()
  end)

  it('sets diagnostics for a buffer', function()
    exec_lua([[
      local ns = vim.api.nvim_create_namespace('test')
      vim.diagnostic.set(ns, 0, {
        { lnum = 0, col = 0, message = 'oops' },
      })
    ]])
    eq(1, exec_lua('return #vim.diagnostic.get(0)'))
  end)
end)

Common helpers from test/functional/testutil.lua:

  • n.clear() — start a fresh nvim child.
  • n.exec_lua(code, ...) — run Lua in the child and return the result.
  • n.api.nvim_… — call API functions on the child via msgpack.
  • n.feed('iHello<esc>') — send keystrokes through nvim_input.
  • n.command(':wq') — run an Ex command via nvim_command.
  • n.expect(...) and t.eq(...) — assertions.
  • n.screen (Screen in test/functional/ui/screen.lua) — a virtual screen that snapshots redraw events. Used by every UI/redraw test.

Anatomy of a unit test

Unit tests use FFI. They are most useful for testing C functions whose behavior is hard to provoke through the editor:

local helpers = require('test.unit.helpers')
local cimport = helpers.cimport

local marktree = cimport('./src/nvim/marktree.h')

describe('marktree', function()
  it('inserts and queries', function()
    local mt = ffi.new('MarkTree[1]')
    marktree.marktree_put(mt, ...)
    -- ...
  end)
end)

Look at test/unit/marktree_spec.lua for a fully worked example.

What to test

The maintainer expectation is that almost every PR adds tests. Specifically:

  • Bug fix. Add a regression test that fails before your change and passes after. Name the test with the issue number, e.g. it('outputs the EOF as LF (not CRLF) #36853'…). The number lets reviewers check the fix corresponds to the bug.
  • New feature. Add functional tests for the new behavior plus unit tests for any new pure-data structures.
  • Refactor. No new tests required, but existing tests must continue to pass.

Special test environments

  • oldtest/ — the upstream Vim test files, run with the legacy runtest.vim driver. You will mostly touch this when working on vim-patch: ports.
  • benchmark/ — micro-benchmarks. Run with make benchmark. Failures here are usually performance regressions, not correctness ones.
  • Screen snapshots — UI tests use a virtual screen that snapshots a 2D grid of cells. The snapshot is asserted with screen:expect{grid=[[...]]}. When you change rendering, you'll be updating these.

Logs

When a functional test fails, the child editor's log goes to $NVIM_LOG_FILE (set by the test harness). The default location is build/.nvimlog or per-test temp dirs. :edit $NVIM_LOG_FILE in a paused test environment is a quick way to see what the child saw.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – Neovim wiki | Factory