aboutsummaryrefslogtreecommitdiff
path: root/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/eval/helpers.lua85
-rw-r--r--test/unit/eval/tv_clear_spec.lua127
-rw-r--r--test/unit/helpers.lua76
-rw-r--r--test/unit/preprocess.lua1
4 files changed, 271 insertions, 18 deletions
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index 712530a0aa..c3c27e4fed 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -5,7 +5,8 @@ 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'}
@@ -122,6 +123,32 @@ typvalt2lua = function(t, processed)
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
+
lst2tbl = function(l, processed)
if l == nil then
return null_list
@@ -133,11 +160,8 @@ lst2tbl = function(l, processed)
end
local ret = {[type_key]=list_type}
processed[p_key] = ret
- local li = l.lv_first
- -- (listitem_T *) NULL is equal to nil, but yet it is not false.
- while li ~= nil do
- ret[#ret + 1] = typvalt2lua(li.li_tv, processed)
- li = li.li_next
+ for i, li in list_iter(l) do
+ ret[i] = typvalt2lua(li.li_tv, processed)
end
if ret[1] then
ret[type_key] = nil
@@ -145,7 +169,9 @@ lst2tbl = function(l, processed)
return ret
end
-local function dict_iter(d)
+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,
@@ -153,13 +179,18 @@ local function dict_iter(d)
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 ~= eval.hash_removed then
+ if s.hi.hi_key ~= nil and s.hi.hi_key ~= hi_key_removed then
local key = ffi.string(s.hi.hi_key)
- local di = ffi.cast('dictitem_T*',
- s.hi.hi_key - ffi.offsetof('dictitem_T', 'di_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, di
+ return key, ret
end
s.hi = s.hi + 1
end
@@ -172,6 +203,16 @@ local function first_di(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
@@ -324,6 +365,22 @@ lua2typvalt = function(l, processed)
end
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,
@@ -350,5 +407,11 @@ return {
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)
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 45bbaaeb10..1bfdd32739 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -9,6 +9,12 @@ local neq = global_helpers.neq
local eq = global_helpers.eq
local ok = global_helpers.ok
+-- C constants.
+local NULL = ffi.cast('void*', 0)
+
+local OK = 1
+local FAIL = 0
+
-- add some standard header locations
for _, p in ipairs(Paths.include_paths) do
Preprocess.add_to_include_path(p)
@@ -118,6 +124,67 @@ local function cppimport(path)
return cimport(Paths.test_include_path .. '/' .. path)
end
+local function alloc_log_new()
+ local log = {
+ log={},
+ lib=cimport('./src/nvim/memory.h'),
+ original_functions={},
+ null={['\0:is_null']=true},
+ }
+ local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'}
+ function log:save_original_functions()
+ for _, funcname in ipairs(allocator_functions) do
+ self.original_functions[funcname] = self.lib['mem_' .. funcname]
+ end
+ end
+ function log:set_mocks()
+ for _, k in ipairs(allocator_functions) do
+ do
+ local kk = k
+ self.lib['mem_' .. k] = function(...)
+ local log_entry = {func=kk, args={...}}
+ self.log[#self.log + 1] = log_entry
+ if kk == 'free' then
+ self.original_functions[kk](...)
+ else
+ log_entry.ret = self.original_functions[kk](...)
+ end
+ for i, v in ipairs(log_entry.args) do
+ if v == nil then
+ -- XXX This thing thinks that {NULL} ~= {NULL}.
+ log_entry.args[i] = self.null
+ end
+ end
+ if self.hook then self:hook(log_entry) end
+ if log_entry.ret then
+ return log_entry.ret
+ end
+ end
+ end
+ end
+ end
+ function log:clear()
+ self.log = {}
+ end
+ function log:check(exp)
+ eq(exp, self.log)
+ self:clear()
+ end
+ function log:restore_original_functions()
+ for k, v in pairs(self.original_functions) do
+ self.lib['mem_' .. k] = v
+ end
+ end
+ function log:before_each()
+ log:save_original_functions()
+ log:set_mocks()
+ end
+ function log:after_each()
+ log:restore_original_functions()
+ end
+ return log
+end
+
cimport('./src/nvim/types.h')
-- take a pointer to a C-allocated string and return an interned
@@ -142,12 +209,6 @@ do
main.event_init()
end
--- C constants.
-local NULL = ffi.cast('void*', 0)
-
-local OK = 1
-local FAIL = 0
-
return {
cimport = cimport,
cppimport = cppimport,
@@ -161,5 +222,6 @@ return {
to_cstr = to_cstr,
NULL = NULL,
OK = OK,
- FAIL = FAIL
+ FAIL = FAIL,
+ alloc_log_new = alloc_log_new,
}
diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua
index 8c2a5c73e5..1c9b290462 100644
--- a/test/unit/preprocess.lua
+++ b/test/unit/preprocess.lua
@@ -123,6 +123,7 @@ function Gcc:init_defines()
self:define('INIT', {'...'}, '')
self:define('_GNU_SOURCE')
self:define('INCLUDE_GENERATED_DECLARATIONS')
+ self:define('UNIT_TESTING')
-- Needed for FreeBSD
self:define('_Thread_local', nil, '')
-- Needed for macOS Sierra