version_spec.lua 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local clear, fn, eq = n.clear, n.fn, t.eq
  4. local api = n.api
  5. local function read_mpack_file(fname)
  6. local fd = io.open(fname, 'rb')
  7. if fd == nil then
  8. return nil
  9. end
  10. local data = fd:read('*a')
  11. fd:close()
  12. local unpack = vim.mpack.Unpacker()
  13. return unpack(data)
  14. end
  15. describe("api_info()['version']", function()
  16. before_each(clear)
  17. it('returns API level', function()
  18. local version = fn.api_info()['version']
  19. local current = version['api_level']
  20. local compat = version['api_compatible']
  21. eq('number', type(current))
  22. eq('number', type(compat))
  23. assert(current >= compat)
  24. end)
  25. it('returns Nvim version', function()
  26. local version = fn.api_info()['version']
  27. local major = version['major']
  28. local minor = version['minor']
  29. local patch = version['patch']
  30. local prerelease = version['prerelease']
  31. local build = version['build']
  32. eq('number', type(major))
  33. eq('number', type(minor))
  34. eq('number', type(patch))
  35. eq('boolean', type(prerelease))
  36. eq(1, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. patch))
  37. eq(0, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. (patch + 1)))
  38. eq(0, fn.has('nvim-' .. major .. '.' .. (minor + 1) .. '.' .. patch))
  39. eq(0, fn.has('nvim-' .. (major + 1) .. '.' .. minor .. '.' .. patch))
  40. assert(build == nil or type(build) == 'string')
  41. end)
  42. end)
  43. describe('api metadata', function()
  44. before_each(clear)
  45. local function name_table(entries)
  46. local by_name = {}
  47. for _, e in ipairs(entries) do
  48. by_name[e.name] = e
  49. end
  50. return by_name
  51. end
  52. -- Remove or patch metadata that is not essential to backwards-compatibility.
  53. local function normalize_func_metadata(f)
  54. -- Dictionary was renamed to Dict. That doesn't break back-compat because clients don't actually
  55. -- use the `return_type` field (evidence: "ArrayOf(…)" didn't break clients).
  56. f.return_type = f.return_type:gsub('Dictionary', 'Dict')
  57. f.deprecated_since = nil
  58. for idx, _ in ipairs(f.parameters) do
  59. -- Dictionary was renamed to Dict. Doesn't break back-compat because clients don't actually
  60. -- use the `parameters` field of API metadata (evidence: "ArrayOf(…)" didn't break clients).
  61. f.parameters[idx][1] = f.parameters[idx][1]:gsub('Dictionary', 'Dict')
  62. f.parameters[idx][2] = '' -- Remove parameter name.
  63. end
  64. if string.sub(f.name, 1, 4) ~= 'nvim' then
  65. f.method = nil
  66. end
  67. return f
  68. end
  69. local function check_ui_event_compatible(old_e, new_e)
  70. -- check types of existing params are the same
  71. -- adding parameters is ok, but removing params is not (gives nil error)
  72. eq(old_e.since, new_e.since, old_e.name)
  73. for i, p in ipairs(old_e.parameters) do
  74. eq(new_e.parameters[i][1], p[1], old_e.name)
  75. end
  76. end
  77. -- Level 0 represents methods from 0.1.5 and earlier, when 'since' was not
  78. -- yet defined, and metadata was not filtered of internal keys like 'async'.
  79. local function clean_level_0(metadata)
  80. for _, f in ipairs(metadata.functions) do
  81. f.can_fail = nil
  82. f.async = nil
  83. f.receives_channel_id = nil
  84. f.since = 0
  85. end
  86. end
  87. local api_info, compat, stable, api_level
  88. local old_api = {}
  89. setup(function()
  90. clear() -- Ensure a session before requesting api_info.
  91. api_info = api.nvim_get_api_info()[2]
  92. compat = api_info.version.api_compatible
  93. api_level = api_info.version.api_level
  94. if api_info.version.api_prerelease then
  95. stable = api_level - 1
  96. else
  97. stable = api_level
  98. end
  99. for level = compat, stable do
  100. local path = ('test/functional/fixtures/api_level_' .. tostring(level) .. '.mpack')
  101. old_api[level] = read_mpack_file(path)
  102. if old_api[level] == nil then
  103. local errstr = 'missing metadata fixture for stable level ' .. level .. '. '
  104. if level == api_level and not api_info.version.api_prerelease then
  105. errstr = (
  106. errstr
  107. .. 'If NVIM_API_CURRENT was bumped, '
  108. .. "don't forget to set NVIM_API_PRERELEASE to true."
  109. )
  110. end
  111. error(errstr)
  112. end
  113. if level == 0 then
  114. clean_level_0(old_api[level])
  115. end
  116. end
  117. end)
  118. it('functions are compatible with old metadata or have new level', function()
  119. local funcs_new = name_table(api_info.functions)
  120. local funcs_compat = {}
  121. for level = compat, stable do
  122. for _, f in ipairs(old_api[level].functions) do
  123. if funcs_new[f.name] == nil then
  124. if f.since >= compat then
  125. error(
  126. 'function '
  127. .. f.name
  128. .. ' was removed but exists in level '
  129. .. f.since
  130. .. ' which nvim should be compatible with'
  131. )
  132. end
  133. else
  134. eq(normalize_func_metadata(f), normalize_func_metadata(funcs_new[f.name]))
  135. end
  136. end
  137. funcs_compat[level] = name_table(old_api[level].functions)
  138. end
  139. for _, f in ipairs(api_info.functions) do
  140. if f.since <= stable then
  141. local f_old = funcs_compat[f.since][f.name]
  142. if f_old == nil then
  143. if string.sub(f.name, 1, 4) == 'nvim' then
  144. local errstr = (
  145. 'function '
  146. .. f.name
  147. .. ' has too low since value. '
  148. .. 'For new functions set it to '
  149. .. (stable + 1)
  150. .. '.'
  151. )
  152. if not api_info.version.api_prerelease then
  153. errstr = (
  154. errstr
  155. .. ' Also bump NVIM_API_CURRENT and set '
  156. .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.'
  157. )
  158. end
  159. error(errstr)
  160. else
  161. error("function name '" .. f.name .. "' doesn't begin with 'nvim_'")
  162. end
  163. end
  164. elseif f.since > api_level then
  165. if api_info.version.api_prerelease then
  166. error('New function ' .. f.name .. ' should use since value ' .. api_level)
  167. else
  168. error(
  169. 'function '
  170. .. f.name
  171. .. ' has since value > api_level. '
  172. .. 'Bump NVIM_API_CURRENT and set '
  173. .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.'
  174. )
  175. end
  176. end
  177. end
  178. end)
  179. it('UI events are compatible with old metadata or have new level', function()
  180. local ui_events_new = name_table(api_info.ui_events)
  181. local ui_events_compat = {}
  182. -- UI events were formalized in level 3
  183. for level = 3, stable do
  184. for _, e in ipairs(old_api[level].ui_events) do
  185. local new_e = ui_events_new[e.name]
  186. if new_e ~= nil then
  187. check_ui_event_compatible(e, new_e)
  188. end
  189. end
  190. ui_events_compat[level] = name_table(old_api[level].ui_events)
  191. end
  192. for _, e in ipairs(api_info.ui_events) do
  193. if e.since <= stable then
  194. local e_old = ui_events_compat[e.since][e.name]
  195. if e_old == nil then
  196. local errstr = (
  197. 'UI event '
  198. .. e.name
  199. .. ' has too low since value. '
  200. .. 'For new events set it to '
  201. .. (stable + 1)
  202. .. '.'
  203. )
  204. if not api_info.version.api_prerelease then
  205. errstr = (
  206. errstr
  207. .. ' Also bump NVIM_API_CURRENT and set '
  208. .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.'
  209. )
  210. end
  211. error(errstr)
  212. end
  213. elseif e.since > api_level then
  214. if api_info.version.api_prerelease then
  215. error('New UI event ' .. e.name .. ' should use since value ' .. api_level)
  216. else
  217. error(
  218. 'UI event '
  219. .. e.name
  220. .. ' has since value > api_level. '
  221. .. 'Bump NVIM_API_CURRENT and set '
  222. .. 'NVIM_API_PRERELEASE to true in CMakeLists.txt.'
  223. )
  224. end
  225. end
  226. end
  227. end)
  228. it('ui_options are preserved from older levels', function()
  229. local available_options = {}
  230. for _, option in ipairs(api_info.ui_options) do
  231. available_options[option] = true
  232. end
  233. -- UI options were versioned from level 4
  234. for level = 4, stable do
  235. for _, option in ipairs(old_api[level].ui_options) do
  236. if not available_options[option] then
  237. error('UI option ' .. option .. ' from stable metadata is missing')
  238. end
  239. end
  240. end
  241. end)
  242. end)