TEST
, TEST_F
and TEST_P
TEST_P
, only Range
, Values
and Bool
parameter generators are
supported. The name generator is not supported either. See
INSTANTIATE_TEST_SUITE_P
for more.TEST_CASE
, TEST_CASE_METHOD
, SCENARIO
TEST_CASE
, TEST_CASE_FIXTURE
, SCENARIO
The framework versions listed above are the ones that have been tested, but older versions may work as well.
gtest_discover_tests()
, catch_discover_tests()
, etc.CTestTestfile.cmake
is expected to be on path from project root (max two
levels deep)
<dir>/CTestTestfile.cmake
or
<dir>/<config>/CTestTestfile.cmake
.#include <gtest/gtest.h>
as opposed to
#include "gtest/gtest.h"
.catch2
and doctest
, enumerates test case
names interchangeably. This makes it impossible for neotest-ctest to reliably
map them to neotest positions. Please ensure that test case names are uniquely
defined if you use multiple frameworks together!dap
strategy for debugging tests (yet)See Neotest Installation Instructions.
The following example is based on
lazy.nvim
:
{
"nvim-neotest/neotest",
dependencies = {
"nvim-lua/plenary.nvim",
-- Other neotest dependencies here
"orjangj/neotest-ctest",
},
config = function()
-- Optional, but recommended, if you have enabled neotest's diagnostic option
local neotest_ns = vim.api.nvim_create_namespace("neotest")
vim.diagnostic.config({
virtual_text = {
format = function(diagnostic)
-- Convert newlines, tabs and whitespaces into a single whitespace
-- for improved virtual text readability
local message = diagnostic.message:gsub("[\r\n\t%s]+", " ")
return message
end,
},
}, neotest_ns)
require("neotest").setup({
adapters = {
-- Load with default config
require("neotest-ctest").setup({})
}
})
end
}
require("neotest-ctest").setup({
-- fun(string) -> string: Find the project root directory given a current directory
-- to work from.
root = function(dir)
-- by default, it will use neotest.lib.files.match_root_pattern with the following entries
return require("neotest.lib").files.match_root_pattern(
-- NOTE: CMakeLists.txt is not a good candidate as it can be found in
-- more than one directory
"CMakePresets.json",
"compile_commands.json",
".clangd",
".clang-format",
".clang-tidy",
"build",
"out",
".git"
)(dir)
end
),
-- fun(string) -> bool: Takes a file path as string and returns true if it contains tests.
-- This function is called often by neotest, so make sure you don't do any heavy duty work.
is_test_file = function(file)
-- by default, returns true if the file stem ends with _test and the file extension is
-- one of cpp/cc/cxx.
end,
-- fun(string, string, string) -> bool: Filter directories when searching for test files.
-- Best to keep this as-is and set per-project settings in neotest instead.
-- See :h neotest.Config.discovery.
filter_dir = function(name, rel_path, root)
-- If you don't configure filter_dir through neotest, and you leave it as-is,
-- it will filter the following directories by default: build, cmake, doc,
-- docs, examples, out, scripts, tools, venv.
end,
-- What frameworks to consider when performing auto-detection of test files.
-- Priority can be configured by ordering/removing list items to your needs.
-- By default, each test file will be queried with the given frameworks in the
-- following order.
frameworks = { "gtest", "catch2", "doctest"},
-- What extra args should ALWAYS be sent to CTest? Note that most of CTest arguments
-- are not expected to be used (or work) with this plugin, but some might be useful
-- depending on your needs. For instance:
-- extra_args = {
-- "--stop-on-failure",
-- "--schedule-random",
-- "--timeout",
-- "<seconds>",
-- }
-- If you want to send extra_args for one given invocation only, send them to
-- `neotest.run.run({extra_args = ...})` instead. see :h neotest.RunArgs for details.
extra_args = {},
})
It's possible to configure the adapter per project using Neotest's projects
option if you need more fine-grained control:
require("neotest").setup({
-- other options
projects = {
["~/path/to/some/project"] = {
discovery = {
filter_dir = function(name, rel_path, root)
-- Do not look for tests in `build` folder for this specific project
return name ~= "build"
end,
},
adapters = {
require("neotest-ctest").setup({
is_test_file = function(file_path)
-- your implementation
end,
frameworks = { "catch2" },
}),
},
},
},
})
See Neotest Usage. The following example of keybindings can be used as a starting point:
{
"nvim-neotest/neotest",
dependencies = {
"nvim-lua/plenary.nvim",
-- Other neotest dependencies here
"orjangj/neotest-ctest",
},
keys = function()
local neotest = require("neotest")
return {
{ "<leader>tf", function() neotest.run.run(vim.fn.expand("%")) end, desc = "Run File" },
{ "<leader>tt", function() neotest.run.run() end, desc = "Run Nearest" },
{ "<leader>tw", function() neotest.run.run(vim.loop.cwd()) end, desc = "Run Workspace" },
{
"<leader>tr",
function()
-- This will only show the output from the test framework
neotest.output.open({ short = true, auto_close = true })
end,
desc = "Results (short)",
},
{
"<leader>tR",
function()
-- This will show the classic CTest log output.
-- The output usually spans more than can fit the neotest floating window,
-- so using 'enter = true' to enable normal navigation within the window
-- is recommended.
neotest.output.open({ enter = true })
end,
desc = "Results (full)",
},
-- Other keybindings
}
end,
config = function()
require("neotest").setup({
adapters = {
-- Load with default config
require("neotest-ctest").setup({})
}
})
end
}