diff --git a/.busted.example b/.busted.example deleted file mode 100644 index a1dadc6..0000000 --- a/.busted.example +++ /dev/null @@ -1,14 +0,0 @@ -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 4851117..f050a2a 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/ scripts/ + args: --check lua/ tests/ test_files/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4124721..ee17f9d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,6 @@ 2. Make changes. 3. Make sure tests and styling checks are passing. * Run tests by running `./tests/run_tests.sh` in the project directory. Running the tests requires [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim), [`neotest`](https://github.com/nvim-neotest/neotest), [`nvim-nio`](https://github.com/nvim-neotest/nvim-nio), and [`nvim-treesitter`](https://github.com/nvim-treesitter/nvim-treesitter). You may need to update the paths in `./tests/minimal_init.lua` to match those of your local installations to be able to run the tests. A `busted` executable is also needed to run the tests so set it up as per the instructions in the [README](/README.md). - * Install [stylua](https://github.com/JohnnyMorganz/StyLua) and check styling using `stylua --check lua/ scripts/ tests/ test_files/`. Omit `--check` in order to fix styling. + * Install [stylua](https://github.com/JohnnyMorganz/StyLua) and check styling using `stylua --check lua/ tests/ test_files/`. Omit `--check` in order to fix styling. 4. Submit a pull request. 5. Get it approved. diff --git a/README.md b/README.md index 9f04334..9da93c9 100644 --- a/README.md +++ b/README.md @@ -212,24 +212,62 @@ The following command will install busted in your home directory. ## Running from the command line -A `test-runner.lua` script is provided in the `scripts/` folder for running -tests via the command line. This is useful for running all tests during CI for -example. +There are several ways to run your tests from the command line. -If you do not provide a `minimal_init.lua` to set up your test environment, the -script will look for one and source it. If you don't specify any tests to run, -the command will automatically try to find your tests in a `spec/`, `test/`, or -`tests/` directory. +> [!WARNING] +> Running busted with neovim as the lua interpreter means that the same neovim +> instance is used in all your tests which could break test isolation. For +> example, setting `_G.foo = 10` in a test that runs before a test containing +> `vim.print(_G.foo)` will print 10. -```shell -$ nvim -l /scripts/test-runner.lua tests/my_spec.lua -``` +
+Using plenary.nvim + +This repo uses [`plenary.nvim`](https://github.com/nvim-lua/plenary.nvim) to run +its tests so feel free to use the setup in your own projects. + +Running tests this way has the benefit that a separate neovim instance is used +for each test file giving better test isolation than running busted with neovim +as the lua interpreter. + +See `plenary.nvim`'s GitHub repo or run `:help plenary-test` if you already have it +installed. + +--- -### Using busted directly +
-You can also provide a `.busted` config file and run your tests using busted. +
+Using a busted configuration file + +You can 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). +docs](https://lunarmodules.github.io/busted/#usage). + +```lua +return { + _all = { + -- Use neovim as the lua interpreter for all tasks + lua = "nvim -l", + -- Ensures that your plugin and test files will be found + lpath = "lua/?.lua;lua/?/init.lua;tests/?.lua", + }, + -- Default task to run if no task was specified + default = { + -- Runs your minimal init file (if any) so package dependencies can be found + helper = "./tests/minimal_init.lua", + }, + -- Some other task + integration = { + tags = "integration", + shuffle_files = true, + }, +} +``` + +Then run your tests using either `busted ` or use `luarocks test +--test-type busted ` (or omit `--test-type busted` if you set up a +test command in the rockspec, see below). Pass extra arguments to `neotest` to run a specific task. For example, to run the `"integration"` task in a test file: @@ -238,6 +276,101 @@ the `"integration"` task in a test file: require("neotest").run.run({ vim.fn.expand("%"), extra_args = { "--run", "integration" } }) ``` +--- + +
+ +
+Using luarocks + +Luarocks allows you to specify a test command in the rockspec which can be run +using `luarocks test`. Additionally, you can specify `test_dependencies` and +they will automatically be installed before running tests. + +If your tests do not need to run in a neovim context the rockspec below should +suffice, otherwise you can use a `.busted` config file to setup this up (see +above). + +```lua +rockspec_format = "3.0" +package = "rockspec-example.nvim" +version = "scm-1" + +description = { + summary = "Example rockspec", +} + +-- More definitions... + +test_dependencies = { + "busted >= 2.2.0, < 3.0.0", +} + +test = { + type = "busted", +} +``` + +This will work if you use a [user-](#user-home-directory-install) or +[system-level](#global-install) luarocks installation but if you want to use a +[project-level](#directory-local-install) luarocks installation, you can use +this small script to correctly set up the paths. + +```lua +---@param command_name string +---@param args string[] +---@return string +local function run_command(command_name, args) + local command = vim.list_extend({ command_name }, args) + local result = vim.fn.system(command) + + if vim.v.shell_error ~= 0 then + error(("Failed to run command: '%s'"):format(command)) + end + + return result +end + +-- Path for the plugin being tested +vim.opt.rtp:append(".") + +local lua_path = run_command("luarocks", { "path", "--lr-path" }) +local lua_cpath = run_command("luarocks", { "path", "--lr-cpath" }) + +-- Paths for the project-local luarocks packages +package.path = package.path .. ";" .. lua_path + +-- Paths for the project-local shared libraries +package.cpath = package.cpath .. ";" .. lua_cpath + +require("busted.runner")({ standalone = false }) +``` + +Then change the test command in your rockspec to the following. + +```lua +test = { + type = "command", + command = "nvim -l ./run-tests.lua", +} +``` + +--- + +
+ + + +
+Using lazy.nvim + +The `lazy.nvim` package manager directly provides a way to run busted tests. +Please see the [official docs](https://lazy.folke.io/developers#minit-minimal-init). + +--- + +
+ ## Contributing Thanks for considering to contribute. Please see the instructions [here](/CONTRIBUTING.md). diff --git a/neotest-busted-scm-1.rockspec b/neotest-busted-scm-1.rockspec index f05f793..b380f8d 100644 --- a/neotest-busted-scm-1.rockspec +++ b/neotest-busted-scm-1.rockspec @@ -28,7 +28,6 @@ build = { type = "builtin", copy_directories = { "doc", - "scripts", }, } diff --git a/scripts/test-runner.lua b/scripts/test-runner.lua deleted file mode 100755 index ad02df0..0000000 --- a/scripts/test-runner.lua +++ /dev/null @@ -1,216 +0,0 @@ -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, - Yellow = 33, - White = 37, - Reset = 0, -} - ----@alias vim.log.levels 0 | 1 | 2 | 3 | 4 | 5 - ----@class LogLevelOptions ----@field name string ----@field color integer ----@field hl_group string - ----@alias LevelOptions table - ----@type LevelOptions -local level_options = { - [vim.log.levels.ERROR] = { - name = "Error", - color = Color.Red, - hl_group = "ErrorMsg", - }, - [vim.log.levels.WARN] = { - name = "Warning", - color = Color.Yellow, - hl_group = "WarningMsg", - }, - [vim.log.levels.INFO] = { - name = "Info", - 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 - ----@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 _level = level or vim.log.levels.OFF - local options = level_options[_level] - local prefix = "" - - if is_headless() then - if _level ~= vim.log.levels.OFF then - prefix = ("%s%s%s: "):format( - color_code(options.color), - options.name, - 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({ - { prefix, options.hl_group }, - { message }, - }, true, {}) - end -end - ----@return string? -local function find_minimal_init() - local glob_matches = vim.fn.glob("**/minimal_init.lua", false, true) - - if #glob_matches == 0 then - print_level("Could not find minimal_init.lua", vim.log.levels.ERROR) - return - end - - return glob_matches[1] -end - ----@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 parsed_args -end - ----@return string[] -local function collect_tests() - local tests = {} - local util = require("neotest-busted.util") - - -- 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", vim.log.levels.ERROR) - return - end - - local minimal_init = find_minimal_init() - - if not minimal_init then - print_level("Could not find a minimal_init.lua file", vim.log.levels.ERROR) - return - end - - vim.cmd.source(minimal_init) - - local ok, adapter_or_error = pcall(require, "neotest-busted") - - if not ok then - print_level( - "neotest-busted could not be loaded. Set up 'runtimepath', provide a minimal configuration via '-u', or create a 'minimal_init.lua' file: " - .. adapter_or_error, - vim.log.levels.ERROR - ) - return - end - - local parsed_args = parse_args() - - if parsed_args.help then - print_level(help_message) - return - end - - local paths = #parsed_args.paths > 0 and parsed_args.paths or collect_tests() - - local test_command = adapter_or_error.create_test_command(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 test_command then - print_level("Could not find a busted executable", vim.log.levels.ERROR) - return - end - - 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), " "))) -end - -run()