diff options
Diffstat (limited to 'test/functional/ex_cmds/echo_spec.lua')
-rw-r--r-- | test/functional/ex_cmds/echo_spec.lua | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua new file mode 100644 index 0000000000..10c7230896 --- /dev/null +++ b/test/functional/ex_cmds/echo_spec.lua @@ -0,0 +1,321 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local NIL = helpers.NIL +local eval = helpers.eval +local clear = helpers.clear +local meths = helpers.meths +local funcs = helpers.funcs +local source = helpers.source +local dedent = helpers.dedent +local command = helpers.command +local exc_exec = helpers.exc_exec +local redir_exec = helpers.redir_exec + +describe(':echo', function() + before_each(function() + clear() + source([[ + function String(s) + return execute('echo a:s')[1:] + endfunction + ]]) + end) + + describe('used to represent floating-point values', function() + it('dumps NaN values', function() + eq('str2float(\'nan\')', eval('String(str2float(\'nan\'))')) + end) + + it('dumps infinite values', function() + eq('str2float(\'inf\')', eval('String(str2float(\'inf\'))')) + eq('-str2float(\'inf\')', eval('String(str2float(\'-inf\'))')) + end) + + it('dumps regular values', function() + eq('1.5', funcs.String(1.5)) + eq('1.56e-20', funcs.String(1.56000e-020)) + eq('0.0', eval('String(0.0)')) + 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', funcs.String(true)) + eq('v:false', funcs.String(false)) + eq('v:null', funcs.String(NIL)) + end) + + it('dumps values with at most six digits after the decimal point', + function() + eq('1.234568e-20', funcs.String(1.23456789123456789123456789e-020)) + eq('1.234568', funcs.String(1.23456789123456789123456789)) + end) + + it('dumps values with at most seven digits before the decimal point', + function() + eq('1234567.891235', funcs.String(1234567.89123456789123456789)) + eq('1.234568e7', funcs.String(12345678.9123456789123456789)) + end) + + it('dumps negative values', function() + eq('-1.5', funcs.String(-1.5)) + eq('-1.56e-20', funcs.String(-1.56000e-020)) + eq('-1.234568e-20', funcs.String(-1.23456789123456789123456789e-020)) + eq('-1.234568', funcs.String(-1.23456789123456789123456789)) + eq('-1234567.891235', funcs.String(-1234567.89123456789123456789)) + eq('-1.234568e7', funcs.String(-12345678.9123456789123456789)) + end) + end) + + describe('used to represent numbers', function() + it('dumps regular values', function() + eq('0', funcs.String(0)) + eq('-1', funcs.String(-1)) + eq('1', funcs.String(1)) + end) + + it('dumps large values', function() + eq('2147483647', funcs.String(2^31-1)) + eq('-2147483648', funcs.String(-2^31)) + end) + end) + + describe('used to represent strings', function() + it('dumps regular strings', function() + eq('test', funcs.String('test')) + end) + + it('dumps empty strings', function() + eq('', funcs.String('')) + end) + + it('dumps strings with \' inside', function() + eq('\'\'\'', funcs.String('\'\'\'')) + eq('a\'b\'\'', funcs.String('a\'b\'\'')) + eq('\'b\'\'d', funcs.String('\'b\'\'d')) + eq('a\'b\'c\'d', funcs.String('a\'b\'c\'d')) + end) + + it('dumps NULL strings', function() + eq('', eval('String($XXX_UNEXISTENT_VAR_XXX)')) + end) + + it('dumps NULL lists', function() + eq('[]', eval('String(v:_null_list)')) + end) + + it('dumps NULL dictionaries', function() + eq('{}', eval('String(v:_null_dict)')) + 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"))')) + end) + + it('dumps references to user functions', function() + eq('Test1', eval('String(function("Test1"))')) + eq('g:Test3', eval('String(function("g:Test3"))')) + end) + + it('dumps references to script functions', function() + eq('<SNR>2_Test2', eval('String(Test2_f)')) + 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})}) + function('TestDict', {'tdr': function('TestDict', {...@1})})]]), + redir_exec('echo String(d.tdr)')) + end) + + it('dumps automatically created partials', function() + eq('function(\'<SNR>2_Test2\', {\'f\': function(\'<SNR>2_Test2\')})', + eval('String({"f": Test2_f}.f)')) + eq('function(\'<SNR>2_Test2\', [1], {\'f\': function(\'<SNR>2_Test2\', [1])})', + eval('String({"f": function(Test2_f, [1])}.f)')) + end) + + it('dumps manually created partials', function() + eq('function(\'Test3\', [1, 2], {})', + eval('String(function("Test3", [1, 2], {}))')) + eq('function(\'Test3\', {})', + eval('String(function("Test3", {}))')) + eq('function(\'Test3\', [1, 2])', + eval('String(function("Test3", [1, 2]))')) + end) + + it('does not crash or halt when dumping partials with reference cycles in self', + function() + meths.set_var('d', {v=true}) + eq(dedent([[ + + {'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true} + {'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]]), + redir_exec('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() + meths.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_helpers_spec.lua. + eval('add(l, function("Test1", l))') + eq(dedent([=[ + + function('Test1', [[[...@2], function('Test1', [[...@2]])], function('Test1', [[[...@4], function('Test1', [[...@4]])]])]) + function('Test1', [[[...@2], function('Test1', [[...@2]])], function('Test1', [[[...@4], function('Test1', [[...@4]])]])])]=]), + redir_exec('echo String(function("Test1", l))')) + end) + + it('does not crash or halt when dumping partials with reference cycles in self and arguments', + function() + meths.set_var('d', {v=true}) + meths.set_var('l', {}) + eval('add(l, l)') + eval('add(l, function("Test1", l))') + eval('add(l, function("Test1", d))') + eq(dedent([=[ + + {'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true} + {'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]=]), + redir_exec('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() + eq('[]', funcs.String({})) + end) + + it('dumps nested lists', function() + eq('[[[[[]]]]]', funcs.String({{{{{}}}}})) + end) + + it('dumps nested non-empty lists', function() + eq('[1, [[3, [[5], 4]], 2]]', funcs.String({1, {{3, {{5}, 4}}, 2}})) + end) + + it('does not error when dumping recursive lists', function() + meths.set_var('l', {}) + eval('add(l, l)') + eq(0, exc_exec('echo String(l)')) + end) + + it('dumps recursive lists without error', function() + meths.set_var('l', {}) + eval('add(l, l)') + eq('\n[[...@0]]\n[[...@0]]', redir_exec('echo String(l)')) + eq('\n[[[...@1]]]\n[[[...@1]]]', redir_exec('echo String([l])')) + end) + end) + + describe('used to represent dictionaries', function() + it('dumps empty dictionary', function() + eq('{}', eval('String({})')) + end) + + it('dumps list with two same empty dictionaries, also in partials', function() + command('let d = {}') + eq('[{}, {}]', eval('String([d, d])')) + 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() + eq('{\'t\'\'est\': 1}', funcs.String({['t\'est']=1})) + end) + + it('does not error when dumping recursive dictionaries', function() + meths.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() + meths.set_var('d', {d=1}) + eval('extend(d, {"d": d})') + eq('\n{\'d\': {...@0}}\n{\'d\': {...@0}}', + redir_exec('echo String(d)')) + eq('\n{\'out\': {\'d\': {...@1}}}\n{\'out\': {\'d\': {...@1}}}', + redir_exec('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>', funcs.String(chr(0x80))) + eq('<81>', funcs.String(chr(0x81))) + eq('<8e>', funcs.String(chr(0x8e))) + eq('<c2>', funcs.String(('«'):sub(1, 1))) + eq('«', funcs.String(('«'):sub(1, 2))) + end) + it('displays ASCII control characters using ^X notation', function() + eq('^C', funcs.String(ctrl('c'))) + eq('^A', funcs.String(ctrl('a'))) + eq('^F', funcs.String(ctrl('f'))) + end) + it('prints CR, NL and tab as-is', function() + eq('\n', funcs.String('\n')) + eq('\r', funcs.String('\r')) + eq('\t', funcs.String('\t')) + end) + it('prints non-printable UTF-8 in <> notation', function() + -- SINGLE SHIFT TWO, unicode control + eq('<8e>', funcs.String(funcs.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>', funcs.String(funcs.nr2char(0xD83C))) + eq('<dca0>', funcs.String(funcs.nr2char(0xDCA0))) + eq('<d83c><dca0>', funcs.String(funcs.nr2char(0xD83C) .. funcs.nr2char(0xDCA0))) + end) + end) +end) |