aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/eval/typval.c1
-rw-r--r--test/unit/eval/helpers.lua11
-rw-r--r--test/unit/eval/typval_spec.lua210
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)