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