diff options
Diffstat (limited to 'test/unit/eval')
-rw-r--r-- | test/unit/eval/decode_spec.lua | 2 | ||||
-rw-r--r-- | test/unit/eval/encode_spec.lua | 34 | ||||
-rw-r--r-- | test/unit/eval/helpers.lua | 403 | ||||
-rw-r--r-- | test/unit/eval/tv_clear_spec.lua | 127 |
4 files changed, 519 insertions, 47 deletions
diff --git a/test/unit/eval/decode_spec.lua b/test/unit/eval/decode_spec.lua index d94d809c14..742b754d8a 100644 --- a/test/unit/eval/decode_spec.lua +++ b/test/unit/eval/decode_spec.lua @@ -32,7 +32,7 @@ describe('json_decode_string()', function() 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 + -- 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) diff --git a/test/unit/eval/encode_spec.lua b/test/unit/eval/encode_spec.lua index f151a191fb..98fc8305e0 100644 --- a/test/unit/eval/encode_spec.lua +++ b/test/unit/eval/encode_spec.lua @@ -27,74 +27,74 @@ describe('encode_list_write()', function() 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)) + eq({'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)) + eq({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({null_string, 'abc'}, lst2tbl(l)) eq(0, encode_list_write(l, '\nabc')) - eq({[type_key]=list_type, null_string, 'abc', 'abc'}, lst2tbl(l)) + eq({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)) + eq({'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({'abc', null_string}, lst2tbl(l)) eq(0, encode_list_write(l, 'abc\n')) - eq({[type_key]=list_type, 'abc', 'abc', null_string}, lst2tbl(l)) + eq({'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({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)) + eq({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({'\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)) + eq({'\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({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)) + eq({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({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)) + eq({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({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)) + eq({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 index 2367f03e0d..c3c27e4fed 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -5,12 +5,32 @@ 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 eval = cimport('./src/nvim/eval.h', './src/nvim/eval_defs.h', + './src/nvim/hashtab.h') local null_string = {[true]='NULL string'} local null_list = {[true]='NULL list'} +local null_dict = {[true]='NULL dict'} local type_key = {[true]='type key'} local list_type = {[true]='list type'} +local dict_type = {[true]='dict type'} +local func_type = {[true]='func type'} +local int_type = {[true]='int type'} +local flt_type = {[true]='flt type'} + +local nil_value = {[true]='nil'} + +local lua2typvalt + +local function li_alloc(nogc) + local gcfunc = eval.listitem_free + if nogc then gcfunc = nil end + local li = ffi.gc(eval.listitem_alloc(), gcfunc) + li.li_next = nil + li.li_prev = nil + li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED} + return li +end local function list(...) local ret = ffi.gc(eval.list_alloc(), eval.list_unref) @@ -18,55 +38,380 @@ local function list(...) 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) + local li_tv = ffi.gc(lua2typvalt(val), nil) + local li = li_alloc(true) + li.li_tv = li_tv + eval.tv_list_append(ret, li) + end + return ret +end + +local special_tab = { + [eval.kSpecialVarFalse] = false, + [eval.kSpecialVarNull] = nil_value, + [eval.kSpecialVarTrue] = true, +} + +local ptr2key = function(ptr) + return tostring(ptr) +end + +local lst2tbl +local dct2tbl + +local typvalt2lua +local typvalt2lua_tab + +typvalt2lua_tab = { + [tonumber(eval.VAR_SPECIAL)] = function(t) + return special_tab[t.vval.v_special] + end, + [tonumber(eval.VAR_NUMBER)] = function(t) + return {[type_key]=int_type, value=tonumber(t.vval.v_number)} + end, + [tonumber(eval.VAR_FLOAT)] = function(t) + return tonumber(t.vval.v_float) + end, + [tonumber(eval.VAR_STRING)] = function(t) + local str = t.vval.v_string + if str == nil then + return null_string else - assert(false, 'Not implemented yet') + return ffi.string(str) + end + end, + [tonumber(eval.VAR_LIST)] = function(t, processed) + return lst2tbl(t.vval.v_list, processed) + end, + [tonumber(eval.VAR_DICT)] = function(t, processed) + return dct2tbl(t.vval.v_dict, processed) + end, + [tonumber(eval.VAR_FUNC)] = function(t, processed) + return {[type_key]=func_type, value=typvalt2lua_tab[eval.VAR_STRING](t, processed or {})} + end, + [tonumber(eval.VAR_PARTIAL)] = function(t, processed) + local p_key = ptr2key(t) + if processed[p_key] then + return processed[p_key] + end + local pt = t.vval.v_partial + local value, auto, dict, argv = nil, nil, nil, nil + if pt ~= nil then + value = ffi.string(pt.pt_name) + auto = pt.pt_auto and true or nil + argv = {} + for i = 1, pt.pt_argc do + argv[i] = typvalt2lua(pt.pt_argv[i - 1], processed) + end + if pt.pt_dict ~= nil then + dict = dct2tbl(pt.pt_dict) + end + end + return { + [type_key]=func_type, + value=value, + auto=auto, + args=argv, + dict=dict, + } + end, +} + +typvalt2lua = function(t, processed) + return ((typvalt2lua_tab[tonumber(t.v_type)] or function(t_inner) + assert(false, 'Converting ' .. tonumber(t_inner.v_type) .. ' was not implemented yet') + end)(t, processed or {})) +end + +local function list_iter(l) + local init_s = { + idx=0, + li=l.lv_first, + } + local function f(s, _) + -- (listitem_T *) NULL is equal to nil, but yet it is not false. + if s.li == nil then + return nil end + local ret_li = s.li + s.li = s.li.li_next + s.idx = s.idx + 1 + return s.idx, ret_li + end + return f, init_s, nil +end + +local function list_items(l) + local ret = {} + for i, li in list_iter(l) do + ret[i] = li end return ret end -local lst2tbl = function(l) - local ret = {[type_key]=list_type} +lst2tbl = function(l, processed) if l == nil then + return null_list + end + processed = processed or {} + local p_key = ptr2key(l) + if processed[p_key] then + return processed[p_key] + end + local ret = {[type_key]=list_type} + processed[p_key] = ret + for i, li in list_iter(l) do + ret[i] = typvalt2lua(li.li_tv, processed) + end + if ret[1] then + ret[type_key] = nil + end + return ret +end + +local hi_key_removed = eval._hash_key_removed() + +local function dict_iter(d, return_hi) + local init_s = { + todo=d.dv_hashtab.ht_used, + hi=d.dv_hashtab.ht_array, + } + local function f(s, _) + if s.todo == 0 then return nil end + while s.todo > 0 do + if s.hi.hi_key ~= nil and s.hi.hi_key ~= hi_key_removed then + local key = ffi.string(s.hi.hi_key) + local ret + if return_hi then + ret = s.hi + else + ret = ffi.cast('dictitem_T*', + s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_key')) + end + s.todo = s.todo - 1 + s.hi = s.hi + 1 + return key, ret + end + s.hi = s.hi + 1 + end + end + return f, init_s, nil +end + +local function first_di(d) + local f, init_s, v = dict_iter(d) + return select(2, f(init_s, v)) +end + +local function dict_items(d) + local ret = {[0]=0} + for k, hi in dict_iter(d) do + ret[k] = hi + ret[0] = ret[0] + 1 + ret[ret[0]] = hi + end + return ret +end + +dct2tbl = function(d, processed) + if d == nil then + return null_dict + end + processed = processed or {} + local p_key = ptr2key(d) + if processed[p_key] then + return processed[p_key] + end + local ret = {} + processed[p_key] = ret + for k, di in dict_iter(d) do + ret[k] = typvalt2lua(di.di_tv, processed) + end + return ret +end + +local typvalt = function(typ, vval) + if typ == nil then + typ = eval.VAR_UNKNOWN + elseif type(typ) == 'string' then + typ = eval[typ] + end + return ffi.gc(ffi.new('typval_T', {v_type=typ, vval=vval}), eval.clear_tv) +end + +local lua2typvalt_type_tab = { + [int_type] = function(l, _) + return typvalt(eval.VAR_NUMBER, {v_number=l.value}) + end, + [flt_type] = function(l, processed) + return lua2typvalt(l.value, processed) + end, + [list_type] = function(l, processed) + if processed[l] then + processed[l].lv_refcount = processed[l].lv_refcount + 1 + return typvalt(eval.VAR_LIST, {v_list=processed[l]}) + end + local lst = eval.list_alloc() + lst.lv_refcount = 1 + processed[l] = lst + local ret = typvalt(eval.VAR_LIST, {v_list=lst}) + for i = 1, #l do + local item_tv = ffi.gc(lua2typvalt(l[i], processed), nil) + eval.list_append_tv(lst, item_tv) + eval.clear_tv(item_tv) + end + return ret + end, + [dict_type] = function(l, processed) + if processed[l] then + processed[l].dv_refcount = processed[l].dv_refcount + 1 + return typvalt(eval.VAR_DICT, {v_dict=processed[l]}) + end + local dct = eval.dict_alloc() + dct.dv_refcount = 1 + processed[l] = dct + local ret = typvalt(eval.VAR_DICT, {v_dict=dct}) + for k, v in pairs(l) do + if type(k) == 'string' then + local di = eval.dictitem_alloc(to_cstr(k)) + local val_tv = ffi.gc(lua2typvalt(v, processed), nil) + eval.copy_tv(val_tv, di.di_tv) + eval.clear_tv(val_tv) + eval.dict_add(dct, di) + end + end return ret + end, + [func_type] = function(l, processed) + if processed[l] then + processed[l].pt_refcount = processed[l].pt_refcount + 1 + return typvalt(eval.VAR_PARTIAL, {v_partial=processed[l]}) + end + if l.args or l.dict then + local pt = ffi.gc(ffi.cast('partial_T*', eval.xmalloc(ffi.sizeof('partial_T'))), nil) + processed[l] = pt + local argv = nil + if l.args and #l.args > 0 then + argv = ffi.gc(ffi.cast('typval_T*', eval.xmalloc(ffi.sizeof('typval_T') * #l.args)), nil) + for i, arg in ipairs(l.args) do + local arg_tv = ffi.gc(lua2typvalt(arg, processed), nil) + eval.copy_tv(arg_tv, argv[i - 1]) + eval.clear_tv(arg_tv) + end + end + local dict = nil + if l.dict then + local dict_tv = ffi.gc(lua2typvalt(l.dict, processed), nil) + assert(dict_tv.v_type == eval.VAR_DICT) + dict = dict_tv.vval.v_dict + end + pt.pt_refcount = 1 + pt.pt_name = eval.xmemdupz(to_cstr(l.value), #l.value) + pt.pt_auto = not not l.auto + pt.pt_argc = l.args and #l.args or 0 + pt.pt_argv = argv + pt.pt_dict = dict + return typvalt(eval.VAR_PARTIAL, {v_partial=pt}) + else + return typvalt(eval.VAR_FUNC, { + v_string=eval.xmemdupz(to_cstr(l.value), #l.value) + }) + end + end, +} + +local special_vals = { + [null_string] = {eval.VAR_STRING, {v_string=ffi.cast('char_u*', nil)}}, + [null_list] = {eval.VAR_LIST, {v_list=ffi.cast('list_T*', nil)}}, + [null_dict] = {eval.VAR_DICT, {v_dict=ffi.cast('dict_T*', nil)}}, + [nil_value] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarNull}}, + [true] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarTrue}}, + [false] = {eval.VAR_SPECIAL, {v_special=eval.kSpecialVarFalse}}, +} + +for k, v in pairs(special_vals) do + local tmp = function(typ, vval) + special_vals[k] = function() + return typvalt(typ, vval) + end 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 - local str = li.li_tv.vval.v_string - if str == nil then - ret[#ret + 1] = null_string + tmp(v[1], v[2]) +end + +lua2typvalt = function(l, processed) + processed = processed or {} + if l == nil or l == nil_value then + return special_vals[nil_value]() + elseif special_vals[l] then + return special_vals[l]() + elseif type(l) == 'table' then + if l[type_key] then + return lua2typvalt_type_tab[l[type_key]](l, processed) + else + if l[1] then + return lua2typvalt_type_tab[list_type](l, processed) else - ret[#ret + 1] = ffi.string(str) + return lua2typvalt_type_tab[dict_type](l, processed) end - else - assert(false, 'Not implemented yet') end - li = li.li_next + elseif type(l) == 'number' then + return typvalt(eval.VAR_FLOAT, {v_float=l}) + elseif type(l) == 'string' then + return typvalt(eval.VAR_STRING, {v_string=eval.xmemdupz(to_cstr(l), #l)}) + elseif type(l) == 'cdata' then + local tv = typvalt(eval.VAR_UNKNOWN) + eval.tv_copy(l, tv) + return tv end - return ret end +local function void(ptr) + return ffi.cast('void*', ptr) +end + +local alloc_logging_helpers = { + list = function(l) return {func='calloc', args={1, ffi.sizeof('list_T')}, ret=void(l)} end, + li = function(li) return {func='malloc', args={ffi.sizeof('listitem_T')}, ret=void(li)} end, + dict = function(d) return {func='malloc', args={ffi.sizeof('dict_T')}, ret=void(d)} end, + di = function(di, size) + return {func='malloc', args={ffi.offsetof('dictitem_T', 'di_key') + size + 1}, ret=void(di)} + end, + str = function(s, size) return {func='malloc', args={size + 1}, ret=void(s)} end, + + freed = function(p) return {func='free', args={p and void(p)}} end, +} + return { null_string=null_string, null_list=null_list, + null_dict=null_dict, list_type=list_type, + dict_type=dict_type, + func_type=func_type, + int_type=int_type, + flt_type=flt_type, + + nil_value=nil_value, + type_key=type_key, list=list, lst2tbl=lst2tbl, + dct2tbl=dct2tbl, + + lua2typvalt=lua2typvalt, + typvalt2lua=typvalt2lua, + + typvalt=typvalt, + + li_alloc=li_alloc, + + dict_iter=dict_iter, + list_iter=list_iter, + first_di=first_di, + + alloc_logging_helpers=alloc_logging_helpers, + + list_items=list_items, + dict_items=dict_items, } diff --git a/test/unit/eval/tv_clear_spec.lua b/test/unit/eval/tv_clear_spec.lua new file mode 100644 index 0000000000..96eccdbd71 --- /dev/null +++ b/test/unit/eval/tv_clear_spec.lua @@ -0,0 +1,127 @@ +local helpers = require('test.unit.helpers') +local eval_helpers = require('test.unit.eval.helpers') + +local alloc_log_new = helpers.alloc_log_new +local cimport = helpers.cimport +local ffi = helpers.ffi +local eq = helpers.eq + +local a = eval_helpers.alloc_logging_helpers +local type_key = eval_helpers.type_key +local list_type = eval_helpers.list_type +local list_items = eval_helpers.list_items +local dict_items = eval_helpers.dict_items +local lua2typvalt = eval_helpers.lua2typvalt + +local lib = cimport('./src/nvim/eval_defs.h', './src/nvim/eval.h') + +local alloc_log = alloc_log_new() + +before_each(function() + alloc_log:before_each() +end) + +after_each(function() + alloc_log:after_each() +end) + +describe('clear_tv()', function() + it('successfully frees all lists in [&l [1], *l, *l]', function() + local l_inner = {1} + local list = {l_inner, l_inner, l_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local list_inner_p = lis[1].li_tv.vval.v_list + local lis_inner = list_items(list_inner_p) + alloc_log:check({ + a.list(list_p), + a.list(list_inner_p), + a.li(lis_inner[1]), + a.li(lis[1]), + a.li(lis[2]), + a.li(lis[3]), + }) + eq(3, list_inner_p.lv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(lis_inner[1]), + a.freed(list_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(lis[3]), + a.freed(list_p), + }) + end) + it('successfully frees all lists in [&l [], *l, *l]', function() + local l_inner = {[type_key]=list_type} + local list = {l_inner, l_inner, l_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local list_inner_p = lis[1].li_tv.vval.v_list + alloc_log:check({ + a.list(list_p), + a.list(list_inner_p), + a.li(lis[1]), + a.li(lis[2]), + a.li(lis[3]), + }) + eq(3, list_inner_p.lv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(list_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(lis[3]), + a.freed(list_p), + }) + end) + it('successfully frees all dictionaries in [&d {}, *d]', function() + local d_inner = {} + local list = {d_inner, d_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local dict_inner_p = lis[1].li_tv.vval.v_dict + alloc_log:check({ + a.list(list_p), + a.dict(dict_inner_p), + a.li(lis[1]), + a.li(lis[2]), + }) + eq(2, dict_inner_p.dv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(dict_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(list_p), + }) + end) + it('successfully frees all dictionaries in [&d {a: 1}, *d]', function() + local d_inner = {a=1} + local list = {d_inner, d_inner} + local list_tv = ffi.gc(lua2typvalt(list), nil) + local list_p = list_tv.vval.v_list + local lis = list_items(list_p) + local dict_inner_p = lis[1].li_tv.vval.v_dict + local dis = dict_items(dict_inner_p) + alloc_log:check({ + a.list(list_p), + a.dict(dict_inner_p), + a.di(dis.a, 1), + a.li(lis[1]), + a.li(lis[2]), + }) + eq(2, dict_inner_p.dv_refcount) + lib.clear_tv(list_tv) + alloc_log:check({ + a.freed(dis.a), + a.freed(dict_inner_p), + a.freed(lis[1]), + a.freed(lis[2]), + a.freed(list_p), + }) + end) +end) |