echo_spec.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local eq = t.eq
  4. local NIL = vim.NIL
  5. local eval = n.eval
  6. local clear = n.clear
  7. local api = n.api
  8. local fn = n.fn
  9. local source = n.source
  10. local dedent = t.dedent
  11. local command = n.command
  12. local exc_exec = n.exc_exec
  13. local exec_capture = n.exec_capture
  14. local matches = t.matches
  15. describe(':echo :echon :echomsg :echoerr', function()
  16. local fn_tbl = { 'String', 'StringN', 'StringMsg', 'StringErr' }
  17. local function assert_same_echo_dump(expected, input, use_eval)
  18. for _, v in pairs(fn_tbl) do
  19. eq(expected, use_eval and eval(v .. '(' .. input .. ')') or fn[v](input))
  20. end
  21. end
  22. local function assert_matches_echo_dump(expected, input, use_eval)
  23. for _, v in pairs(fn_tbl) do
  24. matches(expected, use_eval and eval(v .. '(' .. input .. ')') or fn[v](input))
  25. end
  26. end
  27. before_each(function()
  28. clear()
  29. source([[
  30. function String(s)
  31. return execute('echo a:s')[1:]
  32. endfunction
  33. function StringMsg(s)
  34. return execute('echomsg a:s')[1:]
  35. endfunction
  36. function StringN(s)
  37. return execute('echon a:s')
  38. endfunction
  39. function StringErr(s)
  40. try
  41. execute 'echoerr a:s'
  42. catch
  43. return substitute(v:exception, '^Vim(echoerr):', '', '')
  44. endtry
  45. endfunction
  46. ]])
  47. end)
  48. describe('used to represent floating-point values', function()
  49. it('dumps NaN values', function()
  50. assert_same_echo_dump("str2float('nan')", "str2float('nan')", true)
  51. end)
  52. it('dumps infinite values', function()
  53. assert_same_echo_dump("str2float('inf')", "str2float('inf')", true)
  54. assert_same_echo_dump("-str2float('inf')", "str2float('-inf')", true)
  55. end)
  56. it('dumps regular values', function()
  57. assert_same_echo_dump('1.5', 1.5)
  58. assert_same_echo_dump('1.56e-20', 1.56000e-020)
  59. assert_same_echo_dump('0.0', '0.0', true)
  60. end)
  61. it('dumps special v: values', function()
  62. eq('v:true', eval('String(v:true)'))
  63. eq('v:false', eval('String(v:false)'))
  64. eq('v:null', eval('String(v:null)'))
  65. eq('v:true', fn.String(true))
  66. eq('v:false', fn.String(false))
  67. eq('v:null', fn.String(NIL))
  68. eq('v:true', eval('StringMsg(v:true)'))
  69. eq('v:false', eval('StringMsg(v:false)'))
  70. eq('v:null', eval('StringMsg(v:null)'))
  71. eq('v:true', fn.StringMsg(true))
  72. eq('v:false', fn.StringMsg(false))
  73. eq('v:null', fn.StringMsg(NIL))
  74. eq('v:true', eval('StringErr(v:true)'))
  75. eq('v:false', eval('StringErr(v:false)'))
  76. eq('v:null', eval('StringErr(v:null)'))
  77. eq('v:true', fn.StringErr(true))
  78. eq('v:false', fn.StringErr(false))
  79. eq('v:null', fn.StringErr(NIL))
  80. end)
  81. it('dumps values with at most six digits after the decimal point', function()
  82. assert_same_echo_dump('1.234568e-20', 1.23456789123456789123456789e-020)
  83. assert_same_echo_dump('1.234568', 1.23456789123456789123456789)
  84. end)
  85. it('dumps values with at most seven digits before the decimal point', function()
  86. assert_same_echo_dump('1234567.891235', 1234567.89123456789123456789)
  87. assert_same_echo_dump('1.234568e7', 12345678.9123456789123456789)
  88. end)
  89. it('dumps negative values', function()
  90. assert_same_echo_dump('-1.5', -1.5)
  91. assert_same_echo_dump('-1.56e-20', -1.56000e-020)
  92. assert_same_echo_dump('-1.234568e-20', -1.23456789123456789123456789e-020)
  93. assert_same_echo_dump('-1.234568', -1.23456789123456789123456789)
  94. assert_same_echo_dump('-1234567.891235', -1234567.89123456789123456789)
  95. assert_same_echo_dump('-1.234568e7', -12345678.9123456789123456789)
  96. end)
  97. end)
  98. describe('used to represent numbers', function()
  99. it('dumps regular values', function()
  100. assert_same_echo_dump('0', 0)
  101. assert_same_echo_dump('-1', -1)
  102. assert_same_echo_dump('1', 1)
  103. end)
  104. it('dumps large values', function()
  105. assert_same_echo_dump('2147483647', 2 ^ 31 - 1)
  106. assert_same_echo_dump('-2147483648', -2 ^ 31)
  107. end)
  108. end)
  109. describe('used to represent strings', function()
  110. it('dumps regular strings', function()
  111. assert_same_echo_dump('test', 'test')
  112. end)
  113. it('dumps empty strings', function()
  114. assert_same_echo_dump('', '')
  115. end)
  116. it("dumps strings with ' inside", function()
  117. assert_same_echo_dump("'''", "'''")
  118. assert_same_echo_dump("a'b''", "a'b''")
  119. assert_same_echo_dump("'b''d", "'b''d")
  120. assert_same_echo_dump("a'b'c'd", "a'b'c'd")
  121. end)
  122. it('dumps NULL strings', function()
  123. assert_same_echo_dump('', '$XXX_UNEXISTENT_VAR_XXX', true)
  124. end)
  125. it('dumps NULL lists', function()
  126. assert_same_echo_dump('[]', 'v:_null_list', true)
  127. end)
  128. it('dumps NULL dictionaries', function()
  129. assert_same_echo_dump('{}', 'v:_null_dict', true)
  130. end)
  131. end)
  132. describe('used to represent funcrefs', function()
  133. before_each(function()
  134. source([[
  135. function Test1()
  136. endfunction
  137. function s:Test2() dict
  138. endfunction
  139. function g:Test3() dict
  140. endfunction
  141. let g:Test2_f = function('s:Test2')
  142. ]])
  143. end)
  144. it('dumps references to built-in functions', function()
  145. eq('function', eval('String(function("function"))'))
  146. eq("function('function')", eval('StringMsg(function("function"))'))
  147. eq("function('function')", eval('StringErr(function("function"))'))
  148. end)
  149. it('dumps references to user functions', function()
  150. eq('Test1', eval('String(function("Test1"))'))
  151. eq('g:Test3', eval('String(function("g:Test3"))'))
  152. eq("function('Test1')", eval("StringMsg(function('Test1'))"))
  153. eq("function('g:Test3')", eval("StringMsg(function('g:Test3'))"))
  154. eq("function('Test1')", eval("StringErr(function('Test1'))"))
  155. eq("function('g:Test3')", eval("StringErr(function('g:Test3'))"))
  156. end)
  157. it('dumps references to script functions', function()
  158. eq('<SNR>1_Test2', eval('String(Test2_f)'))
  159. eq("function('<SNR>1_Test2')", eval('StringMsg(Test2_f)'))
  160. eq("function('<SNR>1_Test2')", eval('StringErr(Test2_f)'))
  161. end)
  162. it('dump references to lambdas', function()
  163. assert_matches_echo_dump("function%('<lambda>%d+'%)", '{-> 1234}', true)
  164. end)
  165. it('dumps partials with self referencing a partial', function()
  166. source([[
  167. function TestDict() dict
  168. endfunction
  169. let d = {}
  170. let TestDictRef = function('TestDict', d)
  171. let d.tdr = TestDictRef
  172. ]])
  173. eq(
  174. dedent([[
  175. function('TestDict', {'tdr': function('TestDict', {...@1})})]]),
  176. exec_capture('echo String(d.tdr)')
  177. )
  178. end)
  179. it('dumps automatically created partials', function()
  180. assert_same_echo_dump(
  181. "function('<SNR>1_Test2', {'f': function('<SNR>1_Test2')})",
  182. '{"f": Test2_f}.f',
  183. true
  184. )
  185. assert_same_echo_dump(
  186. "function('<SNR>1_Test2', [1], {'f': function('<SNR>1_Test2', [1])})",
  187. '{"f": function(Test2_f, [1])}.f',
  188. true
  189. )
  190. end)
  191. it('dumps manually created partials', function()
  192. assert_same_echo_dump("function('Test3', [1, 2], {})", "function('Test3', [1, 2], {})", true)
  193. assert_same_echo_dump("function('Test3', [1, 2])", "function('Test3', [1, 2])", true)
  194. assert_same_echo_dump("function('Test3', {})", "function('Test3', {})", true)
  195. end)
  196. it('does not crash or halt when dumping partials with reference cycles in self', function()
  197. api.nvim_set_var('d', { v = true })
  198. eq(
  199. dedent(
  200. [[
  201. {'p': function('<SNR>1_Test2', {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]
  202. ),
  203. exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')
  204. )
  205. end)
  206. it('does not show errors when dumping partials referencing the same dictionary', function()
  207. command('let d = {}')
  208. -- Regression for “eval/typval_encode: Dump empty dictionary before
  209. -- checking for refcycle”, results in error.
  210. eq(
  211. "[function('tr', {}), function('tr', {})]",
  212. eval('String([function("tr", d), function("tr", d)])')
  213. )
  214. -- Regression for “eval: Work with reference cycles in partials (self)
  215. -- properly”, results in crash.
  216. eval('extend(d, {"a": 1})')
  217. eq(
  218. "[function('tr', {'a': 1}), function('tr', {'a': 1})]",
  219. eval('String([function("tr", d), function("tr", d)])')
  220. )
  221. end)
  222. it('does not crash or halt when dumping partials with reference cycles in arguments', function()
  223. api.nvim_set_var('l', {})
  224. eval('add(l, l)')
  225. -- Regression: the below line used to crash (add returns original list and
  226. -- there was error in dumping partials). Tested explicitly in
  227. -- test/unit/api/private_t_spec.lua.
  228. eval('add(l, function("Test1", l))')
  229. eq(
  230. dedent(
  231. [=[
  232. function('Test1', [[[...@2], function('Test1', [[...@2]])], function('Test1', [[[...@4], function('Test1', [[...@4]])]])])]=]
  233. ),
  234. exec_capture('echo String(function("Test1", l))')
  235. )
  236. end)
  237. it(
  238. 'does not crash or halt when dumping partials with reference cycles in self and arguments',
  239. function()
  240. api.nvim_set_var('d', { v = true })
  241. api.nvim_set_var('l', {})
  242. eval('add(l, l)')
  243. eval('add(l, function("Test1", l))')
  244. eval('add(l, function("Test1", d))')
  245. eq(
  246. dedent(
  247. [=[
  248. {'p': function('<SNR>1_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]=]
  249. ),
  250. exec_capture(
  251. 'echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))'
  252. )
  253. )
  254. end
  255. )
  256. end)
  257. describe('used to represent lists', function()
  258. it('dumps empty list', function()
  259. assert_same_echo_dump('[]', {})
  260. end)
  261. it('dumps non-empty list', function()
  262. assert_same_echo_dump('[1, 2]', { 1, 2 })
  263. end)
  264. it('dumps nested lists', function()
  265. assert_same_echo_dump('[[[[[]]]]]', { { { { {} } } } })
  266. end)
  267. it('dumps nested non-empty lists', function()
  268. assert_same_echo_dump('[1, [[3, [[5], 4]], 2]]', { 1, { { 3, { { 5 }, 4 } }, 2 } })
  269. end)
  270. it('does not error when dumping recursive lists', function()
  271. api.nvim_set_var('l', {})
  272. eval('add(l, l)')
  273. eq(0, exc_exec('echo String(l)'))
  274. end)
  275. it('dumps recursive lists without error', function()
  276. api.nvim_set_var('l', {})
  277. eval('add(l, l)')
  278. eq('[[...@0]]', exec_capture('echo String(l)'))
  279. eq('[[[...@1]]]', exec_capture('echo String([l])'))
  280. end)
  281. end)
  282. describe('used to represent dictionaries', function()
  283. it('dumps empty dictionary', function()
  284. assert_same_echo_dump('{}', '{}', true)
  285. end)
  286. it('dumps list with two same empty dictionaries, also in partials', function()
  287. command('let d = {}')
  288. assert_same_echo_dump('[{}, {}]', '[d, d]', true)
  289. eq("[function('tr', {}), {}]", eval('String([function("tr", d), d])'))
  290. eq("[{}, function('tr', {})]", eval('String([d, function("tr", d)])'))
  291. end)
  292. it('dumps non-empty dictionary', function()
  293. assert_same_echo_dump("{'t''est': 1}", { ["t'est"] = 1 })
  294. end)
  295. it('does not error when dumping recursive dictionaries', function()
  296. api.nvim_set_var('d', { d = 1 })
  297. eval('extend(d, {"d": d})')
  298. eq(0, exc_exec('echo String(d)'))
  299. end)
  300. it('dumps recursive dictionaries without the error', function()
  301. api.nvim_set_var('d', { d = 1 })
  302. eval('extend(d, {"d": d})')
  303. eq("{'d': {...@0}}", exec_capture('echo String(d)'))
  304. eq("{'out': {'d': {...@1}}}", exec_capture('echo String({"out": d})'))
  305. end)
  306. end)
  307. describe('used to represent special values', function()
  308. local function chr(_n)
  309. return ('%c'):format(_n)
  310. end
  311. local function ctrl(c)
  312. return ('%c'):format(c:upper():byte() - 0x40)
  313. end
  314. it('displays hex as hex', function()
  315. -- Regression: due to missing (uint8_t) cast \x80 was represented as
  316. -- ~@<80>.
  317. eq('<80>', fn.String(chr(0x80)))
  318. eq('<81>', fn.String(chr(0x81)))
  319. eq('<8e>', fn.String(chr(0x8e)))
  320. eq('<c2>', fn.String(('«'):sub(1, 1)))
  321. eq('«', fn.String(('«'):sub(1, 2)))
  322. eq('<80>', fn.StringMsg(chr(0x80)))
  323. eq('<81>', fn.StringMsg(chr(0x81)))
  324. eq('<8e>', fn.StringMsg(chr(0x8e)))
  325. eq('<c2>', fn.StringMsg(('«'):sub(1, 1)))
  326. eq('«', fn.StringMsg(('«'):sub(1, 2)))
  327. end)
  328. it('displays ASCII control characters using ^X notation', function()
  329. eq('^C', fn.String(ctrl('c')))
  330. eq('^A', fn.String(ctrl('a')))
  331. eq('^F', fn.String(ctrl('f')))
  332. eq('^C', fn.StringMsg(ctrl('c')))
  333. eq('^A', fn.StringMsg(ctrl('a')))
  334. eq('^F', fn.StringMsg(ctrl('f')))
  335. end)
  336. it('prints CR, NL and tab as-is', function()
  337. eq('\n', fn.String('\n'))
  338. eq('\r', fn.String('\r'))
  339. eq('\t', fn.String('\t'))
  340. end)
  341. it('prints non-printable UTF-8 in <> notation', function()
  342. -- SINGLE SHIFT TWO, unicode control
  343. eq('<8e>', fn.String(fn.nr2char(0x8E)))
  344. eq('<8e>', fn.StringMsg(fn.nr2char(0x8E)))
  345. -- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as
  346. -- 0xD83C 0xDCA0. This is not valid in UTF-8.
  347. eq('<d83c>', fn.String(fn.nr2char(0xD83C)))
  348. eq('<dca0>', fn.String(fn.nr2char(0xDCA0)))
  349. eq('<d83c><dca0>', fn.String(fn.nr2char(0xD83C) .. fn.nr2char(0xDCA0)))
  350. eq('<d83c>', fn.StringMsg(fn.nr2char(0xD83C)))
  351. eq('<dca0>', fn.StringMsg(fn.nr2char(0xDCA0)))
  352. eq('<d83c><dca0>', fn.StringMsg(fn.nr2char(0xD83C) .. fn.nr2char(0xDCA0)))
  353. end)
  354. end)
  355. end)