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) |