diff options
| -rw-r--r-- | src/nvim/eval/typval.c | 1 | ||||
| -rw-r--r-- | test/unit/eval/helpers.lua | 11 | ||||
| -rw-r--r-- | test/unit/eval/typval_spec.lua | 210 | 
3 files changed, 218 insertions, 4 deletions
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4777e00a76..a26afb20c6 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -228,6 +228,7 @@ void tv_list_unref(list_T *const l)  /// @param[in]  item2  Last item to remove.  void tv_list_remove_items(list_T *const l, listitem_T *const item,                            listitem_T *const item2) +  FUNC_ATTR_NONNULL_ALL  {    // notify watchers    for (listitem_T *ip = item; ip != NULL; ip = ip->li_next) { diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index c603953a05..8bf06e61f1 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -12,6 +12,7 @@ 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 locks_key = {[true]='locks key'}  local list_type = {[true]='list type'}  local dict_type = {[true]='dict type'}  local func_type = {[true]='func type'} @@ -23,9 +24,9 @@ local nil_value = {[true]='nil'}  local lua2typvalt  local function li_alloc(nogc) -  local gcfunc = eval.listitem_free +  local gcfunc = eval.tv_list_item_free    if nogc then gcfunc = nil end -  local li = ffi.gc(eval.listitem_alloc(), gcfunc) +  local li = ffi.gc(eval.tv_list_item_alloc(), gcfunc)    li.li_next = nil    li.li_prev = nil    li.li_tv = {v_type=eval.VAR_UNKNOWN, v_lock=eval.VAR_UNLOCKED} @@ -373,8 +374,9 @@ lua2typvalt = function(l, processed)    end  end +local void_ptr = ffi.typeof('void *')  local function void(ptr) -  return ffi.cast('void*', ptr) +  return ffi.cast(void_ptr, ptr)  end  local alloc_logging_helpers = { @@ -386,7 +388,7 @@ local alloc_logging_helpers = {    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, +  freed = function(p) return {func='free', args={type(p) == 'table' and p or void(p)}} end,  }  return { @@ -402,6 +404,7 @@ return {    nil_value=nil_value,    type_key=type_key, +  locks_key=locks_key,    list=list,    lst2tbl=lst2tbl, diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua new file mode 100644 index 0000000000..f2cb593cf5 --- /dev/null +++ b/test/unit/eval/typval_spec.lua @@ -0,0 +1,210 @@ +local helpers = require('test.unit.helpers')(after_each) +local eval_helpers = require('test.unit.eval.helpers') + +local itp = helpers.gen_itp(it) + +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local NULL = helpers.NULL +local cimport = helpers.cimport +local alloc_log_new = helpers.alloc_log_new + +local a = eval_helpers.alloc_logging_helpers +local list = eval_helpers.list +local lst2tbl = eval_helpers.lst2tbl +local type_key  = eval_helpers.type_key +local dict_type  = eval_helpers.dict_type +local lua2typvalt  = eval_helpers.lua2typvalt + +local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h') + +local function li_alloc(nogc) +  local gcfunc = lib.tv_list_item_free +  if nogc then gcfunc = nil end +  local li = ffi.gc(lib.tv_list_item_alloc(), gcfunc) +  li.li_next = nil +  li.li_prev = nil +  li.li_tv = {v_type=lib.VAR_UNKNOWN, v_lock=lib.VAR_UNLOCKED} +  return li +end + +local function list_index(l, idx) +  return tv_list_find(l, idx) +end + +local function list_items(l) +  local lis = {} +  local li = l.lv_first +  for i = 1, l.lv_len do +    lis[i] = li +    li = li.li_next +  end +  return lis +end + +local function list_watch(li) +  return ffi.new('listwatch_T', {lw_item=li}) +end + +local alloc_log +local restore_allocators + +local to_cstr_nofree = function(v) return lib.xstrdup(v) end + +local alloc_log = alloc_log_new() + +before_each(function() +  alloc_log:before_each() +end) + +after_each(function() +  alloc_log:after_each() +end) + +describe('typval.c', function() +  describe('list', function() +    describe('item', function() +      describe('alloc()/free()', function() +        itp('works', function() +          local li = li_alloc(true) +          neq(nil, li) +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.freed(li), +          }) +        end) +        itp('also frees the value', function() +          local li +          local s +          local l +          local tv +          li = li_alloc(true) +          li.li_tv.v_type = lib.VAR_NUMBER +          li.li_tv.vval.v_number = 10 +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.freed(li), +          }) + +          li = li_alloc(true) +          li.li_tv.v_type = lib.VAR_FLOAT +          li.li_tv.vval.v_float = 10.5 +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.freed(li), +          }) + +          li = li_alloc(true) +          li.li_tv.v_type = lib.VAR_STRING +          li.li_tv.vval.v_string = nil +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.freed(alloc_log.null), +            a.freed(li), +          }) + +          li = li_alloc(true) +          li.li_tv.v_type = lib.VAR_STRING +          s = to_cstr_nofree('test') +          li.li_tv.vval.v_string = s +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.str(s, #('test')), +            a.freed(s), +            a.freed(li), +          }) + +          li = li_alloc(true) +          li.li_tv.v_type = lib.VAR_LIST +          l = list() +          l.lv_refcount = 2 +          li.li_tv.vval.v_list = l +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.list(l), +            a.freed(li), +          }) +          eq(1, l.lv_refcount) + +          li = li_alloc(true) +          tv = lua2typvalt({[type_key]=dict_type}) +          tv.vval.v_dict.dv_refcount = 2 +          li.li_tv = tv +          lib.tv_list_item_free(li) +          alloc_log:check({ +            a.li(li), +            a.dict(tv.vval.v_dict), +            a.freed(li), +          }) +          eq(1, tv.vval.v_dict.dv_refcount) +        end) +      end) +      describe('remove()', function() +        itp('works', function() +          local l = list(1, 2, 3, 4, 5, 6, 7) +          neq(nil, l) +          local lis = list_items(l) +          alloc_log:check({ +            a.list(l), +            a.li(lis[1]), +            a.li(lis[2]), +            a.li(lis[3]), +            a.li(lis[4]), +            a.li(lis[5]), +            a.li(lis[6]), +            a.li(lis[7]), +          }) + +          lib.tv_list_item_remove(l, lis[1]) +          alloc_log:check({ +            a.freed(table.remove(lis, 1)), +          }) +          eq(lis, list_items(l)) + +          lib.tv_list_item_remove(l, lis[6]) +          alloc_log:check({ +            a.freed(table.remove(lis)), +          }) +          eq(lis, list_items(l)) + +          lib.tv_list_item_remove(l, lis[3]) +          alloc_log:check({ +            a.freed(table.remove(lis, 3)), +          }) +          eq(lis, list_items(l)) +        end) +        itp('works and adjusts watchers correctly', function() +          local l = ffi.gc(list(1, 2, 3, 4, 5, 6, 7), nil) +          neq(nil, l) +          local lis = list_items(l) +          -- Three watchers: pointing to first, middle and last elements. +          local lws = {list_watch(lis[1]), list_watch(lis[4]), list_watch(lis[7])} +          lib.tv_list_watch_add(l, lws[1]) +          lib.tv_list_watch_add(l, lws[2]) +          lib.tv_list_watch_add(l, lws[3]) + +          lib.tv_list_item_remove(l, lis[4]) +          eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) + +          lib.tv_list_item_remove(l, lis[2]) +          eq({lis[1], lis[5], lis[7]}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item}) + +          lib.tv_list_item_remove(l, lis[7]) +          eq({lis[1], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + +          lib.tv_list_item_remove(l, lis[1]) +          eq({lis[3], lis[5], nil}, {lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil}) + +          lib.tv_list_free(l) +        end) +      end) +    end) +  end) +end)  | 
