echo_spec.lua 14 KB

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