neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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)