mirror of
https://github.com/neovim/neovim.git
synced 2026-05-06 08:26:45 -04: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:
@@ -74,10 +74,6 @@ jobs:
|
||||
name: 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'
|
||||
name: lintsh
|
||||
run: cmake --build build --target lintsh
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
"version": "LuaJIT"
|
||||
},
|
||||
"workspace": {
|
||||
"library": [
|
||||
"${3rd}/busted/library"
|
||||
],
|
||||
"ignoreDir": [
|
||||
".deps",
|
||||
"build",
|
||||
|
||||
@@ -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.
|
||||
|
||||
`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.
|
||||
|
||||
|
||||
+1
-25
@@ -243,15 +243,6 @@ mark_as_advanced(TS_QUERY_LS_PRG)
|
||||
|
||||
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(
|
||||
TARGET lintlua-stylua
|
||||
COMMAND ${STYLUA_PRG}
|
||||
@@ -271,7 +262,7 @@ add_custom_target(lintlua-stylua2
|
||||
add_dependencies(lintlua-stylua lintlua-stylua2)
|
||||
|
||||
add_custom_target(lintlua)
|
||||
add_dependencies(lintlua lintlua-luacheck lintlua-stylua)
|
||||
add_dependencies(lintlua lintlua-stylua)
|
||||
|
||||
add_glob_target(
|
||||
TARGET lintsh
|
||||
@@ -355,21 +346,6 @@ ExternalProject_Add(uncrustify
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
${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")
|
||||
set(LUALS_ARCH arm64)
|
||||
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 lua_dev_deps = b.dependency("lua_dev_deps", .{});
|
||||
|
||||
const test_deps = b.step("test_deps", "test prerequisites");
|
||||
test_deps.dependOn(&nvim_exe_install.step);
|
||||
// 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,
|
||||
nvim_exe,
|
||||
test_deps,
|
||||
lua_dev_deps.path("."),
|
||||
test_config_step.getDirectory(),
|
||||
unit_headers,
|
||||
);
|
||||
@@ -804,7 +801,7 @@ pub fn test_config(b: *std.Build) ![]u8 {
|
||||
\\M.test_source_path = "{[src_path]s}"
|
||||
\\M.test_lua_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 {{}}
|
||||
\\
|
||||
\\return M
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
.hash = "libiconv-1.18.0-p9sJwWnqAACzVYeWgXB5r5lOQ74XwTPlptixV0JPRO28",
|
||||
.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 = .{
|
||||
.url = "git+https://github.com/tree-sitter/tree-sitter-c?ref=v0.24.1#7fa1be1b694b6e763686793d97da01f36a0e5c12",
|
||||
.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_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_BIN_DIR "${DEPS_INSTALL_DIR}/bin")
|
||||
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_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads")
|
||||
|
||||
@@ -60,18 +60,18 @@ if(IS_ABSOLUTE ${TEST_PATH})
|
||||
file(RELATIVE_PATH TEST_PATH "${ROOT_DIR}" "${TEST_PATH}")
|
||||
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 "")
|
||||
list(APPEND BUSTED_ARGS --tags $ENV{TEST_TAG})
|
||||
list(APPEND TEST_ARGS --tags $ENV{TEST_TAG})
|
||||
endif()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
# TMPDIR: for testutil.tmpname() and Nvim tempname().
|
||||
@@ -95,14 +95,14 @@ endif()
|
||||
execute_process(
|
||||
# Note: because of "-ll" (low-level interpreter mode), some modules like
|
||||
# _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
|
||||
-Xoutput "{\"test_path\": \"${TEST_PATH}\", \"summary_file\": \"${TEST_SUMMARY_FILE}\"}"
|
||||
--lazy --helper=${TEST_DIR}/${TEST_TYPE}/preload.lua
|
||||
COMMAND ${NVIM_PRG} -ll ${ROOT_DIR}/test/runner.lua -v
|
||||
--summary-file=${TEST_SUMMARY_FILE}
|
||||
--helper=${TEST_DIR}/${TEST_TYPE}/preload.lua
|
||||
--lpath=${BUILD_DIR}/?.lua
|
||||
--lpath=${ROOT_DIR}/src/?.lua
|
||||
--lpath=${ROOT_DIR}/runtime/lua/?.lua
|
||||
--lpath=?.lua
|
||||
${BUSTED_ARGS}
|
||||
${TEST_ARGS}
|
||||
${TEST_PATH}
|
||||
TIMEOUT $ENV{TEST_TIMEOUT}
|
||||
WORKING_DIRECTORY ${TEST_XDG_PREFIX}
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
# Uncomment these entries to instead use system-wide installations of
|
||||
# them.
|
||||
#
|
||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_BUSTED=OFF
|
||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_GETTEXT=OFF
|
||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBICONV=OFF
|
||||
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBUV=OFF
|
||||
|
||||
+25
-13
@@ -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
|
||||
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.
|
||||
|
||||
Depending on the presence of binaries (e.g., `xclip`) some tests will be
|
||||
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
|
||||
|
||||
@@ -56,7 +69,8 @@ Test Layout
|
||||
- `/test/includes` : include-files for use by luajit `ffi.cdef` C definitions
|
||||
parser: normally used to make macros not accessible via this mechanism
|
||||
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
|
||||
runner
|
||||
- `/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)
|
||||
3. [nlua](https://github.com/mfussenegger/nlua)
|
||||
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
|
||||
`exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the
|
||||
path to your `nlua`)
|
||||
5. An `nlua` command in `$PATH`.
|
||||
|
||||
|
||||
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",
|
||||
cwd = "${workspaceFolder}",
|
||||
program = {
|
||||
command = "nbusted",
|
||||
command = "nlua",
|
||||
},
|
||||
args = {
|
||||
"--ignore-lua",
|
||||
"--lazy",
|
||||
"-ll",
|
||||
"test/runner.lua",
|
||||
"--helper=test/functional/preload.lua",
|
||||
"--lpath=build/?.lua",
|
||||
"--lpath=?.lua",
|
||||
@@ -274,7 +286,7 @@ Limitations:
|
||||
if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then
|
||||
require("lldebugger").start()
|
||||
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
|
||||
(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
|
||||
|
||||
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 >
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
Currently only compilers with gcc-compatible arguments are supported.
|
||||
|
||||
@@ -388,7 +388,7 @@ Gdb *dev-tools-gdb*
|
||||
|
||||
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 ...", ...)`):
|
||||
>bash
|
||||
GDB=1 TEST_TAG=foo make functionaltest
|
||||
@@ -403,7 +403,7 @@ Then, in another terminal:
|
||||
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
|
||||
|
||||
|
||||
@@ -64,6 +64,31 @@ function vim.deepcopy(orig, noref)
|
||||
return deepcopy(orig, not noref and {} or nil)
|
||||
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
|
||||
--- @inlinedoc
|
||||
---
|
||||
@@ -678,6 +703,8 @@ local function deep_equal(left, right, seen)
|
||||
return false
|
||||
end
|
||||
|
||||
---@cast left table<any, any>
|
||||
---@cast right table<any, any>
|
||||
seen = seen or {}
|
||||
local seen_left = seen[left]
|
||||
if seen_left and seen_left[right] ~= nil then
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-6
@@ -6,12 +6,7 @@
|
||||
"workspace": {
|
||||
"library": [
|
||||
"../runtime/lua",
|
||||
"../src",
|
||||
"../build/usr/share/lua/5.1",
|
||||
"../build",
|
||||
"${3rd}/busted/library",
|
||||
"${3rd}/luassert/library",
|
||||
"${3rd}/luv/library"
|
||||
"../src"
|
||||
],
|
||||
"checkThirdParty": "Disable"
|
||||
},
|
||||
|
||||
+4
-5
@@ -7,7 +7,6 @@ set(TEST_OPTIONS
|
||||
-D BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||
-D CIRRUS_CI=$ENV{CIRRUS_CI}
|
||||
-D CI_BUILD=${CI_BUILD}
|
||||
-D DEPS_INSTALL_DIR=${DEPS_INSTALL_DIR}
|
||||
-D NVIM_PRG=$<TARGET_FILE:nvim_bin>
|
||||
-D TEST_DIR=${TEST_DIR}
|
||||
-D ROOT_DIR=${PROJECT_SOURCE_DIR})
|
||||
@@ -20,7 +19,7 @@ if(LUA_HAS_FFI)
|
||||
${TEST_OPTIONS}
|
||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||
USES_TERMINAL)
|
||||
add_dependencies(unittest lua_dev_deps nvim)
|
||||
add_dependencies(unittest nvim)
|
||||
else()
|
||||
message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}")
|
||||
endif()
|
||||
@@ -36,7 +35,7 @@ add_custom_target(benchmark
|
||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||
DEPENDS tty-test
|
||||
USES_TERMINAL)
|
||||
add_dependencies(benchmark lua_dev_deps nvim)
|
||||
add_dependencies(benchmark nvim)
|
||||
|
||||
add_custom_target(functionaltest
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
@@ -45,7 +44,7 @@ add_custom_target(functionaltest
|
||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||
DEPENDS printenv-test printargs-test shell-test pwsh-test streams-test tty-test
|
||||
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.
|
||||
set(group_targets "")
|
||||
@@ -66,7 +65,7 @@ foreach(test_file ${test_files})
|
||||
${TEST_OPTIONS}
|
||||
-P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
|
||||
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 summary_files ${summary_file})
|
||||
endif()
|
||||
|
||||
+23
-8
@@ -1,10 +1,25 @@
|
||||
--- @meta
|
||||
|
||||
do -- Mark block as optional
|
||||
---Mark a test as placeholder.
|
||||
---
|
||||
---This will not fail or pass, it will simply be marked as "pending".
|
||||
---@param name string
|
||||
---@param block? fun()
|
||||
function pending(name, block) end
|
||||
end
|
||||
--- @param name string
|
||||
--- @param fn? fun()
|
||||
function it(name, fn) end
|
||||
|
||||
--- @param name string
|
||||
--- @param fn fun()
|
||||
function describe(name, fn) 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
@@ -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.
|
||||
-- Busted started doing this to help provide more isolation. See issue #62
|
||||
-- for more information about this.
|
||||
-- Modules loaded here will not be cleared and reloaded by the local harness.
|
||||
-- Keeping these preloaded preserves cross-file setup while still resetting
|
||||
-- non-helper modules between files.
|
||||
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 SUBTBL = {
|
||||
@@ -124,7 +122,7 @@ function M.format_luav(v, indent, opts)
|
||||
else
|
||||
print(type(v))
|
||||
-- Not implemented yet
|
||||
luaassert(false)
|
||||
assert(false)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
@@ -12,6 +12,6 @@ describe('autocmd FileType', function()
|
||||
command('let g:foo = 0')
|
||||
command('autocmd FileType help let g:foo = g:foo + 1')
|
||||
command('help help')
|
||||
assert.same(1, eval('g:foo'))
|
||||
assert.eq(1, eval('g:foo'))
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -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)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ local api = n.api
|
||||
local fn = n.fn
|
||||
local clear = n.clear
|
||||
local eq = t.eq
|
||||
local fail = t.fail
|
||||
local exec_lua = n.exec_lua
|
||||
local feed = n.feed
|
||||
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 _, elem in ipairs(event) do
|
||||
if type(elem) == 'number' and elem < 0 then
|
||||
fail(string.format('Received event has negative values'))
|
||||
error('Received event has negative values')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ describe('vim.net.request', function()
|
||||
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
|
||||
t.eq(headers.Authorization[1], 'Bearer test-token', 'Expected Authorization header')
|
||||
t.eq(headers['X-Custom-Header'][1], 'custom-value', 'Expected X-Custom-Header')
|
||||
|
||||
@@ -834,6 +834,26 @@ describe('lua stdlib', function()
|
||||
)
|
||||
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()
|
||||
eq('foo%-bar', exec_lua([[return 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={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 [[ 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(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) ]])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Modules loaded here will NOT be cleared and reloaded by Busted.
|
||||
-- Busted started doing this to help provide more isolation. See issue #62
|
||||
-- for more information about this.
|
||||
-- Modules loaded here will not be cleared and reloaded by the local harness.
|
||||
-- Keeping these preloaded preserves cross-file setup while still resetting
|
||||
-- non-helper modules between files.
|
||||
local t = require('test.testutil')
|
||||
require('test.functional.testnvim')()
|
||||
require('test.functional.ui.screen')
|
||||
|
||||
@@ -80,14 +80,14 @@ describe(':terminal', function()
|
||||
|
||||
send_osc_with_terminator(BEL)
|
||||
--- @type string
|
||||
assert.same(
|
||||
assert.eq(
|
||||
{ sequence = OSC_PREFIX .. '10;?', terminator = BEL },
|
||||
exec_lua([[return _G.osc10_response]])
|
||||
)
|
||||
|
||||
send_osc_with_terminator(ST)
|
||||
--- @type string
|
||||
assert.same(
|
||||
assert.eq(
|
||||
{ sequence = OSC_PREFIX .. '10;?', terminator = ST },
|
||||
exec_lua([[return _G.osc10_response]])
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local uv = vim.uv
|
||||
local t = require('test.testutil')
|
||||
local busted = require('busted')
|
||||
---@type test.harness
|
||||
local harness = require('test.harness')
|
||||
|
||||
local Session = require('test.client.session')
|
||||
local uv_stream = require('test.client.uv_stream')
|
||||
@@ -528,7 +529,7 @@ function M.new_session(keep, ...)
|
||||
return new_session
|
||||
end
|
||||
|
||||
busted.subscribe({ 'suite', 'end' }, function()
|
||||
harness.on_suite_end(function()
|
||||
M.check_close(true)
|
||||
local timed_out = false
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
|
||||
local t = require('test.testutil')
|
||||
local n = require('test.functional.testnvim')()
|
||||
local busted = require('busted')
|
||||
local uv = vim.uv
|
||||
|
||||
local deepcopy = vim.deepcopy
|
||||
@@ -911,7 +910,7 @@ between asynchronous (feed(), nvim_input()) and synchronous API calls.
|
||||
if eof then
|
||||
err = err .. '\n\n' .. eof[2]
|
||||
end
|
||||
busted.fail(err .. '\n\nSnapshot:\n' .. self:_print_snapshot(), 3)
|
||||
error(err .. '\n\nSnapshot:\n' .. self:_print_snapshot(), 3)
|
||||
elseif did_warn then
|
||||
if eof then
|
||||
print(eof[2])
|
||||
@@ -922,9 +921,9 @@ between asynchronous (feed(), nvim_input()) and synchronous API calls.
|
||||
end
|
||||
|
||||
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
|
||||
busted.fail(
|
||||
error(
|
||||
'Expected screen state to be unchanged.\nIntermediate screen state:\n'
|
||||
.. intermediate_state_snapshot,
|
||||
3
|
||||
|
||||
+1694
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
|
||||
@@ -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
|
||||
+8
-10
@@ -1,24 +1,22 @@
|
||||
const std = @import("std");
|
||||
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);
|
||||
test_step.addArg("-ll");
|
||||
test_step.addFileArg(b.path("./test/lua_runner.lua"));
|
||||
test_step.addDirectoryArg(lua_deps);
|
||||
test_step.addFileArg(b.path("./test/runner.lua"));
|
||||
if (include_path) |paths| {
|
||||
for (paths) |path| {
|
||||
test_step.addPrefixedDirectoryArg("-I", path);
|
||||
}
|
||||
}
|
||||
test_step.addArgs(&.{ "busted", "-v", "-o", "test.busted.outputHandlers.nvim", "--lazy" });
|
||||
// TODO(bfredl): a bit funky with paths, should work even if we run "zig build" in a nested dir
|
||||
test_step.addArg(b.fmt("./test/{s}/preload.lua", .{kind}));
|
||||
test_step.addArg("-v");
|
||||
test_step.addArg(b.fmt("--helper=./test/{s}/preload.lua", .{kind}));
|
||||
test_step.addArg("--lpath=./src/?.lua");
|
||||
test_step.addArg("--lpath=./runtime/lua/?.lua");
|
||||
test_step.addArg("--lpath=./?.lua");
|
||||
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| {
|
||||
test_step.addArgs(args); // accept TEST_FILE as a positional argument
|
||||
} else {
|
||||
@@ -40,12 +38,12 @@ pub fn testStep(b: *std.Build, kind: []const u8, nvim_bin: *std.Build.Step.Compi
|
||||
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();
|
||||
_ = empty_dir.add(".touch", "");
|
||||
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(&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);
|
||||
|
||||
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(&tmpdir_create.step);
|
||||
|
||||
|
||||
@@ -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)
|
||||
+11
-40
@@ -1,10 +1,9 @@
|
||||
local luaassert = require('luassert')
|
||||
local busted = require('busted')
|
||||
local test_assert = require('test.assert')
|
||||
---@type test.harness
|
||||
local harness = require('test.harness')
|
||||
local uv = vim.uv
|
||||
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).
|
||||
--- @class test.testutil
|
||||
local M = {
|
||||
@@ -86,7 +85,7 @@ function M.retry(max, max_ms, fn)
|
||||
end
|
||||
uv.update_time() -- Update cached value of luv.now() (libuv: uv_now()).
|
||||
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
|
||||
tries = tries + 1
|
||||
uv.sleep(20) -- Avoid hot loop...
|
||||
@@ -100,10 +99,10 @@ local check_logs_useless_lines = {
|
||||
}
|
||||
|
||||
function M.eq(expected, actual, context)
|
||||
return luaassert.are.same(expected, actual, context)
|
||||
return test_assert.eq(expected, actual, context)
|
||||
end
|
||||
function M.neq(expected, actual, context)
|
||||
return luaassert.are_not.same(expected, actual, context)
|
||||
return test_assert.neq(expected, actual, context)
|
||||
end
|
||||
|
||||
--- Compare paths after resolving symlinks with realpath.
|
||||
@@ -125,15 +124,6 @@ function M.ok(cond, expected, actual)
|
||||
return assert(cond, msg)
|
||||
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 actual string
|
||||
--- @return boolean
|
||||
@@ -365,7 +355,7 @@ function M.check_logs()
|
||||
end
|
||||
end
|
||||
end
|
||||
luaassert(
|
||||
test_assert(
|
||||
0 == #runtime_errors,
|
||||
string.format('Found runtime errors in logfile(s): %s', table.concat(runtime_errors, ', '))
|
||||
)
|
||||
@@ -789,34 +779,15 @@ end
|
||||
--- @param name? 'cirrus'|'github'
|
||||
--- @return boolean
|
||||
function M.is_ci(name)
|
||||
local any = (name == nil)
|
||||
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
|
||||
return harness.is_ci(name)
|
||||
end
|
||||
|
||||
-- Gets the (tail) contents of `logfile`.
|
||||
-- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments.
|
||||
function M.read_nvim_log(logfile, ci_rename)
|
||||
logfile = logfile or os.getenv('NVIM_LOG_FILE') or 'nvim.log'
|
||||
assert(uv.fs_stat(logfile), ('logfile not found: %q'):format(logfile))
|
||||
local is_ci = M.is_ci()
|
||||
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
|
||||
local log = harness.read_nvim_log(logfile, ci_rename)
|
||||
assert(log, ('logfile not found: %q'):format(logfile))
|
||||
return log
|
||||
end
|
||||
|
||||
@@ -843,7 +814,7 @@ function M.expect_events(expected, received, kind)
|
||||
for _, e in ipairs(expected) do
|
||||
msg = msg .. ' ' .. vim.inspect(e) .. ';\n'
|
||||
end
|
||||
M.fail(msg)
|
||||
error(msg, 2)
|
||||
end
|
||||
return received
|
||||
end
|
||||
|
||||
@@ -76,8 +76,8 @@ describe('fs.c', function()
|
||||
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')
|
||||
-- The tests are invoked with an absolute path to `busted` executable.
|
||||
absolute_executable = arg[0]
|
||||
-- The tests are invoked with an absolute executable path in arg[0].
|
||||
absolute_executable = vim.fs.normalize(assert(uv.exepath()))
|
||||
-- Split the absolute_executable path into a directory and filename.
|
||||
directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$')
|
||||
end)
|
||||
|
||||
@@ -397,14 +397,6 @@ describe('path.c', function()
|
||||
setup(function()
|
||||
mkdir('unit-test-directory')
|
||||
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)
|
||||
|
||||
teardown(function()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Modules loaded here will not be cleared and reloaded by Busted.
|
||||
-- Busted started doing this to help provide more isolation. See issue #62
|
||||
-- for more information about this.
|
||||
-- Modules loaded here will not be cleared and reloaded by the local harness.
|
||||
-- Keeping these preloaded preserves cross-file setup while still resetting
|
||||
-- non-helper modules between files.
|
||||
require('ffi')
|
||||
require('test.unit.testutil')
|
||||
require('test.unit.preprocess')
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('tempfile related functions', function()
|
||||
|
||||
itp('generate name of non-existing file', function()
|
||||
local file = vim_tempname()
|
||||
assert.truthy(file)
|
||||
assert(file)
|
||||
assert.False(lib.os_path_exists(file))
|
||||
end)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
local t = require('test.unit.testutil')
|
||||
local assert = require('luassert')
|
||||
|
||||
local itp = t.gen_itp(it)
|
||||
|
||||
@@ -11,7 +10,7 @@ if os.getenv('NVIM_TEST_RUN_TESTTEST') ~= '1' then
|
||||
end
|
||||
describe('test code', function()
|
||||
itp('does not hang when working with lengthy errors', function()
|
||||
assert.just_fail(('x'):rep(65536))
|
||||
error(('x'):rep(65536), 0)
|
||||
end)
|
||||
itp('shows trace after exiting abnormally', function()
|
||||
sc.exit(0)
|
||||
|
||||
+3
-17
@@ -4,8 +4,7 @@ local Set = require('test.unit.set')
|
||||
local Preprocess = require('test.unit.preprocess')
|
||||
local t_global = require('test.testutil')
|
||||
local paths = t_global.paths
|
||||
local assert = require('luassert')
|
||||
local say = require('say')
|
||||
local assert = require('test.assert')
|
||||
|
||||
local check_cores = t_global.check_cores
|
||||
local dedent = t_global.dedent
|
||||
@@ -538,19 +537,6 @@ if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
|
||||
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_sfnamelen = 30
|
||||
local hook_numlen = 5
|
||||
@@ -769,7 +755,7 @@ local function check_child_err(rd)
|
||||
end
|
||||
end
|
||||
if err ~= '' then
|
||||
assert.just_fail(err)
|
||||
error(err, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -810,7 +796,7 @@ local function gen_itp(it)
|
||||
end
|
||||
|
||||
-- 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.
|
||||
local location = debug.traceback()
|
||||
it(name, function()
|
||||
|
||||
@@ -275,6 +275,13 @@ local function push(input, vt)
|
||||
vterm.vterm_input_write(vt, input, string.len(input))
|
||||
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 actual = read_rm()
|
||||
t.eq(expected .. '\n', actual)
|
||||
|
||||
Reference in New Issue
Block a user