123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- local t = require('test.testutil')
- local n = require('test.functional.testnvim')()
- local eq = t.eq
- local NIL = vim.NIL
- local eval = n.eval
- local clear = n.clear
- local api = n.api
- local fn = n.fn
- local source = n.source
- local dedent = t.dedent
- local command = n.command
- local exc_exec = n.exc_exec
- local exec_capture = n.exec_capture
- local matches = t.matches
- describe(':echo :echon :echomsg :echoerr', function()
- local fn_tbl = { 'String', 'StringN', 'StringMsg', 'StringErr' }
- local function assert_same_echo_dump(expected, input, use_eval)
- for _, v in pairs(fn_tbl) do
- eq(expected, use_eval and eval(v .. '(' .. input .. ')') or fn[v](input))
- end
- end
- local function assert_matches_echo_dump(expected, input, use_eval)
- for _, v in pairs(fn_tbl) do
- matches(expected, use_eval and eval(v .. '(' .. input .. ')') or fn[v](input))
- end
- end
- before_each(function()
- clear()
- source([[
- function String(s)
- return execute('echo a:s')[1:]
- endfunction
- function StringMsg(s)
- return execute('echomsg a:s')[1:]
- endfunction
- function StringN(s)
- return execute('echon a:s')
- endfunction
- function StringErr(s)
- try
- execute 'echoerr a:s'
- catch
- return substitute(v:exception, '^Vim(echoerr):', '', '')
- endtry
- endfunction
- ]])
- end)
- describe('used to represent floating-point values', function()
- it('dumps NaN values', function()
- assert_same_echo_dump("str2float('nan')", "str2float('nan')", true)
- end)
- it('dumps infinite values', function()
- assert_same_echo_dump("str2float('inf')", "str2float('inf')", true)
- assert_same_echo_dump("-str2float('inf')", "str2float('-inf')", true)
- end)
- it('dumps regular values', function()
- assert_same_echo_dump('1.5', 1.5)
- assert_same_echo_dump('1.56e-20', 1.56000e-020)
- assert_same_echo_dump('0.0', '0.0', true)
- end)
- it('dumps special v: values', function()
- eq('v:true', eval('String(v:true)'))
- eq('v:false', eval('String(v:false)'))
- eq('v:null', eval('String(v:null)'))
- eq('v:true', fn.String(true))
- eq('v:false', fn.String(false))
- eq('v:null', fn.String(NIL))
- eq('v:true', eval('StringMsg(v:true)'))
- eq('v:false', eval('StringMsg(v:false)'))
- eq('v:null', eval('StringMsg(v:null)'))
- eq('v:true', fn.StringMsg(true))
- eq('v:false', fn.StringMsg(false))
- eq('v:null', fn.StringMsg(NIL))
- eq('v:true', eval('StringErr(v:true)'))
- eq('v:false', eval('StringErr(v:false)'))
- eq('v:null', eval('StringErr(v:null)'))
- eq('v:true', fn.StringErr(true))
- eq('v:false', fn.StringErr(false))
- eq('v:null', fn.StringErr(NIL))
- end)
- it('dumps values with at most six digits after the decimal point', function()
- assert_same_echo_dump('1.234568e-20', 1.23456789123456789123456789e-020)
- assert_same_echo_dump('1.234568', 1.23456789123456789123456789)
- end)
- it('dumps values with at most seven digits before the decimal point', function()
- assert_same_echo_dump('1234567.891235', 1234567.89123456789123456789)
- assert_same_echo_dump('1.234568e7', 12345678.9123456789123456789)
- end)
- it('dumps negative values', function()
- assert_same_echo_dump('-1.5', -1.5)
- assert_same_echo_dump('-1.56e-20', -1.56000e-020)
- assert_same_echo_dump('-1.234568e-20', -1.23456789123456789123456789e-020)
- assert_same_echo_dump('-1.234568', -1.23456789123456789123456789)
- assert_same_echo_dump('-1234567.891235', -1234567.89123456789123456789)
- assert_same_echo_dump('-1.234568e7', -12345678.9123456789123456789)
- end)
- end)
- describe('used to represent numbers', function()
- it('dumps regular values', function()
- assert_same_echo_dump('0', 0)
- assert_same_echo_dump('-1', -1)
- assert_same_echo_dump('1', 1)
- end)
- it('dumps large values', function()
- assert_same_echo_dump('2147483647', 2 ^ 31 - 1)
- assert_same_echo_dump('-2147483648', -2 ^ 31)
- end)
- end)
- describe('used to represent strings', function()
- it('dumps regular strings', function()
- assert_same_echo_dump('test', 'test')
- end)
- it('dumps empty strings', function()
- assert_same_echo_dump('', '')
- end)
- it("dumps strings with ' inside", function()
- assert_same_echo_dump("'''", "'''")
- assert_same_echo_dump("a'b''", "a'b''")
- assert_same_echo_dump("'b''d", "'b''d")
- assert_same_echo_dump("a'b'c'd", "a'b'c'd")
- end)
- it('dumps NULL strings', function()
- assert_same_echo_dump('', '$XXX_UNEXISTENT_VAR_XXX', true)
- end)
- it('dumps NULL lists', function()
- assert_same_echo_dump('[]', 'v:_null_list', true)
- end)
- it('dumps NULL dictionaries', function()
- assert_same_echo_dump('{}', 'v:_null_dict', true)
- end)
- end)
- describe('used to represent funcrefs', function()
- before_each(function()
- source([[
- function Test1()
- endfunction
- function s:Test2() dict
- endfunction
- function g:Test3() dict
- endfunction
- let g:Test2_f = function('s:Test2')
- ]])
- end)
- it('dumps references to built-in functions', function()
- eq('function', eval('String(function("function"))'))
- eq("function('function')", eval('StringMsg(function("function"))'))
- eq("function('function')", eval('StringErr(function("function"))'))
- end)
- it('dumps references to user functions', function()
- eq('Test1', eval('String(function("Test1"))'))
- eq('g:Test3', eval('String(function("g:Test3"))'))
- eq("function('Test1')", eval("StringMsg(function('Test1'))"))
- eq("function('g:Test3')", eval("StringMsg(function('g:Test3'))"))
- eq("function('Test1')", eval("StringErr(function('Test1'))"))
- eq("function('g:Test3')", eval("StringErr(function('g:Test3'))"))
- end)
- it('dumps references to script functions', function()
- eq('<SNR>1_Test2', eval('String(Test2_f)'))
- eq("function('<SNR>1_Test2')", eval('StringMsg(Test2_f)'))
- eq("function('<SNR>1_Test2')", eval('StringErr(Test2_f)'))
- end)
- it('dump references to lambdas', function()
- assert_matches_echo_dump("function%('<lambda>%d+'%)", '{-> 1234}', true)
- end)
- it('dumps partials with self referencing a partial', function()
- source([[
- function TestDict() dict
- endfunction
- let d = {}
- let TestDictRef = function('TestDict', d)
- let d.tdr = TestDictRef
- ]])
- eq(
- dedent([[
- function('TestDict', {'tdr': function('TestDict', {...@1})})]]),
- exec_capture('echo String(d.tdr)')
- )
- end)
- it('dumps automatically created partials', function()
- assert_same_echo_dump(
- "function('<SNR>1_Test2', {'f': function('<SNR>1_Test2')})",
- '{"f": Test2_f}.f',
- true
- )
- assert_same_echo_dump(
- "function('<SNR>1_Test2', [1], {'f': function('<SNR>1_Test2', [1])})",
- '{"f": function(Test2_f, [1])}.f',
- true
- )
- end)
- it('dumps manually created partials', function()
- assert_same_echo_dump("function('Test3', [1, 2], {})", "function('Test3', [1, 2], {})", true)
- assert_same_echo_dump("function('Test3', [1, 2])", "function('Test3', [1, 2])", true)
- assert_same_echo_dump("function('Test3', {})", "function('Test3', {})", true)
- end)
- it('does not crash or halt when dumping partials with reference cycles in self', function()
- api.nvim_set_var('d', { v = true })
- eq(
- dedent(
- [[
- {'p': function('<SNR>1_Test2', {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]
- ),
- exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')
- )
- end)
- it('does not show errors when dumping partials referencing the same dictionary', function()
- command('let d = {}')
- -- Regression for “eval/typval_encode: Dump empty dictionary before
- -- checking for refcycle”, results in error.
- eq(
- "[function('tr', {}), function('tr', {})]",
- eval('String([function("tr", d), function("tr", d)])')
- )
- -- Regression for “eval: Work with reference cycles in partials (self)
- -- properly”, results in crash.
- eval('extend(d, {"a": 1})')
- eq(
- "[function('tr', {'a': 1}), function('tr', {'a': 1})]",
- eval('String([function("tr", d), function("tr", d)])')
- )
- end)
- it('does not crash or halt when dumping partials with reference cycles in arguments', function()
- api.nvim_set_var('l', {})
- eval('add(l, l)')
- -- Regression: the below line used to crash (add returns original list and
- -- there was error in dumping partials). Tested explicitly in
- -- test/unit/api/private_t_spec.lua.
- eval('add(l, function("Test1", l))')
- eq(
- dedent(
- [=[
- function('Test1', [[[...@2], function('Test1', [[...@2]])], function('Test1', [[[...@4], function('Test1', [[...@4]])]])])]=]
- ),
- exec_capture('echo String(function("Test1", l))')
- )
- end)
- it(
- 'does not crash or halt when dumping partials with reference cycles in self and arguments',
- function()
- api.nvim_set_var('d', { v = true })
- api.nvim_set_var('l', {})
- eval('add(l, l)')
- eval('add(l, function("Test1", l))')
- eval('add(l, function("Test1", d))')
- eq(
- dedent(
- [=[
- {'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}]=]
- ),
- exec_capture(
- 'echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))'
- )
- )
- end
- )
- end)
- describe('used to represent lists', function()
- it('dumps empty list', function()
- assert_same_echo_dump('[]', {})
- end)
- it('dumps non-empty list', function()
- assert_same_echo_dump('[1, 2]', { 1, 2 })
- end)
- it('dumps nested lists', function()
- assert_same_echo_dump('[[[[[]]]]]', { { { { {} } } } })
- end)
- it('dumps nested non-empty lists', function()
- assert_same_echo_dump('[1, [[3, [[5], 4]], 2]]', { 1, { { 3, { { 5 }, 4 } }, 2 } })
- end)
- it('does not error when dumping recursive lists', function()
- api.nvim_set_var('l', {})
- eval('add(l, l)')
- eq(0, exc_exec('echo String(l)'))
- end)
- it('dumps recursive lists without error', function()
- api.nvim_set_var('l', {})
- eval('add(l, l)')
- eq('[[...@0]]', exec_capture('echo String(l)'))
- eq('[[[...@1]]]', exec_capture('echo String([l])'))
- end)
- end)
- describe('used to represent dictionaries', function()
- it('dumps empty dictionary', function()
- assert_same_echo_dump('{}', '{}', true)
- end)
- it('dumps list with two same empty dictionaries, also in partials', function()
- command('let d = {}')
- assert_same_echo_dump('[{}, {}]', '[d, d]', true)
- eq("[function('tr', {}), {}]", eval('String([function("tr", d), d])'))
- eq("[{}, function('tr', {})]", eval('String([d, function("tr", d)])'))
- end)
- it('dumps non-empty dictionary', function()
- assert_same_echo_dump("{'t''est': 1}", { ["t'est"] = 1 })
- end)
- it('does not error when dumping recursive dictionaries', function()
- api.nvim_set_var('d', { d = 1 })
- eval('extend(d, {"d": d})')
- eq(0, exc_exec('echo String(d)'))
- end)
- it('dumps recursive dictionaries without the error', function()
- api.nvim_set_var('d', { d = 1 })
- eval('extend(d, {"d": d})')
- eq("{'d': {...@0}}", exec_capture('echo String(d)'))
- eq("{'out': {'d': {...@1}}}", exec_capture('echo String({"out": d})'))
- end)
- end)
- describe('used to represent special values', function()
- local function chr(_n)
- return ('%c'):format(_n)
- end
- local function ctrl(c)
- return ('%c'):format(c:upper():byte() - 0x40)
- end
- it('displays hex as hex', function()
- -- Regression: due to missing (uint8_t) cast \x80 was represented as
- -- ~@<80>.
- eq('<80>', fn.String(chr(0x80)))
- eq('<81>', fn.String(chr(0x81)))
- eq('<8e>', fn.String(chr(0x8e)))
- eq('<c2>', fn.String(('«'):sub(1, 1)))
- eq('«', fn.String(('«'):sub(1, 2)))
- eq('<80>', fn.StringMsg(chr(0x80)))
- eq('<81>', fn.StringMsg(chr(0x81)))
- eq('<8e>', fn.StringMsg(chr(0x8e)))
- eq('<c2>', fn.StringMsg(('«'):sub(1, 1)))
- eq('«', fn.StringMsg(('«'):sub(1, 2)))
- end)
- it('displays ASCII control characters using ^X notation', function()
- eq('^C', fn.String(ctrl('c')))
- eq('^A', fn.String(ctrl('a')))
- eq('^F', fn.String(ctrl('f')))
- eq('^C', fn.StringMsg(ctrl('c')))
- eq('^A', fn.StringMsg(ctrl('a')))
- eq('^F', fn.StringMsg(ctrl('f')))
- end)
- it('prints CR, NL and tab as-is', function()
- eq('\n', fn.String('\n'))
- eq('\r', fn.String('\r'))
- eq('\t', fn.String('\t'))
- end)
- it('prints non-printable UTF-8 in <> notation', function()
- -- SINGLE SHIFT TWO, unicode control
- eq('<8e>', fn.String(fn.nr2char(0x8E)))
- eq('<8e>', fn.StringMsg(fn.nr2char(0x8E)))
- -- Surrogate pair: U+1F0A0 PLAYING CARD BACK is represented in UTF-16 as
- -- 0xD83C 0xDCA0. This is not valid in UTF-8.
- eq('<d83c>', fn.String(fn.nr2char(0xD83C)))
- eq('<dca0>', fn.String(fn.nr2char(0xDCA0)))
- eq('<d83c><dca0>', fn.String(fn.nr2char(0xD83C) .. fn.nr2char(0xDCA0)))
- eq('<d83c>', fn.StringMsg(fn.nr2char(0xD83C)))
- eq('<dca0>', fn.StringMsg(fn.nr2char(0xDCA0)))
- eq('<d83c><dca0>', fn.StringMsg(fn.nr2char(0xD83C) .. fn.nr2char(0xDCA0)))
- end)
- end)
- end)
|