mirror of
https://github.com/neovim/neovim.git
synced 2026-07-17 14:41:33 +00:00
test: replace busted with local harness
Replace the busted-based Lua test runner with a repo-local harness. The new harness runs spec files directly under `nvim -ll`, ships its own reporter and lightweight `luassert` shim, and keeps the helper/preload flow used by the functional and unit test suites. Keep the file boundary model shallow and busted-like by restoring `_G`, `package.loaded`, `package.preload`, `arg`, and the process environment between files, without carrying extra reset APIs or custom assertion machinery. Update the build and test entrypoints to use the new runner, add black-box coverage for the harness itself, and drop the bundled busted/luacheck dependency path. AI-assisted: Codex
This commit is contained in:
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -74,10 +74,6 @@ jobs:
|
|||||||
name: luals
|
name: luals
|
||||||
run: cmake --build build --target luals
|
run: cmake --build build --target luals
|
||||||
|
|
||||||
- if: success() || failure() && steps.abort_job.outputs.status == 'success'
|
|
||||||
name: luacheck
|
|
||||||
run: cmake --build build --target lintlua-luacheck
|
|
||||||
|
|
||||||
- if: success() || failure() && steps.abort_job.outputs.status == 'success'
|
- if: success() || failure() && steps.abort_job.outputs.status == 'success'
|
||||||
name: lintsh
|
name: lintsh
|
||||||
run: cmake --build build --target lintsh
|
run: cmake --build build --target lintsh
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
"version": "LuaJIT"
|
"version": "LuaJIT"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"library": [
|
|
||||||
"${3rd}/busted/library"
|
|
||||||
],
|
|
||||||
"ignoreDir": [
|
"ignoreDir": [
|
||||||
".deps",
|
".deps",
|
||||||
"build",
|
"build",
|
||||||
|
|||||||
2
BUILD.md
2
BUILD.md
@@ -569,7 +569,7 @@ Note that the C++ compiler is explicitly set so that it can be found when the de
|
|||||||
|
|
||||||
See "Available System Integrations" in `zig build -h` to see available system integrations. Enabling an integration, e.g. `zig build -fsys=utf8proc` will use the system's installation of utf8proc.
|
See "Available System Integrations" in `zig build -h` to see available system integrations. Enabling an integration, e.g. `zig build -fsys=utf8proc` will use the system's installation of utf8proc.
|
||||||
|
|
||||||
`zig build --system deps_dir` will enable all integrations and turn off dependency fetching. This requires you to pre-download the dependencies which don't have a system integration into `deps_dir` (at the time of writing these are ziglua, [`lua_dev_deps`](https://github.com/neovim/deps/blob/master/opt/lua-dev-deps.tar.gz), and the built-in tree-sitter parsers). You have to create subdirectories whose names are the respective package's hash under `deps_dir` and unpack the dependencies inside that directory - ziglua should go under `deps_dir/zlua-0.1.0-hGRpC1dCBQDf-IqqUifYvyr8B9-4FlYXqY8cl7HIetrC` and so on. Hashes should be taken from `build.zig.zon`.
|
`zig build --system deps_dir` will enable all integrations and turn off dependency fetching. This requires you to pre-download the dependencies which don't have a system integration into `deps_dir` (at the time of writing these are ziglua and the built-in tree-sitter parsers). You have to create subdirectories whose names are the respective package's hash under `deps_dir` and unpack the dependencies inside that directory - ziglua should go under `deps_dir/zlua-0.1.0-hGRpC1dCBQDf-IqqUifYvyr8B9-4FlYXqY8cl7HIetrC` and so on. Hashes should be taken from `build.zig.zon`.
|
||||||
|
|
||||||
See the `prepare` function of [this `PKGBUILD`](https://git.sr.ht/~chinmay/nvim_build/tree/26364a4cf9b4819f52a3e785fa5a43285fb9cea2/item/PKGBUILD#L90) for an example.
|
See the `prepare` function of [this `PKGBUILD`](https://git.sr.ht/~chinmay/nvim_build/tree/26364a4cf9b4819f52a3e785fa5a43285fb9cea2/item/PKGBUILD#L90) for an example.
|
||||||
|
|
||||||
|
|||||||
@@ -243,15 +243,6 @@ mark_as_advanced(TS_QUERY_LS_PRG)
|
|||||||
|
|
||||||
set(STYLUA_DIRS runtime scripts src test contrib)
|
set(STYLUA_DIRS runtime scripts src test contrib)
|
||||||
|
|
||||||
add_glob_target(
|
|
||||||
TARGET lintlua-luacheck
|
|
||||||
COMMAND $<TARGET_FILE:nvim_bin>
|
|
||||||
FLAGS -ll ${PROJECT_SOURCE_DIR}/test/lua_runner.lua ${CMAKE_BINARY_DIR}/usr/share/lua/5.1 luacheck -q
|
|
||||||
GLOB_DIRS runtime scripts src test
|
|
||||||
GLOB_PAT *.lua
|
|
||||||
TOUCH_STRATEGY PER_DIR)
|
|
||||||
add_dependencies(lintlua-luacheck lua_dev_deps)
|
|
||||||
|
|
||||||
add_glob_target(
|
add_glob_target(
|
||||||
TARGET lintlua-stylua
|
TARGET lintlua-stylua
|
||||||
COMMAND ${STYLUA_PRG}
|
COMMAND ${STYLUA_PRG}
|
||||||
@@ -271,7 +262,7 @@ add_custom_target(lintlua-stylua2
|
|||||||
add_dependencies(lintlua-stylua lintlua-stylua2)
|
add_dependencies(lintlua-stylua lintlua-stylua2)
|
||||||
|
|
||||||
add_custom_target(lintlua)
|
add_custom_target(lintlua)
|
||||||
add_dependencies(lintlua lintlua-luacheck lintlua-stylua)
|
add_dependencies(lintlua lintlua-stylua)
|
||||||
|
|
||||||
add_glob_target(
|
add_glob_target(
|
||||||
TARGET lintsh
|
TARGET lintsh
|
||||||
@@ -355,21 +346,6 @@ ExternalProject_Add(uncrustify
|
|||||||
EXCLUDE_FROM_ALL TRUE
|
EXCLUDE_FROM_ALL TRUE
|
||||||
${EXTERNALPROJECT_OPTIONS})
|
${EXTERNALPROJECT_OPTIONS})
|
||||||
|
|
||||||
option(USE_BUNDLED_BUSTED "Use bundled busted" ON)
|
|
||||||
if(USE_BUNDLED_BUSTED)
|
|
||||||
get_externalproject_options(lua_dev_deps ${DEPS_IGNORE_SHA})
|
|
||||||
ExternalProject_Add(lua_dev_deps
|
|
||||||
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/lua_dev_deps
|
|
||||||
SOURCE_DIR ${DEPS_SHARE_DIR}
|
|
||||||
CONFIGURE_COMMAND ""
|
|
||||||
BUILD_COMMAND ""
|
|
||||||
INSTALL_COMMAND ""
|
|
||||||
EXCLUDE_FROM_ALL TRUE
|
|
||||||
${EXTERNALPROJECT_OPTIONS})
|
|
||||||
else()
|
|
||||||
add_custom_target(lua_dev_deps)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "arm|aarch")
|
if (CMAKE_SYSTEM_PROCESSOR MATCHES "arm|aarch")
|
||||||
set(LUALS_ARCH arm64)
|
set(LUALS_ARCH arm64)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -545,8 +545,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, funcs_data);
|
const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, funcs_data);
|
||||||
|
|
||||||
const lua_dev_deps = b.dependency("lua_dev_deps", .{});
|
|
||||||
|
|
||||||
const test_deps = b.step("test_deps", "test prerequisites");
|
const test_deps = b.step("test_deps", "test prerequisites");
|
||||||
test_deps.dependOn(&nvim_exe_install.step);
|
test_deps.dependOn(&nvim_exe_install.step);
|
||||||
// running tests doesn't require copying the static runtime, only the generated stuff
|
// running tests doesn't require copying the static runtime, only the generated stuff
|
||||||
@@ -673,7 +671,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
b,
|
b,
|
||||||
nvim_exe,
|
nvim_exe,
|
||||||
test_deps,
|
test_deps,
|
||||||
lua_dev_deps.path("."),
|
|
||||||
test_config_step.getDirectory(),
|
test_config_step.getDirectory(),
|
||||||
unit_headers,
|
unit_headers,
|
||||||
);
|
);
|
||||||
@@ -804,7 +801,7 @@ pub fn test_config(b: *std.Build) ![]u8 {
|
|||||||
\\M.test_source_path = "{[src_path]s}"
|
\\M.test_source_path = "{[src_path]s}"
|
||||||
\\M.test_lua_prg = ""
|
\\M.test_lua_prg = ""
|
||||||
\\M.test_luajit_prg = ""
|
\\M.test_luajit_prg = ""
|
||||||
\\ -- include path passed on the cmdline, see test/lua_runner.lua
|
\\ -- include path passed on the cmdline, see test/runner.lua
|
||||||
\\M.include_paths = _G.c_include_path or {{}}
|
\\M.include_paths = _G.c_include_path or {{}}
|
||||||
\\
|
\\
|
||||||
\\return M
|
\\return M
|
||||||
|
|||||||
@@ -41,10 +41,6 @@
|
|||||||
.hash = "libiconv-1.18.0-p9sJwWnqAACzVYeWgXB5r5lOQ74XwTPlptixV0JPRO28",
|
.hash = "libiconv-1.18.0-p9sJwWnqAACzVYeWgXB5r5lOQ74XwTPlptixV0JPRO28",
|
||||||
.lazy = true,
|
.lazy = true,
|
||||||
},
|
},
|
||||||
.lua_dev_deps = .{
|
|
||||||
.url = "https://github.com/neovim/deps/raw/06ef2b58b0876f8de1a3f5a710473dcd7afff251/opt/lua-dev-deps.tar.gz",
|
|
||||||
.hash = "N-V-__8AAGevEQCHAkCozca5AIdN9DFc3Luf3g3r2AcbyOrm",
|
|
||||||
},
|
|
||||||
.treesitter_c = .{
|
.treesitter_c = .{
|
||||||
.url = "git+https://github.com/tree-sitter/tree-sitter-c?ref=v0.24.1#7fa1be1b694b6e763686793d97da01f36a0e5c12",
|
.url = "git+https://github.com/tree-sitter/tree-sitter-c?ref=v0.24.1#7fa1be1b694b6e763686793d97da01f36a0e5c12",
|
||||||
.hash = "N-V-__8AANxPSABzw3WBTSH_YkwaGAfrK6PBqAMqQedkDDim",
|
.hash = "N-V-__8AANxPSABzw3WBTSH_YkwaGAfrK6PBqAMqQedkDDim",
|
||||||
|
|||||||
@@ -51,6 +51,3 @@ WASMTIME_SHA256 c4a3c596a07c02ba6adce503154a2095fd98037a1e50d56add9773f0269ec9b7
|
|||||||
|
|
||||||
UNCRUSTIFY_URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.82.0.tar.gz
|
UNCRUSTIFY_URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.82.0.tar.gz
|
||||||
UNCRUSTIFY_SHA256 e05f8d5ee36aaa1acfa032fe97546b7be46b1f4620e7c38037f8a42e25fe676f
|
UNCRUSTIFY_SHA256 e05f8d5ee36aaa1acfa032fe97546b7be46b1f4620e7c38037f8a42e25fe676f
|
||||||
# This is where we get busted, luassert, ...
|
|
||||||
LUA_DEV_DEPS_URL https://github.com/neovim/deps/raw/06ef2b58b0876f8de1a3f5a710473dcd7afff251/opt/lua-dev-deps.tar.gz
|
|
||||||
LUA_DEV_DEPS_SHA256 49f8399e453103064a23c65534f266f3067cda716b6502f016bfafeed5799354
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr")
|
set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr")
|
||||||
set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin")
|
set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin")
|
||||||
set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib")
|
set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib")
|
||||||
set(DEPS_SHARE_DIR "${DEPS_INSTALL_DIR}/share/lua/5.1")
|
|
||||||
|
|
||||||
set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build")
|
set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build")
|
||||||
set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads")
|
set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads")
|
||||||
|
|||||||
@@ -60,18 +60,18 @@ if(IS_ABSOLUTE ${TEST_PATH})
|
|||||||
file(RELATIVE_PATH TEST_PATH "${ROOT_DIR}" "${TEST_PATH}")
|
file(RELATIVE_PATH TEST_PATH "${ROOT_DIR}" "${TEST_PATH}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
separate_arguments(BUSTED_ARGS NATIVE_COMMAND $ENV{BUSTED_ARGS})
|
separate_arguments(TEST_ARGS NATIVE_COMMAND $ENV{TEST_ARGS})
|
||||||
|
|
||||||
if(DEFINED ENV{TEST_TAG} AND NOT "$ENV{TEST_TAG}" STREQUAL "")
|
if(DEFINED ENV{TEST_TAG} AND NOT "$ENV{TEST_TAG}" STREQUAL "")
|
||||||
list(APPEND BUSTED_ARGS --tags $ENV{TEST_TAG})
|
list(APPEND TEST_ARGS --tags $ENV{TEST_TAG})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(DEFINED ENV{TEST_FILTER} AND NOT "$ENV{TEST_FILTER}" STREQUAL "")
|
if(DEFINED ENV{TEST_FILTER} AND NOT "$ENV{TEST_FILTER}" STREQUAL "")
|
||||||
list(APPEND BUSTED_ARGS --filter $ENV{TEST_FILTER})
|
list(APPEND TEST_ARGS --filter $ENV{TEST_FILTER})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(DEFINED ENV{TEST_FILTER_OUT} AND NOT "$ENV{TEST_FILTER_OUT}" STREQUAL "")
|
if(DEFINED ENV{TEST_FILTER_OUT} AND NOT "$ENV{TEST_FILTER_OUT}" STREQUAL "")
|
||||||
list(APPEND BUSTED_ARGS --filter-out $ENV{TEST_FILTER_OUT})
|
list(APPEND TEST_ARGS --filter-out $ENV{TEST_FILTER_OUT})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# TMPDIR: for testutil.tmpname() and Nvim tempname().
|
# TMPDIR: for testutil.tmpname() and Nvim tempname().
|
||||||
@@ -95,14 +95,14 @@ endif()
|
|||||||
execute_process(
|
execute_process(
|
||||||
# Note: because of "-ll" (low-level interpreter mode), some modules like
|
# Note: because of "-ll" (low-level interpreter mode), some modules like
|
||||||
# _core/editor.lua are not loaded.
|
# _core/editor.lua are not loaded.
|
||||||
COMMAND ${NVIM_PRG} -ll ${ROOT_DIR}/test/lua_runner.lua ${DEPS_INSTALL_DIR}/share/lua/5.1/ busted -v -o test.busted.outputHandlers.nvim
|
COMMAND ${NVIM_PRG} -ll ${ROOT_DIR}/test/runner.lua -v
|
||||||
-Xoutput "{\"test_path\": \"${TEST_PATH}\", \"summary_file\": \"${TEST_SUMMARY_FILE}\"}"
|
--summary-file=${TEST_SUMMARY_FILE}
|
||||||
--lazy --helper=${TEST_DIR}/${TEST_TYPE}/preload.lua
|
--helper=${TEST_DIR}/${TEST_TYPE}/preload.lua
|
||||||
--lpath=${BUILD_DIR}/?.lua
|
--lpath=${BUILD_DIR}/?.lua
|
||||||
--lpath=${ROOT_DIR}/src/?.lua
|
--lpath=${ROOT_DIR}/src/?.lua
|
||||||
--lpath=${ROOT_DIR}/runtime/lua/?.lua
|
--lpath=${ROOT_DIR}/runtime/lua/?.lua
|
||||||
--lpath=?.lua
|
--lpath=?.lua
|
||||||
${BUSTED_ARGS}
|
${TEST_ARGS}
|
||||||
${TEST_PATH}
|
${TEST_PATH}
|
||||||
TIMEOUT $ENV{TEST_TIMEOUT}
|
TIMEOUT $ENV{TEST_TIMEOUT}
|
||||||
WORKING_DIRECTORY ${TEST_XDG_PREFIX}
|
WORKING_DIRECTORY ${TEST_XDG_PREFIX}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
# Uncomment these entries to instead use system-wide installations of
|
# Uncomment these entries to instead use system-wide installations of
|
||||||
# them.
|
# them.
|
||||||
#
|
#
|
||||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_BUSTED=OFF
|
|
||||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_GETTEXT=OFF
|
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_GETTEXT=OFF
|
||||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBICONV=OFF
|
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBICONV=OFF
|
||||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBUV=OFF
|
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBUV=OFF
|
||||||
|
|||||||
@@ -37,12 +37,25 @@ You can learn [Lua concepts 15 minutes](https://learnxinyminutes.com/docs/lua/),
|
|||||||
see also |lua-guide|. Use any existing test as a template to start writing new
|
see also |lua-guide|. Use any existing test as a template to start writing new
|
||||||
tests, or see |dev-quickstart|.
|
tests, or see |dev-quickstart|.
|
||||||
|
|
||||||
Tests are run by the `/cmake/RunTests.cmake` script using `busted` (a Lua test-runner).
|
Tests are run by the `/cmake/RunTests.cmake` script using the repo-local Lua
|
||||||
|
test harness in `/test/harness.lua`.
|
||||||
For some failures, `./build/nvim.log` (or `$NVIM_LOG_FILE`) may provide insight.
|
For some failures, `./build/nvim.log` (or `$NVIM_LOG_FILE`) may provide insight.
|
||||||
|
|
||||||
Depending on the presence of binaries (e.g., `xclip`) some tests will be
|
Depending on the presence of binaries (e.g., `xclip`) some tests will be
|
||||||
skipped.
|
skipped.
|
||||||
|
|
||||||
|
Harness isolation *dev-test-harness-isolation*
|
||||||
|
|
||||||
|
The Lua harness runs all selected spec files in one low-level `nvim -ll`
|
||||||
|
process, then restores a baseline before each file. For each suite iteration,
|
||||||
|
the file boundary baseline is captured after `--helper` is loaded, so
|
||||||
|
helper-provided modules and defaults persist across files.
|
||||||
|
Helper files are preload-only: they may require modules, set defaults, and
|
||||||
|
register suite-end cleanup, but they do not define tests or hooks.
|
||||||
|
|
||||||
|
The harness restores `_G`, `package.loaded`, `package.preload`, `arg`, and the
|
||||||
|
process environment with the same shallow file boundary model that busted uses.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Test Layout
|
Test Layout
|
||||||
|
|
||||||
@@ -56,7 +69,8 @@ Test Layout
|
|||||||
- `/test/includes` : include-files for use by luajit `ffi.cdef` C definitions
|
- `/test/includes` : include-files for use by luajit `ffi.cdef` C definitions
|
||||||
parser: normally used to make macros not accessible via this mechanism
|
parser: normally used to make macros not accessible via this mechanism
|
||||||
accessible the other way.
|
accessible the other way.
|
||||||
- `/test/*/preload.lua` : modules preloaded by busted `--helper` option
|
- `/test/*/preload.lua` : modules and defaults preloaded by the harness
|
||||||
|
`--helper` option
|
||||||
- `/test/**/testutil.lua` : common utility functions in the context of the test
|
- `/test/**/testutil.lua` : common utility functions in the context of the test
|
||||||
runner
|
runner
|
||||||
- `/test/**/testnvim.lua` : common utility functions in the context of the
|
- `/test/**/testnvim.lua` : common utility functions in the context of the
|
||||||
@@ -145,9 +159,7 @@ need to install and configure:
|
|||||||
2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode)
|
2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode)
|
||||||
3. [nlua](https://github.com/mfussenegger/nlua)
|
3. [nlua](https://github.com/mfussenegger/nlua)
|
||||||
4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`)
|
4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`)
|
||||||
5. A `nbusted` command in `$PATH`. This command can be a copy of `busted` with
|
5. An `nlua` command in `$PATH`.
|
||||||
`exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the
|
|
||||||
path to your `nlua`)
|
|
||||||
|
|
||||||
|
|
||||||
The setup roughly looks like this: >
|
The setup roughly looks like this: >
|
||||||
@@ -163,7 +175,7 @@ The setup roughly looks like this: >
|
|||||||
│ │
|
│ │
|
||||||
▼ │
|
▼ │
|
||||||
┌─────────┐ │
|
┌─────────┐ │
|
||||||
│ nbusted │ │
|
│ nlua │ │
|
||||||
└─────────┘ │
|
└─────────┘ │
|
||||||
│ │
|
│ │
|
||||||
▼ │
|
▼ │
|
||||||
@@ -206,11 +218,11 @@ With these installed you can use a configuration like this: >
|
|||||||
request = "launch",
|
request = "launch",
|
||||||
cwd = "${workspaceFolder}",
|
cwd = "${workspaceFolder}",
|
||||||
program = {
|
program = {
|
||||||
command = "nbusted",
|
command = "nlua",
|
||||||
},
|
},
|
||||||
args = {
|
args = {
|
||||||
"--ignore-lua",
|
"-ll",
|
||||||
"--lazy",
|
"test/runner.lua",
|
||||||
"--helper=test/functional/preload.lua",
|
"--helper=test/functional/preload.lua",
|
||||||
"--lpath=build/?.lua",
|
"--lpath=build/?.lua",
|
||||||
"--lpath=?.lua",
|
"--lpath=?.lua",
|
||||||
@@ -274,7 +286,7 @@ Limitations:
|
|||||||
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
|
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
|
||||||
require("lldebugger").start()
|
require("lldebugger").start()
|
||||||
end
|
end
|
||||||
< This is a [local-lua-debugger limitation](https://github.com/tomblind/local-lua-debugger-vscode?tab=readme-ov-file#busted)
|
< This is a local-lua-debugger limitation.
|
||||||
- You cannot step into code of files which get baked into the nvim binary
|
- You cannot step into code of files which get baked into the nvim binary
|
||||||
(such as `_core/*.lua` and `inspect.lua`).
|
(such as `_core/*.lua` and `inspect.lua`).
|
||||||
|
|
||||||
@@ -314,10 +326,10 @@ or >
|
|||||||
cmake -E env "TEST_FILE=test/functional/foo.lua" cmake --build build --target functionaltest
|
cmake -E env "TEST_FILE=test/functional/foo.lua" cmake --build build --target functionaltest
|
||||||
|
|
||||||
To _repeat_ a test: >
|
To _repeat_ a test: >
|
||||||
BUSTED_ARGS="--repeat=100 --no-keep-going" TEST_FILE=test/functional/foo_spec.lua make functionaltest
|
TEST_ARGS="--repeat=100 --no-keep-going" TEST_FILE=test/functional/foo_spec.lua make functionaltest
|
||||||
|
|
||||||
or >
|
or >
|
||||||
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
|
cmake -E env "TEST_FILE=test/functional/foo_spec.lua" cmake -E env TEST_ARGS="--repeat=100 --no-keep-going" cmake --build build --target functionaltest
|
||||||
|
|
||||||
FILTER BY TAG
|
FILTER BY TAG
|
||||||
|
|
||||||
@@ -430,7 +442,7 @@ Test behaviour is affected by environment variables. Currently supported
|
|||||||
treated as Integer; when defined, treated as String; when defined, treated as
|
treated as Integer; when defined, treated as String; when defined, treated as
|
||||||
Number; !must be defined to function properly):
|
Number; !must be defined to function properly):
|
||||||
|
|
||||||
- `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`.
|
- `TEST_ARGS` (F) (U): arguments forwarded to the local test harness.
|
||||||
|
|
||||||
- `CC` (U) (S): specifies which C compiler to use to preprocess files.
|
- `CC` (U) (S): specifies which C compiler to use to preprocess files.
|
||||||
Currently only compilers with gcc-compatible arguments are supported.
|
Currently only compilers with gcc-compatible arguments are supported.
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ Gdb *dev-tools-gdb*
|
|||||||
|
|
||||||
USING GDB TO STEP THROUGH FUNCTIONAL TESTS
|
USING GDB TO STEP THROUGH FUNCTIONAL TESTS
|
||||||
|
|
||||||
Use `TEST_TAG` to run tests matching busted tags (of the form `#foo` e.g.
|
Use `TEST_TAG` to run tests matching `#foo` tags (e.g.
|
||||||
`it("test #foo ...", ...)`):
|
`it("test #foo ...", ...)`):
|
||||||
>bash
|
>bash
|
||||||
GDB=1 TEST_TAG=foo make functionaltest
|
GDB=1 TEST_TAG=foo make functionaltest
|
||||||
@@ -403,7 +403,7 @@ Then, in another terminal:
|
|||||||
USING LLDB TO STEP THROUGH UNIT TESTS
|
USING LLDB TO STEP THROUGH UNIT TESTS
|
||||||
|
|
||||||
>
|
>
|
||||||
lldb .deps/usr/bin/luajit -- .deps/usr/bin/busted --lpath="./build/?.lua" test/unit/
|
lldb build/bin/nvim -- -ll test/runner.lua --lpath=./build/?.lua test/unit/
|
||||||
<
|
<
|
||||||
USING GDB
|
USING GDB
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,31 @@ function vim.deepcopy(orig, noref)
|
|||||||
return deepcopy(orig, not noref and {} or nil)
|
return deepcopy(orig, not noref and {} or nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Returns a shallow copy of `orig`.
|
||||||
|
---
|
||||||
|
--- Non-table values are returned as-is. Table keys and values are copied by
|
||||||
|
--- reference, and the original metatable is preserved. Use |vim.deepcopy()|
|
||||||
|
--- for a recursive copy.
|
||||||
|
---
|
||||||
|
--- @nodoc
|
||||||
|
--- @generic T
|
||||||
|
--- @param orig T
|
||||||
|
--- @return T
|
||||||
|
function vim._copy(orig)
|
||||||
|
if type(orig) ~= 'table' then
|
||||||
|
return orig
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @cast orig table<any,any>
|
||||||
|
|
||||||
|
local copy = {} --- @type table<any,any>
|
||||||
|
for k, v in pairs(orig) do
|
||||||
|
copy[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(copy, getmetatable(orig))
|
||||||
|
end
|
||||||
|
|
||||||
--- @class vim.gsplit.Opts
|
--- @class vim.gsplit.Opts
|
||||||
--- @inlinedoc
|
--- @inlinedoc
|
||||||
---
|
---
|
||||||
@@ -678,6 +703,8 @@ local function deep_equal(left, right, seen)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@cast left table<any, any>
|
||||||
|
---@cast right table<any, any>
|
||||||
seen = seen or {}
|
seen = seen or {}
|
||||||
local seen_left = seen[left]
|
local seen_left = seen[left]
|
||||||
if seen_left and seen_left[right] ~= nil then
|
if seen_left and seen_left[right] ~= nil then
|
||||||
|
|||||||
25
test/.emmyrc.json
Normal file
25
test/.emmyrc.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
|
||||||
|
"format": {
|
||||||
|
"externalTool": {
|
||||||
|
"program": "stylua",
|
||||||
|
"args": [
|
||||||
|
"-",
|
||||||
|
"--stdin-filepath",
|
||||||
|
"${file}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"diagnostics": {
|
||||||
|
"disable": [
|
||||||
|
"unnecessary-if"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"codeAction": {
|
||||||
|
"insertSpace": true
|
||||||
|
},
|
||||||
|
"strict": {
|
||||||
|
"typeCall": true,
|
||||||
|
"arrayIndex": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,7 @@
|
|||||||
"workspace": {
|
"workspace": {
|
||||||
"library": [
|
"library": [
|
||||||
"../runtime/lua",
|
"../runtime/lua",
|
||||||
"../src",
|
"../src"
|
||||||
"../build/usr/share/lua/5.1",
|
|
||||||
"../build",
|
|
||||||
"${3rd}/busted/library",
|
|
||||||
"${3rd}/luassert/library",
|
|
||||||
"${3rd}/luv/library"
|
|
||||||
],
|
],
|
||||||
"checkThirdParty": "Disable"
|
"checkThirdParty": "Disable"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ set(TEST_OPTIONS
|
|||||||
-D BUILD_DIR=${CMAKE_BINARY_DIR}
|
-D BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||||
-D CIRRUS_CI=$ENV{CIRRUS_CI}
|
-D CIRRUS_CI=$ENV{CIRRUS_CI}
|
||||||
-D CI_BUILD=${CI_BUILD}
|
-D CI_BUILD=${CI_BUILD}
|
||||||
-D DEPS_INSTALL_DIR=${DEPS_INSTALL_DIR}
|
|
||||||
-D NVIM_PRG=$<TARGET_FILE:nvim_bin>
|
-D NVIM_PRG=$<TARGET_FILE:nvim_bin>
|
||||||
-D TEST_DIR=${TEST_DIR}
|
-D TEST_DIR=${TEST_DIR}
|
||||||
-D ROOT_DIR=${PROJECT_SOURCE_DIR})
|
-D ROOT_DIR=${PROJECT_SOURCE_DIR})
|
||||||
@@ -20,7 +19,7 @@ if(LUA_HAS_FFI)
|
|||||||
${TEST_OPTIONS}
|
${TEST_OPTIONS}
|
||||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||||
USES_TERMINAL)
|
USES_TERMINAL)
|
||||||
add_dependencies(unittest lua_dev_deps nvim)
|
add_dependencies(unittest nvim)
|
||||||
else()
|
else()
|
||||||
message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}")
|
message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}")
|
||||||
endif()
|
endif()
|
||||||
@@ -36,7 +35,7 @@ add_custom_target(benchmark
|
|||||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||||
DEPENDS tty-test
|
DEPENDS tty-test
|
||||||
USES_TERMINAL)
|
USES_TERMINAL)
|
||||||
add_dependencies(benchmark lua_dev_deps nvim)
|
add_dependencies(benchmark nvim)
|
||||||
|
|
||||||
add_custom_target(functionaltest
|
add_custom_target(functionaltest
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
@@ -45,7 +44,7 @@ add_custom_target(functionaltest
|
|||||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||||
DEPENDS printenv-test printargs-test shell-test pwsh-test streams-test tty-test
|
DEPENDS printenv-test printargs-test shell-test pwsh-test streams-test tty-test
|
||||||
USES_TERMINAL)
|
USES_TERMINAL)
|
||||||
add_dependencies(functionaltest lua_dev_deps nvim)
|
add_dependencies(functionaltest nvim)
|
||||||
|
|
||||||
# Create multiple targets for groups of functional tests to enable parallel testing.
|
# Create multiple targets for groups of functional tests to enable parallel testing.
|
||||||
set(group_targets "")
|
set(group_targets "")
|
||||||
@@ -66,7 +65,7 @@ foreach(test_file ${test_files})
|
|||||||
${TEST_OPTIONS}
|
${TEST_OPTIONS}
|
||||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||||
DEPENDS printenv-test printargs-test shell-test pwsh-test streams-test tty-test)
|
DEPENDS printenv-test printargs-test shell-test pwsh-test streams-test tty-test)
|
||||||
add_dependencies(${group_target} lua_dev_deps nvim)
|
add_dependencies(${group_target} nvim)
|
||||||
list(APPEND group_targets ${group_target})
|
list(APPEND group_targets ${group_target})
|
||||||
list(APPEND summary_files ${summary_file})
|
list(APPEND summary_files ${summary_file})
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
--- @meta
|
--- @meta
|
||||||
|
|
||||||
do -- Mark block as optional
|
--- @param name string
|
||||||
---Mark a test as placeholder.
|
--- @param fn? fun()
|
||||||
---
|
function it(name, fn) end
|
||||||
---This will not fail or pass, it will simply be marked as "pending".
|
|
||||||
---@param name string
|
--- @param name string
|
||||||
---@param block? fun()
|
--- @param fn fun()
|
||||||
function pending(name, block) end
|
function describe(name, fn) end
|
||||||
end
|
|
||||||
|
--- @param name? string
|
||||||
|
--- @param block? fun()|string
|
||||||
|
function pending(name, block) end
|
||||||
|
|
||||||
|
--- @param fn fun()
|
||||||
|
function setup(fn) end
|
||||||
|
|
||||||
|
--- @param fn fun()
|
||||||
|
function before_each(fn) end
|
||||||
|
|
||||||
|
--- @param fn fun()
|
||||||
|
function after_each(fn) end
|
||||||
|
|
||||||
|
--- @param fn fun()
|
||||||
|
function teardown(fn) end
|
||||||
|
|||||||
102
test/assert.lua
Normal file
102
test/assert.lua
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
--- @class test.assert
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local FORMAT_DEPTH = 100
|
||||||
|
|
||||||
|
--- @param value any
|
||||||
|
--- @return string
|
||||||
|
local function format_value(value)
|
||||||
|
if type(value) == 'string' then
|
||||||
|
return string.format('%q', value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, inspected = pcall(vim.inspect, value, { depth = FORMAT_DEPTH })
|
||||||
|
if ok then
|
||||||
|
return inspected
|
||||||
|
end
|
||||||
|
|
||||||
|
return tostring(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param condition boolean
|
||||||
|
--- @param value any
|
||||||
|
--- @param context any
|
||||||
|
--- @param fallback string
|
||||||
|
local function assert_value(condition, value, context, fallback)
|
||||||
|
if not condition then
|
||||||
|
local message = context ~= nil and tostring(context) or fallback
|
||||||
|
error(message, 0)
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param expected any
|
||||||
|
--- @param actual any
|
||||||
|
--- @param comparator string
|
||||||
|
--- @return string
|
||||||
|
local function comparison_message(expected, actual, comparator)
|
||||||
|
return ('Expected values to be %s.\nExpected:\n%s\nActual:\n%s'):format(
|
||||||
|
comparator,
|
||||||
|
format_value(expected),
|
||||||
|
format_value(actual)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param expected any
|
||||||
|
--- @param actual any
|
||||||
|
--- @param context? any
|
||||||
|
--- @return any
|
||||||
|
function M.eq(expected, actual, context)
|
||||||
|
return assert_value(
|
||||||
|
vim.deep_equal(expected, actual),
|
||||||
|
actual,
|
||||||
|
context,
|
||||||
|
comparison_message(expected, actual, 'equal')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param expected any
|
||||||
|
--- @param actual any
|
||||||
|
--- @param context? any
|
||||||
|
--- @return any
|
||||||
|
function M.neq(expected, actual, context)
|
||||||
|
return assert_value(
|
||||||
|
not vim.deep_equal(expected, actual),
|
||||||
|
actual,
|
||||||
|
context,
|
||||||
|
('Expected values to differ.\nValue:\n%s'):format(format_value(actual))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param value any
|
||||||
|
--- @param context? any
|
||||||
|
--- @return any
|
||||||
|
function M.is_true(value, context)
|
||||||
|
return M.eq(true, value, context)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param value any
|
||||||
|
--- @param context? any
|
||||||
|
--- @return any
|
||||||
|
function M.is_false(value, context)
|
||||||
|
return M.eq(false, value, context)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO(lewis6991): remove these aliases
|
||||||
|
M.True = M.is_true
|
||||||
|
M.False = M.is_false
|
||||||
|
M.equals = M.eq
|
||||||
|
M.Equal = M.eq
|
||||||
|
|
||||||
|
return setmetatable(M, {
|
||||||
|
--- @param condition any
|
||||||
|
--- @param message? string
|
||||||
|
--- @param level? integer
|
||||||
|
__call = function(_, condition, message, level, ...)
|
||||||
|
if condition then
|
||||||
|
return condition, message, level, ...
|
||||||
|
end
|
||||||
|
|
||||||
|
error(message or 'assertion failed!', (type(level) == 'number' and level or 1) + 1)
|
||||||
|
end,
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
-- Modules loaded here will not be cleared and reloaded by Busted.
|
-- Modules loaded here will not be cleared and reloaded by the local harness.
|
||||||
-- Busted started doing this to help provide more isolation. See issue #62
|
-- Keeping these preloaded preserves cross-file setup while still resetting
|
||||||
-- for more information about this.
|
-- non-helper modules between files.
|
||||||
require('test.functional.testnvim')()
|
require('test.functional.testnvim')()
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
-- Extends the upstream TAP handler, to display the log with suiteEnd.
|
|
||||||
|
|
||||||
local t_global = require('test.testutil')
|
|
||||||
|
|
||||||
return function(options)
|
|
||||||
local busted = require 'busted'
|
|
||||||
local handler = require 'busted.outputHandlers.TAP'(options)
|
|
||||||
|
|
||||||
local suiteEnd = function()
|
|
||||||
io.write(t_global.read_nvim_log(nil, true))
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
busted.subscribe({ 'suite', 'end' }, suiteEnd)
|
|
||||||
|
|
||||||
return handler
|
|
||||||
end
|
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
local pretty = require 'pl.pretty'
|
|
||||||
local t_global = require('test.testutil')
|
|
||||||
|
|
||||||
local colors = setmetatable({}, {
|
|
||||||
__index = function()
|
|
||||||
return function(s)
|
|
||||||
return s == nil and '' or tostring(s)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
local enable_colors = true
|
|
||||||
if os.getenv 'TEST_COLORS' then
|
|
||||||
local test_colors = os.getenv('TEST_COLORS'):lower()
|
|
||||||
local disable_colors = test_colors == 'false'
|
|
||||||
or test_colors == '0'
|
|
||||||
or test_colors == 'no'
|
|
||||||
or test_colors == 'off'
|
|
||||||
enable_colors = not disable_colors
|
|
||||||
end
|
|
||||||
if enable_colors then
|
|
||||||
colors = require 'term.colors'
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(options)
|
|
||||||
local busted = require 'busted'
|
|
||||||
local handler = require 'busted.outputHandlers.base'()
|
|
||||||
local args = options.arguments
|
|
||||||
args = vim.json.decode(#args > 0 and table.concat(args, ',') or '{}')
|
|
||||||
|
|
||||||
local c = {
|
|
||||||
succ = function(s)
|
|
||||||
return colors.bright(colors.green(s))
|
|
||||||
end,
|
|
||||||
skip = function(s)
|
|
||||||
return colors.bright(colors.yellow(s))
|
|
||||||
end,
|
|
||||||
fail = function(s)
|
|
||||||
return colors.bright(colors.magenta(s))
|
|
||||||
end,
|
|
||||||
errr = function(s)
|
|
||||||
return colors.bright(colors.red(s))
|
|
||||||
end,
|
|
||||||
test = tostring,
|
|
||||||
file = colors.cyan,
|
|
||||||
time = colors.dim,
|
|
||||||
note = colors.yellow,
|
|
||||||
sect = function(s)
|
|
||||||
return colors.green(colors.dim(s))
|
|
||||||
end,
|
|
||||||
nmbr = colors.bright,
|
|
||||||
}
|
|
||||||
|
|
||||||
local repeatSuiteString = '\nRepeating all tests (run %d of %d) . . .\n\n'
|
|
||||||
local randomizeString = c.note('Note: Randomizing test order with a seed of %d.\n')
|
|
||||||
local globalSetup = c.sect('--------') .. ' Global test environment setup.\n'
|
|
||||||
local fileStartString = c.sect('--------') .. ' Running tests from ' .. c.file('%s') .. '\n'
|
|
||||||
local runString = c.sect('RUN ') .. ' ' .. c.test('%s') .. ': '
|
|
||||||
local successString = c.succ('OK') .. '\n'
|
|
||||||
local skippedString = c.skip('SKIP') .. '\n'
|
|
||||||
local failureString = c.fail('FAIL') .. '\n'
|
|
||||||
local errorString = c.errr('ERR') .. '\n'
|
|
||||||
local fileEndString = c.sect('--------')
|
|
||||||
.. ' '
|
|
||||||
.. c.nmbr('%d')
|
|
||||||
.. ' %s from '
|
|
||||||
.. c.file('%s')
|
|
||||||
.. ' '
|
|
||||||
.. c.time('(%.2f ms total)')
|
|
||||||
.. '\n\n'
|
|
||||||
local globalTeardown = c.sect('--------') .. ' Global test environment teardown.\n'
|
|
||||||
local suiteEndString = c.sect('========')
|
|
||||||
.. ' '
|
|
||||||
.. c.nmbr('%d')
|
|
||||||
.. ' %s from '
|
|
||||||
.. c.nmbr('%d')
|
|
||||||
.. ' test %s ran. '
|
|
||||||
.. c.time('(%.2f ms total)')
|
|
||||||
.. '\n'
|
|
||||||
local successStatus = c.succ('PASSED ') .. ' ' .. c.nmbr('%d') .. ' %s.\n'
|
|
||||||
local timeString = c.time('%.2f ms')
|
|
||||||
|
|
||||||
local summaryStrings = {
|
|
||||||
skipped = {
|
|
||||||
header = c.skip('SKIPPED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
|
|
||||||
test = c.skip('SKIPPED ') .. ' %s\n',
|
|
||||||
footer = ' ' .. c.nmbr('%d') .. ' SKIPPED %s\n',
|
|
||||||
},
|
|
||||||
|
|
||||||
failure = {
|
|
||||||
header = c.fail('FAILED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
|
|
||||||
test = c.fail('FAILED ') .. ' %s\n',
|
|
||||||
footer = ' ' .. c.nmbr('%d') .. ' FAILED %s\n',
|
|
||||||
},
|
|
||||||
|
|
||||||
error = {
|
|
||||||
header = c.errr('ERROR ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n',
|
|
||||||
test = c.errr('ERROR ') .. ' %s\n',
|
|
||||||
footer = ' ' .. c.nmbr('%d') .. ' %s\n',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
local fileCount = 0
|
|
||||||
local fileTestCount = 0
|
|
||||||
local testCount = 0
|
|
||||||
local successCount = 0
|
|
||||||
local skippedCount = 0
|
|
||||||
local failureCount = 0
|
|
||||||
local errorCount = 0
|
|
||||||
|
|
||||||
local naCheck = function(pending)
|
|
||||||
if vim.list_contains(vim.split(pending.name, '[ :]'), 'N/A') then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if type(pending.message) ~= 'string' then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if vim.list_contains(vim.split(pending.message, '[ :]'), 'N/A') then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local pendingDescription = function(pending)
|
|
||||||
local string = ''
|
|
||||||
|
|
||||||
if type(pending.message) == 'string' then
|
|
||||||
string = string .. pending.message .. '\n'
|
|
||||||
elseif pending.message ~= nil then
|
|
||||||
string = string .. pretty.write(pending.message) .. '\n'
|
|
||||||
end
|
|
||||||
|
|
||||||
return string
|
|
||||||
end
|
|
||||||
|
|
||||||
local failureDescription = function(failure)
|
|
||||||
local string = failure.randomseed and ('Random seed: ' .. failure.randomseed .. '\n') or ''
|
|
||||||
if type(failure.message) == 'string' then
|
|
||||||
string = string .. failure.message
|
|
||||||
elseif failure.message == nil then
|
|
||||||
string = string .. 'Nil error'
|
|
||||||
else
|
|
||||||
string = string .. pretty.write(failure.message)
|
|
||||||
end
|
|
||||||
|
|
||||||
string = string .. '\n'
|
|
||||||
|
|
||||||
if options.verbose and failure.trace and failure.trace.traceback then
|
|
||||||
string = string .. failure.trace.traceback .. '\n'
|
|
||||||
end
|
|
||||||
|
|
||||||
return string
|
|
||||||
end
|
|
||||||
|
|
||||||
local getFileLine = function(element)
|
|
||||||
local fileline = ''
|
|
||||||
if element.trace or element.trace.short_src then
|
|
||||||
local fname = vim.fs.normalize(element.trace.short_src)
|
|
||||||
fileline = colors.cyan(fname) .. ' @ ' .. colors.cyan(element.trace.currentline) .. ': '
|
|
||||||
end
|
|
||||||
return fileline
|
|
||||||
end
|
|
||||||
|
|
||||||
local getTestList = function(status, count, list, getDescription)
|
|
||||||
local string = ''
|
|
||||||
local header = summaryStrings[status].header
|
|
||||||
if count > 0 and header then
|
|
||||||
local tests = (count == 1 and 'test' or 'tests')
|
|
||||||
local errors = (count == 1 and 'error' or 'errors')
|
|
||||||
string = header:format(count, status == 'error' and errors or tests)
|
|
||||||
|
|
||||||
local testString = summaryStrings[status].test
|
|
||||||
if testString then
|
|
||||||
local naCount = 0
|
|
||||||
for _, t in ipairs(list) do
|
|
||||||
if status == 'skipped' and naCheck(t) then
|
|
||||||
naCount = naCount + 1
|
|
||||||
else
|
|
||||||
local fullname = getFileLine(t.element) .. colors.bright(t.name)
|
|
||||||
string = string .. testString:format(fullname)
|
|
||||||
string = string .. getDescription(t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if naCount > 0 then
|
|
||||||
string = string
|
|
||||||
.. colors.bright(
|
|
||||||
('%d N/A %s not shown\n'):format(naCount, naCount == 1 and 'test' or 'tests')
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return string
|
|
||||||
end
|
|
||||||
|
|
||||||
local getSummary = function(status, count)
|
|
||||||
local string = ''
|
|
||||||
local footer = summaryStrings[status].footer
|
|
||||||
if count > 0 and footer then
|
|
||||||
local tests = (count == 1 and 'TEST' or 'TESTS')
|
|
||||||
local errors = (count == 1 and 'ERROR' or 'ERRORS')
|
|
||||||
string = footer:format(count, status == 'error' and errors or tests)
|
|
||||||
end
|
|
||||||
return string
|
|
||||||
end
|
|
||||||
|
|
||||||
local getSummaryString = function()
|
|
||||||
local tests = (successCount == 1 and 'test' or 'tests')
|
|
||||||
local string = successStatus:format(successCount, tests)
|
|
||||||
|
|
||||||
string = string .. getTestList('skipped', skippedCount, handler.pendings, pendingDescription)
|
|
||||||
string = string .. getTestList('failure', failureCount, handler.failures, failureDescription)
|
|
||||||
string = string .. getTestList('error', errorCount, handler.errors, failureDescription)
|
|
||||||
|
|
||||||
string = string .. ((skippedCount + failureCount + errorCount) > 0 and '\n' or '')
|
|
||||||
string = string .. getSummary('skipped', skippedCount)
|
|
||||||
string = string .. getSummary('failure', failureCount)
|
|
||||||
string = string .. getSummary('error', errorCount)
|
|
||||||
|
|
||||||
return string
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.suiteReset = function()
|
|
||||||
fileCount = 0
|
|
||||||
fileTestCount = 0
|
|
||||||
testCount = 0
|
|
||||||
successCount = 0
|
|
||||||
skippedCount = 0
|
|
||||||
failureCount = 0
|
|
||||||
errorCount = 0
|
|
||||||
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.suiteStart = function(_suite, count, total, randomseed)
|
|
||||||
if total > 1 then
|
|
||||||
io.write(repeatSuiteString:format(count, total))
|
|
||||||
end
|
|
||||||
if randomseed then
|
|
||||||
io.write(randomizeString:format(randomseed))
|
|
||||||
end
|
|
||||||
io.write(globalSetup)
|
|
||||||
io.flush()
|
|
||||||
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getElapsedTime(tbl)
|
|
||||||
if tbl.duration then
|
|
||||||
return tbl.duration * 1000
|
|
||||||
else
|
|
||||||
return tonumber('nan')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.suiteEnd = function(suite, _count, _total)
|
|
||||||
local elapsedTime_ms = getElapsedTime(suite)
|
|
||||||
local tests = (testCount == 1 and 'test' or 'tests')
|
|
||||||
local files = (fileCount == 1 and 'file' or 'files')
|
|
||||||
if type(args.test_path) == 'string' then
|
|
||||||
files = files .. ' of ' .. args.test_path
|
|
||||||
end
|
|
||||||
local sf = type(args.summary_file) == 'string'
|
|
||||||
and args.summary_file ~= '-'
|
|
||||||
and io.open(args.summary_file, 'w')
|
|
||||||
or io.stdout
|
|
||||||
io.write(globalTeardown)
|
|
||||||
io.flush()
|
|
||||||
sf:write('\n')
|
|
||||||
sf:write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms))
|
|
||||||
sf:write(getSummaryString())
|
|
||||||
if failureCount > 0 or errorCount > 0 then
|
|
||||||
sf:write(t_global.read_nvim_log(nil, true))
|
|
||||||
end
|
|
||||||
sf:flush()
|
|
||||||
if sf ~= io.stdout then
|
|
||||||
sf:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.fileStart = function(file)
|
|
||||||
fileTestCount = 0
|
|
||||||
io.write(fileStartString:format(vim.fs.normalize(file.name)))
|
|
||||||
io.flush()
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.fileEnd = function(file)
|
|
||||||
local elapsedTime_ms = getElapsedTime(file)
|
|
||||||
local tests = (fileTestCount == 1 and 'test' or 'tests')
|
|
||||||
fileCount = fileCount + 1
|
|
||||||
io.write(
|
|
||||||
fileEndString:format(fileTestCount, tests, vim.fs.normalize(file.name), elapsedTime_ms)
|
|
||||||
)
|
|
||||||
io.flush()
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.testStart = function(element, _parent)
|
|
||||||
local testid = _G._nvim_test_id or ''
|
|
||||||
local desc = ('%s %s'):format(testid, handler.getFullName(element))
|
|
||||||
io.write(runString:format(desc))
|
|
||||||
io.flush()
|
|
||||||
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write_status(element, string)
|
|
||||||
io.write(timeString:format(getElapsedTime(element)) .. ' ' .. string)
|
|
||||||
io.flush()
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.testEnd = function(element, _parent, status, _debug)
|
|
||||||
local string
|
|
||||||
|
|
||||||
fileTestCount = fileTestCount + 1
|
|
||||||
testCount = testCount + 1
|
|
||||||
if status == 'success' then
|
|
||||||
successCount = successCount + 1
|
|
||||||
string = successString
|
|
||||||
elseif status == 'pending' then
|
|
||||||
skippedCount = skippedCount + 1
|
|
||||||
string = skippedString
|
|
||||||
elseif status == 'failure' then
|
|
||||||
failureCount = failureCount + 1
|
|
||||||
string = failureString .. failureDescription(handler.failures[#handler.failures])
|
|
||||||
elseif status == 'error' then
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
string = errorString .. failureDescription(handler.errors[#handler.errors])
|
|
||||||
else
|
|
||||||
string = 'unexpected test status! (' .. status .. ')'
|
|
||||||
end
|
|
||||||
write_status(element, string)
|
|
||||||
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
handler.error = function(element, _parent, _message, _debug)
|
|
||||||
if element.descriptor ~= 'it' then
|
|
||||||
write_status(element, failureDescription(handler.errors[#handler.errors]))
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, true
|
|
||||||
end
|
|
||||||
|
|
||||||
busted.subscribe({ 'suite', 'reset' }, handler.suiteReset)
|
|
||||||
busted.subscribe({ 'suite', 'start' }, handler.suiteStart)
|
|
||||||
busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
|
|
||||||
busted.subscribe({ 'file', 'start' }, handler.fileStart)
|
|
||||||
busted.subscribe({ 'file', 'end' }, handler.fileEnd)
|
|
||||||
busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending })
|
|
||||||
busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
|
|
||||||
busted.subscribe({ 'failure' }, handler.error)
|
|
||||||
busted.subscribe({ 'error' }, handler.error)
|
|
||||||
|
|
||||||
return handler
|
|
||||||
end
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
local luaassert = require('luassert')
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local SUBTBL = {
|
local SUBTBL = {
|
||||||
@@ -124,7 +122,7 @@ function M.format_luav(v, indent, opts)
|
|||||||
else
|
else
|
||||||
print(type(v))
|
print(type(v))
|
||||||
-- Not implemented yet
|
-- Not implemented yet
|
||||||
luaassert(false)
|
assert(false)
|
||||||
end
|
end
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ describe('autocmd FileType', function()
|
|||||||
command('let g:foo = 0')
|
command('let g:foo = 0')
|
||||||
command('autocmd FileType help let g:foo = g:foo + 1')
|
command('autocmd FileType help let g:foo = g:foo + 1')
|
||||||
command('help help')
|
command('help help')
|
||||||
assert.same(1, eval('g:foo'))
|
assert.eq(1, eval('g:foo'))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
28
test/functional/harness/assert_spec.lua
Normal file
28
test/functional/harness/assert_spec.lua
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
local assert = require('test.assert')
|
||||||
|
|
||||||
|
describe('test.assert', function()
|
||||||
|
it('ignores aliasing differences', function()
|
||||||
|
local shared = {}
|
||||||
|
|
||||||
|
assert.eq({ 1, shared, 1, shared }, { 1, {}, 1, {} })
|
||||||
|
assert.eq({ 1, {}, 1, {} }, { 1, shared, 1, shared })
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('handles cyclic tables', function()
|
||||||
|
local expected = {}
|
||||||
|
local actual = {}
|
||||||
|
|
||||||
|
expected[1] = expected
|
||||||
|
actual[1] = actual
|
||||||
|
|
||||||
|
assert.eq(expected, actual)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('still rejects different structures', function()
|
||||||
|
local expected = {}
|
||||||
|
|
||||||
|
expected[1] = expected
|
||||||
|
|
||||||
|
assert.neq(expected, { {} })
|
||||||
|
end)
|
||||||
|
end)
|
||||||
1382
test/functional/harness/harness_spec.lua
Normal file
1382
test/functional/harness/harness_spec.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ local api = n.api
|
|||||||
local fn = n.fn
|
local fn = n.fn
|
||||||
local clear = n.clear
|
local clear = n.clear
|
||||||
local eq = t.eq
|
local eq = t.eq
|
||||||
local fail = t.fail
|
|
||||||
local exec_lua = n.exec_lua
|
local exec_lua = n.exec_lua
|
||||||
local feed = n.feed
|
local feed = n.feed
|
||||||
local expect_events = t.expect_events
|
local expect_events = t.expect_events
|
||||||
@@ -507,7 +506,7 @@ describe('lua: nvim_buf_attach on_bytes', function()
|
|||||||
for _, event in ipairs(events) do
|
for _, event in ipairs(events) do
|
||||||
for _, elem in ipairs(event) do
|
for _, elem in ipairs(event) do
|
||||||
if type(elem) == 'number' and elem < 0 then
|
if type(elem) == 'number' and elem < 0 then
|
||||||
fail(string.format('Received event has negative values'))
|
error('Received event has negative values')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ describe('vim.net.request', function()
|
|||||||
return result
|
return result
|
||||||
]])
|
]])
|
||||||
|
|
||||||
assert.is_table(headers)
|
t.eq('table', type(headers), 'Expected headers to be a table')
|
||||||
-- httpbingo.org/request returns each header as a list in the returned value
|
-- httpbingo.org/request returns each header as a list in the returned value
|
||||||
t.eq(headers.Authorization[1], 'Bearer test-token', 'Expected Authorization header')
|
t.eq(headers.Authorization[1], 'Bearer test-token', 'Expected Authorization header')
|
||||||
t.eq(headers['X-Custom-Header'][1], 'custom-value', 'Expected X-Custom-Header')
|
t.eq(headers['X-Custom-Header'][1], 'custom-value', 'Expected X-Custom-Header')
|
||||||
|
|||||||
@@ -834,6 +834,26 @@ describe('lua stdlib', function()
|
|||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('vim._copy', function()
|
||||||
|
ok(exec_lua([[
|
||||||
|
local inner = { x = 1 }
|
||||||
|
local mt = { tag = true }
|
||||||
|
local a = setmetatable({ inner = inner }, mt)
|
||||||
|
local b = vim._copy(a)
|
||||||
|
|
||||||
|
local c = vim.empty_dict()
|
||||||
|
c.inner = inner
|
||||||
|
local d = vim._copy(c)
|
||||||
|
|
||||||
|
return b ~= a
|
||||||
|
and b.inner == inner
|
||||||
|
and getmetatable(b) == mt
|
||||||
|
and d ~= c
|
||||||
|
and d.inner == inner
|
||||||
|
and not vim.islist(d)
|
||||||
|
]]))
|
||||||
|
end)
|
||||||
|
|
||||||
it('vim.pesc', function()
|
it('vim.pesc', function()
|
||||||
eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
|
eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]]))
|
||||||
eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
|
eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
|
||||||
@@ -1249,7 +1269,11 @@ describe('lua stdlib', function()
|
|||||||
eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]])
|
eq(true, exec_lua [[ return vim.deep_equal({a={b=1}}, {a={b=1}}) ]])
|
||||||
eq(true, exec_lua [[ return vim.deep_equal({a={b={nil}}}, {a={b={}}}) ]])
|
eq(true, exec_lua [[ return vim.deep_equal({a={b={nil}}}, {a={b={}}}) ]])
|
||||||
eq(true, exec_lua [[ return vim.deep_equal({a=1, [5]=5}, {nil,nil,nil,nil,5,a=1}) ]])
|
eq(true, exec_lua [[ return vim.deep_equal({a=1, [5]=5}, {nil,nil,nil,nil,5,a=1}) ]])
|
||||||
eq(true, exec_lua [[ local shared = {}; return vim.deep_equal({ 1, shared, 1, shared }, { 1, {}, 1, {} }) ]])
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua [[ local shared = {}; return vim.deep_equal({ 1, shared, 1, shared }, { 1, {}, 1, {} }) ]]
|
||||||
|
)
|
||||||
|
-- cyclic table
|
||||||
eq(true, exec_lua [[ local a,b={},{}; a[1]=a; b[1]=b; return vim.deep_equal(a, b) ]])
|
eq(true, exec_lua [[ local a,b={},{}; a[1]=a; b[1]=b; return vim.deep_equal(a, b) ]])
|
||||||
eq(false, exec_lua [[ return vim.deep_equal(1, {nil,nil,nil,nil,5,a=1}) ]])
|
eq(false, exec_lua [[ return vim.deep_equal(1, {nil,nil,nil,nil,5,a=1}) ]])
|
||||||
eq(false, exec_lua [[ return vim.deep_equal(1, 3) ]])
|
eq(false, exec_lua [[ return vim.deep_equal(1, 3) ]])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- Modules loaded here will NOT be cleared and reloaded by Busted.
|
-- Modules loaded here will not be cleared and reloaded by the local harness.
|
||||||
-- Busted started doing this to help provide more isolation. See issue #62
|
-- Keeping these preloaded preserves cross-file setup while still resetting
|
||||||
-- for more information about this.
|
-- non-helper modules between files.
|
||||||
local t = require('test.testutil')
|
local t = require('test.testutil')
|
||||||
require('test.functional.testnvim')()
|
require('test.functional.testnvim')()
|
||||||
require('test.functional.ui.screen')
|
require('test.functional.ui.screen')
|
||||||
|
|||||||
@@ -80,14 +80,14 @@ describe(':terminal', function()
|
|||||||
|
|
||||||
send_osc_with_terminator(BEL)
|
send_osc_with_terminator(BEL)
|
||||||
--- @type string
|
--- @type string
|
||||||
assert.same(
|
assert.eq(
|
||||||
{ sequence = OSC_PREFIX .. '10;?', terminator = BEL },
|
{ sequence = OSC_PREFIX .. '10;?', terminator = BEL },
|
||||||
exec_lua([[return _G.osc10_response]])
|
exec_lua([[return _G.osc10_response]])
|
||||||
)
|
)
|
||||||
|
|
||||||
send_osc_with_terminator(ST)
|
send_osc_with_terminator(ST)
|
||||||
--- @type string
|
--- @type string
|
||||||
assert.same(
|
assert.eq(
|
||||||
{ sequence = OSC_PREFIX .. '10;?', terminator = ST },
|
{ sequence = OSC_PREFIX .. '10;?', terminator = ST },
|
||||||
exec_lua([[return _G.osc10_response]])
|
exec_lua([[return _G.osc10_response]])
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local uv = vim.uv
|
local uv = vim.uv
|
||||||
local t = require('test.testutil')
|
local t = require('test.testutil')
|
||||||
local busted = require('busted')
|
---@type test.harness
|
||||||
|
local harness = require('test.harness')
|
||||||
|
|
||||||
local Session = require('test.client.session')
|
local Session = require('test.client.session')
|
||||||
local uv_stream = require('test.client.uv_stream')
|
local uv_stream = require('test.client.uv_stream')
|
||||||
@@ -528,7 +529,7 @@ function M.new_session(keep, ...)
|
|||||||
return new_session
|
return new_session
|
||||||
end
|
end
|
||||||
|
|
||||||
busted.subscribe({ 'suite', 'end' }, function()
|
harness.on_suite_end(function()
|
||||||
M.check_close(true)
|
M.check_close(true)
|
||||||
local timed_out = false
|
local timed_out = false
|
||||||
local timer = assert(vim.uv.new_timer())
|
local timer = assert(vim.uv.new_timer())
|
||||||
|
|||||||
@@ -47,7 +47,6 @@
|
|||||||
|
|
||||||
local t = require('test.testutil')
|
local t = require('test.testutil')
|
||||||
local n = require('test.functional.testnvim')()
|
local n = require('test.functional.testnvim')()
|
||||||
local busted = require('busted')
|
|
||||||
local uv = vim.uv
|
local uv = vim.uv
|
||||||
|
|
||||||
local deepcopy = vim.deepcopy
|
local deepcopy = vim.deepcopy
|
||||||
@@ -911,7 +910,7 @@ between asynchronous (feed(), nvim_input()) and synchronous API calls.
|
|||||||
if eof then
|
if eof then
|
||||||
err = err .. '\n\n' .. eof[2]
|
err = err .. '\n\n' .. eof[2]
|
||||||
end
|
end
|
||||||
busted.fail(err .. '\n\nSnapshot:\n' .. self:_print_snapshot(), 3)
|
error(err .. '\n\nSnapshot:\n' .. self:_print_snapshot(), 3)
|
||||||
elseif did_warn then
|
elseif did_warn then
|
||||||
if eof then
|
if eof then
|
||||||
print(eof[2])
|
print(eof[2])
|
||||||
@@ -922,9 +921,9 @@ between asynchronous (feed(), nvim_input()) and synchronous API calls.
|
|||||||
end
|
end
|
||||||
|
|
||||||
if flags.intermediate and not intermediate_seen then
|
if flags.intermediate and not intermediate_seen then
|
||||||
busted.fail('Expected intermediate screen state before final screen state', 3)
|
error('Expected intermediate screen state before final screen state', 3)
|
||||||
elseif flags.unchanged and intermediate_seen then
|
elseif flags.unchanged and intermediate_seen then
|
||||||
busted.fail(
|
error(
|
||||||
'Expected screen state to be unchanged.\nIntermediate screen state:\n'
|
'Expected screen state to be unchanged.\nIntermediate screen state:\n'
|
||||||
.. intermediate_state_snapshot,
|
.. intermediate_state_snapshot,
|
||||||
3
|
3
|
||||||
|
|||||||
1694
test/harness.lua
Normal file
1694
test/harness.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,91 +0,0 @@
|
|||||||
local platform = vim.uv.os_uname()
|
|
||||||
local deps_install_dir = table.remove(_G.arg, 1)
|
|
||||||
_G.c_include_path = {}
|
|
||||||
while vim.startswith(_G.arg[1], '-I') do
|
|
||||||
table.insert(_G.c_include_path, string.sub(table.remove(_G.arg, 1), 3))
|
|
||||||
end
|
|
||||||
local subcommand = table.remove(_G.arg, 1)
|
|
||||||
local suffix = (platform and platform.sysname:lower():find 'windows') and '.dll' or '.so'
|
|
||||||
package.path = (deps_install_dir .. '/?.lua;')
|
|
||||||
.. (deps_install_dir .. '/?/init.lua;')
|
|
||||||
.. package.path
|
|
||||||
package.cpath = deps_install_dir .. '/?' .. suffix .. ';' .. package.cpath
|
|
||||||
|
|
||||||
local uv = vim.uv
|
|
||||||
|
|
||||||
-- we use busted and luacheck and their lua dependencies
|
|
||||||
-- But installing their binary dependencies with luarocks is very
|
|
||||||
-- slow, replace them with vim.uv wrappers
|
|
||||||
|
|
||||||
local system = {}
|
|
||||||
package.loaded['system.core'] = system
|
|
||||||
function system.monotime()
|
|
||||||
uv.update_time()
|
|
||||||
return uv.now() * 1e-3
|
|
||||||
end
|
|
||||||
function system.gettime()
|
|
||||||
local sec, usec = uv.gettimeofday()
|
|
||||||
return sec + usec * 1e-6
|
|
||||||
end
|
|
||||||
function system.sleep(sec)
|
|
||||||
uv.sleep(sec * 1e3)
|
|
||||||
end
|
|
||||||
|
|
||||||
local term = {}
|
|
||||||
package.loaded['term.core'] = term
|
|
||||||
function term.isatty(_)
|
|
||||||
return uv.guess_handle(1) == 'tty'
|
|
||||||
end
|
|
||||||
|
|
||||||
local lfs = { _VERSION = 'fake' }
|
|
||||||
package.loaded['lfs'] = lfs
|
|
||||||
|
|
||||||
function lfs.attributes(path, attr)
|
|
||||||
local stat = uv.fs_stat(path)
|
|
||||||
if attr == 'mode' then
|
|
||||||
return stat and stat.type or ''
|
|
||||||
elseif attr == 'modification' then
|
|
||||||
if not stat then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local mtime = stat.mtime
|
|
||||||
return mtime.sec + mtime.nsec * 1e-9
|
|
||||||
else
|
|
||||||
error('not implemented')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function lfs.currentdir()
|
|
||||||
return uv.cwd()
|
|
||||||
end
|
|
||||||
|
|
||||||
function lfs.chdir(dir)
|
|
||||||
local status, err = pcall(uv.chdir, dir)
|
|
||||||
if status then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return nil, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function lfs.dir(path)
|
|
||||||
local fs = uv.fs_scandir(path)
|
|
||||||
return function()
|
|
||||||
if not fs then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
return uv.fs_scandir_next(fs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function lfs.mkdir(dir)
|
|
||||||
return uv.fs_mkdir(dir, 493) -- octal 755
|
|
||||||
end
|
|
||||||
|
|
||||||
if subcommand == 'busted' then
|
|
||||||
require 'busted.runner'({ standalone = false })
|
|
||||||
elseif subcommand == 'luacheck' then
|
|
||||||
require 'luacheck.main'
|
|
||||||
else
|
|
||||||
error 'unknown subcommand'
|
|
||||||
end
|
|
||||||
461
test/reporter.lua
Normal file
461
test/reporter.lua
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
--- @alias test.reporter.SummaryStatus 'skipped' | 'failure' | 'error'
|
||||||
|
|
||||||
|
--- @class test.reporter.ColorMap
|
||||||
|
--- @field bright fun(s: any): string
|
||||||
|
--- @field green fun(s: any): string
|
||||||
|
--- @field yellow fun(s: any): string
|
||||||
|
--- @field magenta fun(s: any): string
|
||||||
|
--- @field red fun(s: any): string
|
||||||
|
--- @field cyan fun(s: any): string
|
||||||
|
--- @field dim fun(s: any): string
|
||||||
|
|
||||||
|
--- @class test.reporter.FileElement
|
||||||
|
--- @field name string
|
||||||
|
--- @field duration? number
|
||||||
|
|
||||||
|
--- @class test.reporter.Options
|
||||||
|
--- @field verbose boolean
|
||||||
|
--- @field summary_file string
|
||||||
|
|
||||||
|
--- @class test.base_reporter
|
||||||
|
--- @field new fun(opts: test.reporter.Options): test.base_reporter
|
||||||
|
--- @field suite_start fun(self: test.base_reporter, repeat_index?: integer, repeat_count?: integer)
|
||||||
|
--- @field file_start fun(self: test.base_reporter, file: test.reporter.FileElement)
|
||||||
|
--- @field test_start fun(self: test.base_reporter, name: string)
|
||||||
|
--- @field test_end fun(self: test.base_reporter, record: test.harness.Record)
|
||||||
|
--- @field file_end fun(self: test.base_reporter, file: test.reporter.FileElement, test_count: integer)
|
||||||
|
--- @field suite_end fun(self: test.base_reporter, duration: number, run_summary: test.harness.RunSummary, failure_output?: string)
|
||||||
|
|
||||||
|
--- @class test.reporter : test.base_reporter
|
||||||
|
--- @field opts test.reporter.Options
|
||||||
|
--- @field colors test.reporter.ColorMap
|
||||||
|
local M = {}
|
||||||
|
M.__index = M
|
||||||
|
|
||||||
|
--- @return test.reporter.ColorMap
|
||||||
|
local function identity_colors()
|
||||||
|
return setmetatable({}, {
|
||||||
|
__index = function()
|
||||||
|
return function(s)
|
||||||
|
return s == nil and '' or tostring(s)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param code string
|
||||||
|
--- @return fun(s: any): string
|
||||||
|
local function ansi_color(code)
|
||||||
|
return function(s)
|
||||||
|
return ('\27[%sm%s\27[0m'):format(code, s == nil and '' or tostring(s))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ansi_colors = {
|
||||||
|
bright = ansi_color('1'),
|
||||||
|
green = ansi_color('32'),
|
||||||
|
yellow = ansi_color('33'),
|
||||||
|
magenta = ansi_color('35'),
|
||||||
|
red = ansi_color('31'),
|
||||||
|
cyan = ansi_color('36'),
|
||||||
|
dim = ansi_color('2'),
|
||||||
|
}
|
||||||
|
|
||||||
|
--- @return boolean
|
||||||
|
local function use_colors()
|
||||||
|
local test_colors = os.getenv('TEST_COLORS')
|
||||||
|
if not test_colors then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local value = test_colors:lower()
|
||||||
|
return not (value == 'false' or value == '0' or value == 'no' or value == 'off')
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param path? string
|
||||||
|
--- @return file
|
||||||
|
local function open_summary_file(path)
|
||||||
|
if type(path) ~= 'string' or path == '-' then
|
||||||
|
return io.stdout
|
||||||
|
end
|
||||||
|
|
||||||
|
return (assert(io.open(path, 'w')))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param opts test.reporter.Options
|
||||||
|
--- @return test.reporter
|
||||||
|
function M.new(opts)
|
||||||
|
local colors = identity_colors()
|
||||||
|
if use_colors() then
|
||||||
|
colors = ansi_colors
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @type test.reporter
|
||||||
|
local self = setmetatable({
|
||||||
|
opts = opts,
|
||||||
|
colors = colors,
|
||||||
|
}, M)
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param element? { duration?: number }
|
||||||
|
--- @return number
|
||||||
|
local function get_elapsed_time_ms(element)
|
||||||
|
if element and element.duration then
|
||||||
|
return element.duration * 1000
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber('nan')
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:succ(s)
|
||||||
|
return self.colors.bright(self.colors.green(s))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:skip(s)
|
||||||
|
return self.colors.bright(self.colors.yellow(s))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:fail(s)
|
||||||
|
return self.colors.bright(self.colors.magenta(s))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:errr(s)
|
||||||
|
return self.colors.bright(self.colors.red(s))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:fpath(s)
|
||||||
|
return self.colors.cyan(s)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:time(s)
|
||||||
|
return self.colors.dim(s)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:sect(s)
|
||||||
|
return self.colors.green(self.colors.dim(s))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param s any
|
||||||
|
--- @return string
|
||||||
|
function M:nmbr(s)
|
||||||
|
return self.colors.bright(s)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param status test.harness.ResultStatus
|
||||||
|
--- @return string
|
||||||
|
function M:result_text(status)
|
||||||
|
if status == 'success' then
|
||||||
|
return ('%s\n'):format(self:succ('OK'))
|
||||||
|
elseif status == 'pending' then
|
||||||
|
return ('%s\n'):format(self:skip('SKIP'))
|
||||||
|
elseif status == 'failure' then
|
||||||
|
return ('%s\n'):format(self:fail('FAIL'))
|
||||||
|
end
|
||||||
|
|
||||||
|
return ('%s\n'):format(self:errr('ERR'))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param status test.reporter.SummaryStatus
|
||||||
|
--- @return string
|
||||||
|
function M:summary_label(status)
|
||||||
|
if status == 'skipped' then
|
||||||
|
return self:skip('SKIPPED ')
|
||||||
|
elseif status == 'failure' then
|
||||||
|
return self:fail('FAILED ')
|
||||||
|
end
|
||||||
|
|
||||||
|
return self:errr('ERROR ')
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param status test.reporter.SummaryStatus
|
||||||
|
--- @param count integer
|
||||||
|
--- @return string
|
||||||
|
function M:summary_header_noun(status, count)
|
||||||
|
if status == 'error' then
|
||||||
|
return count == 1 and 'error' or 'errors'
|
||||||
|
end
|
||||||
|
|
||||||
|
return count == 1 and 'test' or 'tests'
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param status test.reporter.SummaryStatus
|
||||||
|
--- @param count integer
|
||||||
|
--- @return string
|
||||||
|
function M:summary_footer_noun(status, count)
|
||||||
|
if status == 'skipped' then
|
||||||
|
return count == 1 and 'SKIPPED TEST' or 'SKIPPED TESTS'
|
||||||
|
elseif status == 'failure' then
|
||||||
|
return count == 1 and 'FAILED TEST' or 'FAILED TESTS'
|
||||||
|
elseif status == 'error' then
|
||||||
|
return count == 1 and 'ERROR' or 'ERRORS'
|
||||||
|
end
|
||||||
|
|
||||||
|
return count == 1 and 'TEST' or 'TESTS'
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param message any
|
||||||
|
--- @return string
|
||||||
|
local function stringify_message(message)
|
||||||
|
if type(message) == 'string' then
|
||||||
|
return message
|
||||||
|
elseif message == nil then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
return vim.inspect(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param pending test.harness.Record
|
||||||
|
--- @return boolean
|
||||||
|
function M:na_check(pending)
|
||||||
|
if pending.name and vim.list_contains(vim.split(pending.name, '[ :]'), 'N/A') then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(pending.message) == 'string' then
|
||||||
|
return vim.list_contains(vim.split(pending.message, '[ :]'), 'N/A')
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param pending test.harness.Record
|
||||||
|
--- @return string
|
||||||
|
function M:pending_description(pending)
|
||||||
|
local message = stringify_message(pending.message)
|
||||||
|
if message == '' then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat({ message, '\n' })
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param failure test.harness.Record
|
||||||
|
--- @return string
|
||||||
|
function M:failure_description(failure)
|
||||||
|
local message = stringify_message(failure.message)
|
||||||
|
if message == '' then
|
||||||
|
message = 'Nil error'
|
||||||
|
end
|
||||||
|
|
||||||
|
local parts = { message, '\n' }
|
||||||
|
if self.opts.verbose and failure.traceback then
|
||||||
|
parts[#parts + 1] = failure.traceback
|
||||||
|
parts[#parts + 1] = '\n'
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param trace? test.harness.Trace
|
||||||
|
--- @return string
|
||||||
|
function M:get_file_line(trace)
|
||||||
|
if not trace or not trace.short_src then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
local source = vim.fs.normalize(trace.short_src)
|
||||||
|
local line = trace.currentline or 0
|
||||||
|
return self:fpath(source) .. ' @ ' .. self:fpath(line) .. ': '
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param status test.reporter.SummaryStatus
|
||||||
|
--- @param count integer
|
||||||
|
--- @param list test.harness.Record[]
|
||||||
|
--- @param describe fun(self: test.reporter, item: test.harness.Record): string
|
||||||
|
--- @return string
|
||||||
|
function M:get_test_list(status, count, list, describe)
|
||||||
|
if count == 0 then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = self:summary_label(status)
|
||||||
|
local parts = {
|
||||||
|
('%s %s %s, listed below:\n'):format(
|
||||||
|
label,
|
||||||
|
self:nmbr(count),
|
||||||
|
self:summary_header_noun(status, count)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
local na_count = 0
|
||||||
|
|
||||||
|
for _, item in ipairs(list) do
|
||||||
|
if status == 'skipped' and self:na_check(item) then
|
||||||
|
na_count = na_count + 1
|
||||||
|
else
|
||||||
|
local fullname = self:get_file_line(item.trace) .. self:nmbr(item.name)
|
||||||
|
parts[#parts + 1] = ('%s %s\n'):format(label, fullname)
|
||||||
|
parts[#parts + 1] = describe(self, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if na_count > 0 then
|
||||||
|
parts[#parts + 1] =
|
||||||
|
self:nmbr(('%d N/A %s not shown\n'):format(na_count, na_count == 1 and 'test' or 'tests'))
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param status test.reporter.SummaryStatus
|
||||||
|
--- @param count integer
|
||||||
|
--- @return string
|
||||||
|
function M:get_summary(status, count)
|
||||||
|
if count == 0 then
|
||||||
|
return ''
|
||||||
|
end
|
||||||
|
|
||||||
|
return (' %s %s\n'):format(self:nmbr(count), self:summary_footer_noun(status, count))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @return string
|
||||||
|
--- @param summary test.harness.RunSummary
|
||||||
|
function M:get_summary_string(summary)
|
||||||
|
local tests = summary.success_count == 1 and 'test' or 'tests'
|
||||||
|
local parts = {
|
||||||
|
('%s %s %s.\n'):format(self:succ('PASSED '), self:nmbr(summary.success_count), tests),
|
||||||
|
self:get_test_list('skipped', summary.skipped_count, summary.pendings, M.pending_description),
|
||||||
|
self:get_test_list('failure', summary.failure_count, summary.failures, M.failure_description),
|
||||||
|
self:get_test_list('error', summary.error_count, summary.errors, M.failure_description),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.skipped_count + summary.failure_count + summary.error_count) > 0 then
|
||||||
|
parts[#parts + 1] = '\n'
|
||||||
|
end
|
||||||
|
|
||||||
|
parts[#parts + 1] = self:get_summary('skipped', summary.skipped_count)
|
||||||
|
parts[#parts + 1] = self:get_summary('failure', summary.failure_count)
|
||||||
|
parts[#parts + 1] = self:get_summary('error', summary.error_count)
|
||||||
|
return table.concat(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @return nil
|
||||||
|
--- @param repeat_index? integer
|
||||||
|
--- @param repeat_count? integer
|
||||||
|
function M:suite_start(repeat_index, repeat_count)
|
||||||
|
if repeat_count and repeat_count > 1 and repeat_index then
|
||||||
|
io.write(('\nRepeating all tests (run %d of %d) . . .\n\n'):format(repeat_index, repeat_count))
|
||||||
|
end
|
||||||
|
io.write(('%s Global test environment setup.\n'):format(self:sect('--------')))
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param file test.reporter.FileElement
|
||||||
|
function M:file_start(file)
|
||||||
|
io.write(
|
||||||
|
('%s Running tests from %s\n'):format(
|
||||||
|
self:sect('--------'),
|
||||||
|
self:fpath(vim.fs.normalize(file.name))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param name string
|
||||||
|
function M:test_start(name)
|
||||||
|
local desc = ('%s %s'):format(_G._nvim_test_id or '', name)
|
||||||
|
io.write(('%s %s: '):format(self:sect('RUN '), desc))
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @private
|
||||||
|
--- @param record { duration?: number }
|
||||||
|
--- @param text string
|
||||||
|
function M:write_status(record, text)
|
||||||
|
io.write(('%s %s'):format(self:time(('%.2f ms'):format(get_elapsed_time_ms(record))), text))
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param record test.harness.Record
|
||||||
|
function M:test_end(record)
|
||||||
|
local text = self:result_text(record.status)
|
||||||
|
if record.status == 'failure' or record.status == 'error' then
|
||||||
|
text = text .. self:failure_description(record)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:write_status(record, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param file test.reporter.FileElement
|
||||||
|
--- @param test_count integer
|
||||||
|
function M:file_end(file, test_count)
|
||||||
|
io.write(
|
||||||
|
('%s %s %s from %s %s\n\n'):format(
|
||||||
|
self:sect('--------'),
|
||||||
|
self:nmbr(test_count),
|
||||||
|
test_count == 1 and 'test' or 'tests',
|
||||||
|
self:fpath(vim.fs.normalize(file.name)),
|
||||||
|
self:time(('(%.2f ms total)'):format(get_elapsed_time_ms(file)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param duration number
|
||||||
|
--- @param run_summary test.harness.RunSummary
|
||||||
|
--- @param failure_output? string
|
||||||
|
function M:suite_end(duration, run_summary, failure_output)
|
||||||
|
local tests = run_summary.test_count == 1 and 'test' or 'tests'
|
||||||
|
local files = run_summary.file_count == 1 and 'file' or 'files'
|
||||||
|
|
||||||
|
io.write(('%s Global test environment teardown.\n'):format(self:sect('--------')))
|
||||||
|
io.flush()
|
||||||
|
|
||||||
|
local summary_file = open_summary_file(self.opts.summary_file)
|
||||||
|
summary_file:write('\n')
|
||||||
|
summary_file:write(
|
||||||
|
('%s %s %s from %s test %s ran. %s\n'):format(
|
||||||
|
self:sect('========'),
|
||||||
|
self:nmbr(run_summary.test_count),
|
||||||
|
tests,
|
||||||
|
self:nmbr(run_summary.file_count),
|
||||||
|
files,
|
||||||
|
self:time(('(%.2f ms total)'):format(duration * 1000))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
summary_file:write(self:get_summary_string(run_summary))
|
||||||
|
if failure_output then
|
||||||
|
summary_file:write(failure_output)
|
||||||
|
end
|
||||||
|
summary_file:flush()
|
||||||
|
if summary_file ~= io.stdout then
|
||||||
|
summary_file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,24 +1,22 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const LazyPath = std.Build.LazyPath;
|
const LazyPath = std.Build.LazyPath;
|
||||||
|
|
||||||
pub fn testStep(b: *std.Build, kind: []const u8, nvim_bin: *std.Build.Step.Compile, lua_deps: LazyPath, config_dir: LazyPath, include_path: ?[]const LazyPath) !*std.Build.Step.Run {
|
pub fn testStep(b: *std.Build, kind: []const u8, nvim_bin: *std.Build.Step.Compile, config_dir: LazyPath, include_path: ?[]const LazyPath) !*std.Build.Step.Run {
|
||||||
const test_step = b.addRunArtifact(nvim_bin);
|
const test_step = b.addRunArtifact(nvim_bin);
|
||||||
test_step.addArg("-ll");
|
test_step.addArg("-ll");
|
||||||
test_step.addFileArg(b.path("./test/lua_runner.lua"));
|
test_step.addFileArg(b.path("./test/runner.lua"));
|
||||||
test_step.addDirectoryArg(lua_deps);
|
|
||||||
if (include_path) |paths| {
|
if (include_path) |paths| {
|
||||||
for (paths) |path| {
|
for (paths) |path| {
|
||||||
test_step.addPrefixedDirectoryArg("-I", path);
|
test_step.addPrefixedDirectoryArg("-I", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test_step.addArgs(&.{ "busted", "-v", "-o", "test.busted.outputHandlers.nvim", "--lazy" });
|
test_step.addArg("-v");
|
||||||
// TODO(bfredl): a bit funky with paths, should work even if we run "zig build" in a nested dir
|
test_step.addArg(b.fmt("--helper=./test/{s}/preload.lua", .{kind}));
|
||||||
test_step.addArg(b.fmt("./test/{s}/preload.lua", .{kind}));
|
|
||||||
test_step.addArg("--lpath=./src/?.lua");
|
test_step.addArg("--lpath=./src/?.lua");
|
||||||
test_step.addArg("--lpath=./runtime/lua/?.lua");
|
test_step.addArg("--lpath=./runtime/lua/?.lua");
|
||||||
test_step.addArg("--lpath=./?.lua");
|
test_step.addArg("--lpath=./?.lua");
|
||||||
test_step.addPrefixedFileArg("--lpath=", config_dir.path(b, "?.lua")); // FULING: not a real file but works anyway?
|
test_step.addPrefixedFileArg("--lpath=", config_dir.path(b, "?.lua")); // FULING: not a real file but works anyway?
|
||||||
// TODO(bfredl): look into $BUSTED_ARGS user hook, TEST_TAG, TEST_FILTER
|
// TODO(bfredl): look into a TEST_ARGS user hook, TEST_TAG, TEST_FILTER.
|
||||||
if (b.args) |args| {
|
if (b.args) |args| {
|
||||||
test_step.addArgs(args); // accept TEST_FILE as a positional argument
|
test_step.addArgs(args); // accept TEST_FILE as a positional argument
|
||||||
} else {
|
} else {
|
||||||
@@ -40,12 +38,12 @@ pub fn testStep(b: *std.Build, kind: []const u8, nvim_bin: *std.Build.Step.Compi
|
|||||||
return test_step;
|
return test_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *std.Build.Step, lua_deps: LazyPath, config_dir: LazyPath, unit_paths: ?[]const LazyPath) !void {
|
pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *std.Build.Step, config_dir: LazyPath, unit_paths: ?[]const LazyPath) !void {
|
||||||
const empty_dir = b.addWriteFiles();
|
const empty_dir = b.addWriteFiles();
|
||||||
_ = empty_dir.add(".touch", "");
|
_ = empty_dir.add(".touch", "");
|
||||||
const tmpdir_create = b.addInstallDirectory(.{ .source_dir = empty_dir.getDirectory(), .install_dir = .prefix, .install_subdir = "Xtest_tmpdir/" });
|
const tmpdir_create = b.addInstallDirectory(.{ .source_dir = empty_dir.getDirectory(), .install_dir = .prefix, .install_subdir = "Xtest_tmpdir/" });
|
||||||
|
|
||||||
const functional_tests = try testStep(b, "functional", nvim_bin, lua_deps, config_dir, null);
|
const functional_tests = try testStep(b, "functional", nvim_bin, config_dir, null);
|
||||||
functional_tests.step.dependOn(depend_on);
|
functional_tests.step.dependOn(depend_on);
|
||||||
functional_tests.step.dependOn(&tmpdir_create.step);
|
functional_tests.step.dependOn(&tmpdir_create.step);
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *
|
|||||||
functionaltest_step.dependOn(&functional_tests.step);
|
functionaltest_step.dependOn(&functional_tests.step);
|
||||||
|
|
||||||
if (unit_paths) |paths| {
|
if (unit_paths) |paths| {
|
||||||
const unit_tests = try testStep(b, "unit", nvim_bin, lua_deps, config_dir, paths);
|
const unit_tests = try testStep(b, "unit", nvim_bin, config_dir, paths);
|
||||||
unit_tests.step.dependOn(depend_on);
|
unit_tests.step.dependOn(depend_on);
|
||||||
unit_tests.step.dependOn(&tmpdir_create.step);
|
unit_tests.step.dependOn(&tmpdir_create.step);
|
||||||
|
|
||||||
|
|||||||
35
test/runner.lua
Normal file
35
test/runner.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local uv = vim.uv
|
||||||
|
|
||||||
|
---@return string
|
||||||
|
local function repo_root()
|
||||||
|
local source = debug.getinfo(1, 'S').source
|
||||||
|
assert(type(source) == 'string' and vim.startswith(source, '@'), 'failed to resolve runner path')
|
||||||
|
local script_path = assert(uv.fs_realpath(source:sub(2)), 'failed to resolve runner path')
|
||||||
|
return vim.fs.dirname(vim.fs.dirname(script_path))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param roots string[]
|
||||||
|
local function prepend_package_roots(roots)
|
||||||
|
local entries = {}
|
||||||
|
for _, root in ipairs(roots) do
|
||||||
|
entries[#entries + 1] = root .. '/?.lua'
|
||||||
|
entries[#entries + 1] = root .. '/?/init.lua'
|
||||||
|
end
|
||||||
|
|
||||||
|
package.path = table.concat(entries, ';') .. ';' .. package.path
|
||||||
|
end
|
||||||
|
|
||||||
|
_G.c_include_path = {}
|
||||||
|
while _G.arg[1] and vim.startswith(_G.arg[1], '-I') do
|
||||||
|
table.insert(_G.c_include_path, string.sub(table.remove(_G.arg, 1), 3))
|
||||||
|
end
|
||||||
|
|
||||||
|
local root = repo_root()
|
||||||
|
prepend_package_roots({ root, root .. '/test', '.', './test' })
|
||||||
|
|
||||||
|
local exit_code = require('test.harness').main(_G.arg)
|
||||||
|
io.stdout:flush()
|
||||||
|
io.stderr:flush()
|
||||||
|
|
||||||
|
-- Close the standalone Lua state before exit so sanitizers see Lua-owned cleanup.
|
||||||
|
os.exit(exit_code, true)
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
local luaassert = require('luassert')
|
local test_assert = require('test.assert')
|
||||||
local busted = require('busted')
|
---@type test.harness
|
||||||
|
local harness = require('test.harness')
|
||||||
local uv = vim.uv
|
local uv = vim.uv
|
||||||
local Paths = require('test.cmakeconfig.paths')
|
local Paths = require('test.cmakeconfig.paths')
|
||||||
|
|
||||||
luaassert:set_parameter('TableFormatLevel', 100)
|
|
||||||
|
|
||||||
--- Functions executing in the context of the test runner (not the current nvim test session).
|
--- Functions executing in the context of the test runner (not the current nvim test session).
|
||||||
--- @class test.testutil
|
--- @class test.testutil
|
||||||
local M = {
|
local M = {
|
||||||
@@ -86,7 +85,7 @@ function M.retry(max, max_ms, fn)
|
|||||||
end
|
end
|
||||||
uv.update_time() -- Update cached value of luv.now() (libuv: uv_now()).
|
uv.update_time() -- Update cached value of luv.now() (libuv: uv_now()).
|
||||||
if (max and tries >= max) or (uv.now() - start_time > timeout) then
|
if (max and tries >= max) or (uv.now() - start_time > timeout) then
|
||||||
busted.fail(string.format('retry() attempts: %d\n%s', tries, tostring(result)), 2)
|
error(string.format('retry() attempts: %d\n%s', tries, tostring(result)), 2)
|
||||||
end
|
end
|
||||||
tries = tries + 1
|
tries = tries + 1
|
||||||
uv.sleep(20) -- Avoid hot loop...
|
uv.sleep(20) -- Avoid hot loop...
|
||||||
@@ -100,10 +99,10 @@ local check_logs_useless_lines = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function M.eq(expected, actual, context)
|
function M.eq(expected, actual, context)
|
||||||
return luaassert.are.same(expected, actual, context)
|
return test_assert.eq(expected, actual, context)
|
||||||
end
|
end
|
||||||
function M.neq(expected, actual, context)
|
function M.neq(expected, actual, context)
|
||||||
return luaassert.are_not.same(expected, actual, context)
|
return test_assert.neq(expected, actual, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Compare paths after resolving symlinks with realpath.
|
--- Compare paths after resolving symlinks with realpath.
|
||||||
@@ -125,15 +124,6 @@ function M.ok(cond, expected, actual)
|
|||||||
return assert(cond, msg)
|
return assert(cond, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function epicfail(state, arguments, _)
|
|
||||||
state.failure_message = arguments[1]
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
luaassert:register('assertion', 'epicfail', epicfail)
|
|
||||||
function M.fail(msg)
|
|
||||||
return luaassert.epicfail(msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @param pat string
|
--- @param pat string
|
||||||
--- @param actual string
|
--- @param actual string
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
@@ -365,7 +355,7 @@ function M.check_logs()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
luaassert(
|
test_assert(
|
||||||
0 == #runtime_errors,
|
0 == #runtime_errors,
|
||||||
string.format('Found runtime errors in logfile(s): %s', table.concat(runtime_errors, ', '))
|
string.format('Found runtime errors in logfile(s): %s', table.concat(runtime_errors, ', '))
|
||||||
)
|
)
|
||||||
@@ -789,34 +779,15 @@ end
|
|||||||
--- @param name? 'cirrus'|'github'
|
--- @param name? 'cirrus'|'github'
|
||||||
--- @return boolean
|
--- @return boolean
|
||||||
function M.is_ci(name)
|
function M.is_ci(name)
|
||||||
local any = (name == nil)
|
return harness.is_ci(name)
|
||||||
assert(any or name == 'github' or name == 'cirrus')
|
|
||||||
local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS'))
|
|
||||||
local cirrus = ((any or name == 'cirrus') and nil ~= os.getenv('CIRRUS_CI'))
|
|
||||||
return gh or cirrus
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Gets the (tail) contents of `logfile`.
|
-- Gets the (tail) contents of `logfile`.
|
||||||
-- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments.
|
-- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments.
|
||||||
function M.read_nvim_log(logfile, ci_rename)
|
function M.read_nvim_log(logfile, ci_rename)
|
||||||
logfile = logfile or os.getenv('NVIM_LOG_FILE') or 'nvim.log'
|
logfile = logfile or os.getenv('NVIM_LOG_FILE') or 'nvim.log'
|
||||||
assert(uv.fs_stat(logfile), ('logfile not found: %q'):format(logfile))
|
local log = harness.read_nvim_log(logfile, ci_rename)
|
||||||
local is_ci = M.is_ci()
|
assert(log, ('logfile not found: %q'):format(logfile))
|
||||||
local keep = is_ci and 100 or 10
|
|
||||||
local lines = M.read_file_list(logfile, -keep) or {}
|
|
||||||
local log = (
|
|
||||||
('-'):rep(78)
|
|
||||||
.. '\n'
|
|
||||||
.. string.format('$NVIM_LOG_FILE: %s\n', logfile)
|
|
||||||
.. (#lines > 0 and '(last ' .. tostring(keep) .. ' lines)\n' or '(empty)\n')
|
|
||||||
)
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
log = log .. line .. '\n'
|
|
||||||
end
|
|
||||||
log = log .. ('-'):rep(78) .. '\n'
|
|
||||||
if is_ci and ci_rename then
|
|
||||||
os.rename(logfile, logfile .. '.displayed')
|
|
||||||
end
|
|
||||||
return log
|
return log
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -843,7 +814,7 @@ function M.expect_events(expected, received, kind)
|
|||||||
for _, e in ipairs(expected) do
|
for _, e in ipairs(expected) do
|
||||||
msg = msg .. ' ' .. vim.inspect(e) .. ';\n'
|
msg = msg .. ' ' .. vim.inspect(e) .. ';\n'
|
||||||
end
|
end
|
||||||
M.fail(msg)
|
error(msg, 2)
|
||||||
end
|
end
|
||||||
return received
|
return received
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ describe('fs.c', function()
|
|||||||
uv.fs_symlink('test.file', 'unit-test-directory/test_link.file')
|
uv.fs_symlink('test.file', 'unit-test-directory/test_link.file')
|
||||||
|
|
||||||
uv.fs_symlink('non_existing_file.file', 'unit-test-directory/test_broken_link.file')
|
uv.fs_symlink('non_existing_file.file', 'unit-test-directory/test_broken_link.file')
|
||||||
-- The tests are invoked with an absolute path to `busted` executable.
|
-- The tests are invoked with an absolute executable path in arg[0].
|
||||||
absolute_executable = arg[0]
|
absolute_executable = vim.fs.normalize(assert(uv.exepath()))
|
||||||
-- Split the absolute_executable path into a directory and filename.
|
-- Split the absolute_executable path into a directory and filename.
|
||||||
directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$')
|
directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$')
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -397,14 +397,6 @@ describe('path.c', function()
|
|||||||
setup(function()
|
setup(function()
|
||||||
mkdir('unit-test-directory')
|
mkdir('unit-test-directory')
|
||||||
io.open('unit-test-directory/test.file', 'w'):close()
|
io.open('unit-test-directory/test.file', 'w'):close()
|
||||||
|
|
||||||
-- Since the tests are executed, they are called by an executable. We use
|
|
||||||
-- that executable for several asserts.
|
|
||||||
local absolute_executable = arg[0]
|
|
||||||
|
|
||||||
-- Split absolute_executable into a directory and the actual file name for
|
|
||||||
-- later usage.
|
|
||||||
local directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') -- luacheck: ignore
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
teardown(function()
|
teardown(function()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
-- Modules loaded here will not be cleared and reloaded by Busted.
|
-- Modules loaded here will not be cleared and reloaded by the local harness.
|
||||||
-- Busted started doing this to help provide more isolation. See issue #62
|
-- Keeping these preloaded preserves cross-file setup while still resetting
|
||||||
-- for more information about this.
|
-- non-helper modules between files.
|
||||||
require('ffi')
|
require('ffi')
|
||||||
require('test.unit.testutil')
|
require('test.unit.testutil')
|
||||||
require('test.unit.preprocess')
|
require('test.unit.preprocess')
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ describe('tempfile related functions', function()
|
|||||||
|
|
||||||
itp('generate name of non-existing file', function()
|
itp('generate name of non-existing file', function()
|
||||||
local file = vim_tempname()
|
local file = vim_tempname()
|
||||||
assert.truthy(file)
|
assert(file)
|
||||||
assert.False(lib.os_path_exists(file))
|
assert.False(lib.os_path_exists(file))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
local t = require('test.unit.testutil')
|
local t = require('test.unit.testutil')
|
||||||
local assert = require('luassert')
|
|
||||||
|
|
||||||
local itp = t.gen_itp(it)
|
local itp = t.gen_itp(it)
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ if os.getenv('NVIM_TEST_RUN_TESTTEST') ~= '1' then
|
|||||||
end
|
end
|
||||||
describe('test code', function()
|
describe('test code', function()
|
||||||
itp('does not hang when working with lengthy errors', function()
|
itp('does not hang when working with lengthy errors', function()
|
||||||
assert.just_fail(('x'):rep(65536))
|
error(('x'):rep(65536), 0)
|
||||||
end)
|
end)
|
||||||
itp('shows trace after exiting abnormally', function()
|
itp('shows trace after exiting abnormally', function()
|
||||||
sc.exit(0)
|
sc.exit(0)
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ local Set = require('test.unit.set')
|
|||||||
local Preprocess = require('test.unit.preprocess')
|
local Preprocess = require('test.unit.preprocess')
|
||||||
local t_global = require('test.testutil')
|
local t_global = require('test.testutil')
|
||||||
local paths = t_global.paths
|
local paths = t_global.paths
|
||||||
local assert = require('luassert')
|
local assert = require('test.assert')
|
||||||
local say = require('say')
|
|
||||||
|
|
||||||
local check_cores = t_global.check_cores
|
local check_cores = t_global.check_cores
|
||||||
local dedent = t_global.dedent
|
local dedent = t_global.dedent
|
||||||
@@ -538,19 +537,6 @@ if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function just_fail(_)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
say:set('assertion.just_fail.positive', '%s')
|
|
||||||
say:set('assertion.just_fail.negative', '%s')
|
|
||||||
assert:register(
|
|
||||||
'assertion',
|
|
||||||
'just_fail',
|
|
||||||
just_fail,
|
|
||||||
'assertion.just_fail.positive',
|
|
||||||
'assertion.just_fail.negative'
|
|
||||||
)
|
|
||||||
|
|
||||||
local hook_fnamelen = 30
|
local hook_fnamelen = 30
|
||||||
local hook_sfnamelen = 30
|
local hook_sfnamelen = 30
|
||||||
local hook_numlen = 5
|
local hook_numlen = 5
|
||||||
@@ -769,7 +755,7 @@ local function check_child_err(rd)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if err ~= '' then
|
if err ~= '' then
|
||||||
assert.just_fail(err)
|
error(err, 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -810,7 +796,7 @@ local function gen_itp(it)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Pre-emptively calculating error location, wasteful, ugh!
|
-- Pre-emptively calculating error location, wasteful, ugh!
|
||||||
-- But the way this code messes around with busted implies the real location is strictly
|
-- But the way this code wraps the local harness means the real location is strictly
|
||||||
-- not available in the parent when an actual error occurs. so we have to do this here.
|
-- not available in the parent when an actual error occurs. so we have to do this here.
|
||||||
local location = debug.traceback()
|
local location = debug.traceback()
|
||||||
it(name, function()
|
it(name, function()
|
||||||
|
|||||||
@@ -275,6 +275,13 @@ local function push(input, vt)
|
|||||||
vterm.vterm_input_write(vt, input, string.len(input))
|
vterm.vterm_input_write(vt, input, string.len(input))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- vterm_input_write() can synchronously invoke the Lua callbacks installed
|
||||||
|
-- above. LuaJIT must not JIT-compile an FFI call that re-enters Lua, or the
|
||||||
|
-- test can panic with "bad callback".
|
||||||
|
if jit then
|
||||||
|
jit.off(push, true)
|
||||||
|
end
|
||||||
|
|
||||||
local function expect(expected)
|
local function expect(expected)
|
||||||
local actual = read_rm()
|
local actual = read_rm()
|
||||||
t.eq(expected .. '\n', actual)
|
t.eq(expected .. '\n', actual)
|
||||||
|
|||||||
Reference in New Issue
Block a user