diff --git a/.busted.example b/.busted.example new file mode 100644 index 0000000..a1dadc6 --- /dev/null +++ b/.busted.example @@ -0,0 +1,14 @@ +return { + -- Use neotest-busted in all tasks + _all = { + lua = "nvim -l path/to/neotest-busted/scripts/test-runner.lua", + }, + -- Default task to run if no task was specified + default = { + verbose = true, + }, + integration = { + tags = "integration", + shuffle_files = true, + }, +} diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index dff462a..4851117 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -16,4 +16,4 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} version: 0.16.1 - args: --check lua/ tests/ + args: --check lua/ tests/ scripts/ diff --git a/README.md b/README.md index cb09ea1..1bb8e21 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,19 @@ the command will automatically try to find your tests in a `spec/`, `test/`, or $ nvim -l /scripts/test-runner.lua tests/my_spec.lua ``` +### Using busted directly + +You can also provide a `.busted` config file and run your tests using busted. +Learn more about busted configuration files from the [official +docs](https://lunarmodules.github.io/busted/#usage) or take a look at the example [here](/.busted.example). + +Pass extra arguments to `neotest` to run a specific task. For example, to run +the `"integration"` task in a test file: + +```lua +require("neotest").run.run({ vim.fn.expand("%"), extra_args = { "--run", "integration" } }) +``` + ## Debugging tests `neotest-busted` has support for debugging tests via [`local-lua-debugger-vscode`](https://github.com/tomblind/local-lua-debugger-vscode) diff --git a/lua/neotest-busted/init.lua b/lua/neotest-busted/init.lua index 64eb473..a9fb326 100644 --- a/lua/neotest-busted/init.lua +++ b/lua/neotest-busted/init.lua @@ -253,10 +253,18 @@ function BustedNeotestAdapter.create_test_command(results_path, paths, filters, vim.list_extend(arguments, busted_command) - if vim.tbl_islist(config.busted_args) and #config.busted_args > 0 then + if vim.tbl_islist(config.busted_args) then vim.list_extend(arguments, config.busted_args) end + if vim.tbl_islist(_options.busted_arguments) then + for _, busted_arg in ipairs(_options.busted_arguments) do + local arg = _options.quote_strings and quote_string(busted_arg) or busted_arg + + table.insert(arguments, arg) + end + end + -- Add test filters for _, filter in ipairs(filters) do local escaped_filter = escape_test_pattern_filter(filter) diff --git a/lua/neotest-busted/types.lua b/lua/neotest-busted/types.lua index c27b7cf..c0eb28e 100644 --- a/lua/neotest-busted/types.lua +++ b/lua/neotest-busted/types.lua @@ -13,9 +13,10 @@ ---@field lua_cpaths string[] ---@class neotest-busted.TestCommandOptions +---@field busted_arguments string[]? ---@field busted_output_handler string? ---@field busted_output_handler_options string[]? ----@field quote_strings boolean +---@field quote_strings boolean? ---@class neotest-busted.TestCommandConfig ---@field nvim_command string diff --git a/scripts/test-runner.lua b/scripts/test-runner.lua old mode 100644 new mode 100755 index 96367b8..ec9c348 --- a/scripts/test-runner.lua +++ b/scripts/test-runner.lua @@ -1,3 +1,18 @@ +local help_message = [[test-runner [...options] [...test_files] [-- [...busted_options] + +Run tests using neotest-busted from the commandline. Options given after '--' +are forwarded to busted. + +Usage: + + -h, --help Show this help message. +]] + +---@class ParsedArgs +---@field help boolean +---@field paths string[] +---@field busted_args string[] + ---@enum Color local Color = { Red = 31, @@ -32,8 +47,23 @@ local level_options = { color = Color.White, hl_group = "MoreMsg", }, + [vim.log.levels.OFF] = { + name = "", + color = Color.Reset, + hl_group = "", + }, } +local function is_windows() + if jit then + return not vim.tbl_contains({ "linux", "osx", "bsd", "posix", "other" }, jit.os:lower()) + else + return package.config:sub(1, 1) == "\\" + end +end + +local _is_windows = is_windows() + local function is_headless() return #vim.api.nvim_list_uis() == 0 end @@ -41,26 +71,37 @@ end ---@param color integer ---@return string local function color_code(color) + if _is_windows then + return "" + end + return ("\x1b[%dm"):format(color) end ---@param message string ---@param level vim.log.levels? local function print_level(message, level) - local options = level_options[level] or level_options[vim.log.levels.ERROR] + local _level = level or vim.log.levels.OFF + local options = level_options[_level] + local prefix = "" if is_headless() then - io.stderr:write( - ("%s%s%s: %s\n"):format( + if _level ~= vim.log.levels.OFF then + prefix = ("%s%s%s: "):format( color_code(options.color), options.name, - color_code(Color.Reset), - message + color_code(Color.Reset) ) - ) + end + + io.stderr:write(("%s%s\n"):format(prefix, message)) else + if _level ~= vim.log.levels.OFF then + prefix = ("[neotest-busted:%s]: "):format(options.name) + end + vim.api.nvim_echo({ - { ("[neotest-busted:%s]: "):format(options.name), options.hl_group }, + { prefix, options.hl_group }, { message }, }, true, {}) end @@ -68,32 +109,40 @@ end ---@return string? local function find_minimal_init() - -- NOTE: Do not use util.glob as we haven't loaded neotest-busted at this point local glob_matches = vim.fn.glob("**/minimal_init.lua", false, true) if #glob_matches == 0 then - print_level("Could not find minimal_init.lua") + print_level("Could not find minimal_init.lua", vim.log.levels.ERROR) return end return glob_matches[1] end ----@param module_name string ----@return any -local function require_checked(module_name) - local ok, module_or_error = pcall(require, module_name) - - if not ok then - return nil +---@return ParsedArgs +local function parse_args() + local parsed_args = { + help = false, + paths = {}, + busted_args = {}, + } + + -- Start from the third argument to skip busted executable and "--ignore-lua" flag + -- TODO: Should we just use them instead of skipping them? + for idx = 3, #_G.arg do + local arg = _G.arg[idx] + + if arg == "-h" or arg == "--help" then + parsed_args.help = true + elseif arg == "--" then + vim.list_extend(parsed_args.busted_args, _G.arg, idx + 1) + break + else + table.insert(parsed_args.paths, arg) + end end - return module_or_error -end - ----@return string[] -local function parse_args() - return vim.list_slice(_G.arg, 1) + return parsed_args end ---@return string[] @@ -101,24 +150,24 @@ local function collect_tests() local tests = {} local util = require("neotest-busted.util") - vim.list_extend(tests, util.glob("./test/**/*_spec.lua")) - vim.list_extend(tests, util.glob("./tests/**/*_spec.lua")) - vim.list_extend(tests, util.glob("./spec/**/*_spec.lua")) + -- TODO: Support other test file patterns (via .busted) + vim.list_extend(tests, util.glob("test/**/*_spec.lua")) + vim.list_extend(tests, util.glob("tests/**/*_spec.lua")) + vim.list_extend(tests, util.glob("spec/**/*_spec.lua")) return tests end local function run() if not is_headless() then - print_level("Script must be run from the command line") + print_level("Script must be run from the command line", vim.log.levels.ERROR) return end - local paths = parse_args() local minimal_init = find_minimal_init() if not minimal_init then - print_level("Could not find a minimal_init.lua file") + print_level("Could not find a minimal_init.lua file", vim.log.levels.ERROR) return end @@ -135,25 +184,33 @@ local function run() return end - if #paths == 0 then - paths = collect_tests() + local parsed_args = parse_args() + + if parsed_args.help then + print_level(help_message) + return end - local busted = adapter_or_error.create_test_command(nil, paths, {}, { + local paths = #parsed_args.paths > 0 and parsed_args.paths or collect_tests() + + local test_command = adapter_or_error.create_test_command(nil, paths, {}, { busted_output_handler = "utfTerminal", busted_output_handler_options = { "--color" }, + -- If we don't add --ignore-lua the subsequent busted command (run via + -- neovim) will use the .busted config file and use the 'lua' option + -- again for running the tests (this script) which will cause an + -- infinite process spawning loop + busted_arguments = vim.list_extend({ "--ignore-lua" }, parsed_args.busted_args), }) - if not busted then - print_level("Could not find a busted executable") + if not test_command then + print_level("Could not find a busted executable", vim.log.levels.ERROR) return end - local command = vim.list_extend({ busted.nvim_command }, busted.arguments) + local command = vim.list_extend({ test_command.nvim_command }, test_command.arguments) - io.stdout:write( - vim.fn.system(table.concat(vim.tbl_map(vim.fn.shellescape, command), " ")) - ) + io.stdout:write(vim.fn.system(table.concat(vim.tbl_map(vim.fn.shellescape, command), " "))) end run()