nvim-treesitter/tests/indent/common.lua
Stephan Seitz c7634f16de Mark failing indent tests to add them to CI
Expected failures should be monitored so that we don't have regressions
and also remove failure marks when they are resolved.
2021-11-24 17:29:55 +01:00

191 lines
5.9 KiB
Lua

local M = {}
local assert = require "luassert"
local say = require "say"
local scan_dir = require("plenary.scandir").scan_dir
local Path = require "plenary.path"
M.XFAIL = "xfail"
local function same_indent(state, arguments)
local before = arguments[1]
local after = arguments[2]
local ok = true
local errors = { before = {}, after = {} }
for line = 1, #before do
if before[line] ~= after[line] then
if before[line] and after[line] then
-- store the actual indentation length for each line
errors.before[line] = #string.match(before[line], "^%s*")
errors.after[line] = #string.match(after[line], "^%s*")
end
ok = false
end
end
-- we will always use only a single argument, passing the other one in fmtargs
arguments.fmtargs = { { errors = errors, other = after } }
arguments.fmtargs[2] = { errors = errors, other = after }
return ok
end
local function format_indent(arg, fmtargs)
if not arg or not fmtargs then
return
end
-- find minimal width if any line is longer
local width = 40
for _, line in ipairs(fmtargs.other) do
width = #line > width and #line or width
end
width = width + 3
local header_fmt = "%8s %2s%-" .. tostring(width + 1) .. "s %s"
local fmt = "%8s %2s |%-" .. tostring(width) .. "s |%s"
local output = { header_fmt:format("", "", "Found:", "Expected:") }
for i, line in ipairs(arg) do
if fmtargs.errors.before[i] then
local indents = string.format("%d vs %d", fmtargs.errors.after[i], fmtargs.errors.before[i])
table.insert(output, fmt:format(indents, "=>", fmtargs.other[i], line))
else
table.insert(output, fmt:format("", "", fmtargs.other[i], line))
end
end
return table.concat(output, "\n")
end
say:set_namespace "en"
say:set("assertion.same_indent.positive", "Incorrect indentation\n%s")
say:set("assertion.same_indent.negative", "Incorrect indentation\n%s")
assert:register(
"assertion",
"same_indent",
same_indent,
"assertion.same_indent.positive",
"assert.same_indent.negative"
)
-- Custom assertion better suited for indentation diffs
local function compare_indent(before, after, xfail)
assert:add_formatter(format_indent)
if xfail then
io.stdout:write "Warning! Known failure of this test! Please help to fix it! "
assert.is_not.same_indent(before, after)
else
assert.is.same_indent(before, after)
end
assert:remove_formatter(format_indent)
end
local function set_buf_indent_opts(opts)
local optnames = { "tabstop", "shiftwidth", "softtabstop", "expandtab", "filetype" }
for _, opt in ipairs(optnames) do
if opts[opt] ~= nil then
vim.bo[opt] = opts[opt]
end
end
end
function M.run_indent_test(file, runner, opts)
assert.are.same(1, vim.fn.filereadable(file), string.format('File "%s" not readable', file))
-- load reference file
vim.cmd(string.format("edit %s", file))
vim.bo.indentexpr = "nvim_treesitter#indent()"
local before = vim.api.nvim_buf_get_lines(0, 0, -1, true)
assert.are.same("nvim_treesitter#indent()", vim.bo.indentexpr)
set_buf_indent_opts(opts)
-- perform the test
runner()
-- get file content after the test
local after = vim.api.nvim_buf_get_lines(0, 0, -1, true)
-- clear any changes to avoid 'No write since last change (add ! to override)'
vim.cmd "edit!"
return before, after
end
function M.indent_whole_file(file, opts, xfail)
local before, after = M.run_indent_test(file, function()
vim.cmd "silent normal gg=G"
end, opts)
compare_indent(before, after, xfail)
end
-- Open a file, use `normal o` to insert a new line and compare results
-- @param file path to the initial file
-- @param spec a table with keys:
-- on_line: line on which `normal o` is executed
-- text: text inserted in the new line
-- indent: expected indent before the inserted text (string or int)
-- @param opts buffer options passed to set_buf_indent_opts
function M.indent_new_line(file, spec, opts, xfail)
local before, after = M.run_indent_test(file, function()
-- move to the line and input the new one
vim.cmd(string.format("normal! %dG", spec.on_line))
vim.cmd(string.format("normal! o%s", spec.text))
end, opts)
local indent = type(spec.indent) == "string" and spec.indent or string.rep(" ", spec.indent)
table.insert(before, spec.on_line + 1, indent .. spec.text)
compare_indent(before, after, xfail)
end
local Runner = {}
Runner.__index = Runner
-- Helper to avoid boilerplate when defining tests
-- @param it the "it" function that busted defines globally in spec files
-- @param base_dir all other paths will be resolved relative to this directory
-- @param buf_opts buffer options passed to set_buf_indent_opts
function Runner:new(it, base_dir, buf_opts)
local runner = {}
runner.it = it
runner.base_dir = Path:new(base_dir)
runner.buf_opts = buf_opts
return setmetatable(runner, self)
end
function Runner:whole_file(dirs, opts)
opts = opts or {}
local expected_failures = opts.expected_failures or {}
expected_failures = vim.tbl_map(function(f)
return Path:new(f):make_relative(self.base_dir.filename)
end, expected_failures)
dirs = type(dirs) == "table" and dirs or { dirs }
dirs = vim.tbl_map(function(dir)
dir = self.base_dir / Path:new(dir)
assert.is.same(1, vim.fn.isdirectory(dir.filename))
return dir.filename
end, dirs)
local files = vim.tbl_flatten(vim.tbl_map(scan_dir, dirs))
for _, file in ipairs(files) do
local relpath = Path:new(file):make_relative(self.base_dir.filename)
self.it(relpath, function()
M.indent_whole_file(file, self.buf_opts, vim.tbl_contains(expected_failures, relpath))
end)
end
end
function Runner:new_line(file, spec, title, xfail)
title = title and title or tostring(spec.on_line)
self.it(string.format("%s[%s]", file, title), function()
local path = self.base_dir / file
M.indent_new_line(path.filename, spec, self.buf_opts, xfail)
end)
end
M.Runner = Runner
return M