local t = require('test.testutil') local n = require('test.functional.testnvim')() local clear, fn, eq = n.clear, n.fn, t.eq local api = n.api local function read_mpack_file(fname) local fd = io.open(fname, 'rb') if fd == nil then return nil end local data = fd:read('*a') fd:close() local unpack = vim.mpack.Unpacker() return unpack(data) end describe("api_info()['version']", function() before_each(clear) it('returns API level', function() local version = fn.api_info()['version'] local current = version['api_level'] local compat = version['api_compatible'] eq('number', type(current)) eq('number', type(compat)) assert(current >= compat) end) it('returns Nvim version', function() local version = fn.api_info()['version'] local major = version['major'] local minor = version['minor'] local patch = version['patch'] local prerelease = version['prerelease'] local build = version['build'] eq('number', type(major)) eq('number', type(minor)) eq('number', type(patch)) eq('boolean', type(prerelease)) eq(1, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. patch)) eq(0, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. (patch + 1))) eq(0, fn.has('nvim-' .. major .. '.' .. (minor + 1) .. '.' .. patch)) eq(0, fn.has('nvim-' .. (major + 1) .. '.' .. minor .. '.' .. patch)) assert(build == vim.NIL or type(build) == 'string') end) end) describe('api metadata', function() before_each(clear) local function name_table(entries) local by_name = {} for _, e in ipairs(entries) do by_name[e.name] = e end return by_name end -- Remove or patch metadata that is not essential to backwards-compatibility. local function normalize_func_metadata(f) -- Dictionary was renamed to Dict. That doesn't break back-compat because clients don't actually -- use the `return_type` field (evidence: "ArrayOf(…)" didn't break clients). f.return_type = f.return_type:gsub('Dictionary', 'Dict') f.deprecated_since = nil for idx, _ in ipairs(f.parameters) do -- Dictionary was renamed to Dict. Doesn't break back-compat because clients don't actually -- use the `parameters` field of API metadata (evidence: "ArrayOf(…)" didn't break clients). f.parameters[idx][1] = f.parameters[idx][1]:gsub('Dictionary', 'Dict') f.parameters[idx][2] = '' -- Remove parameter name. end if string.sub(f.name, 1, 4) ~= 'nvim' then f.method = nil end return f end local function check_ui_event_compatible(old_e, new_e) -- check types of existing params are the same -- adding parameters is ok, but removing params is not (gives nil error) eq(old_e.since, new_e.since, old_e.name) for i, p in ipairs(old_e.parameters) do eq(new_e.parameters[i][1], p[1], old_e.name) end end -- Level 0 represents methods from 0.1.5 and earlier, when 'since' was not -- yet defined, and metadata was not filtered of internal keys like 'async'. local function clean_level_0(metadata) for _, f in ipairs(metadata.functions) do f.can_fail = nil f.async = nil -- XXX: renamed to "fast". f.receives_channel_id = nil f.since = 0 end end local api_info --[[@type table]] local compat --[[@type integer]] local stable --[[@type integer]] local api_level --[[@type integer]] local old_api = {} setup(function() clear() -- Ensure a session before requesting api_info. --[[@type { version: {api_compatible: integer, api_level: integer, api_prerelease: boolean} }]] api_info = api.nvim_get_api_info()[2] compat = api_info.version.api_compatible api_level = api_info.version.api_level stable = api_info.version.api_prerelease and api_level - 1 or api_level for level = compat, stable do local path = ('test/functional/fixtures/api_level_' .. tostring(level) .. '.mpack') old_api[level] = read_mpack_file(path) --[[@type table]] if old_api[level] == nil then local errstr = 'missing metadata fixture for stable level ' .. level .. '. ' if level == api_level and not api_info.version.api_prerelease then errstr = ( errstr .. 'If NVIM_API_CURRENT was bumped, ' .. "don't forget to set NVIM_API_PRERELEASE to true." ) end error(errstr) end if level == 0 then clean_level_0(old_api[level]) end end end) it('functions are compatible with old metadata or have new level', function() local funcs_new = name_table(api_info.functions) local funcs_compat = {} for level = compat, stable do for _, f in ipairs(old_api[level].functions) do if funcs_new[f.name] == nil then if f.since >= compat then error( 'function ' .. f.name .. ' was removed but exists in level ' .. f.since .. ' which nvim should be compatible with' ) end else eq(normalize_func_metadata(f), normalize_func_metadata(funcs_new[f.name])) end end funcs_compat[level] = name_table(old_api[level].functions) end for _, f in ipairs(api_info.functions) do if f.since <= stable then local f_old = funcs_compat[f.since][f.name] if f_old == nil then if string.sub(f.name, 1, 4) == 'nvim' then local errstr = ( 'function ' .. f.name .. ' has too low since value. ' .. 'For new functions set it to ' .. (stable + 1) .. '.' ) if not api_info.version.api_prerelease then errstr = ( errstr .. ' Also bump NVIM_API_CURRENT and set ' .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.' ) end error(errstr) else error("function name '" .. f.name .. "' doesn't begin with 'nvim_'") end end elseif f.since > api_level then if api_info.version.api_prerelease then error('New function ' .. f.name .. ' should use since value ' .. api_level) else error( 'function ' .. f.name .. ' has since value > api_level. ' .. 'Bump NVIM_API_CURRENT and set ' .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.' ) end end end end) it('UI events are compatible with old metadata or have new level', function() local ui_events_new = name_table(api_info.ui_events) local ui_events_compat = {} -- UI events were formalized in level 3 for level = 3, stable do for _, e in ipairs(old_api[level].ui_events) do local new_e = ui_events_new[e.name] if new_e ~= nil then check_ui_event_compatible(e, new_e) end end ui_events_compat[level] = name_table(old_api[level].ui_events) end for _, e in ipairs(api_info.ui_events) do if e.since <= stable then local e_old = ui_events_compat[e.since][e.name] if e_old == nil then local errstr = ( 'UI event ' .. e.name .. ' has too low since value. ' .. 'For new events set it to ' .. (stable + 1) .. '.' ) if not api_info.version.api_prerelease then errstr = ( errstr .. ' Also bump NVIM_API_CURRENT and set ' .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.' ) end error(errstr) end elseif e.since > api_level then if api_info.version.api_prerelease then error('New UI event ' .. e.name .. ' should use since value ' .. api_level) else error( 'UI event ' .. e.name .. ' has since value > api_level. ' .. 'Bump NVIM_API_CURRENT and set ' .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.' ) end end end end) it('ui_options are preserved from older levels', function() local available_options = {} for _, option in ipairs(api_info.ui_options) do available_options[option] = true end -- UI options were versioned from level 4 for level = 4, stable do for _, option in ipairs(old_api[level].ui_options) do if not available_options[option] then error('UI option ' .. option .. ' from stable metadata is missing') end end end end) end)