diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/functional/api/buffer_spec.lua | 6 | ||||
-rw-r--r-- | test/functional/api/tabpage_spec.lua | 8 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 15 | ||||
-rw-r--r-- | test/functional/api/window_spec.lua | 7 | ||||
-rw-r--r-- | test/functional/eval/json_functions_spec.lua | 799 | ||||
-rw-r--r-- | test/functional/eval/msgpack_functions_spec.lua | 82 | ||||
-rw-r--r-- | test/functional/eval/special_vars_spec.lua | 171 | ||||
-rw-r--r-- | test/functional/eval/string_spec.lua | 21 | ||||
-rw-r--r-- | test/functional/legacy/marks_spec.lua | 53 | ||||
-rw-r--r-- | test/functional/plugin/msgpack_spec.lua | 21 | ||||
-rw-r--r-- | test/functional/shada/variables_spec.lua | 13 | ||||
-rw-r--r-- | test/unit/eval/decode_spec.lua | 142 | ||||
-rw-r--r-- | test/unit/eval/encode_spec.lua | 100 | ||||
-rw-r--r-- | test/unit/eval/helpers.lua | 72 | ||||
-rw-r--r-- | test/unit/eval/tricks_spec.lua | 43 | ||||
-rw-r--r-- | test/unit/helpers.lua | 11 |
16 files changed, 1538 insertions, 26 deletions
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index a15d489a1f..0eefa25a13 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers') local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq local curbufmeths, ok = helpers.curbufmeths, helpers.ok +local funcs = helpers.funcs describe('buffer_* functions', function() before_each(clear) @@ -234,11 +235,14 @@ describe('buffer_* functions', function() end) - describe('{get,set}_var', function() + describe('{get,set,del}_var', function() it('works', function() curbuf('set_var', 'lua', {1, 2, {['3'] = 1}}) eq({1, 2, {['3'] = 1}}, curbuf('get_var', 'lua')) eq({1, 2, {['3'] = 1}}, nvim('eval', 'b:lua')) + eq(1, funcs.exists('b:lua')) + curbufmeths.del_var('lua') + eq(0, funcs.exists('b:lua')) end) end) diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 9937e0c72e..0f59bbea7b 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -3,6 +3,9 @@ local helpers = require('test.functional.helpers') local clear, nvim, tabpage, curtab, eq, ok = helpers.clear, helpers.nvim, helpers.tabpage, helpers.curtab, helpers.eq, helpers.ok +local wait = helpers.wait +local curtabmeths = helpers.curtabmeths +local funcs = helpers.funcs describe('tabpage_* functions', function() before_each(clear) @@ -21,11 +24,14 @@ describe('tabpage_* functions', function() end) end) - describe('{get,set}_var', function() + describe('{get,set,del}_var', function() it('works', function() curtab('set_var', 'lua', {1, 2, {['3'] = 1}}) eq({1, 2, {['3'] = 1}}, curtab('get_var', 'lua')) eq({1, 2, {['3'] = 1}}, nvim('eval', 't:lua')) + eq(1, funcs.exists('t:lua')) + curtabmeths.del_var('lua') + eq(0, funcs.exists('t:lua')) end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 699a0bd588..389badb423 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -4,6 +4,8 @@ local Screen = require('test.functional.ui.screen') local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed local os_name = helpers.os_name +local meths = helpers.meths +local funcs = helpers.funcs describe('vim_* functions', function() before_each(clear) @@ -70,11 +72,14 @@ describe('vim_* functions', function() end) end) - describe('{get,set}_var', function() + describe('{get,set,del}_var', function() it('works', function() nvim('set_var', 'lua', {1, 2, {['3'] = 1}}) eq({1, 2, {['3'] = 1}}, nvim('get_var', 'lua')) eq({1, 2, {['3'] = 1}}, nvim('eval', 'g:lua')) + eq(1, funcs.exists('g:lua')) + meths.del_var('lua') + eq(0, funcs.exists('g:lua')) end) it('set_var returns the old value', function() @@ -84,6 +89,14 @@ describe('vim_* functions', function() eq(val1, nvim('set_var', 'lua', val2)) end) + it('del_var returns the old value', function() + local val1 = {1, 2, {['3'] = 1}} + local val2 = {4, 7} + eq(NIL, meths.set_var('lua', val1)) + eq(val1, meths.set_var('lua', val2)) + eq(val2, meths.del_var('lua')) + end) + it('truncates values with NULs in them', function() nvim('set_var', 'xxx', 'ab\0cd') eq('ab', nvim('get_var', 'xxx')) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 17aacafe9b..92a33b4cdb 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -5,6 +5,8 @@ local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq, helpers.neq, helpers.ok, helpers.feed, helpers.insert, helpers.eval local wait = helpers.wait +local curwinmeths = helpers.curwinmeths +local funcs = helpers.funcs -- check if str is visible at the beginning of some line local function is_visible(str) @@ -126,11 +128,14 @@ describe('window_* functions', function() end) end) - describe('{get,set}_var', function() + describe('{get,set,del}_var', function() it('works', function() curwin('set_var', 'lua', {1, 2, {['3'] = 1}}) eq({1, 2, {['3'] = 1}}, curwin('get_var', 'lua')) eq({1, 2, {['3'] = 1}}, nvim('eval', 'w:lua')) + eq(1, funcs.exists('w:lua')) + curwinmeths.del_var('lua') + eq(0, funcs.exists('w:lua')) end) end) diff --git a/test/functional/eval/json_functions_spec.lua b/test/functional/eval/json_functions_spec.lua new file mode 100644 index 0000000000..1cece78ce1 --- /dev/null +++ b/test/functional/eval/json_functions_spec.lua @@ -0,0 +1,799 @@ +local helpers = require('test.functional.helpers') +local clear = helpers.clear +local funcs = helpers.funcs +local meths = helpers.meths +local eq = helpers.eq +local eval = helpers.eval +local execute = helpers.execute +local exc_exec = helpers.exc_exec +local redir_exec = helpers.redir_exec + +describe('json_decode() function', function() + local restart = function(cmd) + clear(cmd) + execute('language C') + execute([[ + function Eq(exp, act) + let act = a:act + let exp = a:exp + if type(exp) != type(act) + return 0 + endif + if type(exp) == type({}) + if sort(keys(exp)) !=# sort(keys(act)) + return 0 + endif + if sort(keys(exp)) ==# ['_TYPE', '_VAL'] + let exp_typ = v:msgpack_types[exp._TYPE] + let act_typ = act._TYPE + if exp_typ isnot act_typ + return 0 + endif + return Eq(exp._VAL, act._VAL) + else + return empty(filter(copy(exp), '!Eq(v:val, act[v:key])')) + endif + else + if type(exp) == type([]) + if len(exp) != len(act) + return 0 + endif + return empty(filter(copy(exp), '!Eq(v:val, act[v:key])')) + endif + return exp ==# act + endif + return 1 + endfunction + ]]) + execute([[ + function EvalEq(exp, act_expr) + let act = eval(a:act_expr) + if Eq(a:exp, act) + return 1 + else + return string(act) + endif + endfunction + ]]) + end + before_each(restart) + + local speq = function(expected, actual_expr) + eq(1, funcs.EvalEq(expected, actual_expr)) + end + + it('accepts readfile()-style list', function() + eq({Test=1}, funcs.json_decode({ + '{', + '\t"Test": 1', + '}', + })) + end) + + it('accepts strings with newlines', function() + eq({Test=1}, funcs.json_decode([[ + { + "Test": 1 + } + ]])) + end) + + it('parses null, true, false', function() + eq(NIL, funcs.json_decode('null')) + eq(true, funcs.json_decode('true')) + eq(false, funcs.json_decode('false')) + end) + + it('fails to parse incomplete null, true, false', function() + eq('Vim(call):E474: Expected null: n', + exc_exec('call json_decode("n")')) + eq('Vim(call):E474: Expected null: nu', + exc_exec('call json_decode("nu")')) + eq('Vim(call):E474: Expected null: nul', + exc_exec('call json_decode("nul")')) + eq('Vim(call):E474: Expected null: nul\n\t', + exc_exec('call json_decode("nul\\n\\t")')) + + eq('Vim(call):E474: Expected true: t', + exc_exec('call json_decode("t")')) + eq('Vim(call):E474: Expected true: tr', + exc_exec('call json_decode("tr")')) + eq('Vim(call):E474: Expected true: tru', + exc_exec('call json_decode("tru")')) + eq('Vim(call):E474: Expected true: tru\t\n', + exc_exec('call json_decode("tru\\t\\n")')) + + eq('Vim(call):E474: Expected false: f', + exc_exec('call json_decode("f")')) + eq('Vim(call):E474: Expected false: fa', + exc_exec('call json_decode("fa")')) + eq('Vim(call):E474: Expected false: fal', + exc_exec('call json_decode("fal")')) + eq('Vim(call):E474: Expected false: fal <', + exc_exec('call json_decode(" fal <")')) + eq('Vim(call):E474: Expected false: fals', + exc_exec('call json_decode("fals")')) + end) + + it('parses integer numbers', function() + eq(100000, funcs.json_decode('100000')) + eq(-100000, funcs.json_decode('-100000')) + eq(100000, funcs.json_decode(' 100000 ')) + eq(-100000, funcs.json_decode(' -100000 ')) + eq(0, funcs.json_decode('0')) + eq(0, funcs.json_decode('-0')) + end) + + it('fails to parse +numbers and .number', function() + eq('Vim(call):E474: Unidentified byte: +1000', + exc_exec('call json_decode("+1000")')) + eq('Vim(call):E474: Unidentified byte: .1000', + exc_exec('call json_decode(".1000")')) + end) + + it('fails to parse numbers with leading zeroes', function() + eq('Vim(call):E474: Leading zeroes are not allowed: 00.1', + exc_exec('call json_decode("00.1")')) + eq('Vim(call):E474: Leading zeroes are not allowed: 01', + exc_exec('call json_decode("01")')) + eq('Vim(call):E474: Leading zeroes are not allowed: -01', + exc_exec('call json_decode("-01")')) + eq('Vim(call):E474: Leading zeroes are not allowed: -001.0', + exc_exec('call json_decode("-001.0")')) + end) + + it('fails to parse incomplete numbers', function() + eq('Vim(call):E474: Missing number after minus sign: -.1', + exc_exec('call json_decode("-.1")')) + eq('Vim(call):E474: Missing number after minus sign: -', + exc_exec('call json_decode("-")')) + eq('Vim(call):E474: Missing number after decimal dot: -1.', + exc_exec('call json_decode("-1.")')) + eq('Vim(call):E474: Missing number after decimal dot: 0.', + exc_exec('call json_decode("0.")')) + eq('Vim(call):E474: Missing exponent: 0.0e', + exc_exec('call json_decode("0.0e")')) + eq('Vim(call):E474: Missing exponent: 0.0e+', + exc_exec('call json_decode("0.0e+")')) + eq('Vim(call):E474: Missing exponent: 0.0e-', + exc_exec('call json_decode("0.0e-")')) + eq('Vim(call):E474: Missing exponent: 0.0e-', + exc_exec('call json_decode("0.0e-")')) + eq('Vim(call):E474: Missing number after decimal dot: 1.e5', + exc_exec('call json_decode("1.e5")')) + eq('Vim(call):E474: Missing number after decimal dot: 1.e+5', + exc_exec('call json_decode("1.e+5")')) + eq('Vim(call):E474: Missing number after decimal dot: 1.e+', + exc_exec('call json_decode("1.e+")')) + end) + + it('parses floating-point numbers', function() + eq('100000.0', eval('string(json_decode("100000.0"))')) + eq(100000.5, funcs.json_decode('100000.5')) + eq(-100000.5, funcs.json_decode('-100000.5')) + eq(-100000.5e50, funcs.json_decode('-100000.5e50')) + eq(100000.5e50, funcs.json_decode('100000.5e50')) + eq(100000.5e50, funcs.json_decode('100000.5e+50')) + eq(-100000.5e-50, funcs.json_decode('-100000.5e-50')) + eq(100000.5e-50, funcs.json_decode('100000.5e-50')) + eq(100000e-50, funcs.json_decode('100000e-50')) + eq(0.5, funcs.json_decode('0.5')) + eq(0.005, funcs.json_decode('0.005')) + eq(0.005, funcs.json_decode('0.00500')) + eq(0.5, funcs.json_decode('0.00500e+002')) + eq(0.00005, funcs.json_decode('0.00500e-002')) + + eq(-0.0, funcs.json_decode('-0.0')) + eq(-0.0, funcs.json_decode('-0.0e0')) + eq(-0.0, funcs.json_decode('-0.0e+0')) + eq(-0.0, funcs.json_decode('-0.0e-0')) + eq(-0.0, funcs.json_decode('-0e-0')) + eq(-0.0, funcs.json_decode('-0e-2')) + eq(-0.0, funcs.json_decode('-0e+2')) + + eq(0.0, funcs.json_decode('0.0')) + eq(0.0, funcs.json_decode('0.0e0')) + eq(0.0, funcs.json_decode('0.0e+0')) + eq(0.0, funcs.json_decode('0.0e-0')) + eq(0.0, funcs.json_decode('0e-0')) + eq(0.0, funcs.json_decode('0e-2')) + eq(0.0, funcs.json_decode('0e+2')) + end) + + it('fails to parse numbers with spaces inside', function() + eq('Vim(call):E474: Missing number after minus sign: - 1000', + exc_exec('call json_decode("- 1000")')) + eq('Vim(call):E474: Missing number after decimal dot: 0. ', + exc_exec('call json_decode("0. ")')) + eq('Vim(call):E474: Missing number after decimal dot: 0. 0', + exc_exec('call json_decode("0. 0")')) + eq('Vim(call):E474: Missing exponent: 0.0e 1', + exc_exec('call json_decode("0.0e 1")')) + eq('Vim(call):E474: Missing exponent: 0.0e+ 1', + exc_exec('call json_decode("0.0e+ 1")')) + eq('Vim(call):E474: Missing exponent: 0.0e- 1', + exc_exec('call json_decode("0.0e- 1")')) + end) + + it('fails to parse "," and ":"', function() + eq('Vim(call):E474: Comma not inside container: , ', + exc_exec('call json_decode(" , ")')) + eq('Vim(call):E474: Colon not inside container: : ', + exc_exec('call json_decode(" : ")')) + end) + + it('parses empty containers', function() + eq({}, funcs.json_decode('[]')) + eq('[]', eval('string(json_decode("[]"))')) + end) + + it('fails to parse "[" and "{"', function() + eq('Vim(call):E474: Unexpected end of input: {', + exc_exec('call json_decode("{")')) + eq('Vim(call):E474: Unexpected end of input: [', + exc_exec('call json_decode("[")')) + end) + + it('fails to parse "}" and "]"', function() + eq('Vim(call):E474: No container to close: ]', + exc_exec('call json_decode("]")')) + eq('Vim(call):E474: No container to close: }', + exc_exec('call json_decode("}")')) + end) + + it('fails to parse containers which are closed by different brackets', + function() + eq('Vim(call):E474: Closing dictionary with square bracket: ]', + exc_exec('call json_decode("{]")')) + eq('Vim(call):E474: Closing list with curly bracket: }', + exc_exec('call json_decode("[}")')) + end) + + it('fails to parse concat inside container', function() + eq('Vim(call):E474: Expected comma before list item: []]', + exc_exec('call json_decode("[[][]]")')) + eq('Vim(call):E474: Expected comma before list item: {}]', + exc_exec('call json_decode("[{}{}]")')) + eq('Vim(call):E474: Expected comma before list item: ]', + exc_exec('call json_decode("[1 2]")')) + eq('Vim(call):E474: Expected comma before dictionary key: ": 4}', + exc_exec('call json_decode("{\\"1\\": 2 \\"3\\": 4}")')) + eq('Vim(call):E474: Expected colon before dictionary value: , "3" 4}', + exc_exec('call json_decode("{\\"1\\" 2, \\"3\\" 4}")')) + end) + + it('fails to parse containers with leading comma or colon', function() + eq('Vim(call):E474: Leading comma: ,}', + exc_exec('call json_decode("{,}")')) + eq('Vim(call):E474: Leading comma: ,]', + exc_exec('call json_decode("[,]")')) + eq('Vim(call):E474: Using colon not in dictionary: :]', + exc_exec('call json_decode("[:]")')) + eq('Vim(call):E474: Unexpected colon: :}', + exc_exec('call json_decode("{:}")')) + end) + + it('fails to parse containers with trailing comma', function() + eq('Vim(call):E474: Trailing comma: ]', + exc_exec('call json_decode("[1,]")')) + eq('Vim(call):E474: Trailing comma: }', + exc_exec('call json_decode("{\\"1\\": 2,}")')) + end) + + it('fails to parse dictionaries with missing value', function() + eq('Vim(call):E474: Expected value after colon: }', + exc_exec('call json_decode("{\\"1\\":}")')) + eq('Vim(call):E474: Expected value: }', + exc_exec('call json_decode("{\\"1\\"}")')) + end) + + it('fails to parse containers with two commas or colons', function() + eq('Vim(call):E474: Duplicate comma: , "2": 2}', + exc_exec('call json_decode("{\\"1\\": 1,, \\"2\\": 2}")')) + eq('Vim(call):E474: Duplicate comma: , "2", 2]', + exc_exec('call json_decode("[\\"1\\", 1,, \\"2\\", 2]")')) + eq('Vim(call):E474: Duplicate colon: : 2}', + exc_exec('call json_decode("{\\"1\\": 1, \\"2\\":: 2}")')) + eq('Vim(call):E474: Comma after colon: , 2}', + exc_exec('call json_decode("{\\"1\\": 1, \\"2\\":, 2}")')) + eq('Vim(call):E474: Unexpected colon: : "2": 2}', + exc_exec('call json_decode("{\\"1\\": 1,: \\"2\\": 2}")')) + eq('Vim(call):E474: Unexpected colon: :, "2": 2}', + exc_exec('call json_decode("{\\"1\\": 1:, \\"2\\": 2}")')) + end) + + it('fails to parse concat of two values', function() + eq('Vim(call):E474: Trailing characters: []', + exc_exec('call json_decode("{}[]")')) + end) + + it('parses containers', function() + eq({1}, funcs.json_decode('[1]')) + eq({NIL, 1}, funcs.json_decode('[null, 1]')) + eq({['1']=2}, funcs.json_decode('{"1": 2}')) + eq({['1']=2, ['3']={{['4']={['5']={{}, 1}}}}}, + funcs.json_decode('{"1": 2, "3": [{"4": {"5": [[], 1]}}]}')) + end) + + it('fails to parse incomplete strings', function() + eq('Vim(call):E474: Expected string end: \t"', + exc_exec('call json_decode("\\t\\"")')) + eq('Vim(call):E474: Expected string end: \t"abc', + exc_exec('call json_decode("\\t\\"abc")')) + eq('Vim(call):E474: Unfinished escape sequence: \t"abc\\', + exc_exec('call json_decode("\\t\\"abc\\\\")')) + eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u', + exc_exec('call json_decode("\\t\\"abc\\\\u")')) + eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u0', + exc_exec('call json_decode("\\t\\"abc\\\\u0")')) + eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u00', + exc_exec('call json_decode("\\t\\"abc\\\\u00")')) + eq('Vim(call):E474: Unfinished unicode escape sequence: \t"abc\\u000', + exc_exec('call json_decode("\\t\\"abc\\\\u000")')) + eq('Vim(call):E474: Expected four hex digits after \\u: \\u" ', + exc_exec('call json_decode("\\t\\"abc\\\\u\\" ")')) + eq('Vim(call):E474: Expected four hex digits after \\u: \\u0" ', + exc_exec('call json_decode("\\t\\"abc\\\\u0\\" ")')) + eq('Vim(call):E474: Expected four hex digits after \\u: \\u00" ', + exc_exec('call json_decode("\\t\\"abc\\\\u00\\" ")')) + eq('Vim(call):E474: Expected four hex digits after \\u: \\u000" ', + exc_exec('call json_decode("\\t\\"abc\\\\u000\\" ")')) + eq('Vim(call):E474: Expected string end: \t"abc\\u0000', + exc_exec('call json_decode("\\t\\"abc\\\\u0000")')) + end) + + it('fails to parse unknown escape sequnces', function() + eq('Vim(call):E474: Unknown escape sequence: \\a"', + exc_exec('call json_decode("\\t\\"\\\\a\\"")')) + end) + + it('parses strings properly', function() + eq('\n', funcs.json_decode('"\\n"')) + eq('', funcs.json_decode('""')) + eq('\\/"\t\b\n\r\f', funcs.json_decode([["\\\/\"\t\b\n\r\f"]])) + eq('/a', funcs.json_decode([["\/a"]])) + -- Unicode characters: 2-byte, 3-byte, 4-byte + eq({ + '«', + 'ફ', + '\240\144\128\128', + }, funcs.json_decode({ + '[', + '"«",', + '"ફ",', + '"\240\144\128\128"', + ']', + })) + end) + + it('fails on strings with invalid bytes', function() + eq('Vim(call):E474: Only UTF-8 strings allowed: \255"', + exc_exec('call json_decode("\\t\\"\\xFF\\"")')) + eq('Vim(call):E474: ASCII control characters cannot be present inside string: ', + exc_exec('call json_decode(["\\"\\n\\""])')) + -- 0xC2 starts 2-byte unicode character + eq('Vim(call):E474: Only UTF-8 strings allowed: \194"', + exc_exec('call json_decode("\\t\\"\\xC2\\"")')) + -- 0xE0 0xAA starts 3-byte unicode character + eq('Vim(call):E474: Only UTF-8 strings allowed: \224"', + exc_exec('call json_decode("\\t\\"\\xE0\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \224\170"', + exc_exec('call json_decode("\\t\\"\\xE0\\xAA\\"")')) + -- 0xF0 0x90 0x80 starts 4-byte unicode character + eq('Vim(call):E474: Only UTF-8 strings allowed: \240"', + exc_exec('call json_decode("\\t\\"\\xF0\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \240\144"', + exc_exec('call json_decode("\\t\\"\\xF0\\x90\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \240\144\128"', + exc_exec('call json_decode("\\t\\"\\xF0\\x90\\x80\\"")')) + -- 0xF9 0x80 0x80 0x80 starts 5-byte unicode character + eq('Vim(call):E474: Only UTF-8 strings allowed: \249"', + exc_exec('call json_decode("\\t\\"\\xF9\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \249\128"', + exc_exec('call json_decode("\\t\\"\\xF9\\x80\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \249\128\128"', + exc_exec('call json_decode("\\t\\"\\xF9\\x80\\x80\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \249\128\128\128"', + exc_exec('call json_decode("\\t\\"\\xF9\\x80\\x80\\x80\\"")')) + -- 0xFC 0x90 0x80 0x80 0x80 starts 6-byte unicode character + eq('Vim(call):E474: Only UTF-8 strings allowed: \252"', + exc_exec('call json_decode("\\t\\"\\xFC\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144"', + exc_exec('call json_decode("\\t\\"\\xFC\\x90\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144\128"', + exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144\128\128"', + exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\x80\\"")')) + eq('Vim(call):E474: Only UTF-8 strings allowed: \252\144\128\128\128"', + exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\x80\\x80\\"")')) + -- Specification does not allow unquoted characters above 0x10FFFF + eq('Vim(call):E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: \249\128\128\128\128"', + exc_exec('call json_decode("\\t\\"\\xF9\\x80\\x80\\x80\\x80\\"")')) + eq('Vim(call):E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: \252\144\128\128\128\128"', + exc_exec('call json_decode("\\t\\"\\xFC\\x90\\x80\\x80\\x80\\x80\\"")')) + -- '"\249\128\128\128\128"', + -- '"\252\144\128\128\128\128"', + end) + + it('parses surrogate pairs properly', function() + eq('\240\144\128\128', funcs.json_decode('"\\uD800\\uDC00"')) + eq('\237\160\128a\237\176\128', funcs.json_decode('"\\uD800a\\uDC00"')) + eq('\237\160\128\t\237\176\128', funcs.json_decode('"\\uD800\\t\\uDC00"')) + + eq('\237\160\128', funcs.json_decode('"\\uD800"')) + eq('\237\160\128a', funcs.json_decode('"\\uD800a"')) + eq('\237\160\128\t', funcs.json_decode('"\\uD800\\t"')) + + eq('\237\176\128', funcs.json_decode('"\\uDC00"')) + eq('\237\176\128a', funcs.json_decode('"\\uDC00a"')) + eq('\237\176\128\t', funcs.json_decode('"\\uDC00\\t"')) + + eq('\237\176\128', funcs.json_decode('"\\uDC00"')) + eq('a\237\176\128', funcs.json_decode('"a\\uDC00"')) + eq('\t\237\176\128', funcs.json_decode('"\\t\\uDC00"')) + + eq('\237\160\128¬', funcs.json_decode('"\\uD800\\u00AC"')) + + eq('\237\160\128\237\160\128', funcs.json_decode('"\\uD800\\uD800"')) + end) + + local sp_decode_eq = function(expected, json) + meths.set_var('__json', json) + speq(expected, 'json_decode(g:__json)') + execute('unlet! g:__json') + end + + it('parses strings with NUL properly', function() + sp_decode_eq({_TYPE='string', _VAL={'\n'}}, '"\\u0000"') + sp_decode_eq({_TYPE='string', _VAL={'\n', '\n'}}, '"\\u0000\\n\\u0000"') + sp_decode_eq({_TYPE='string', _VAL={'\n«\n'}}, '"\\u0000\\u00AB\\u0000"') + end) + + it('parses dictionaries with duplicate keys to special maps', function() + sp_decode_eq({_TYPE='map', _VAL={{'a', 1}, {'a', 2}}}, + '{"a": 1, "a": 2}') + sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'a', 2}}}, + '{"b": 3, "a": 1, "a": 2}') + sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}}}, + '{"b": 3, "a": 1, "c": 4, "a": 2}') + sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}, + '{"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}') + sp_decode_eq({{_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}, + '[{"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}]') + sp_decode_eq({{d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}}, + '[{"d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]') + sp_decode_eq({1, {d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}}, + '[1, {"d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]') + sp_decode_eq({1, {a={}, d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}}, + '[1, {"a": [], "d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]') + end) + + it('parses dictionaries with empty keys to special maps', function() + sp_decode_eq({_TYPE='map', _VAL={{'', 4}}}, + '{"": 4}') + sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}}, + '{"b": 3, "a": 1, "c": 4, "d": 2, "": 4}') + sp_decode_eq({_TYPE='map', _VAL={{'', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}}, + '{"": 3, "a": 1, "c": 4, "d": 2, "": 4}') + sp_decode_eq({{_TYPE='map', _VAL={{'', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}}}, + '[{"": 3, "a": 1, "c": 4, "d": 2, "": 4}]') + end) + + it('parses dictionaries with keys with NUL bytes to special maps', function() + sp_decode_eq({_TYPE='map', _VAL={{{_TYPE='string', _VAL={'a\n', 'b'}}, 4}}}, + '{"a\\u0000\\nb": 4}') + sp_decode_eq({_TYPE='map', _VAL={{{_TYPE='string', _VAL={'a\n', 'b', ''}}, 4}}}, + '{"a\\u0000\\nb\\n": 4}') + sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {{_TYPE='string', _VAL={'\n'}}, 4}}}, + '{"b": 3, "a": 1, "c": 4, "d": 2, "\\u0000": 4}') + end) + + it('converts strings to latin1 when &encoding is latin1', function() + restart('set encoding=latin1') + eq('\171', funcs.json_decode('"\\u00AB"')) + sp_decode_eq({_TYPE='string', _VAL={'\n\171\n'}}, '"\\u0000\\u00AB\\u0000"') + end) + + it('fails to convert string to latin1 if it is impossible', function() + restart('set encoding=latin1') + eq('Vim(call):E474: Failed to convert string "ꯍ" from UTF-8', + exc_exec('call json_decode(\'"\\uABCD"\')')) + end) + + it('parses U+00C3 correctly', function() + eq('\195\131', funcs.json_decode('"\195\131"')) + end) + + it('fails to parse empty string', function() + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode("")')) + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode([])')) + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode([""])')) + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode(" ")')) + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode("\\t")')) + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode("\\n")')) + eq('Vim(call):E474: Attempt to decode a blank string', + exc_exec('call json_decode(" \\t\\n \\n\\t\\t \\n\\t\\n \\n \\t\\n\\t ")')) + end) + + it('accepts all spaces in every position where space may be put', function() + local s = ' \t\n\r \t\r\n \n\t\r \n\r\t \r\t\n \r\n\t\t \n\r\t \r\n\t\n \r\t\n\r \t\r \n\t\r\n \n \t\r\n \r\t\n\t \r\n\t\r \n\r \t\n\r\t \r \t\n\r \n\t\r\t \n\r\t\n \r\n \t\r\n\t' + local str = ('%s{%s"key"%s:%s[%s"val"%s,%s"val2"%s]%s,%s"key2"%s:%s1%s}%s'):gsub('%%s', s) + eq({key={'val', 'val2'}, key2=1}, funcs.json_decode(str)) + end) + + it('always treats input as UTF-8', function() + -- When &encoding is latin1 string "«" is U+00C2 U+00AB U+00C2: «Â. So if + -- '"«"' was parsed as latin1 json_decode would return three characters, and + -- only one U+00AB when this string is parsed as latin1. + restart('set encoding=latin1') + eq(('%c'):format(0xAB), funcs.json_decode('"«"')) + end) + + it('does not overflow when writing error message about decoding ["", ""]', + function() + eq('\nE474: Attempt to decode a blank string' + .. '\nE474: Failed to parse \n', + redir_exec('call json_decode(["", ""])')) + end) +end) + +describe('json_encode() function', function() + before_each(function() + clear() + execute('language C') + end) + + it('dumps strings', function() + eq('"Test"', funcs.json_encode('Test')) + eq('""', funcs.json_encode('')) + eq('"\\t"', funcs.json_encode('\t')) + eq('"\\n"', funcs.json_encode('\n')) + eq('"\\u001B"', funcs.json_encode('\27')) + eq('"þÿþ"', funcs.json_encode('þÿþ')) + end) + + it('dumps numbers', function() + eq('0', funcs.json_encode(0)) + eq('10', funcs.json_encode(10)) + eq('-10', funcs.json_encode(-10)) + end) + + it('dumps floats', function() + eq('0.0', eval('json_encode(0.0)')) + eq('10.5', funcs.json_encode(10.5)) + eq('-10.5', funcs.json_encode(-10.5)) + eq('-1.0e-5', funcs.json_encode(-1e-5)) + eq('1.0e50', eval('json_encode(1.0e50)')) + end) + + it('fails to dump NaN and infinite values', function() + eq('Vim(call):E474: Unable to represent NaN value in JSON', + exc_exec('call json_encode(str2float("nan"))')) + eq('Vim(call):E474: Unable to represent infinity in JSON', + exc_exec('call json_encode(str2float("inf"))')) + eq('Vim(call):E474: Unable to represent infinity in JSON', + exc_exec('call json_encode(-str2float("inf"))')) + end) + + it('dumps lists', function() + eq('[]', funcs.json_encode({})) + eq('[[]]', funcs.json_encode({{}})) + eq('[[], []]', funcs.json_encode({{}, {}})) + end) + + it('dumps dictionaries', function() + eq('{}', eval('json_encode({})')) + eq('{"d": []}', funcs.json_encode({d={}})) + eq('{"d": [], "e": []}', funcs.json_encode({d={}, e={}})) + end) + + it('cannot dump generic mapping with generic mapping keys and values', + function() + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}') + execute('let todumpv1 = {"_TYPE": v:msgpack_types.map, "_VAL": []}') + execute('let todumpv2 = {"_TYPE": v:msgpack_types.map, "_VAL": []}') + execute('call add(todump._VAL, [todumpv1, todumpv2])') + eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)')) + end) + + it('cannot dump generic mapping with ext key', function() + execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}') + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') + eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)')) + end) + + it('cannot dump generic mapping with array key', function() + execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}') + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') + eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)')) + end) + + it('cannot dump generic mapping with UINT64_MAX key', function() + execute('let todump = {"_TYPE": v:msgpack_types.integer}') + execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]') + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') + eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)')) + end) + + it('cannot dump generic mapping with floating-point key', function() + execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}') + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') + eq('Vim(call):E474: Invalid key in special dictionary', exc_exec('call json_encode(todump)')) + end) + + it('can dump generic mapping with STR special key and NUL', function() + execute('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n"]}') + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') + eq('{"\\u0000": 1}', eval('json_encode(todump)')) + end) + + it('can dump generic mapping with BIN special key and NUL', function() + execute('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n"]}') + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') + eq('{"\\u0000": 1}', eval('json_encode(todump)')) + end) + + it('can dump STR special mapping with NUL and NL', function() + execute('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n", ""]}') + eq('"\\u0000\\n"', eval('json_encode(todump)')) + end) + + it('can dump BIN special mapping with NUL and NL', function() + execute('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n", ""]}') + eq('"\\u0000\\n"', eval('json_encode(todump)')) + end) + + it('cannot dump special ext mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}') + eq('Vim(call):E474: Unable to convert EXT string to JSON', exc_exec('call json_encode(todump)')) + end) + + it('can dump special array mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}') + eq('[5, [""]]', eval('json_encode(todump)')) + end) + + it('can dump special UINT64_MAX mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.integer}') + execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]') + eq('18446744073709551615', eval('json_encode(todump)')) + end) + + it('can dump special INT64_MIN mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.integer}') + execute('let todump._VAL = [-1, 2, 0, 0]') + eq('-9223372036854775808', eval('json_encode(todump)')) + end) + + it('can dump special BOOLEAN true mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}') + eq('true', eval('json_encode(todump)')) + end) + + it('can dump special BOOLEAN false mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}') + eq('false', eval('json_encode(todump)')) + end) + + it('can dump special NIL mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}') + eq('null', eval('json_encode(todump)')) + end) + + it('fails to dump a function reference', function() + eq('Vim(call):E474: Error while dumping encode_tv2json() argument, itself: attempt to dump function reference', + exc_exec('call json_encode(function("tr"))')) + end) + + it('fails to dump a function reference in a list', function() + eq('Vim(call):E474: Error while dumping encode_tv2json() argument, index 0: attempt to dump function reference', + exc_exec('call json_encode([function("tr")])')) + end) + + it('fails to dump a recursive list', function() + execute('let todump = [[[]]]') + execute('call add(todump[0][0], todump)') + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call json_encode(todump)')) + end) + + it('fails to dump a recursive dict', function() + execute('let todump = {"d": {"d": {}}}') + execute('call extend(todump.d.d, {"d": todump})') + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call json_encode([todump])')) + end) + + it('can dump dict with two same dicts inside', function() + execute('let inter = {}') + execute('let todump = {"a": inter, "b": inter}') + eq('{"a": {}, "b": {}}', eval('json_encode(todump)')) + end) + + it('can dump list with two same lists inside', function() + execute('let inter = []') + execute('let todump = [inter, inter]') + eq('[[], []]', eval('json_encode(todump)')) + end) + + it('fails to dump a recursive list in a special dict', function() + execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}') + execute('call add(todump._VAL, todump)') + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call json_encode(todump)')) + end) + + it('fails to dump a recursive (val) map in a special dict', function() + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": []}') + execute('call add(todump._VAL, ["", todump])') + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call json_encode([todump])')) + end) + + it('fails to dump a recursive (val) map in a special dict, _VAL reference', function() + execute('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [["", []]]}') + execute('call add(todump._VAL[0][1], todump._VAL)') + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call json_encode(todump)')) + end) + + it('fails to dump a recursive (val) special list in a special dict', + function() + execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": []}') + execute('call add(todump._VAL, ["", todump._VAL])') + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call json_encode(todump)')) + end) + + it('fails when called with no arguments', function() + eq('Vim(call):E119: Not enough arguments for function: json_encode', + exc_exec('call json_encode()')) + end) + + it('fails when called with two arguments', function() + eq('Vim(call):E118: Too many arguments for function: json_encode', + exc_exec('call json_encode(["", ""], 1)')) + end) + + it('converts strings from latin1 when &encoding is latin1', function() + clear('set encoding=latin1') + eq('"\\u00AB"', funcs.json_encode('\171')) + eq('"\\u0000\\u00AB\\u0000"', eval('json_encode({"_TYPE": v:msgpack_types.string, "_VAL": ["\\n\171\\n"]})')) + end) + + it('ignores improper values in &isprint', function() + meths.set_option('isprint', '1') + eq(1, eval('"\1" =~# "\\\\p"')) + eq('"\\u0001"', funcs.json_encode('\1')) + end) + + it('fails when using surrogate character in a UTF-8 string', function() + eq('Vim(call):E474: UTF-8 string contains code point which belongs to a surrogate pair: \237\160\128', + exc_exec('call json_encode("\237\160\128")')) + eq('Vim(call):E474: UTF-8 string contains code point which belongs to a surrogate pair: \237\175\191', + exc_exec('call json_encode("\237\175\191")')) + end) + + it('dumps control characters as expected', function() + eq([["\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000B\f\r\u000E\u000F\u0010\u0011\u0012\u0013"]], + eval('json_encode({"_TYPE": v:msgpack_types.string, "_VAL": ["\n\1\2\3\4\5\6\7\8\9", "\11\12\13\14\15\16\17\18\19"]})')) + end) + + it('can dump NULL string', function() + eq('""', eval('json_encode($XXX_UNEXISTENT_VAR_XXX)')) + end) + + it('can dump NULL list', function() + eq('[]', eval('json_encode(v:_null_list)')) + end) + + it('can dump NULL dictionary', function() + eq('{}', eval('json_encode(v:_null_dict)')) + end) +end) diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua index 41b0faf76c..9e501353a5 100644 --- a/test/functional/eval/msgpack_functions_spec.lua +++ b/test/functional/eval/msgpack_functions_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.functional.helpers') local clear = helpers.clear +local funcs = helpers.funcs local eval, eq = helpers.eval, helpers.eq local execute = helpers.execute local nvim = helpers.nvim @@ -382,30 +383,32 @@ describe('msgpack*() functions', function() eq({"\n"}, eval('parsed')) eq(1, eval('dumped ==# dumped2')) end) + + it('dump and restore special mapping with floating-point value', function() + execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}') + eq({0.125}, eval('msgpackparse(msgpackdump([todump]))')) + end) end) describe('msgpackparse() function', function() before_each(clear) - it('restores nil as special dict', function() + it('restores nil as v:null', function() execute('let dumped = ["\\xC0"]') execute('let parsed = msgpackparse(dumped)') - eq({{_TYPE={}, _VAL=0}}, eval('parsed')) - eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.nil')) + eq('[v:null]', eval('string(parsed)')) end) - it('restores boolean false as zero', function() + it('restores boolean false as v:false', function() execute('let dumped = ["\\xC2"]') execute('let parsed = msgpackparse(dumped)') - eq({{_TYPE={}, _VAL=0}}, eval('parsed')) - eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.boolean')) + eq({false}, eval('parsed')) end) - it('restores boolean true as one', function() + it('restores boolean true as v:true', function() execute('let dumped = ["\\xC3"]') execute('let parsed = msgpackparse(dumped)') - eq({{_TYPE={}, _VAL=1}}, eval('parsed')) - eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.boolean')) + eq({true}, eval('parsed')) end) it('restores FIXSTR as special dict', function() @@ -512,33 +515,55 @@ describe('msgpackdump() function', function() eq({'\129\128\128'}, eval('msgpackdump([todump])')) end) - it('can dump generic mapping with ext', function() + it('can dump v:true', function() + eq({'\195'}, funcs.msgpackdump({true})) + end) + + it('can dump v:false', function() + eq({'\194'}, funcs.msgpackdump({false})) + end) + + it('can v:null', function() + execute('let todump = v:null') + end) + + it('can dump special bool mapping (true)', function() + execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 1}') + eq({'\195'}, eval('msgpackdump([todump])')) + end) + + it('can dump special bool mapping (false)', function() + execute('let todump = {"_TYPE": v:msgpack_types.boolean, "_VAL": 0}') + eq({'\194'}, eval('msgpackdump([todump])')) + end) + + it('can dump special nil mapping', function() + execute('let todump = {"_TYPE": v:msgpack_types.nil, "_VAL": 0}') + eq({'\192'}, eval('msgpackdump([todump])')) + end) + + it('can dump special ext mapping', function() execute('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}') eq({'\212\005', ''}, eval('msgpackdump([todump])')) end) - it('can dump generic mapping with array', function() + it('can dump special array mapping', function() execute('let todump = {"_TYPE": v:msgpack_types.array, "_VAL": [5, [""]]}') eq({'\146\005\145\196\n'}, eval('msgpackdump([todump])')) end) - it('can dump generic mapping with UINT64_MAX', function() + it('can dump special UINT64_MAX mapping', function() execute('let todump = {"_TYPE": v:msgpack_types.integer}') execute('let todump._VAL = [1, 3, 0x7FFFFFFF, 0x7FFFFFFF]') eq({'\207\255\255\255\255\255\255\255\255'}, eval('msgpackdump([todump])')) end) - it('can dump generic mapping with INT64_MIN', function() + it('can dump special INT64_MIN mapping', function() execute('let todump = {"_TYPE": v:msgpack_types.integer}') execute('let todump._VAL = [-1, 2, 0, 0]') eq({'\211\128\n\n\n\n\n\n\n'}, eval('msgpackdump([todump])')) end) - it('dump and restore generic mapping with floating-point value', function() - execute('let todump = {"_TYPE": v:msgpack_types.float, "_VAL": 0.125}') - eq({0.125}, eval('msgpackparse(msgpackdump([todump]))')) - end) - it('fails to dump a function reference', function() execute('let Todump = function("tr")') eq('Vim(call):E951: Error while dumping msgpackdump() argument, index 0, itself: attempt to dump function reference', @@ -654,4 +679,25 @@ describe('msgpackdump() function', function() eq('Vim(call):E686: Argument of msgpackdump() must be a List', exc_exec('call msgpackdump(0.0)')) end) + + it('fails to dump special value', function() + for _, val in ipairs({'v:true', 'v:false', 'v:null'}) do + eq('Vim(call):E686: Argument of msgpackdump() must be a List', + exc_exec('call msgpackdump(' .. val .. ')')) + end + end) + + it('can dump NULL string', function() + eq({'\196\n'}, eval('msgpackdump([$XXX_UNEXISTENT_VAR_XXX])')) + eq({'\196\n'}, eval('msgpackdump([{"_TYPE": v:msgpack_types.binary, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}])')) + eq({'\160'}, eval('msgpackdump([{"_TYPE": v:msgpack_types.string, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}])')) + end) + + it('can dump NULL list', function() + eq({'\144'}, eval('msgpackdump([v:_null_list])')) + end) + + it('can dump NULL dictionary', function() + eq({'\128'}, eval('msgpackdump([v:_null_dict])')) + end) end) diff --git a/test/functional/eval/special_vars_spec.lua b/test/functional/eval/special_vars_spec.lua new file mode 100644 index 0000000000..6ab4e6a7d7 --- /dev/null +++ b/test/functional/eval/special_vars_spec.lua @@ -0,0 +1,171 @@ +local helpers = require('test.functional.helpers') +local exc_exec = helpers.exc_exec +local execute = helpers.execute +local meths = helpers.meths +local funcs = helpers.funcs +local meths = helpers.meths +local clear = helpers.clear +local eval = helpers.eval +local eq = helpers.eq + +describe('Special values', function() + before_each(clear) + + it('do not cause error when freed', function() + execute([[ + function Test() + try + return v:true + finally + return 'something else' + endtry + endfunction + ]]) + eq(0, exc_exec('call Test()')) + end) + + it('work with empty()', function() + eq(0, funcs.empty(true)) + eq(1, funcs.empty(false)) + eq(1, funcs.empty(NIL)) + end) + + it('can be stringified and eval’ed back', function() + eq(true, funcs.eval(funcs.string(true))) + eq(false, funcs.eval(funcs.string(false))) + eq(NIL, funcs.eval(funcs.string(NIL))) + end) + + it('work with is/isnot properly', function() + eq(1, eval('v:null is v:null')) + eq(0, eval('v:null is v:true')) + eq(0, eval('v:null is v:false')) + eq(1, eval('v:true is v:true')) + eq(0, eval('v:true is v:false')) + eq(1, eval('v:false is v:false')) + + eq(0, eval('v:null is 0')) + eq(0, eval('v:true is 0')) + eq(0, eval('v:false is 0')) + + eq(0, eval('v:null is 1')) + eq(0, eval('v:true is 1')) + eq(0, eval('v:false is 1')) + + eq(0, eval('v:null is ""')) + eq(0, eval('v:true is ""')) + eq(0, eval('v:false is ""')) + + eq(0, eval('v:null is "null"')) + eq(0, eval('v:true is "true"')) + eq(0, eval('v:false is "false"')) + + eq(0, eval('v:null is []')) + eq(0, eval('v:true is []')) + eq(0, eval('v:false is []')) + + eq(0, eval('v:null isnot v:null')) + eq(1, eval('v:null isnot v:true')) + eq(1, eval('v:null isnot v:false')) + eq(0, eval('v:true isnot v:true')) + eq(1, eval('v:true isnot v:false')) + eq(0, eval('v:false isnot v:false')) + + eq(1, eval('v:null isnot 0')) + eq(1, eval('v:true isnot 0')) + eq(1, eval('v:false isnot 0')) + + eq(1, eval('v:null isnot 1')) + eq(1, eval('v:true isnot 1')) + eq(1, eval('v:false isnot 1')) + + eq(1, eval('v:null isnot ""')) + eq(1, eval('v:true isnot ""')) + eq(1, eval('v:false isnot ""')) + + eq(1, eval('v:null isnot "null"')) + eq(1, eval('v:true isnot "true"')) + eq(1, eval('v:false isnot "false"')) + + eq(1, eval('v:null isnot []')) + eq(1, eval('v:true isnot []')) + eq(1, eval('v:false isnot []')) + end) + + it('work with +/-/* properly', function() + eq(1, eval('0 + v:true')) + eq(0, eval('0 + v:null')) + eq(0, eval('0 + v:false')) + + eq(-1, eval('0 - v:true')) + eq( 0, eval('0 - v:null')) + eq( 0, eval('0 - v:false')) + + eq(1, eval('1 * v:true')) + eq(0, eval('1 * v:null')) + eq(0, eval('1 * v:false')) + end) + + it('does not work with +=/-=/.=', function() + meths.set_var('true', true) + meths.set_var('false', false) + execute('let null = v:null') + + eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let true += 1')) + eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let false += 1')) + eq('Vim(let):E734: Wrong variable type for +=', exc_exec('let null += 1')) + + eq('Vim(let):E734: Wrong variable type for -=', exc_exec('let true -= 1')) + eq('Vim(let):E734: Wrong variable type for -=', exc_exec('let false -= 1')) + eq('Vim(let):E734: Wrong variable type for -=', exc_exec('let null -= 1')) + + eq('Vim(let):E734: Wrong variable type for .=', exc_exec('let true .= 1')) + eq('Vim(let):E734: Wrong variable type for .=', exc_exec('let false .= 1')) + eq('Vim(let):E734: Wrong variable type for .=', exc_exec('let null .= 1')) + end) + + it('work with . (concat) properly', function() + eq("true", eval('"" . v:true')) + eq("null", eval('"" . v:null')) + eq("false", eval('"" . v:false')) + end) + + it('work with type()', function() + eq(6, funcs.type(true)) + eq(6, funcs.type(false)) + eq(7, funcs.type(NIL)) + end) + + it('work with copy() and deepcopy()', function() + eq(true, funcs.deepcopy(true)) + eq(false, funcs.deepcopy(false)) + eq(NIL, funcs.deepcopy(NIL)) + + eq(true, funcs.copy(true)) + eq(false, funcs.copy(false)) + eq(NIL, funcs.copy(NIL)) + end) + + it('fails in index', function() + eq('Vim(echo):E909: Cannot index a special variable', exc_exec('echo v:true[0]')) + eq('Vim(echo):E909: Cannot index a special variable', exc_exec('echo v:false[0]')) + eq('Vim(echo):E909: Cannot index a special variable', exc_exec('echo v:null[0]')) + end) + + it('is accepted by assert_true and assert_false', function() + funcs.assert_false(false) + funcs.assert_false(true) + funcs.assert_false(NIL) + + funcs.assert_true(false) + funcs.assert_true(true) + funcs.assert_true(NIL) + + eq({ + 'Expected False but got v:true', + 'Expected False but got v:null', + 'Expected True but got v:false', + 'Expected True but got v:null', + }, meths.get_vvar('errors')) + end) +end) diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua index f7f5dca70a..0fd7587edb 100644 --- a/test/functional/eval/string_spec.lua +++ b/test/functional/eval/string_spec.lua @@ -28,6 +28,15 @@ describe('string() function', function() 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)) @@ -78,6 +87,18 @@ describe('string() function', function() 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() diff --git a/test/functional/legacy/marks_spec.lua b/test/functional/legacy/marks_spec.lua new file mode 100644 index 0000000000..8e9ceb1653 --- /dev/null +++ b/test/functional/legacy/marks_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers') +local feed, insert, source = helpers.feed, helpers.insert, helpers.source +local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect + +describe('marks', function() + before_each(function() + clear() + end) + + it('restores a deleted mark after delete-undo-redo-undo', function() + insert([[ + + textline A + textline B + textline C + + Results:]]) + + execute([[:/^\t/+1]]) + feed([[maddu<C-R>u]]) + source([[ + let g:a = string(getpos("'a")) + $put ='Mark after delete-undo-redo-undo: '.g:a + ]]) + + expect([=[ + + textline A + textline B + textline C + + Results: + Mark after delete-undo-redo-undo: [0, 3, 2, 0]]=]) + end) + + it("CTRL-A and CTRL-X updates last changed mark '[, ']", function() + insert([[ + CTRL-A CTRL-X: + 123 123 123 + 123 123 123 + 123 123 123]]) + + source([[ + /^123/ + execute "normal! \<C-A>`[v`]rAjwvjw\<C-X>`[v`]rX"]]) + + expect([=[ + CTRL-A CTRL-X: + AAA 123 123 + 123 XXXXXXX + XXX 123 123]=]) + end) +end) diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua index 90cc2af9c0..246b26188f 100644 --- a/test/functional/plugin/msgpack_spec.lua +++ b/test/functional/plugin/msgpack_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers') +local meths = helpers.meths local eq, nvim_eval, nvim_command, exc_exec = helpers.eq, helpers.eval, helpers.command, helpers.exc_exec local ok = helpers.ok @@ -409,6 +410,12 @@ describe('In autoload/msgpack.vim', function() string_eq('nan', '(1.0/0.0-1.0/0.0)') string_eq('nan', '-(1.0/0.0-1.0/0.0)') end) + + it('works for special v: values like v:true', function() + string_eq('TRUE', 'v:true') + string_eq('FALSE', 'v:false') + string_eq('NIL', 'v:null') + end) end) describe('function msgpack#deepcopy', function() @@ -523,6 +530,20 @@ describe('In autoload/msgpack.vim', function() eq(2.0, nvim_eval('flt2')) eq('abc', nvim_eval('bin2')) end) + + it('works for special v: values like v:true', function() + meths.set_var('true', true) + meths.set_var('false', false) + meths.set_var('nil', NIL) + + nvim_command('let true2 = msgpack#deepcopy(true)') + nvim_command('let false2 = msgpack#deepcopy(false)') + nvim_command('let nil2 = msgpack#deepcopy(nil)') + + eq(true, meths.get_var('true')) + eq(false, meths.get_var('false')) + eq(NIL, meths.get_var('nil')) + end) end) describe('function msgpack#eval', function() diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index 3becf1bc32..7ceeafdc71 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -22,12 +22,17 @@ describe('ShaDa support code', function() eq('foo', meths.get_var('STRVAR')) end) - local autotest = function(tname, varname, varval) + local autotest = function(tname, varname, varval, val_is_expr) it('is able to dump and read back ' .. tname .. ' variable automatically', function() set_additional_cmd('set shada+=!') reset() - meths.set_var(varname, varval) + if val_is_expr then + nvim_command('let g:' .. varname .. ' = ' .. varval) + varval = meths.get_var(varname) + else + meths.set_var(varname, varval) + end -- Exit during `reset` is not a regular exit: it does not write shada -- automatically nvim_command('qall') @@ -41,6 +46,10 @@ describe('ShaDa support code', function() autotest('float', 'FLTVAR', 42.5) autotest('dictionary', 'DCTVAR', {a=10}) autotest('list', 'LSTVAR', {{a=10}, {b=10.5}, {c='str'}}) + autotest('true', 'TRUEVAR', true) + autotest('false', 'FALSEVAR', false) + autotest('null', 'NULLVAR', 'v:null', true) + autotest('ext', 'EXTVAR', '{"_TYPE": v:msgpack_types.ext, "_VAL": [2, ["", ""]]}', true) it('does not read back variables without `!` in &shada', function() meths.set_var('STRVAR', 'foo') diff --git a/test/unit/eval/decode_spec.lua b/test/unit/eval/decode_spec.lua new file mode 100644 index 0000000000..d94d809c14 --- /dev/null +++ b/test/unit/eval/decode_spec.lua @@ -0,0 +1,142 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local to_cstr = helpers.to_cstr +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi + +local decode = cimport('./src/nvim/eval/decode.h', './src/nvim/eval_defs.h', + './src/nvim/globals.h', './src/nvim/memory.h', + './src/nvim/message.h') + +describe('json_decode_string()', function() + local saved_p_enc = nil + + before_each(function() + saved_p_enc = decode.p_enc + end) + + after_each(function() + decode.emsg_silent = 0 + decode.p_enc = saved_p_enc + while decode.delete_first_msg() == 1 do + -- Delete all messages + end + end) + + local char = function(c) + return ffi.gc(decode.xmemdup(c, 1), decode.xfree) + end + + it('does not overflow when running with `n…`, `t…`, `f…`', function() + local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN}) + decode.emsg_silent = 1 + -- This will not crash, but if `len` argument will be ignored it will parse + -- `null` as `null` and if not it will parse `null` as `n`. + eq(0, decode.json_decode_string('null', 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('true', 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('false', 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('null', 2, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('true', 2, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('false', 2, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('null', 3, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('true', 3, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('false', 3, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('false', 4, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + end) + + it('does not overflow and crash when running with `n`, `t`, `f`', function() + local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN}) + decode.emsg_silent = 1 + eq(0, decode.json_decode_string(char('n'), 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string(char('t'), 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string(char('f'), 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + end) + + it('does not overflow when running with `"…`', function() + local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN}) + decode.emsg_silent = 1 + eq(0, decode.json_decode_string('"t"', 2, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + eq(0, decode.json_decode_string('""', 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + end) + + local check_failure = function(s, len, msg) + local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN}) + eq(0, decode.json_decode_string(s, len, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + neq(nil, decode.last_msg_hist) + eq(msg, ffi.string(decode.last_msg_hist.msg)) + end + + it('does not overflow in error messages', function() + check_failure(']test', 1, 'E474: No container to close: ]') + check_failure('[}test', 2, 'E474: Closing list with curly bracket: }') + check_failure('{]test', 2, + 'E474: Closing dictionary with square bracket: ]') + check_failure('[1,]test', 4, 'E474: Trailing comma: ]') + check_failure('{"1":}test', 6, 'E474: Expected value after colon: }') + check_failure('{"1"}test', 5, 'E474: Expected value: }') + check_failure(',test', 1, 'E474: Comma not inside container: ,') + check_failure('[1,,1]test', 6, 'E474: Duplicate comma: ,1]') + check_failure('{"1":,}test', 7, 'E474: Comma after colon: ,}') + check_failure('{"1",}test', 6, 'E474: Using comma in place of colon: ,}') + check_failure('{,}test', 3, 'E474: Leading comma: ,}') + check_failure('[,]test', 3, 'E474: Leading comma: ,]') + check_failure(':test', 1, 'E474: Colon not inside container: :') + check_failure('[:]test', 3, 'E474: Using colon not in dictionary: :]') + check_failure('{:}test', 3, 'E474: Unexpected colon: :}') + check_failure('{"1"::1}test', 8, 'E474: Duplicate colon: :1}') + check_failure('ntest', 1, 'E474: Expected null: n') + check_failure('ttest', 1, 'E474: Expected true: t') + check_failure('ftest', 1, 'E474: Expected false: f') + check_failure('"\\test', 2, 'E474: Unfinished escape sequence: "\\') + check_failure('"\\u"test', 4, + 'E474: Unfinished unicode escape sequence: "\\u"') + check_failure('"\\uXXXX"est', 8, + 'E474: Expected four hex digits after \\u: \\uXXXX"') + check_failure('"\\?"test', 4, 'E474: Unknown escape sequence: \\?"') + check_failure( + '"\t"test', 3, + 'E474: ASCII control characters cannot be present inside string: \t"') + check_failure('"\194"test', 3, 'E474: Only UTF-8 strings allowed: \194"') + check_failure('"\252\144\128\128\128\128"test', 8, 'E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: \252\144\128\128\128\128"') + check_failure('"test', 1, 'E474: Expected string end: "') + decode.p_enc = to_cstr('latin1') + check_failure('"\\uABCD"test', 8, + 'E474: Failed to convert string "ꯍ" from UTF-8') + decode.p_enc = saved_p_enc + check_failure('-test', 1, 'E474: Missing number after minus sign: -') + check_failure('-1.test', 3, 'E474: Missing number after decimal dot: -1.') + check_failure('-1.0etest', 5, 'E474: Missing exponent: -1.0e') + check_failure('?test', 1, 'E474: Unidentified byte: ?') + check_failure('1?test', 2, 'E474: Trailing characters: ?') + check_failure('[1test', 2, 'E474: Unexpected end of input: [1') + end) + + it('does not overflow with `-`', function() + check_failure('-0', 1, 'E474: Missing number after minus sign: -') + end) + + it('does not overflow and crash when running with `"`', function() + local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN}) + decode.emsg_silent = 1 + eq(0, decode.json_decode_string(char('"'), 1, rettv)) + eq(decode.VAR_UNKNOWN, rettv.v_type) + end) +end) diff --git a/test/unit/eval/encode_spec.lua b/test/unit/eval/encode_spec.lua new file mode 100644 index 0000000000..f151a191fb --- /dev/null +++ b/test/unit/eval/encode_spec.lua @@ -0,0 +1,100 @@ +local helpers = require('test.unit.helpers') +local eval_helpers = require('test.unit.eval.helpers') + +local cimport = helpers.cimport +local to_cstr = helpers.to_cstr +local eq = helpers.eq + +local list = eval_helpers.list +local lst2tbl = eval_helpers.lst2tbl +local type_key = eval_helpers.type_key +local list_type = eval_helpers.list_type +local null_string = eval_helpers.null_string + +local encode = cimport('./src/nvim/eval/encode.h') + +describe('encode_list_write()', function() + local encode_list_write = function(l, s) + return encode.encode_list_write(l, to_cstr(s), #s) + end + + it('writes empty string', function() + local l = list() + eq(0, encode_list_write(l, '')) + eq({[type_key]=list_type}, lst2tbl(l)) + end) + + it('writes ASCII string literal with printable characters', function() + local l = list() + eq(0, encode_list_write(l, 'abc')) + eq({[type_key]=list_type, 'abc'}, lst2tbl(l)) + end) + + it('writes string starting with NL', function() + local l = list() + eq(0, encode_list_write(l, '\nabc')) + eq({[type_key]=list_type, null_string, 'abc'}, lst2tbl(l)) + end) + + it('writes string starting with NL twice', function() + local l = list() + eq(0, encode_list_write(l, '\nabc')) + eq({[type_key]=list_type, null_string, 'abc'}, lst2tbl(l)) + eq(0, encode_list_write(l, '\nabc')) + eq({[type_key]=list_type, null_string, 'abc', 'abc'}, lst2tbl(l)) + end) + + it('writes string ending with NL', function() + local l = list() + eq(0, encode_list_write(l, 'abc\n')) + eq({[type_key]=list_type, 'abc', null_string}, lst2tbl(l)) + end) + + it('writes string ending with NL twice', function() + local l = list() + eq(0, encode_list_write(l, 'abc\n')) + eq({[type_key]=list_type, 'abc', null_string}, lst2tbl(l)) + eq(0, encode_list_write(l, 'abc\n')) + eq({[type_key]=list_type, 'abc', 'abc', null_string}, lst2tbl(l)) + end) + + it('writes string starting, ending and containing NL twice', function() + local l = list() + eq(0, encode_list_write(l, '\na\nb\n')) + eq({[type_key]=list_type, null_string, 'a', 'b', null_string}, lst2tbl(l)) + eq(0, encode_list_write(l, '\na\nb\n')) + eq({[type_key]=list_type, null_string, 'a', 'b', null_string, 'a', 'b', null_string}, lst2tbl(l)) + end) + + it('writes string starting, ending and containing NUL with NL between twice', function() + local l = list() + eq(0, encode_list_write(l, '\0\n\0\n\0')) + eq({[type_key]=list_type, '\n', '\n', '\n'}, lst2tbl(l)) + eq(0, encode_list_write(l, '\0\n\0\n\0')) + eq({[type_key]=list_type, '\n', '\n', '\n\n', '\n', '\n'}, lst2tbl(l)) + end) + + it('writes string starting, ending and containing NL with NUL between twice', function() + local l = list() + eq(0, encode_list_write(l, '\n\0\n\0\n')) + eq({[type_key]=list_type, null_string, '\n', '\n', null_string}, lst2tbl(l)) + eq(0, encode_list_write(l, '\n\0\n\0\n')) + eq({[type_key]=list_type, null_string, '\n', '\n', null_string, '\n', '\n', null_string}, lst2tbl(l)) + end) + + it('writes string containing a single NL twice', function() + local l = list() + eq(0, encode_list_write(l, '\n')) + eq({[type_key]=list_type, null_string, null_string}, lst2tbl(l)) + eq(0, encode_list_write(l, '\n')) + eq({[type_key]=list_type, null_string, null_string, null_string}, lst2tbl(l)) + end) + + it('writes string containing a few NLs twice', function() + local l = list() + eq(0, encode_list_write(l, '\n\n\n')) + eq({[type_key]=list_type, null_string, null_string, null_string, null_string}, lst2tbl(l)) + eq(0, encode_list_write(l, '\n\n\n')) + eq({[type_key]=list_type, null_string, null_string, null_string, null_string, null_string, null_string, null_string}, lst2tbl(l)) + end) +end) diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua new file mode 100644 index 0000000000..da2f5626ff --- /dev/null +++ b/test/unit/eval/helpers.lua @@ -0,0 +1,72 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local to_cstr = helpers.to_cstr +local ffi = helpers.ffi +local eq = helpers.eq + +local eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h') + +local null_string = {[true]='NULL string'} +local null_list = {[true]='NULL list'} +local type_key = {[true]='type key'} +local list_type = {[true]='list type'} + +local list = function(...) + local ret = ffi.gc(eval.list_alloc(), eval.list_unref) + eq(0, ret.lv_refcount) + ret.lv_refcount = 1 + for i = 1, select('#', ...) do + local val = select(i, ...) + local typ = type(val) + if typ == 'string' then + eval.list_append_string(ret, to_cstr(val)) + elseif typ == 'table' and val == null_string then + eval.list_append_string(ret, nil) + elseif typ == 'table' and val == null_list then + eval.list_append_list(ret, nil) + elseif typ == 'table' and val[type_key] == list_type then + local itemlist = ffi.gc(list(table.unpack(val)), nil) + eq(1, itemlist.lv_refcount) + itemlist.lv_refcount = 0 + eval.list_append_list(ret, itemlist) + else + assert(false, 'Not implemented yet') + end + end + return ret +end + +local lst2tbl = function(l) + local ret = {[type_key]=list_type} + if l == nil then + return ret + end + local li = l.lv_first + -- (listitem_T *) NULL is equal to nil, but yet it is not false. + while li ~= nil do + local typ = li.li_tv.v_type + if typ == eval.VAR_STRING then + str = li.li_tv.vval.v_string + if str == nil then + ret[#ret + 1] = null_string + else + ret[#ret + 1] = ffi.string(str) + end + else + assert(false, 'Not implemented yet') + end + li = li.li_next + end + return ret +end + +return { + null_string=null_string, + null_list=null_list, + list_type=list_type, + type_key=type_key, + + list=list, + lst2tbl=lst2tbl, +} diff --git a/test/unit/eval/tricks_spec.lua b/test/unit/eval/tricks_spec.lua new file mode 100644 index 0000000000..4c5184995c --- /dev/null +++ b/test/unit/eval/tricks_spec.lua @@ -0,0 +1,43 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local to_cstr = helpers.to_cstr +local ffi = helpers.ffi +local eq = helpers.eq + +local eval = cimport('./src/nvim/eval.h', './src/nvim/memory.h') + +local eval_expr = function(expr) + return ffi.gc(eval.eval_expr(to_cstr(expr), nil), function(tv) + eval.clear_tv(tv) + eval.xfree(tv) + end) +end + +describe('NULL typval_T', function() + it('is produced by $XXX_UNEXISTENT_VAR_XXX', function() + -- Required for various tests which need to check whether typval_T with NULL + -- string works correctly. This test checks that unexistent environment + -- variable produces NULL string, not that some specific environment + -- variable does not exist. Last bit is left for the test writers. + local unexistent_env = 'XXX_UNEXISTENT_VAR_XXX' + while os.getenv(unexistent_env) ~= nil do + unexistent_env = unexistent_env .. '_XXX' + end + local rettv = eval_expr('$' .. unexistent_env) + eq(eval.VAR_STRING, rettv.v_type) + eq(nil, rettv.vval.v_string) + end) + + it('is produced by v:_null_list', function() + local rettv = eval_expr('v:_null_list') + eq(eval.VAR_LIST, rettv.v_type) + eq(nil, rettv.vval.v_list) + end) + + it('is produced by v:_null_dict', function() + local rettv = eval_expr('v:_null_dict') + eq(eval.VAR_DICT, rettv.v_type) + eq(nil, rettv.vval.v_dict) + end) +end) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 7b43b2218c..9b9c1fef0f 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -28,8 +28,10 @@ local function filter_complex_blocks(body) local result = {} for line in body:gmatch("[^\r\n]+") do - if not (string.find(line, "(^)", 1, true) ~= nil or - string.find(line, "_ISwupper", 1, true)) then + if not (string.find(line, "(^)", 1, true) ~= nil + or string.find(line, "_ISwupper", 1, true) + or string.find(line, "msgpack_zone_push_finalizer") + or string.find(line, "msgpack_unpacker_reserve_buffer")) then result[#result + 1] = line end end @@ -103,6 +105,11 @@ local function cimport(...) -- request a sorted version of the new lines (same relative order as the -- original preprocessed file) and feed that to the LuaJIT ffi local new_lines = new_cdefs:to_table() + if os.getenv('NVIM_TEST_PRINT_CDEF') == '1' then + for lnum, line in ipairs(new_lines) do + print(lnum, line) + end + end ffi.cdef(table.concat(new_lines, "\n")) return libnvim |