neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

typval_spec.lua (121150B)


      1 local bit = require('bit')
      2 local t = require('test.unit.testutil')
      3 local t_eval = require('test.unit.eval.testutil')
      4 
      5 local itp = t.gen_itp(it)
      6 
      7 local OK = t.OK
      8 local eq = t.eq
      9 local neq = t.neq
     10 local ffi = t.ffi
     11 local FAIL = t.FAIL
     12 local NULL = t.NULL
     13 local cimport = t.cimport
     14 local to_cstr = t.to_cstr
     15 local alloc_log_new = t.alloc_log_new
     16 local concat_tables = t.concat_tables
     17 local map = vim.tbl_map
     18 
     19 local a = t_eval.alloc_logging_t
     20 local int = t_eval.int
     21 local list = t_eval.list
     22 local dict = t_eval.dict
     23 local eval0 = t_eval.eval0
     24 local lst2tbl = t_eval.lst2tbl
     25 local dct2tbl = t_eval.dct2tbl
     26 local typvalt = t_eval.typvalt
     27 local type_key = t_eval.type_key
     28 local li_alloc = t_eval.li_alloc
     29 local first_di = t_eval.first_di
     30 local nil_value = t_eval.nil_value
     31 local func_type = t_eval.func_type
     32 local null_list = t_eval.null_list
     33 local null_dict = t_eval.null_dict
     34 local dict_items = t_eval.dict_items
     35 local list_items = t_eval.list_items
     36 local empty_list = t_eval.empty_list
     37 local lua2typvalt = t_eval.lua2typvalt
     38 local typvalt2lua = t_eval.typvalt2lua
     39 local null_string = t_eval.null_string
     40 local callback2tbl = t_eval.callback2tbl
     41 local tbl2callback = t_eval.tbl2callback
     42 local dict_watchers = t_eval.dict_watchers
     43 
     44 local lib = cimport(
     45  './src/nvim/eval/typval.h',
     46  './src/nvim/memory.h',
     47  './src/nvim/mbyte.h',
     48  './src/nvim/garray.h',
     49  './src/nvim/eval.h',
     50  './src/nvim/vim_defs.h',
     51  './src/nvim/globals.h'
     52 )
     53 
     54 local function vimconv_alloc()
     55  return ffi.gc(ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))), function(vc)
     56    lib.convert_setup(vc, nil, nil)
     57    lib.xfree(vc)
     58  end)
     59 end
     60 
     61 local function list_watch_alloc(li)
     62  return ffi.cast('listwatch_T*', ffi.new('listwatch_T[1]', { { lw_item = li } }))
     63 end
     64 
     65 local function list_watch(l, li)
     66  local lw = list_watch_alloc(li or l.lv_first)
     67  lib.tv_list_watch_add(l, lw)
     68  return lw
     69 end
     70 
     71 local function get_alloc_rets(exp_log, res)
     72  setmetatable(res, {
     73    __index = {
     74      freed = function(r, n)
     75        return { func = 'free', args = { r[n] } }
     76      end,
     77    },
     78  })
     79  for i = 1, #exp_log do
     80    if ({ malloc = true, calloc = true })[exp_log[i].func] then
     81      res[#res + 1] = exp_log[i].ret
     82    end
     83  end
     84  return exp_log
     85 end
     86 
     87 local alloc_log = alloc_log_new()
     88 
     89 before_each(function()
     90  alloc_log:before_each()
     91 end)
     92 
     93 after_each(function()
     94  alloc_log:after_each()
     95 end)
     96 
     97 local function ga_alloc(itemsize, growsize)
     98  local ga = ffi.gc(ffi.cast('garray_T*', ffi.new('garray_T[1]', {})), lib.ga_clear)
     99  lib.ga_init(ga, itemsize or 1, growsize or 80)
    100  return ga
    101 end
    102 
    103 local function check_emsg(f, msg)
    104  local saved_msg_hist_last = lib.msg_hist_last
    105  if saved_msg_hist_last == nil then
    106    saved_msg_hist_last = nil
    107  end
    108  local ret = { f() }
    109  if msg ~= nil then
    110    eq(msg, ffi.string(lib.msg_hist_last.msg.items[0].text.data))
    111    neq(saved_msg_hist_last, lib.msg_hist_last)
    112  else
    113    if saved_msg_hist_last ~= lib.msg_hist_last then
    114      eq(nil, lib.msg_hist_last)
    115    else
    116      eq(saved_msg_hist_last, lib.msg_hist_last)
    117    end
    118  end
    119  return unpack(ret)
    120 end
    121 
    122 describe('typval.c', function()
    123  describe('list', function()
    124    describe('item', function()
    125      describe('remove()', function()
    126        itp('works', function()
    127          local l = list(1, 2, 3, 4, 5, 6, 7)
    128          neq(nil, l)
    129          local lis = list_items(l)
    130          alloc_log:check({
    131            a.list(l),
    132            a.li(lis[1]),
    133            a.li(lis[2]),
    134            a.li(lis[3]),
    135            a.li(lis[4]),
    136            a.li(lis[5]),
    137            a.li(lis[6]),
    138            a.li(lis[7]),
    139          })
    140 
    141          eq(lis[2], lib.tv_list_item_remove(l, lis[1]))
    142          alloc_log:check({
    143            a.freed(table.remove(lis, 1)),
    144          })
    145          eq(lis, list_items(l))
    146 
    147          eq(lis[7], lib.tv_list_item_remove(l, lis[6]))
    148          alloc_log:check({
    149            a.freed(table.remove(lis)),
    150          })
    151          eq(lis, list_items(l))
    152 
    153          eq(lis[4], lib.tv_list_item_remove(l, lis[3]))
    154          alloc_log:check({
    155            a.freed(table.remove(lis, 3)),
    156          })
    157          eq(lis, list_items(l))
    158        end)
    159        itp('also frees the value', function()
    160          local l = list('a', 'b', 'c', 'd')
    161          neq(nil, l)
    162          local lis = list_items(l)
    163          alloc_log:check({
    164            a.list(l),
    165            a.str(lis[1].li_tv.vval.v_string, 1),
    166            a.li(lis[1]),
    167            a.str(lis[2].li_tv.vval.v_string, 1),
    168            a.li(lis[2]),
    169            a.str(lis[3].li_tv.vval.v_string, 1),
    170            a.li(lis[3]),
    171            a.str(lis[4].li_tv.vval.v_string, 1),
    172            a.li(lis[4]),
    173          })
    174          local strings = map(function(li)
    175            return li.li_tv.vval.v_string
    176          end, lis)
    177 
    178          eq(lis[2], lib.tv_list_item_remove(l, lis[1]))
    179          alloc_log:check({
    180            a.freed(table.remove(strings, 1)),
    181            a.freed(table.remove(lis, 1)),
    182          })
    183          eq(lis, list_items(l))
    184 
    185          eq(lis[3], lib.tv_list_item_remove(l, lis[2]))
    186          alloc_log:check({
    187            a.freed(table.remove(strings, 2)),
    188            a.freed(table.remove(lis, 2)),
    189          })
    190          eq(lis, list_items(l))
    191 
    192          eq(nil, lib.tv_list_item_remove(l, lis[2]))
    193          alloc_log:check({
    194            a.freed(table.remove(strings, 2)),
    195            a.freed(table.remove(lis, 2)),
    196          })
    197          eq(lis, list_items(l))
    198        end)
    199        itp('works and adjusts watchers correctly', function()
    200          local l = ffi.gc(list(1, 2, 3, 4, 5, 6, 7), nil)
    201          neq(nil, l)
    202          local lis = list_items(l)
    203          -- Three watchers: pointing to first, middle and last elements.
    204          local lws = {
    205            list_watch(l, lis[1]),
    206            list_watch(l, lis[4]),
    207            list_watch(l, lis[7]),
    208          }
    209          alloc_log:check({
    210            a.list(l),
    211            a.li(lis[1]),
    212            a.li(lis[2]),
    213            a.li(lis[3]),
    214            a.li(lis[4]),
    215            a.li(lis[5]),
    216            a.li(lis[6]),
    217            a.li(lis[7]),
    218          })
    219 
    220          eq(lis[5], lib.tv_list_item_remove(l, lis[4]))
    221          alloc_log:check({ a.freed(lis[4]) })
    222          eq({ lis[1], lis[5], lis[7] }, { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item })
    223 
    224          eq(lis[3], lib.tv_list_item_remove(l, lis[2]))
    225          alloc_log:check({ a.freed(lis[2]) })
    226          eq({ lis[1], lis[5], lis[7] }, { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item })
    227 
    228          eq(nil, lib.tv_list_item_remove(l, lis[7]))
    229          alloc_log:check({ a.freed(lis[7]) })
    230          eq(
    231            { lis[1], lis[5], nil },
    232            { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil }
    233          )
    234 
    235          eq(lis[3], lib.tv_list_item_remove(l, lis[1]))
    236          alloc_log:check({ a.freed(lis[1]) })
    237          eq(
    238            { lis[3], lis[5], nil },
    239            { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil }
    240          )
    241 
    242          lib.tv_list_watch_remove(l, lws[2])
    243          lib.tv_list_watch_remove(l, lws[3])
    244          lib.tv_list_watch_remove(l, lws[1])
    245          lib.tv_list_free(l)
    246          alloc_log:check({
    247            a.freed(lis[3]),
    248            a.freed(lis[5]),
    249            a.freed(lis[6]),
    250            a.freed(l),
    251          })
    252        end)
    253      end)
    254    end)
    255    describe('watch', function()
    256      describe('remove()', function()
    257        itp('works', function()
    258          local l = ffi.gc(list(1, 2, 3, 4, 5, 6, 7), nil)
    259          eq(nil, l.lv_watch)
    260          local lw = list_watch(l)
    261          neq(nil, l.lv_watch)
    262          alloc_log:clear()
    263          lib.tv_list_watch_remove(l, lw)
    264          eq(nil, l.lv_watch)
    265          alloc_log:check({
    266            -- Does not free anything.
    267          })
    268          local lws = { list_watch(l), list_watch(l), list_watch(l) }
    269          alloc_log:clear()
    270          lib.tv_list_watch_remove(l, lws[2])
    271          eq(lws[3], l.lv_watch)
    272          eq(lws[1], l.lv_watch.lw_next)
    273          lib.tv_list_watch_remove(l, lws[1])
    274          eq(lws[3], l.lv_watch)
    275          eq(nil, l.lv_watch.lw_next)
    276          lib.tv_list_watch_remove(l, lws[3])
    277          eq(nil, l.lv_watch)
    278          alloc_log:check({
    279            -- Does not free anything.
    280          })
    281        end)
    282        itp('ignores not found watchers', function()
    283          local l = list(1, 2, 3, 4, 5, 6, 7)
    284          local lw = list_watch_alloc()
    285          lib.tv_list_watch_remove(l, lw)
    286        end)
    287      end)
    288    end)
    289    -- add() and fix() were tested when testing tv_list_item_remove()
    290    describe('free()', function()
    291      itp('recursively frees list', function()
    292        local l1 = ffi.gc(list(1, 'abc'), nil)
    293        local l2 = ffi.gc(list({}), nil)
    294        local l3 = ffi.gc(list(empty_list), nil)
    295        local alloc_rets = {}
    296        alloc_log:check(get_alloc_rets({
    297          a.list(l1),
    298          a.li(l1.lv_first),
    299          a.str(l1.lv_last.li_tv.vval.v_string, #'abc'),
    300          a.li(l1.lv_last),
    301          a.list(l2),
    302          a.dict(l2.lv_first.li_tv.vval.v_dict),
    303          a.li(l2.lv_first),
    304          a.list(l3),
    305          a.list(l3.lv_first.li_tv.vval.v_list),
    306          a.li(l3.lv_first),
    307        }, alloc_rets))
    308        lib.tv_list_free(l1)
    309        alloc_log:check({
    310          alloc_rets:freed(2),
    311          alloc_rets:freed(3),
    312          alloc_rets:freed(4),
    313          alloc_rets:freed(1),
    314        })
    315        lib.tv_list_free(l2)
    316        alloc_log:check({
    317          alloc_rets:freed(6),
    318          alloc_rets:freed(7),
    319          alloc_rets:freed(5),
    320        })
    321        lib.tv_list_free(l3)
    322        alloc_log:check({
    323          alloc_rets:freed(9),
    324          alloc_rets:freed(10),
    325          alloc_rets:freed(8),
    326        })
    327      end)
    328    end)
    329    describe('free_list()', function()
    330      itp('does not free list contents', function()
    331        local l1 = ffi.gc(list(1, 'abc'), nil)
    332        local l2 = ffi.gc(list({}), nil)
    333        local l3 = ffi.gc(list(empty_list), nil)
    334        local alloc_rets = {}
    335        alloc_log:check(get_alloc_rets({
    336          a.list(l1),
    337          a.li(l1.lv_first),
    338          a.str(l1.lv_last.li_tv.vval.v_string, #'abc'),
    339          a.li(l1.lv_last),
    340          a.list(l2),
    341          a.dict(l2.lv_first.li_tv.vval.v_dict),
    342          a.li(l2.lv_first),
    343          a.list(l3),
    344          a.list(l3.lv_first.li_tv.vval.v_list),
    345          a.li(l3.lv_first),
    346        }, alloc_rets))
    347        lib.tv_list_free_list(l1)
    348        alloc_log:check({
    349          alloc_rets:freed(1),
    350        })
    351        lib.tv_list_free_list(l2)
    352        alloc_log:check({
    353          alloc_rets:freed(5),
    354        })
    355        lib.tv_list_free_list(l3)
    356        alloc_log:check({
    357          alloc_rets:freed(8),
    358        })
    359      end)
    360    end)
    361    describe('free_contents()', function()
    362      itp('recursively frees list, except for the list structure itself', function()
    363        local l1 = ffi.gc(list(1, 'abc'), nil)
    364        local l2 = ffi.gc(list({}), nil)
    365        local l3 = ffi.gc(list(empty_list), nil)
    366        local alloc_rets = {}
    367        alloc_log:check(get_alloc_rets({
    368          a.list(l1),
    369          a.li(l1.lv_first),
    370          a.str(l1.lv_last.li_tv.vval.v_string, #'abc'),
    371          a.li(l1.lv_last),
    372          a.list(l2),
    373          a.dict(l2.lv_first.li_tv.vval.v_dict),
    374          a.li(l2.lv_first),
    375          a.list(l3),
    376          a.list(l3.lv_first.li_tv.vval.v_list),
    377          a.li(l3.lv_first),
    378        }, alloc_rets))
    379        lib.tv_list_free_contents(l1)
    380        alloc_log:check({
    381          alloc_rets:freed(2),
    382          alloc_rets:freed(3),
    383          alloc_rets:freed(4),
    384        })
    385        lib.tv_list_free_contents(l2)
    386        alloc_log:check({
    387          alloc_rets:freed(6),
    388          alloc_rets:freed(7),
    389        })
    390        lib.tv_list_free_contents(l3)
    391        alloc_log:check({
    392          alloc_rets:freed(9),
    393          alloc_rets:freed(10),
    394        })
    395      end)
    396    end)
    397    describe('unref()', function()
    398      itp('recursively frees list when reference count goes to 0', function()
    399        local l = ffi.gc(list(empty_list), nil)
    400        local alloc_rets = {}
    401        alloc_log:check(get_alloc_rets({
    402          a.list(l),
    403          a.list(l.lv_first.li_tv.vval.v_list),
    404          a.li(l.lv_first),
    405        }, alloc_rets))
    406        l.lv_refcount = 2
    407        lib.tv_list_unref(l)
    408        alloc_log:check({})
    409        lib.tv_list_unref(l)
    410        alloc_log:check({
    411          alloc_rets:freed(2),
    412          alloc_rets:freed(3),
    413          alloc_rets:freed(1),
    414        })
    415      end)
    416    end)
    417    describe('drop_items()', function()
    418      itp('works', function()
    419        local l_tv = lua2typvalt({ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 })
    420        local l = l_tv.vval.v_list
    421        local lis = list_items(l)
    422        -- Three watchers: pointing to first, middle and last elements.
    423        local lws = {
    424          list_watch(l, lis[1]),
    425          list_watch(l, lis[7]),
    426          list_watch(l, lis[13]),
    427        }
    428        alloc_log:clear()
    429 
    430        lib.tv_list_drop_items(l, lis[1], lis[3])
    431        eq({ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }, typvalt2lua(l_tv))
    432        eq({ lis[4], lis[7], lis[13] }, { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item })
    433 
    434        lib.tv_list_drop_items(l, lis[11], lis[13])
    435        eq({ 4, 5, 6, 7, 8, 9, 10 }, typvalt2lua(l_tv))
    436        eq(
    437          { lis[4], lis[7], nil },
    438          { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil }
    439        )
    440 
    441        lib.tv_list_drop_items(l, lis[6], lis[8])
    442        eq({ 4, 5, 9, 10 }, typvalt2lua(l_tv))
    443        eq(
    444          { lis[4], lis[9], nil },
    445          { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil }
    446        )
    447 
    448        lib.tv_list_drop_items(l, lis[4], lis[10])
    449        eq(empty_list, typvalt2lua(l_tv))
    450        eq(
    451          { true, true, true },
    452          { lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil }
    453        )
    454 
    455        lib.tv_list_watch_remove(l, lws[1])
    456        lib.tv_list_watch_remove(l, lws[2])
    457        lib.tv_list_watch_remove(l, lws[3])
    458 
    459        alloc_log:check({})
    460      end)
    461    end)
    462    describe('remove_items()', function()
    463      itp('works', function()
    464        local l_tv =
    465          lua2typvalt({ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13' })
    466        local l = l_tv.vval.v_list
    467        local lis = list_items(l)
    468        local strings = map(function(li)
    469          return li.li_tv.vval.v_string
    470        end, lis)
    471        -- Three watchers: pointing to first, middle and last elements.
    472        local lws = {
    473          list_watch(l, lis[1]),
    474          list_watch(l, lis[7]),
    475          list_watch(l, lis[13]),
    476        }
    477        alloc_log:clear()
    478 
    479        lib.tv_list_remove_items(l, lis[1], lis[3])
    480        eq({ '4', '5', '6', '7', '8', '9', '10', '11', '12', '13' }, typvalt2lua(l_tv))
    481        eq({ lis[4], lis[7], lis[13] }, { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item })
    482        alloc_log:check({
    483          a.freed(strings[1]),
    484          a.freed(lis[1]),
    485          a.freed(strings[2]),
    486          a.freed(lis[2]),
    487          a.freed(strings[3]),
    488          a.freed(lis[3]),
    489        })
    490 
    491        lib.tv_list_remove_items(l, lis[11], lis[13])
    492        eq({ '4', '5', '6', '7', '8', '9', '10' }, typvalt2lua(l_tv))
    493        eq(
    494          { lis[4], lis[7], nil },
    495          { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil }
    496        )
    497        alloc_log:check({
    498          a.freed(strings[11]),
    499          a.freed(lis[11]),
    500          a.freed(strings[12]),
    501          a.freed(lis[12]),
    502          a.freed(strings[13]),
    503          a.freed(lis[13]),
    504        })
    505 
    506        lib.tv_list_remove_items(l, lis[6], lis[8])
    507        eq({ '4', '5', '9', '10' }, typvalt2lua(l_tv))
    508        eq(
    509          { lis[4], lis[9], nil },
    510          { lws[1].lw_item, lws[2].lw_item, lws[3].lw_item == nil and nil }
    511        )
    512        alloc_log:check({
    513          a.freed(strings[6]),
    514          a.freed(lis[6]),
    515          a.freed(strings[7]),
    516          a.freed(lis[7]),
    517          a.freed(strings[8]),
    518          a.freed(lis[8]),
    519        })
    520 
    521        lib.tv_list_remove_items(l, lis[4], lis[10])
    522        eq(empty_list, typvalt2lua(l_tv))
    523        eq(
    524          { true, true, true },
    525          { lws[1].lw_item == nil, lws[2].lw_item == nil, lws[3].lw_item == nil }
    526        )
    527        alloc_log:check({
    528          a.freed(strings[4]),
    529          a.freed(lis[4]),
    530          a.freed(strings[5]),
    531          a.freed(lis[5]),
    532          a.freed(strings[9]),
    533          a.freed(lis[9]),
    534          a.freed(strings[10]),
    535          a.freed(lis[10]),
    536        })
    537 
    538        lib.tv_list_watch_remove(l, lws[1])
    539        lib.tv_list_watch_remove(l, lws[2])
    540        lib.tv_list_watch_remove(l, lws[3])
    541 
    542        alloc_log:check({})
    543      end)
    544    end)
    545    describe('insert', function()
    546      describe('()', function()
    547        itp('works', function()
    548          local l_tv = lua2typvalt({ 1, 2, 3, 4, 5, 6, 7 })
    549          local l = l_tv.vval.v_list
    550          local lis = list_items(l)
    551          local li
    552 
    553          li = li_alloc(true)
    554          li.li_tv = { v_type = lib.VAR_FLOAT, vval = { v_float = 100500 } }
    555          lib.tv_list_insert(l, li, nil)
    556          eq(l.lv_last, li)
    557          eq({ 1, 2, 3, 4, 5, 6, 7, 100500 }, typvalt2lua(l_tv))
    558 
    559          li = li_alloc(true)
    560          li.li_tv = { v_type = lib.VAR_FLOAT, vval = { v_float = 0 } }
    561          lib.tv_list_insert(l, li, lis[1])
    562          eq(l.lv_first, li)
    563          eq({ 0, 1, 2, 3, 4, 5, 6, 7, 100500 }, typvalt2lua(l_tv))
    564 
    565          li = li_alloc(true)
    566          li.li_tv = { v_type = lib.VAR_FLOAT, vval = { v_float = 4.5 } }
    567          lib.tv_list_insert(l, li, lis[5])
    568          eq(list_items(l)[6], li)
    569          eq({ 0, 1, 2, 3, 4, 4.5, 5, 6, 7, 100500 }, typvalt2lua(l_tv))
    570        end)
    571        itp('works with an empty list', function()
    572          local l_tv = lua2typvalt(empty_list)
    573          local l = l_tv.vval.v_list
    574 
    575          eq(nil, l.lv_first)
    576          eq(nil, l.lv_last)
    577 
    578          local li = li_alloc(true)
    579          li.li_tv = { v_type = lib.VAR_FLOAT, vval = { v_float = 100500 } }
    580          lib.tv_list_insert(l, li, nil)
    581          eq(l.lv_last, li)
    582          eq({ 100500 }, typvalt2lua(l_tv))
    583        end)
    584      end)
    585      describe('tv()', function()
    586        itp('works', function()
    587          local l_tv = lua2typvalt(empty_list)
    588          local l = l_tv.vval.v_list
    589 
    590          local l_l_tv = lua2typvalt(empty_list)
    591          alloc_log:clear()
    592          local l_l = l_l_tv.vval.v_list
    593          eq(1, l_l.lv_refcount)
    594          lib.tv_list_insert_tv(l, l_l_tv, nil)
    595          eq(2, l_l.lv_refcount)
    596          eq(l_l, l.lv_first.li_tv.vval.v_list)
    597          alloc_log:check({
    598            a.li(l.lv_first),
    599          })
    600 
    601          local l_s_tv = lua2typvalt('test')
    602          alloc_log:check({
    603            a.str(l_s_tv.vval.v_string, 'test'),
    604          })
    605          lib.tv_list_insert_tv(l, l_s_tv, l.lv_first)
    606          alloc_log:check({
    607            a.li(l.lv_first),
    608            a.str(l.lv_first.li_tv.vval.v_string, 'test'),
    609          })
    610 
    611          eq({ 'test', empty_list }, typvalt2lua(l_tv))
    612        end)
    613      end)
    614    end)
    615    describe('append', function()
    616      describe('list()', function()
    617        itp('works', function()
    618          local l_tv = lua2typvalt(empty_list)
    619          local l = l_tv.vval.v_list
    620 
    621          local l_l = list(1)
    622          alloc_log:clear()
    623          eq(1, l_l.lv_refcount)
    624          lib.tv_list_append_list(l, l_l)
    625          eq(2, l_l.lv_refcount)
    626          eq(l_l, l.lv_first.li_tv.vval.v_list)
    627          alloc_log:check({
    628            a.li(l.lv_last),
    629          })
    630 
    631          lib.tv_list_append_list(l, nil)
    632          alloc_log:check({
    633            a.li(l.lv_last),
    634          })
    635 
    636          eq({ { 1 }, null_list }, typvalt2lua(l_tv))
    637        end)
    638      end)
    639      describe('dict()', function()
    640        itp('works', function()
    641          local l_tv = lua2typvalt(empty_list)
    642          local l = l_tv.vval.v_list
    643 
    644          local l_d_tv = lua2typvalt({ test = 1 })
    645          local l_d = l_d_tv.vval.v_dict
    646          alloc_log:clear()
    647          eq(1, l_d.dv_refcount)
    648          lib.tv_list_append_dict(l, l_d)
    649          eq(2, l_d.dv_refcount)
    650          eq(l_d, l.lv_first.li_tv.vval.v_list)
    651          alloc_log:check({
    652            a.li(l.lv_last),
    653          })
    654 
    655          lib.tv_list_append_dict(l, nil)
    656          alloc_log:check({
    657            a.li(l.lv_last),
    658          })
    659 
    660          eq({ { test = 1 }, null_dict }, typvalt2lua(l_tv))
    661        end)
    662      end)
    663      describe('string()', function()
    664        itp('works', function()
    665          local l_tv = lua2typvalt(empty_list)
    666          local l = l_tv.vval.v_list
    667 
    668          alloc_log:clear()
    669          lib.tv_list_append_string(l, 'test', 3)
    670          alloc_log:check({
    671            a.str(l.lv_last.li_tv.vval.v_string, 'tes'),
    672            a.li(l.lv_last),
    673          })
    674 
    675          lib.tv_list_append_string(l, nil, 0)
    676          alloc_log:check({
    677            a.li(l.lv_last),
    678          })
    679 
    680          lib.tv_list_append_string(l, nil, -1)
    681          alloc_log:check({
    682            a.li(l.lv_last),
    683          })
    684 
    685          lib.tv_list_append_string(l, 'test', -1)
    686          alloc_log:check({
    687            a.str(l.lv_last.li_tv.vval.v_string, 'test'),
    688            a.li(l.lv_last),
    689          })
    690 
    691          eq({ 'tes', null_string, null_string, 'test' }, typvalt2lua(l_tv))
    692        end)
    693      end)
    694      describe('allocated string()', function()
    695        itp('works', function()
    696          local l_tv = lua2typvalt(empty_list)
    697          local l = l_tv.vval.v_list
    698 
    699          local s = lib.xstrdup('test')
    700          alloc_log:clear()
    701          lib.tv_list_append_allocated_string(l, s)
    702          alloc_log:check({
    703            a.li(l.lv_last),
    704          })
    705 
    706          lib.tv_list_append_allocated_string(l, nil)
    707          alloc_log:check({
    708            a.li(l.lv_last),
    709          })
    710 
    711          lib.tv_list_append_allocated_string(l, nil)
    712          alloc_log:check({
    713            a.li(l.lv_last),
    714          })
    715 
    716          eq({ 'test', null_string, null_string }, typvalt2lua(l_tv))
    717        end)
    718      end)
    719      describe('number()', function()
    720        itp('works', function()
    721          local l_tv = lua2typvalt(empty_list)
    722          local l = l_tv.vval.v_list
    723 
    724          alloc_log:clear()
    725          lib.tv_list_append_number(l, -100500)
    726          alloc_log:check({
    727            a.li(l.lv_last),
    728          })
    729 
    730          lib.tv_list_append_number(l, 100500)
    731          alloc_log:check({
    732            a.li(l.lv_last),
    733          })
    734 
    735          eq({ int(-100500), int(100500) }, typvalt2lua(l_tv))
    736        end)
    737      end)
    738      describe('tv()', function()
    739        itp('works', function()
    740          local l_tv = lua2typvalt(empty_list)
    741          local l = l_tv.vval.v_list
    742 
    743          local l_l_tv = lua2typvalt(empty_list)
    744          alloc_log:clear()
    745          local l_l = l_l_tv.vval.v_list
    746          eq(1, l_l.lv_refcount)
    747          lib.tv_list_append_tv(l, l_l_tv)
    748          eq(2, l_l.lv_refcount)
    749          eq(l_l, l.lv_first.li_tv.vval.v_list)
    750          alloc_log:check({
    751            a.li(l.lv_first),
    752          })
    753 
    754          local l_s_tv = lua2typvalt('test')
    755          alloc_log:check({
    756            a.str(l_s_tv.vval.v_string, 'test'),
    757          })
    758          lib.tv_list_append_tv(l, l_s_tv)
    759          alloc_log:check({
    760            a.li(l.lv_last),
    761            a.str(l.lv_last.li_tv.vval.v_string, 'test'),
    762          })
    763 
    764          eq({ empty_list, 'test' }, typvalt2lua(l_tv))
    765        end)
    766      end)
    767      describe('owned tv()', function()
    768        itp('works', function()
    769          local l_tv = lua2typvalt(empty_list)
    770          local l = l_tv.vval.v_list
    771 
    772          local l_l_tv = lua2typvalt(empty_list)
    773          alloc_log:clear()
    774          local l_l = l_l_tv.vval.v_list
    775          eq(1, l_l.lv_refcount)
    776          lib.tv_list_append_owned_tv(l, l_l_tv)
    777          eq(1, l_l.lv_refcount)
    778          l_l.lv_refcount = l_l.lv_refcount + 1
    779          eq(l_l, l.lv_first.li_tv.vval.v_list)
    780          alloc_log:check({
    781            a.li(l.lv_first),
    782          })
    783 
    784          local l_s_tv = ffi.gc(lua2typvalt('test'), nil)
    785          alloc_log:check({
    786            a.str(l_s_tv.vval.v_string, 'test'),
    787          })
    788          lib.tv_list_append_owned_tv(l, l_s_tv)
    789          eq(l_s_tv.vval.v_string, l.lv_last.li_tv.vval.v_string)
    790          l_s_tv.vval.v_string = nil
    791          alloc_log:check({
    792            a.li(l.lv_last),
    793          })
    794 
    795          eq({ empty_list, 'test' }, typvalt2lua(l_tv))
    796        end)
    797      end)
    798    end)
    799    describe('copy()', function()
    800      local function tv_list_copy(...)
    801        return ffi.gc(lib.tv_list_copy(...), lib.tv_list_unref)
    802      end
    803      itp('copies NULL correctly', function()
    804        eq(nil, lib.tv_list_copy(nil, nil, true, 0))
    805        eq(nil, lib.tv_list_copy(nil, nil, false, 0))
    806        eq(nil, lib.tv_list_copy(nil, nil, true, 1))
    807        eq(nil, lib.tv_list_copy(nil, nil, false, 1))
    808      end)
    809      itp('copies list correctly without converting items', function()
    810        do
    811          local v = { { ['«'] = '»' }, { '„' }, 1, '“', null_string, null_list, null_dict }
    812          local l_tv = lua2typvalt(v)
    813          local l = l_tv.vval.v_list
    814          local lis = list_items(l)
    815          alloc_log:clear()
    816 
    817          eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
    818          eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
    819          local l_copy1 = tv_list_copy(nil, l, false, 0)
    820          eq(2, lis[1].li_tv.vval.v_dict.dv_refcount)
    821          eq(2, lis[2].li_tv.vval.v_list.lv_refcount)
    822          local lis_copy1 = list_items(l_copy1)
    823          eq(lis[1].li_tv.vval.v_dict, lis_copy1[1].li_tv.vval.v_dict)
    824          eq(lis[2].li_tv.vval.v_list, lis_copy1[2].li_tv.vval.v_list)
    825          eq(v, lst2tbl(l_copy1))
    826          alloc_log:check({
    827            a.list(l_copy1),
    828            a.li(lis_copy1[1]),
    829            a.li(lis_copy1[2]),
    830            a.li(lis_copy1[3]),
    831            a.li(lis_copy1[4]),
    832            a.str(lis_copy1[4].li_tv.vval.v_string, #v[4]),
    833            a.li(lis_copy1[5]),
    834            a.li(lis_copy1[6]),
    835            a.li(lis_copy1[7]),
    836          })
    837          lib.tv_list_free(ffi.gc(l_copy1, nil))
    838          alloc_log:clear()
    839 
    840          eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
    841          eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
    842          local l_deepcopy1 = tv_list_copy(nil, l, true, 0)
    843          neq(nil, l_deepcopy1)
    844          eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
    845          eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
    846          local lis_deepcopy1 = list_items(l_deepcopy1)
    847          neq(lis[1].li_tv.vval.v_dict, lis_deepcopy1[1].li_tv.vval.v_dict)
    848          neq(lis[2].li_tv.vval.v_list, lis_deepcopy1[2].li_tv.vval.v_list)
    849          eq(v, lst2tbl(l_deepcopy1))
    850          local di_deepcopy1 = first_di(lis_deepcopy1[1].li_tv.vval.v_dict)
    851          alloc_log:check({
    852            a.list(l_deepcopy1),
    853            a.li(lis_deepcopy1[1]),
    854            a.dict(lis_deepcopy1[1].li_tv.vval.v_dict),
    855            a.di(di_deepcopy1, #'«'),
    856            a.str(di_deepcopy1.di_tv.vval.v_string, #v[1]['«']),
    857            a.li(lis_deepcopy1[2]),
    858            a.list(lis_deepcopy1[2].li_tv.vval.v_list),
    859            a.li(lis_deepcopy1[2].li_tv.vval.v_list.lv_first),
    860            a.str(lis_deepcopy1[2].li_tv.vval.v_list.lv_first.li_tv.vval.v_string, #v[2][1]),
    861            a.li(lis_deepcopy1[3]),
    862            a.li(lis_deepcopy1[4]),
    863            a.str(lis_deepcopy1[4].li_tv.vval.v_string, #v[4]),
    864            a.li(lis_deepcopy1[5]),
    865            a.li(lis_deepcopy1[6]),
    866            a.li(lis_deepcopy1[7]),
    867          })
    868        end
    869        collectgarbage()
    870      end)
    871      itp('copies list correctly and converts items', function()
    872        local vc = vimconv_alloc()
    873        -- UTF-8 ↔ latin1 conversions needs no iconv
    874        eq(OK, lib.convert_setup(vc, to_cstr('utf-8'), to_cstr('latin1')))
    875 
    876        local v = { { ['«'] = '»' }, { '„' }, 1, '“', null_string, null_list, null_dict }
    877        local l_tv = lua2typvalt(v)
    878        local l = l_tv.vval.v_list
    879        local lis = list_items(l)
    880        alloc_log:clear()
    881 
    882        eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
    883        eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
    884        local l_deepcopy1 = tv_list_copy(vc, l, true, 0)
    885        neq(nil, l_deepcopy1)
    886        eq(1, lis[1].li_tv.vval.v_dict.dv_refcount)
    887        eq(1, lis[2].li_tv.vval.v_list.lv_refcount)
    888        local lis_deepcopy1 = list_items(l_deepcopy1)
    889        neq(lis[1].li_tv.vval.v_dict, lis_deepcopy1[1].li_tv.vval.v_dict)
    890        neq(lis[2].li_tv.vval.v_list, lis_deepcopy1[2].li_tv.vval.v_list)
    891        eq(
    892          { { ['\171'] = '\187' }, { '\191' }, 1, '\191', null_string, null_list, null_dict },
    893          lst2tbl(l_deepcopy1)
    894        )
    895        local di_deepcopy1 = first_di(lis_deepcopy1[1].li_tv.vval.v_dict)
    896        alloc_log:clear_tmp_allocs()
    897        alloc_log:check({
    898          a.list(l_deepcopy1),
    899          a.li(lis_deepcopy1[1]),
    900          a.dict(lis_deepcopy1[1].li_tv.vval.v_dict),
    901          a.di(di_deepcopy1, 1),
    902          a.str(di_deepcopy1.di_tv.vval.v_string, 2),
    903          a.li(lis_deepcopy1[2]),
    904          a.list(lis_deepcopy1[2].li_tv.vval.v_list),
    905          a.li(lis_deepcopy1[2].li_tv.vval.v_list.lv_first),
    906          a.str(lis_deepcopy1[2].li_tv.vval.v_list.lv_first.li_tv.vval.v_string, #v[2][1]),
    907          a.li(lis_deepcopy1[3]),
    908          a.li(lis_deepcopy1[4]),
    909          a.str(lis_deepcopy1[4].li_tv.vval.v_string, #v[4]),
    910          a.li(lis_deepcopy1[5]),
    911          a.li(lis_deepcopy1[6]),
    912          a.li(lis_deepcopy1[7]),
    913        })
    914      end)
    915      itp('returns different/same containers with(out) copyID', function()
    916        local l_inner_tv = lua2typvalt(empty_list)
    917        local l_tv = lua2typvalt({ l_inner_tv, l_inner_tv })
    918        eq(3, l_inner_tv.vval.v_list.lv_refcount)
    919        local l = l_tv.vval.v_list
    920        eq(l.lv_first.li_tv.vval.v_list, l.lv_last.li_tv.vval.v_list)
    921 
    922        local l_copy1 = tv_list_copy(nil, l, true, 0)
    923        neq(l_copy1.lv_first.li_tv.vval.v_list, l_copy1.lv_last.li_tv.vval.v_list)
    924        eq({ empty_list, empty_list }, lst2tbl(l_copy1))
    925 
    926        local l_copy2 = tv_list_copy(nil, l, true, 2)
    927        eq(l_copy2.lv_first.li_tv.vval.v_list, l_copy2.lv_last.li_tv.vval.v_list)
    928        eq({ empty_list, empty_list }, lst2tbl(l_copy2))
    929 
    930        eq(3, l_inner_tv.vval.v_list.lv_refcount)
    931      end)
    932      itp('works with self-referencing list with copyID', function()
    933        local l_tv = lua2typvalt(empty_list)
    934        local l = l_tv.vval.v_list
    935        eq(1, l.lv_refcount)
    936        lib.tv_list_append_list(l, l)
    937        eq(2, l.lv_refcount)
    938 
    939        local l_copy1 = tv_list_copy(nil, l, true, 2)
    940        eq(2, l_copy1.lv_refcount)
    941        local v = {}
    942        v[1] = v
    943        eq(v, lst2tbl(l_copy1))
    944 
    945        local lis = list_items(l)
    946        lib.tv_list_item_remove(l, lis[1])
    947        eq(1, l.lv_refcount)
    948 
    949        local lis_copy1 = list_items(l_copy1)
    950        lib.tv_list_item_remove(l_copy1, lis_copy1[1])
    951        eq(1, l_copy1.lv_refcount)
    952      end)
    953    end)
    954    describe('extend()', function()
    955      itp('can extend list with itself', function()
    956        local l
    957 
    958        l = list(1, {})
    959        alloc_log:clear()
    960        eq(1, l.lv_refcount)
    961        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
    962 
    963        lib.tv_list_extend(l, l, nil)
    964        alloc_log:check({
    965          a.li(l.lv_last.li_prev),
    966          a.li(l.lv_last),
    967        })
    968        eq(1, l.lv_refcount)
    969        eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
    970        eq({ 1, {}, 1, {} }, lst2tbl(l))
    971 
    972        l = list(1, {})
    973        alloc_log:clear()
    974        eq(1, l.lv_refcount)
    975        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
    976 
    977        lib.tv_list_extend(l, l, l.lv_last)
    978        alloc_log:check({
    979          a.li(l.lv_last.li_prev.li_prev),
    980          a.li(l.lv_last.li_prev),
    981        })
    982        eq({ 1, 1, {}, {} }, lst2tbl(l))
    983        eq(1, l.lv_refcount)
    984        eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
    985 
    986        l = list(1, {})
    987        alloc_log:clear()
    988        eq(1, l.lv_refcount)
    989        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
    990 
    991        lib.tv_list_extend(l, l, l.lv_first)
    992        alloc_log:check({
    993          a.li(l.lv_first),
    994          a.li(l.lv_first.li_next),
    995        })
    996        eq({ 1, {}, 1, {} }, lst2tbl(l))
    997        eq(1, l.lv_refcount)
    998        eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
    999      end)
   1000      itp('can extend list with an empty list', function()
   1001        local l = list(1, {})
   1002        local el = list()
   1003        alloc_log:clear()
   1004        eq(1, l.lv_refcount)
   1005        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1006        eq(1, el.lv_refcount)
   1007 
   1008        lib.tv_list_extend(l, el, nil)
   1009        alloc_log:check({})
   1010        eq(1, l.lv_refcount)
   1011        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1012        eq(1, el.lv_refcount)
   1013        eq({ 1, {} }, lst2tbl(l))
   1014 
   1015        lib.tv_list_extend(l, el, l.lv_first)
   1016        alloc_log:check({})
   1017        eq(1, l.lv_refcount)
   1018        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1019        eq(1, el.lv_refcount)
   1020        eq({ 1, {} }, lst2tbl(l))
   1021 
   1022        lib.tv_list_extend(l, el, l.lv_last)
   1023        alloc_log:check({})
   1024        eq(1, l.lv_refcount)
   1025        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1026        eq(1, el.lv_refcount)
   1027        eq({ 1, {} }, lst2tbl(l))
   1028      end)
   1029      itp('can extend list with another non-empty list', function()
   1030        local l
   1031        local l2 = list(42, empty_list)
   1032        eq(1, l2.lv_refcount)
   1033        eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1034 
   1035        l = ffi.gc(list(1, {}), nil)
   1036        alloc_log:clear()
   1037        eq(1, l.lv_refcount)
   1038        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1039 
   1040        lib.tv_list_extend(l, l2, nil)
   1041        alloc_log:check({
   1042          a.li(l.lv_last.li_prev),
   1043          a.li(l.lv_last),
   1044        })
   1045        eq(1, l2.lv_refcount)
   1046        eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1047        eq({ 1, {}, 42, empty_list }, lst2tbl(l))
   1048        lib.tv_list_free(l)
   1049        eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1050 
   1051        l = ffi.gc(list(1, {}), nil)
   1052        alloc_log:clear()
   1053        eq(1, l.lv_refcount)
   1054        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1055 
   1056        lib.tv_list_extend(l, l2, l.lv_first)
   1057        alloc_log:check({
   1058          a.li(l.lv_first),
   1059          a.li(l.lv_first.li_next),
   1060        })
   1061        eq(1, l2.lv_refcount)
   1062        eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1063        eq({ 42, empty_list, 1, {} }, lst2tbl(l))
   1064        lib.tv_list_free(l)
   1065        eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1066 
   1067        l = ffi.gc(list(1, {}), nil)
   1068        alloc_log:clear()
   1069        eq(1, l.lv_refcount)
   1070        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1071 
   1072        lib.tv_list_extend(l, l2, l.lv_last)
   1073        alloc_log:check({
   1074          a.li(l.lv_first.li_next),
   1075          a.li(l.lv_first.li_next.li_next),
   1076        })
   1077        eq(1, l2.lv_refcount)
   1078        eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1079        eq({ 1, 42, empty_list, {} }, lst2tbl(l))
   1080        lib.tv_list_free(l)
   1081        eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1082      end)
   1083    end)
   1084    describe('concat()', function()
   1085      itp('works with NULL lists', function()
   1086        local l = list(1, {})
   1087        alloc_log:clear()
   1088        eq(1, l.lv_refcount)
   1089        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1090 
   1091        local rettv1 = typvalt()
   1092        eq(OK, lib.tv_list_concat(nil, l, rettv1))
   1093        eq(1, l.lv_refcount)
   1094        eq(tonumber(lib.VAR_LIST), tonumber(rettv1.v_type))
   1095        eq({ 1, {} }, typvalt2lua(rettv1))
   1096        eq(1, rettv1.vval.v_list.lv_refcount)
   1097        alloc_log:check({
   1098          a.list(rettv1.vval.v_list),
   1099          a.li(rettv1.vval.v_list.lv_first),
   1100          a.li(rettv1.vval.v_list.lv_last),
   1101        })
   1102        eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1103 
   1104        local rettv2 = typvalt()
   1105        eq(OK, lib.tv_list_concat(l, nil, rettv2))
   1106        eq(1, l.lv_refcount)
   1107        eq(tonumber(lib.VAR_LIST), tonumber(rettv2.v_type))
   1108        eq({ 1, {} }, typvalt2lua(rettv2))
   1109        eq(1, rettv2.vval.v_list.lv_refcount)
   1110        alloc_log:check({
   1111          a.list(rettv2.vval.v_list),
   1112          a.li(rettv2.vval.v_list.lv_first),
   1113          a.li(rettv2.vval.v_list.lv_last),
   1114        })
   1115        eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1116 
   1117        local rettv3 = typvalt()
   1118        eq(OK, lib.tv_list_concat(nil, nil, rettv3))
   1119        eq(tonumber(lib.VAR_LIST), tonumber(rettv3.v_type))
   1120        eq(null_list, typvalt2lua(rettv3))
   1121        alloc_log:check({})
   1122      end)
   1123      itp('works with two different lists', function()
   1124        local l1 = list(1, {})
   1125        local l2 = list(3, empty_list)
   1126        eq(1, l1.lv_refcount)
   1127        eq(1, l1.lv_last.li_tv.vval.v_dict.dv_refcount)
   1128        eq(1, l2.lv_refcount)
   1129        eq(1, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1130        alloc_log:clear()
   1131 
   1132        local rettv = typvalt()
   1133        eq(OK, lib.tv_list_concat(l1, l2, rettv))
   1134        eq(1, l1.lv_refcount)
   1135        eq(2, l1.lv_last.li_tv.vval.v_dict.dv_refcount)
   1136        eq(1, l2.lv_refcount)
   1137        eq(2, l2.lv_last.li_tv.vval.v_list.lv_refcount)
   1138        alloc_log:check({
   1139          a.list(rettv.vval.v_list),
   1140          a.li(rettv.vval.v_list.lv_first),
   1141          a.li(rettv.vval.v_list.lv_first.li_next),
   1142          a.li(rettv.vval.v_list.lv_last.li_prev),
   1143          a.li(rettv.vval.v_list.lv_last),
   1144        })
   1145        eq({ 1, {}, 3, empty_list }, typvalt2lua(rettv))
   1146      end)
   1147      itp('can concatenate list with itself', function()
   1148        local l = list(1, {})
   1149        eq(1, l.lv_refcount)
   1150        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1151        alloc_log:clear()
   1152 
   1153        local rettv = typvalt()
   1154        eq(OK, lib.tv_list_concat(l, l, rettv))
   1155        eq(1, l.lv_refcount)
   1156        eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1157        alloc_log:check({
   1158          a.list(rettv.vval.v_list),
   1159          a.li(rettv.vval.v_list.lv_first),
   1160          a.li(rettv.vval.v_list.lv_first.li_next),
   1161          a.li(rettv.vval.v_list.lv_last.li_prev),
   1162          a.li(rettv.vval.v_list.lv_last),
   1163        })
   1164        eq({ 1, {}, 1, {} }, typvalt2lua(rettv))
   1165      end)
   1166      itp('can concatenate empty non-NULL lists', function()
   1167        local l = list(1, {})
   1168        local le = list()
   1169        local le2 = list()
   1170        eq(1, l.lv_refcount)
   1171        eq(1, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1172        eq(1, le.lv_refcount)
   1173        eq(1, le2.lv_refcount)
   1174        alloc_log:clear()
   1175 
   1176        local rettv1 = typvalt()
   1177        eq(OK, lib.tv_list_concat(l, le, rettv1))
   1178        eq(1, l.lv_refcount)
   1179        eq(2, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1180        eq(1, le.lv_refcount)
   1181        eq(1, le2.lv_refcount)
   1182        alloc_log:check({
   1183          a.list(rettv1.vval.v_list),
   1184          a.li(rettv1.vval.v_list.lv_first),
   1185          a.li(rettv1.vval.v_list.lv_last),
   1186        })
   1187        eq({ 1, {} }, typvalt2lua(rettv1))
   1188 
   1189        local rettv2 = typvalt()
   1190        eq(OK, lib.tv_list_concat(le, l, rettv2))
   1191        eq(1, l.lv_refcount)
   1192        eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1193        eq(1, le.lv_refcount)
   1194        eq(1, le2.lv_refcount)
   1195        alloc_log:check({
   1196          a.list(rettv2.vval.v_list),
   1197          a.li(rettv2.vval.v_list.lv_first),
   1198          a.li(rettv2.vval.v_list.lv_last),
   1199        })
   1200        eq({ 1, {} }, typvalt2lua(rettv2))
   1201 
   1202        local rettv3 = typvalt()
   1203        eq(OK, lib.tv_list_concat(le, le, rettv3))
   1204        eq(1, l.lv_refcount)
   1205        eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1206        eq(1, le.lv_refcount)
   1207        eq(1, le2.lv_refcount)
   1208        alloc_log:check({
   1209          a.list(rettv3.vval.v_list),
   1210        })
   1211        eq(empty_list, typvalt2lua(rettv3))
   1212 
   1213        local rettv4 = typvalt()
   1214        eq(OK, lib.tv_list_concat(le, le2, rettv4))
   1215        eq(1, l.lv_refcount)
   1216        eq(3, l.lv_last.li_tv.vval.v_dict.dv_refcount)
   1217        eq(1, le.lv_refcount)
   1218        eq(1, le2.lv_refcount)
   1219        alloc_log:check({
   1220          a.list(rettv4.vval.v_list),
   1221        })
   1222        eq(empty_list, typvalt2lua(rettv4))
   1223      end)
   1224    end)
   1225    describe('join()', function()
   1226      local function list_join(l, sep, join_ret)
   1227        local ga = ga_alloc()
   1228        eq(join_ret or OK, lib.tv_list_join(ga, l, sep))
   1229        local ret = ''
   1230        if ga.ga_data ~= nil then
   1231          ret = ffi.string(ga.ga_data)
   1232        end
   1233        -- For some reason this is not working well in GC
   1234        lib.ga_clear(ffi.gc(ga, nil))
   1235        return ret
   1236      end
   1237      itp('works', function()
   1238        local l
   1239        l = list('boo', 'far')
   1240        eq('boo far', list_join(l, ' '))
   1241        eq('boofar', list_join(l, ''))
   1242 
   1243        l = list('boo')
   1244        eq('boo', list_join(l, ' '))
   1245 
   1246        l = list()
   1247        eq('', list_join(l, ' '))
   1248 
   1249        l = list({}, 'far')
   1250        eq('{} far', list_join(l, ' '))
   1251 
   1252        local recursive_list = {}
   1253        recursive_list[1] = recursive_list
   1254        l = ffi.gc(list(recursive_list, 'far'), nil)
   1255        eq('[[...@0]] far', list_join(l, ' '))
   1256 
   1257        local recursive_l = l.lv_first.li_tv.vval.v_list
   1258        local recursive_li = recursive_l.lv_first
   1259        lib.tv_list_item_remove(recursive_l, recursive_li)
   1260        lib.tv_list_free(l)
   1261      end)
   1262    end)
   1263    describe('equal()', function()
   1264      itp('compares empty and NULL lists correctly', function()
   1265        local l = list()
   1266        local l2 = list()
   1267 
   1268        -- NULL lists are equal to empty lists
   1269        eq(true, lib.tv_list_equal(l, nil, true))
   1270        eq(true, lib.tv_list_equal(nil, l, false))
   1271 
   1272        -- NULL lists are equal themselves
   1273        eq(true, lib.tv_list_equal(nil, nil, true))
   1274        eq(true, lib.tv_list_equal(nil, nil, false))
   1275 
   1276        -- As well as empty lists
   1277        eq(true, lib.tv_list_equal(l, l, true))
   1278        eq(true, lib.tv_list_equal(l, l2, false))
   1279        eq(true, lib.tv_list_equal(l2, l, false))
   1280        eq(true, lib.tv_list_equal(l2, l2, true))
   1281      end)
   1282      itp('compares lists correctly when case is not ignored', function()
   1283        local l1 = list('abc', { 1, 2, 'Abc' }, 'def')
   1284        local l2 = list('abc', { 1, 2, 'Abc' })
   1285        local l3 = list('abc', { 1, 2, 'Abc' }, 'Def')
   1286        local l4 = list('abc', { 1, 2, 'Abc', 4 }, 'def')
   1287        local l5 = list('Abc', { 1, 2, 'Abc' }, 'def')
   1288        local l6 = list('abc', { 1, 2, 'Abc' }, 'def')
   1289        local l7 = list('abc', { 1, 2, 'abc' }, 'def')
   1290        local l8 = list('abc', nil, 'def')
   1291        local l9 = list('abc', { 1, 2, nil }, 'def')
   1292 
   1293        eq(true, lib.tv_list_equal(l1, l1, false))
   1294        eq(false, lib.tv_list_equal(l1, l2, false))
   1295        eq(false, lib.tv_list_equal(l1, l3, false))
   1296        eq(false, lib.tv_list_equal(l1, l4, false))
   1297        eq(false, lib.tv_list_equal(l1, l5, false))
   1298        eq(true, lib.tv_list_equal(l1, l6, false))
   1299        eq(false, lib.tv_list_equal(l1, l7, false))
   1300        eq(false, lib.tv_list_equal(l1, l8, false))
   1301        eq(false, lib.tv_list_equal(l1, l9, false))
   1302      end)
   1303      itp('compares lists correctly when case is ignored', function()
   1304        local l1 = list('abc', { 1, 2, 'Abc' }, 'def')
   1305        local l2 = list('abc', { 1, 2, 'Abc' })
   1306        local l3 = list('abc', { 1, 2, 'Abc' }, 'Def')
   1307        local l4 = list('abc', { 1, 2, 'Abc', 4 }, 'def')
   1308        local l5 = list('Abc', { 1, 2, 'Abc' }, 'def')
   1309        local l6 = list('abc', { 1, 2, 'Abc' }, 'def')
   1310        local l7 = list('abc', { 1, 2, 'abc' }, 'def')
   1311        local l8 = list('abc', nil, 'def')
   1312        local l9 = list('abc', { 1, 2, nil }, 'def')
   1313 
   1314        eq(true, lib.tv_list_equal(l1, l1, true))
   1315        eq(false, lib.tv_list_equal(l1, l2, true))
   1316        eq(true, lib.tv_list_equal(l1, l3, true))
   1317        eq(false, lib.tv_list_equal(l1, l4, true))
   1318        eq(true, lib.tv_list_equal(l1, l5, true))
   1319        eq(true, lib.tv_list_equal(l1, l6, true))
   1320        eq(true, lib.tv_list_equal(l1, l7, true))
   1321        eq(false, lib.tv_list_equal(l1, l8, true))
   1322        eq(false, lib.tv_list_equal(l1, l9, true))
   1323      end)
   1324    end)
   1325    describe('find', function()
   1326      describe('()', function()
   1327        itp('correctly indexes list', function()
   1328          local l = list(1, 2, 3, 4, 5)
   1329          local lis = list_items(l)
   1330          alloc_log:clear()
   1331 
   1332          eq(nil, lib.tv_list_find(nil, -1))
   1333          eq(nil, lib.tv_list_find(nil, 0))
   1334          eq(nil, lib.tv_list_find(nil, 1))
   1335 
   1336          eq(nil, lib.tv_list_find(l, 5))
   1337          eq(nil, lib.tv_list_find(l, -6))
   1338          eq(lis[1], lib.tv_list_find(l, -5))
   1339          eq(lis[5], lib.tv_list_find(l, 4))
   1340          eq(lis[3], lib.tv_list_find(l, 2))
   1341          eq(lis[3], lib.tv_list_find(l, -3))
   1342          eq(lis[3], lib.tv_list_find(l, 2))
   1343          eq(lis[3], lib.tv_list_find(l, 2))
   1344          eq(lis[3], lib.tv_list_find(l, -3))
   1345 
   1346          l.lv_idx_item = nil
   1347          eq(lis[1], lib.tv_list_find(l, -5))
   1348          l.lv_idx_item = nil
   1349          eq(lis[5], lib.tv_list_find(l, 4))
   1350          l.lv_idx_item = nil
   1351          eq(lis[3], lib.tv_list_find(l, 2))
   1352          l.lv_idx_item = nil
   1353          eq(lis[3], lib.tv_list_find(l, -3))
   1354          l.lv_idx_item = nil
   1355          eq(lis[3], lib.tv_list_find(l, 2))
   1356          l.lv_idx_item = nil
   1357          eq(lis[3], lib.tv_list_find(l, 2))
   1358          l.lv_idx_item = nil
   1359          eq(lis[3], lib.tv_list_find(l, -3))
   1360 
   1361          l.lv_idx_item = nil
   1362          eq(lis[3], lib.tv_list_find(l, 2))
   1363          eq(lis[1], lib.tv_list_find(l, -5))
   1364          eq(lis[3], lib.tv_list_find(l, 2))
   1365          eq(lis[5], lib.tv_list_find(l, 4))
   1366          eq(lis[3], lib.tv_list_find(l, 2))
   1367          eq(lis[3], lib.tv_list_find(l, 2))
   1368          eq(lis[3], lib.tv_list_find(l, 2))
   1369          eq(lis[3], lib.tv_list_find(l, -3))
   1370          eq(lis[3], lib.tv_list_find(l, 2))
   1371          eq(lis[3], lib.tv_list_find(l, 2))
   1372          eq(lis[3], lib.tv_list_find(l, 2))
   1373          eq(lis[3], lib.tv_list_find(l, -3))
   1374 
   1375          alloc_log:check({})
   1376        end)
   1377      end)
   1378      describe('nr()', function()
   1379        local function tv_list_find_nr(l, n, msg)
   1380          return check_emsg(function()
   1381            local err = ffi.new('bool[1]', { false })
   1382            local ret = lib.tv_list_find_nr(l, n, err)
   1383            return (err[0] == true), ret
   1384          end, msg)
   1385        end
   1386        itp('returns correct number', function()
   1387          local l = list(int(1), int(2), int(3), int(4), int(5))
   1388          alloc_log:clear()
   1389 
   1390          eq({ false, 1 }, { tv_list_find_nr(l, -5) })
   1391          eq({ false, 5 }, { tv_list_find_nr(l, 4) })
   1392          eq({ false, 3 }, { tv_list_find_nr(l, 2) })
   1393          eq({ false, 3 }, { tv_list_find_nr(l, -3) })
   1394 
   1395          alloc_log:check({})
   1396        end)
   1397        itp('returns correct number when given a string', function()
   1398          local l = list('1', '2', '3', '4', '5')
   1399          alloc_log:clear()
   1400 
   1401          eq({ false, 1 }, { tv_list_find_nr(l, -5) })
   1402          eq({ false, 5 }, { tv_list_find_nr(l, 4) })
   1403          eq({ false, 3 }, { tv_list_find_nr(l, 2) })
   1404          eq({ false, 3 }, { tv_list_find_nr(l, -3) })
   1405 
   1406          alloc_log:check({})
   1407        end)
   1408        itp('returns zero when given a NULL string', function()
   1409          local l = list(null_string)
   1410          alloc_log:clear()
   1411 
   1412          eq({ false, 0 }, { tv_list_find_nr(l, 0) })
   1413 
   1414          alloc_log:check({})
   1415        end)
   1416        itp('errors out on NULL lists', function()
   1417          eq({ true, -1 }, { tv_list_find_nr(nil, -5) })
   1418          eq({ true, -1 }, { tv_list_find_nr(nil, 4) })
   1419          eq({ true, -1 }, { tv_list_find_nr(nil, 2) })
   1420          eq({ true, -1 }, { tv_list_find_nr(nil, -3) })
   1421 
   1422          alloc_log:check({})
   1423        end)
   1424        itp('errors out on out-of-range indexes', function()
   1425          local l = list(int(1), int(2), int(3), int(4), int(5))
   1426          alloc_log:clear()
   1427 
   1428          eq({ true, -1 }, { tv_list_find_nr(l, -6) })
   1429          eq({ true, -1 }, { tv_list_find_nr(l, 5) })
   1430 
   1431          alloc_log:check({})
   1432        end)
   1433        itp('errors out on invalid types', function()
   1434          local l = list(1, empty_list, {})
   1435 
   1436          eq({ true, 0 }, { tv_list_find_nr(l, 0, 'E805: Using a Float as a Number') })
   1437          eq({ true, 0 }, { tv_list_find_nr(l, 1, 'E745: Using a List as a Number') })
   1438          eq({ true, 0 }, { tv_list_find_nr(l, 2, 'E728: Using a Dictionary as a Number') })
   1439          eq({ true, 0 }, { tv_list_find_nr(l, -1, 'E728: Using a Dictionary as a Number') })
   1440          eq({ true, 0 }, { tv_list_find_nr(l, -2, 'E745: Using a List as a Number') })
   1441          eq({ true, 0 }, { tv_list_find_nr(l, -3, 'E805: Using a Float as a Number') })
   1442        end)
   1443      end)
   1444      local function tv_list_find_str(l, n, msg)
   1445        return check_emsg(function()
   1446          local ret = lib.tv_list_find_str(l, n)
   1447          local s = nil
   1448          if ret ~= nil then
   1449            s = ffi.string(ret)
   1450          end
   1451          return s
   1452        end, msg)
   1453      end
   1454      describe('str()', function()
   1455        itp('returns correct string', function()
   1456          local l = list(int(1), 2.5, int(3), int(4), int(5))
   1457          alloc_log:clear()
   1458 
   1459          eq('1', tv_list_find_str(l, -5))
   1460          eq('2.5', tv_list_find_str(l, 1))
   1461          eq('5', tv_list_find_str(l, 4))
   1462          eq('3', tv_list_find_str(l, 2))
   1463          eq('3', tv_list_find_str(l, -3))
   1464 
   1465          alloc_log:check({ a.freed(alloc_log.null), a.freed(alloc_log.null) })
   1466        end)
   1467        itp('returns string when used with VAR_STRING items', function()
   1468          local l = list('1', '2', '3', '4', '5')
   1469          alloc_log:clear()
   1470 
   1471          eq('1', tv_list_find_str(l, -5))
   1472          eq('5', tv_list_find_str(l, 4))
   1473          eq('3', tv_list_find_str(l, 2))
   1474          eq('3', tv_list_find_str(l, -3))
   1475 
   1476          alloc_log:check({})
   1477        end)
   1478        itp('returns empty when used with NULL string', function()
   1479          local l = list(null_string)
   1480          alloc_log:clear()
   1481 
   1482          eq('', tv_list_find_str(l, 0))
   1483 
   1484          alloc_log:check({})
   1485        end)
   1486        itp('fails with error message when index is out of range', function()
   1487          local l = list(int(1), int(2), int(3), int(4), int(5))
   1488 
   1489          eq(nil, tv_list_find_str(l, -6, 'E684: List index out of range: -6'))
   1490          eq(nil, tv_list_find_str(l, 5, 'E684: List index out of range: 5'))
   1491        end)
   1492        itp('fails with error message on invalid types', function()
   1493          local l = list(empty_list, {})
   1494 
   1495          eq('', tv_list_find_str(l, 0, 'E730: Using a List as a String'))
   1496          eq('', tv_list_find_str(l, 1, 'E731: Using a Dictionary as a String'))
   1497          eq('', tv_list_find_str(l, -1, 'E731: Using a Dictionary as a String'))
   1498          eq('', tv_list_find_str(l, -2, 'E730: Using a List as a String'))
   1499        end)
   1500      end)
   1501    end)
   1502    describe('idx_of_item()', function()
   1503      itp('works', function()
   1504        local l = list(1, 2, 3, 4, 5)
   1505        local l2 = list(42, empty_list)
   1506        local lis = list_items(l)
   1507        local lis2 = list_items(l2)
   1508 
   1509        for i, li in ipairs(lis) do
   1510          eq(i - 1, lib.tv_list_idx_of_item(l, li))
   1511        end
   1512        eq(-1, lib.tv_list_idx_of_item(l, lis2[1]))
   1513        eq(-1, lib.tv_list_idx_of_item(l, nil))
   1514        eq(-1, lib.tv_list_idx_of_item(nil, nil))
   1515        eq(-1, lib.tv_list_idx_of_item(nil, lis[1]))
   1516      end)
   1517    end)
   1518  end)
   1519  describe('dict', function()
   1520    describe('watcher', function()
   1521      describe('add()/remove()', function()
   1522        itp('works with an empty key', function()
   1523          local d = dict({})
   1524          eq({}, dict_watchers(d))
   1525          local cb = ffi.gc(tbl2callback({ type = 'none' }), nil)
   1526          alloc_log:clear()
   1527          lib.tv_dict_watcher_add(d, '*', 0, cb[0])
   1528          local ws, qs = dict_watchers(d)
   1529          local key_p = qs[1].key_pattern
   1530          alloc_log:check({
   1531            a.dwatcher(qs[1]),
   1532            a.str(key_p, 0),
   1533          })
   1534          eq({ { busy = false, cb = { type = 'none' }, pat = '' } }, ws)
   1535          eq(true, lib.tv_dict_watcher_remove(d, 'x', 0, cb[0]))
   1536          alloc_log:check({
   1537            a.freed(key_p),
   1538            a.freed(qs[1]),
   1539          })
   1540          eq({}, dict_watchers(d))
   1541        end)
   1542        itp('works with multiple callbacks', function()
   1543          local d = dict({})
   1544          eq({}, dict_watchers(d))
   1545          alloc_log:check({ a.dict(d) })
   1546          local cbs = {}
   1547          cbs[1] = { 'te', ffi.gc(tbl2callback({ type = 'none' }), nil) }
   1548          alloc_log:check({})
   1549          cbs[2] = { 'foo', ffi.gc(tbl2callback({ type = 'fref', fref = 'tr' }), nil) }
   1550          alloc_log:check({
   1551            a.str(cbs[2][2].data.funcref, #'tr'),
   1552          })
   1553          cbs[3] = {
   1554            'te',
   1555            ffi.gc(
   1556              tbl2callback({
   1557                type = 'pt',
   1558                fref = 'tr',
   1559                pt = {
   1560                  value = 'tr',
   1561                  args = { 'test' },
   1562                  dict = {},
   1563                },
   1564              }),
   1565              nil
   1566            ),
   1567          }
   1568          local pt3 = cbs[3][2].data.partial
   1569          local pt3_argv = pt3.pt_argv
   1570          local pt3_dict = pt3.pt_dict
   1571          local pt3_name = pt3.pt_name
   1572          local pt3_str_arg = pt3.pt_argv[0].vval.v_string
   1573          alloc_log:check({
   1574            a.lua_pt(pt3),
   1575            a.lua_tvs(pt3_argv, pt3.pt_argc),
   1576            a.str(pt3_str_arg, #'test'),
   1577            a.dict(pt3_dict),
   1578            a.str(pt3_name, #'tr'),
   1579          })
   1580          for _, v in ipairs(cbs) do
   1581            lib.tv_dict_watcher_add(d, v[1], #v[1], v[2][0])
   1582          end
   1583          local ws, qs, kps = dict_watchers(d)
   1584          eq({
   1585            { busy = false, pat = cbs[1][1], cb = { type = 'none' } },
   1586            { busy = false, pat = cbs[2][1], cb = { type = 'fref', fref = 'tr' } },
   1587            {
   1588              busy = false,
   1589              pat = cbs[3][1],
   1590              cb = {
   1591                type = 'pt',
   1592                fref = 'tr',
   1593                pt = {
   1594                  [type_key] = func_type,
   1595                  value = 'tr',
   1596                  args = { 'test' },
   1597                  dict = {},
   1598                },
   1599              },
   1600            },
   1601          }, ws)
   1602          alloc_log:check({
   1603            a.dwatcher(qs[1]),
   1604            a.str(kps[1][1], kps[1][2]),
   1605            a.dwatcher(qs[2]),
   1606            a.str(kps[2][1], kps[2][2]),
   1607            a.dwatcher(qs[3]),
   1608            a.str(kps[3][1], kps[3][2]),
   1609          })
   1610          eq(true, lib.tv_dict_watcher_remove(d, cbs[2][1], #cbs[2][1], cbs[2][2][0]))
   1611          alloc_log:check({
   1612            a.freed(cbs[2][2].data.funcref),
   1613            a.freed(kps[2][1]),
   1614            a.freed(qs[2]),
   1615          })
   1616          eq(false, lib.tv_dict_watcher_remove(d, cbs[2][1], #cbs[2][1], cbs[2][2][0]))
   1617          eq({
   1618            { busy = false, pat = cbs[1][1], cb = { type = 'none' } },
   1619            {
   1620              busy = false,
   1621              pat = cbs[3][1],
   1622              cb = {
   1623                type = 'pt',
   1624                fref = 'tr',
   1625                pt = {
   1626                  [type_key] = func_type,
   1627                  value = 'tr',
   1628                  args = { 'test' },
   1629                  dict = {},
   1630                },
   1631              },
   1632            },
   1633          }, dict_watchers(d))
   1634          eq(true, lib.tv_dict_watcher_remove(d, cbs[3][1], #cbs[3][1], cbs[3][2][0]))
   1635          alloc_log:check({
   1636            a.freed(pt3_str_arg),
   1637            a.freed(pt3_argv),
   1638            a.freed(pt3_dict),
   1639            a.freed(pt3_name),
   1640            a.freed(pt3),
   1641            a.freed(kps[3][1]),
   1642            a.freed(qs[3]),
   1643          })
   1644          eq(false, lib.tv_dict_watcher_remove(d, cbs[3][1], #cbs[3][1], cbs[3][2][0]))
   1645          eq({ { busy = false, pat = cbs[1][1], cb = { type = 'none' } } }, dict_watchers(d))
   1646          eq(true, lib.tv_dict_watcher_remove(d, cbs[1][1], #cbs[1][1], cbs[1][2][0]))
   1647          alloc_log:check({
   1648            a.freed(kps[1][1]),
   1649            a.freed(qs[1]),
   1650          })
   1651          eq(false, lib.tv_dict_watcher_remove(d, cbs[1][1], #cbs[1][1], cbs[1][2][0]))
   1652          eq({}, dict_watchers(d))
   1653        end)
   1654      end)
   1655      describe('notify', function()
   1656        -- Way too hard to test it here, functional tests in
   1657        -- dict_notifications_spec.lua.
   1658      end)
   1659    end)
   1660    describe('item', function()
   1661      describe('alloc()/free()', function()
   1662        local function check_tv_dict_item_alloc_len(s, len, tv, more_frees)
   1663          local di
   1664          if len == nil then
   1665            di = ffi.gc(lib.tv_dict_item_alloc(s), nil)
   1666            len = #s
   1667          else
   1668            di = ffi.gc(lib.tv_dict_item_alloc_len(s, len or #s), nil)
   1669          end
   1670          eq(s:sub(1, len), ffi.string(di.di_key))
   1671          alloc_log:check({ a.di(di, len) })
   1672          if tv then
   1673            di.di_tv = ffi.gc(tv, nil)
   1674          else
   1675            di.di_tv.v_type = lib.VAR_UNKNOWN
   1676          end
   1677          lib.tv_dict_item_free(di)
   1678          alloc_log:check(concat_tables(more_frees, { a.freed(di) }))
   1679        end
   1680        local function check_tv_dict_item_alloc(s, tv, more_frees)
   1681          return check_tv_dict_item_alloc_len(s, nil, tv, more_frees)
   1682        end
   1683        itp('works', function()
   1684          check_tv_dict_item_alloc('')
   1685          check_tv_dict_item_alloc('t')
   1686          check_tv_dict_item_alloc('TEST')
   1687          check_tv_dict_item_alloc_len('', 0)
   1688          check_tv_dict_item_alloc_len('TEST', 2)
   1689          local tv = lua2typvalt('test')
   1690          alloc_log:check({ a.str(tv.vval.v_string, #'test') })
   1691          check_tv_dict_item_alloc('', tv, { a.freed(tv.vval.v_string) })
   1692          tv = lua2typvalt('test')
   1693          alloc_log:check({ a.str(tv.vval.v_string, #'test') })
   1694          check_tv_dict_item_alloc_len('', 0, tv, { a.freed(tv.vval.v_string) })
   1695        end)
   1696      end)
   1697      describe('add()/remove()', function()
   1698        itp('works', function()
   1699          local d = dict()
   1700          eq({}, dct2tbl(d))
   1701          alloc_log:check({ a.dict(d) })
   1702          local di = ffi.gc(lib.tv_dict_item_alloc(''), nil)
   1703          local tv = lua2typvalt('test')
   1704          di.di_tv = ffi.gc(tv, nil)
   1705          alloc_log:check({ a.di(di, ''), a.str(tv.vval.v_string, 'test') })
   1706          eq(OK, lib.tv_dict_add(d, di))
   1707          alloc_log:check({})
   1708          eq(
   1709            FAIL,
   1710            check_emsg(function()
   1711              return lib.tv_dict_add(d, di)
   1712            end, 'E685: Internal error: hash_add(): duplicate key ""')
   1713          )
   1714          alloc_log:clear()
   1715          lib.tv_dict_item_remove(d, di)
   1716          alloc_log:check({
   1717            a.freed(tv.vval.v_string),
   1718            a.freed(di),
   1719          })
   1720        end)
   1721      end)
   1722    end)
   1723    describe('indexing', function()
   1724      describe('find()', function()
   1725        local function tv_dict_find(d, key, key_len)
   1726          local di = lib.tv_dict_find(d, key, key_len or #key)
   1727          if di == nil then
   1728            return nil, nil, nil
   1729          end
   1730          return typvalt2lua(di.di_tv), ffi.string(di.di_key), di
   1731        end
   1732        itp('works with NULL dict', function()
   1733          eq(nil, lib.tv_dict_find(nil, '', 0))
   1734          eq(nil, lib.tv_dict_find(nil, 'test', -1))
   1735          eq(nil, lib.tv_dict_find(nil, nil, 0))
   1736        end)
   1737        itp('works with empty key', function()
   1738          local lua_d = {
   1739            [''] = 0,
   1740            t = 1,
   1741            te = 2,
   1742            tes = 3,
   1743            test = 4,
   1744            testt = 5,
   1745          }
   1746          local d = dict(lua_d)
   1747          alloc_log:clear()
   1748          eq(lua_d, dct2tbl(d))
   1749          alloc_log:check({})
   1750          local dis = dict_items(d)
   1751          eq({ 0, '', dis[''] }, { tv_dict_find(d, '', 0) })
   1752        end)
   1753        itp('works with len properly', function()
   1754          local lua_d = {
   1755            [''] = 0,
   1756            t = 1,
   1757            te = 2,
   1758            tes = 3,
   1759            test = 4,
   1760            testt = 5,
   1761          }
   1762          local d = dict(lua_d)
   1763          alloc_log:clear()
   1764          eq(lua_d, dct2tbl(d))
   1765          alloc_log:check({})
   1766          for i = 0, 5 do
   1767            local v, k = tv_dict_find(d, 'testt', i)
   1768            eq({ i, ('testt'):sub(1, i) }, { v, k })
   1769          end
   1770          eq(nil, tv_dict_find(d, 'testt', 6)) -- Should take NUL byte
   1771          eq(5, tv_dict_find(d, 'testt', -1))
   1772          alloc_log:check({})
   1773        end)
   1774      end)
   1775      describe('get_number()', function()
   1776        itp('works with NULL dict', function()
   1777          eq(
   1778            0,
   1779            check_emsg(function()
   1780              return lib.tv_dict_get_number(nil, 'test')
   1781            end, nil)
   1782          )
   1783        end)
   1784        itp('works', function()
   1785          local d = ffi.gc(dict({ test = {} }), nil)
   1786          eq(
   1787            0,
   1788            check_emsg(function()
   1789              return lib.tv_dict_get_number(d, 'test')
   1790            end, 'E728: Using a Dictionary as a Number')
   1791          )
   1792          d = ffi.gc(dict({ tes = int(42), t = 44, te = '43' }), nil)
   1793          alloc_log:clear()
   1794          eq(
   1795            0,
   1796            check_emsg(function()
   1797              return lib.tv_dict_get_number(d, 'test')
   1798            end, nil)
   1799          )
   1800          eq(
   1801            42,
   1802            check_emsg(function()
   1803              return lib.tv_dict_get_number(d, 'tes')
   1804            end, nil)
   1805          )
   1806          eq(
   1807            43,
   1808            check_emsg(function()
   1809              return lib.tv_dict_get_number(d, 'te')
   1810            end, nil)
   1811          )
   1812          alloc_log:check({})
   1813          eq(
   1814            0,
   1815            check_emsg(function()
   1816              return lib.tv_dict_get_number(d, 't')
   1817            end, 'E805: Using a Float as a Number')
   1818          )
   1819        end)
   1820      end)
   1821      describe('get_string()', function()
   1822        itp('works with NULL dict', function()
   1823          eq(
   1824            nil,
   1825            check_emsg(function()
   1826              return lib.tv_dict_get_string(nil, 'test', false)
   1827            end, nil)
   1828          )
   1829        end)
   1830        itp('works', function()
   1831          local d = ffi.gc(dict({ test = {} }), nil)
   1832          eq(
   1833            '',
   1834            ffi.string(check_emsg(function()
   1835              return lib.tv_dict_get_string(d, 'test', false)
   1836            end, 'E731: Using a Dictionary as a String'))
   1837          )
   1838          d = ffi.gc(dict({ tes = int(42), t = 44, te = '43', xx = int(45) }), nil)
   1839          alloc_log:clear()
   1840          local dis = dict_items(d)
   1841          eq(
   1842            nil,
   1843            check_emsg(function()
   1844              return lib.tv_dict_get_string(d, 'test', false)
   1845            end, nil)
   1846          )
   1847          local s42 = check_emsg(function()
   1848            return lib.tv_dict_get_string(d, 'tes', false)
   1849          end, nil)
   1850          eq('42', ffi.string(s42))
   1851          local s45 = check_emsg(function()
   1852            return lib.tv_dict_get_string(d, 'xx', false)
   1853          end, nil)
   1854          eq(s42, s45)
   1855          eq('45', ffi.string(s45))
   1856          eq('45', ffi.string(s42))
   1857          local s43 = check_emsg(function()
   1858            return lib.tv_dict_get_string(d, 'te', false)
   1859          end, nil)
   1860          eq('43', ffi.string(s43))
   1861          neq(s42, s43)
   1862          eq(s43, dis.te.di_tv.vval.v_string)
   1863          alloc_log:check({})
   1864          local s44 = check_emsg(function()
   1865            return lib.tv_dict_get_string(d, 't', false)
   1866          end, nil)
   1867          eq('44.0', ffi.string(s44))
   1868          alloc_log:check({ a.freed(alloc_log.null), a.freed(alloc_log.null) })
   1869        end)
   1870        itp('allocates a string copy when requested', function()
   1871          local function tv_dict_get_string_alloc(d, key, emsg, is_float)
   1872            alloc_log:clear()
   1873            local ret = check_emsg(function()
   1874              return lib.tv_dict_get_string(d, key, true)
   1875            end, emsg)
   1876            local s_ret = (ret ~= nil) and ffi.string(ret) or nil
   1877            if not emsg then
   1878              if s_ret then
   1879                if is_float then
   1880                  alloc_log:check({
   1881                    a.freed(alloc_log.null),
   1882                    a.freed(alloc_log.null),
   1883                    a.str(ret, s_ret),
   1884                  })
   1885                else
   1886                  alloc_log:check({ a.str(ret, s_ret) })
   1887                end
   1888              else
   1889                alloc_log:check({})
   1890              end
   1891            end
   1892            lib.xfree(ret)
   1893            return s_ret
   1894          end
   1895          local d = ffi.gc(dict({ test = {} }), nil)
   1896          eq('', tv_dict_get_string_alloc(d, 'test', 'E731: Using a Dictionary as a String'))
   1897          d = ffi.gc(dict({ tes = int(42), t = 44, te = '43', xx = int(45) }), nil)
   1898          alloc_log:clear()
   1899          eq(nil, tv_dict_get_string_alloc(d, 'test'))
   1900          eq('42', tv_dict_get_string_alloc(d, 'tes'))
   1901          eq('45', tv_dict_get_string_alloc(d, 'xx'))
   1902          eq('43', tv_dict_get_string_alloc(d, 'te'))
   1903          eq('44.0', tv_dict_get_string_alloc(d, 't', nil, true))
   1904        end)
   1905      end)
   1906      describe('get_string_buf()', function()
   1907        local function tv_dict_get_string_buf(d, key, is_float, buf, emsg)
   1908          buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree)
   1909          alloc_log:clear()
   1910          local ret = check_emsg(function()
   1911            return lib.tv_dict_get_string_buf(d, key, buf)
   1912          end, emsg)
   1913          local s_ret = (ret ~= nil) and ffi.string(ret) or nil
   1914          if not emsg then
   1915            if is_float then
   1916              alloc_log:check({ a.freed(alloc_log.null), a.freed(alloc_log.null) })
   1917            else
   1918              alloc_log:check({})
   1919            end
   1920          end
   1921          return s_ret, ret, buf
   1922        end
   1923        itp('works with NULL dict', function()
   1924          eq(nil, tv_dict_get_string_buf(nil, 'test'))
   1925        end)
   1926        itp('works', function()
   1927          local lua_d = {
   1928            [''] = {},
   1929            t = 1,
   1930            te = int(2),
   1931            tes = empty_list,
   1932            test = 'tset',
   1933            testt = 5,
   1934          }
   1935          local d = dict(lua_d)
   1936          alloc_log:clear()
   1937          eq(lua_d, dct2tbl(d))
   1938          alloc_log:check({})
   1939          local s, r, b
   1940          s, r, b = tv_dict_get_string_buf(d, 'test')
   1941          neq(r, b)
   1942          eq('tset', s)
   1943          s, r, b = tv_dict_get_string_buf(d, 't', true)
   1944          eq(r, b)
   1945          eq('1.0', s)
   1946          s, r, b = tv_dict_get_string_buf(d, 'te')
   1947          eq(r, b)
   1948          eq('2', s)
   1949        end)
   1950      end)
   1951      describe('get_string_buf_chk()', function()
   1952        local function tv_dict_get_string_buf_chk(d, key, is_float, len, buf, def, emsg)
   1953          buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree)
   1954          def = def or ffi.gc(lib.xstrdup('DEFAULT'), lib.xfree)
   1955          len = len or #key
   1956          alloc_log:clear()
   1957          local ret = check_emsg(function()
   1958            return lib.tv_dict_get_string_buf_chk(d, key, len, buf, def)
   1959          end, emsg)
   1960          local s_ret = (ret ~= nil) and ffi.string(ret) or nil
   1961          if not emsg then
   1962            if is_float then
   1963              alloc_log:check({ a.freed(alloc_log.null), a.freed(alloc_log.null) })
   1964            else
   1965              alloc_log:check({})
   1966            end
   1967          end
   1968          return s_ret, ret, buf, def
   1969        end
   1970        itp('works with NULL dict', function()
   1971          eq('DEFAULT', tv_dict_get_string_buf_chk(nil, 'test'))
   1972        end)
   1973        itp('works', function()
   1974          local lua_d = {
   1975            [''] = {},
   1976            t = 1,
   1977            te = int(2),
   1978            tes = empty_list,
   1979            test = 'tset',
   1980            testt = 5,
   1981          }
   1982          local d = dict(lua_d)
   1983          alloc_log:clear()
   1984          eq(lua_d, dct2tbl(d))
   1985          alloc_log:check({})
   1986          local s, r, b, def
   1987          s, r, b, def = tv_dict_get_string_buf_chk(d, 'test')
   1988          neq(r, b)
   1989          neq(r, def)
   1990          eq('tset', s)
   1991          s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', true, 1)
   1992          eq(r, b)
   1993          neq(r, def)
   1994          eq('1.0', s)
   1995          s, r, b, def = tv_dict_get_string_buf_chk(d, 'te')
   1996          eq(r, b)
   1997          neq(r, def)
   1998          eq('2', s)
   1999          s, r, b, def = tv_dict_get_string_buf_chk(d, 'TEST')
   2000          eq(r, def)
   2001          neq(r, b)
   2002          eq('DEFAULT', s)
   2003        end)
   2004      end)
   2005      describe('get_callback()', function()
   2006        local function tv_dict_get_callback(d, key, key_len, emsg)
   2007          key_len = key_len or #key
   2008          local cb =
   2009            ffi.gc(ffi.cast('Callback*', lib.xmalloc(ffi.sizeof('Callback'))), lib.callback_free)
   2010          alloc_log:clear()
   2011          local ret = check_emsg(function()
   2012            return lib.tv_dict_get_callback(d, key, key_len, cb)
   2013          end, emsg)
   2014          local cb_lua = callback2tbl(cb[0])
   2015          return cb_lua, ret
   2016        end
   2017        itp('works with NULL dict', function()
   2018          eq({ { type = 'none' }, true }, { tv_dict_get_callback(nil, '') })
   2019        end)
   2020        itp('works', function()
   2021          local lua_d = {
   2022            [''] = 'tr',
   2023            t = int(1),
   2024            te = { [type_key] = func_type, value = 'tr' },
   2025            tes = { [type_key] = func_type, value = 'tr', args = { 'a', 'b' } },
   2026            test = { [type_key] = func_type, value = 'Test', dict = { test = 1 }, args = {} },
   2027            testt = { [type_key] = func_type, value = 'Test', dict = { test = 1 }, args = { 1 } },
   2028          }
   2029          local d = dict(lua_d)
   2030          eq(lua_d, dct2tbl(d))
   2031          eq({ { type = 'fref', fref = 'tr' }, true }, { tv_dict_get_callback(d, '', -1) })
   2032          eq({ { type = 'none' }, true }, { tv_dict_get_callback(d, 'x', -1) })
   2033          eq({ { type = 'fref', fref = 'tr' }, true }, { tv_dict_get_callback(d, 'testt', 0) })
   2034          eq({ { type = 'none' }, false }, {
   2035            tv_dict_get_callback(
   2036              d,
   2037              'test',
   2038              1,
   2039              'E6000: Argument is not a function or function name'
   2040            ),
   2041          })
   2042          eq({ { type = 'fref', fref = 'tr' }, true }, { tv_dict_get_callback(d, 'testt', 2) })
   2043          eq({
   2044            {
   2045              type = 'pt',
   2046              fref = 'tr',
   2047              pt = {
   2048                [type_key] = func_type,
   2049                value = 'tr',
   2050                args = { 'a', 'b' },
   2051              },
   2052            },
   2053            true,
   2054          }, { tv_dict_get_callback(d, 'testt', 3) })
   2055          eq({
   2056            {
   2057              type = 'pt',
   2058              fref = 'Test',
   2059              pt = { [type_key] = func_type, value = 'Test', dict = { test = 1 }, args = {} },
   2060            },
   2061            true,
   2062          }, { tv_dict_get_callback(d, 'testt', 4) })
   2063          eq({
   2064            {
   2065              type = 'pt',
   2066              fref = 'Test',
   2067              pt = { [type_key] = func_type, value = 'Test', dict = { test = 1 }, args = { 1 } },
   2068            },
   2069            true,
   2070          }, { tv_dict_get_callback(d, 'testt', 5) })
   2071        end)
   2072      end)
   2073    end)
   2074    describe('add', function()
   2075      describe('()', function()
   2076        itp('works', function()
   2077          local di = lib.tv_dict_item_alloc_len('t-est', 5)
   2078          alloc_log:check({ a.di(di, 't-est') })
   2079          di.di_tv.v_type = lib.VAR_NUMBER
   2080          di.di_tv.vval.v_number = 42
   2081          local d = dict({ test = 10 })
   2082          local dis = dict_items(d)
   2083          alloc_log:check({
   2084            a.dict(d),
   2085            a.di(dis.test, 'test'),
   2086          })
   2087          eq({ test = 10 }, dct2tbl(d))
   2088          alloc_log:clear()
   2089          eq(OK, lib.tv_dict_add(d, di))
   2090          alloc_log:check({})
   2091          eq({ test = 10, ['t-est'] = int(42) }, dct2tbl(d))
   2092          eq(
   2093            FAIL,
   2094            check_emsg(function()
   2095              return lib.tv_dict_add(d, di)
   2096            end, 'E685: Internal error: hash_add(): duplicate key "t-est"')
   2097          )
   2098        end)
   2099      end)
   2100      describe('list()', function()
   2101        itp('works', function()
   2102          local l = list(1, 2, 3)
   2103          alloc_log:clear()
   2104          eq(1, l.lv_refcount)
   2105          local d = dict({ test = 10 })
   2106          alloc_log:clear()
   2107          eq({ test = 10 }, dct2tbl(d))
   2108          eq(OK, lib.tv_dict_add_list(d, 'testt', 3, l))
   2109          local dis = dict_items(d)
   2110          alloc_log:check({ a.di(dis.tes, 'tes') })
   2111          eq({ test = 10, tes = { 1, 2, 3 } }, dct2tbl(d))
   2112          eq(2, l.lv_refcount)
   2113          eq(
   2114            FAIL,
   2115            check_emsg(function()
   2116              return lib.tv_dict_add_list(d, 'testt', 3, l)
   2117            end, 'E685: Internal error: hash_add(): duplicate key "tes"')
   2118          )
   2119          eq(2, l.lv_refcount)
   2120          alloc_log:clear()
   2121          lib.emsg_skip = lib.emsg_skip + 1
   2122          eq(
   2123            FAIL,
   2124            check_emsg(function()
   2125              return lib.tv_dict_add_list(d, 'testt', 3, l)
   2126            end, nil)
   2127          )
   2128          eq(2, l.lv_refcount)
   2129          lib.emsg_skip = lib.emsg_skip - 1
   2130          alloc_log:clear_tmp_allocs()
   2131          alloc_log:check({})
   2132        end)
   2133      end)
   2134      describe('dict()', function()
   2135        itp('works', function()
   2136          local d2 = dict({ foo = 42 })
   2137          alloc_log:clear()
   2138          eq(1, d2.dv_refcount)
   2139          local d = dict({ test = 10 })
   2140          alloc_log:clear()
   2141          eq({ test = 10 }, dct2tbl(d))
   2142          eq(OK, lib.tv_dict_add_dict(d, 'testt', 3, d2))
   2143          local dis = dict_items(d)
   2144          alloc_log:check({ a.di(dis.tes, 'tes') })
   2145          eq({ test = 10, tes = { foo = 42 } }, dct2tbl(d))
   2146          eq(2, d2.dv_refcount)
   2147          eq(
   2148            FAIL,
   2149            check_emsg(function()
   2150              return lib.tv_dict_add_dict(d, 'testt', 3, d2)
   2151            end, 'E685: Internal error: hash_add(): duplicate key "tes"')
   2152          )
   2153          eq(2, d2.dv_refcount)
   2154          alloc_log:clear()
   2155          lib.emsg_skip = lib.emsg_skip + 1
   2156          eq(
   2157            FAIL,
   2158            check_emsg(function()
   2159              return lib.tv_dict_add_dict(d, 'testt', 3, d2)
   2160            end, nil)
   2161          )
   2162          eq(2, d2.dv_refcount)
   2163          lib.emsg_skip = lib.emsg_skip - 1
   2164          alloc_log:clear_tmp_allocs()
   2165          alloc_log:check({})
   2166        end)
   2167      end)
   2168      describe('nr()', function()
   2169        itp('works', function()
   2170          local d = dict({ test = 10 })
   2171          alloc_log:clear()
   2172          eq({ test = 10 }, dct2tbl(d))
   2173          eq(OK, lib.tv_dict_add_nr(d, 'testt', 3, 2))
   2174          local dis = dict_items(d)
   2175          alloc_log:check({ a.di(dis.tes, 'tes') })
   2176          eq({ test = 10, tes = int(2) }, dct2tbl(d))
   2177          eq(
   2178            FAIL,
   2179            check_emsg(function()
   2180              return lib.tv_dict_add_nr(d, 'testt', 3, 2)
   2181            end, 'E685: Internal error: hash_add(): duplicate key "tes"')
   2182          )
   2183          alloc_log:clear()
   2184          lib.emsg_skip = lib.emsg_skip + 1
   2185          eq(
   2186            FAIL,
   2187            check_emsg(function()
   2188              return lib.tv_dict_add_nr(d, 'testt', 3, 2)
   2189            end, nil)
   2190          )
   2191          lib.emsg_skip = lib.emsg_skip - 1
   2192          alloc_log:clear_tmp_allocs()
   2193          alloc_log:check({})
   2194        end)
   2195      end)
   2196      describe('float()', function()
   2197        itp('works', function()
   2198          local d = dict({ test = 10 })
   2199          alloc_log:clear()
   2200          eq({ test = 10 }, dct2tbl(d))
   2201          eq(OK, lib.tv_dict_add_float(d, 'testt', 3, 1.5))
   2202          local dis = dict_items(d)
   2203          alloc_log:check({ a.di(dis.tes, 'tes') })
   2204          eq({ test = 10, tes = 1.5 }, dct2tbl(d))
   2205          eq(
   2206            FAIL,
   2207            check_emsg(function()
   2208              return lib.tv_dict_add_float(d, 'testt', 3, 1.5)
   2209            end, 'E685: Internal error: hash_add(): duplicate key "tes"')
   2210          )
   2211          alloc_log:clear()
   2212          lib.emsg_skip = lib.emsg_skip + 1
   2213          eq(
   2214            FAIL,
   2215            check_emsg(function()
   2216              return lib.tv_dict_add_float(d, 'testt', 3, 1.5)
   2217            end, nil)
   2218          )
   2219          lib.emsg_skip = lib.emsg_skip - 1
   2220          alloc_log:clear_tmp_allocs()
   2221          alloc_log:check({})
   2222        end)
   2223      end)
   2224      describe('str()', function()
   2225        itp('works', function()
   2226          local d = dict({ test = 10 })
   2227          alloc_log:clear()
   2228          eq({ test = 10 }, dct2tbl(d))
   2229          eq(OK, lib.tv_dict_add_str(d, 'testt', 3, 'TEST'))
   2230          local dis = dict_items(d)
   2231          alloc_log:check({
   2232            a.str(dis.tes.di_tv.vval.v_string, 'TEST'),
   2233            a.di(dis.tes, 'tes'),
   2234          })
   2235          eq({ test = 10, tes = 'TEST' }, dct2tbl(d))
   2236          eq(
   2237            FAIL,
   2238            check_emsg(function()
   2239              return lib.tv_dict_add_str(d, 'testt', 3, 'TEST')
   2240            end, 'E685: Internal error: hash_add(): duplicate key "tes"')
   2241          )
   2242          alloc_log:clear()
   2243          lib.emsg_skip = lib.emsg_skip + 1
   2244          eq(
   2245            FAIL,
   2246            check_emsg(function()
   2247              return lib.tv_dict_add_str(d, 'testt', 3, 'TEST')
   2248            end, nil)
   2249          )
   2250          lib.emsg_skip = lib.emsg_skip - 1
   2251          alloc_log:clear_tmp_allocs()
   2252          alloc_log:check({})
   2253        end)
   2254      end)
   2255      describe('allocated_str()', function()
   2256        itp('works', function()
   2257          local d = dict({ test = 10 })
   2258          eq({ test = 10 }, dct2tbl(d))
   2259          alloc_log:clear()
   2260          local s1 = lib.xstrdup('TEST')
   2261          local s2 = lib.xstrdup('TEST')
   2262          local s3 = lib.xstrdup('TEST')
   2263          alloc_log:check({
   2264            a.str(s1, 'TEST'),
   2265            a.str(s2, 'TEST'),
   2266            a.str(s3, 'TEST'),
   2267          })
   2268          eq(OK, lib.tv_dict_add_allocated_str(d, 'testt', 3, s1))
   2269          local dis = dict_items(d)
   2270          alloc_log:check({
   2271            a.di(dis.tes, 'tes'),
   2272          })
   2273          eq({ test = 10, tes = 'TEST' }, dct2tbl(d))
   2274          eq(
   2275            FAIL,
   2276            check_emsg(function()
   2277              return lib.tv_dict_add_allocated_str(d, 'testt', 3, s2)
   2278            end, 'E685: Internal error: hash_add(): duplicate key "tes"')
   2279          )
   2280          alloc_log:clear()
   2281          lib.emsg_skip = lib.emsg_skip + 1
   2282          eq(
   2283            FAIL,
   2284            check_emsg(function()
   2285              return lib.tv_dict_add_allocated_str(d, 'testt', 3, s3)
   2286            end, nil)
   2287          )
   2288          lib.emsg_skip = lib.emsg_skip - 1
   2289          alloc_log:clear_tmp_allocs()
   2290          alloc_log:check({
   2291            a.freed(s3),
   2292          })
   2293        end)
   2294      end)
   2295    end)
   2296    describe('clear()', function()
   2297      itp('works', function()
   2298        local d = dict()
   2299        alloc_log:check({ a.dict(d) })
   2300        eq({}, dct2tbl(d))
   2301        lib.tv_dict_clear(d)
   2302        eq({}, dct2tbl(d))
   2303        lib.tv_dict_add_str(d, 'TEST', 3, 'tEsT')
   2304        local dis = dict_items(d)
   2305        local di = dis.TES
   2306        local di_s = di.di_tv.vval.v_string
   2307        alloc_log:check({ a.str(di_s), a.di(di) })
   2308        eq({ TES = 'tEsT' }, dct2tbl(d))
   2309        lib.tv_dict_clear(d)
   2310        alloc_log:check({ a.freed(di_s), a.freed(di) })
   2311        eq({}, dct2tbl(d))
   2312      end)
   2313    end)
   2314    describe('extend()', function()
   2315      local function tv_dict_extend(d1, d2, action, emsg)
   2316        action = action or 'force'
   2317        check_emsg(function()
   2318          return lib.tv_dict_extend(d1, d2, action)
   2319        end, emsg)
   2320      end
   2321      itp('works', function()
   2322        local d1 = dict()
   2323        alloc_log:check({ a.dict(d1) })
   2324        eq({}, dct2tbl(d1))
   2325        local d2 = dict()
   2326        alloc_log:check({ a.dict(d2) })
   2327        eq({}, dct2tbl(d2))
   2328        tv_dict_extend(d1, d2, 'error')
   2329        tv_dict_extend(d1, d2, 'keep')
   2330        tv_dict_extend(d1, d2, 'force')
   2331        alloc_log:check({})
   2332 
   2333        d1 = dict({ a = 'TEST' })
   2334        eq({ a = 'TEST' }, dct2tbl(d1))
   2335        local dis1 = dict_items(d1)
   2336        local a1_s = dis1.a.di_tv.vval.v_string
   2337        alloc_log:clear_tmp_allocs()
   2338        alloc_log:check({
   2339          a.dict(d1),
   2340          a.di(dis1.a),
   2341          a.str(a1_s),
   2342        })
   2343        d2 = dict({ a = 'TSET' })
   2344        eq({ a = 'TSET' }, dct2tbl(d2))
   2345        local dis2 = dict_items(d2)
   2346        local a2_s = dis2.a.di_tv.vval.v_string
   2347        alloc_log:clear_tmp_allocs()
   2348        alloc_log:check({
   2349          a.dict(d2),
   2350          a.di(dis2.a),
   2351          a.str(a2_s),
   2352        })
   2353 
   2354        tv_dict_extend(d1, d2, 'error', 'E737: Key already exists: a')
   2355        eq({ a = 'TEST' }, dct2tbl(d1))
   2356        eq({ a = 'TSET' }, dct2tbl(d2))
   2357        alloc_log:clear()
   2358 
   2359        tv_dict_extend(d1, d2, 'keep')
   2360        alloc_log:check({})
   2361        eq({ a = 'TEST' }, dct2tbl(d1))
   2362        eq({ a = 'TSET' }, dct2tbl(d2))
   2363 
   2364        tv_dict_extend(d1, d2, 'force')
   2365        alloc_log:check({
   2366          a.freed(a1_s),
   2367          a.str(dis1.a.di_tv.vval.v_string),
   2368        })
   2369        eq({ a = 'TSET' }, dct2tbl(d1))
   2370        eq({ a = 'TSET' }, dct2tbl(d2))
   2371      end)
   2372      pending('disallows overriding builtin or user functions: here be the dragons', function()
   2373        -- pending: see TODO below
   2374        local d = dict()
   2375        d.dv_scope = lib.VAR_DEF_SCOPE
   2376        local f_lua = {
   2377          [type_key] = func_type,
   2378          value = 'tr',
   2379        }
   2380        local f_tv = lua2typvalt(f_lua)
   2381        local p_lua = {
   2382          [type_key] = func_type,
   2383          value = 'tr',
   2384          args = { 1 },
   2385        }
   2386        local p_tv = lua2typvalt(p_lua)
   2387        eq(lib.VAR_PARTIAL, p_tv.v_type)
   2388        local d2 = dict({ tr = f_tv })
   2389        local d3 = dict({ tr = p_tv })
   2390        local d4 = dict({ ['TEST:THIS'] = p_tv })
   2391        local d5 = dict({ Test = f_tv })
   2392        local d6 = dict({ Test = p_tv })
   2393        eval0([[execute("function Test()\nendfunction")]])
   2394        -- TODO: test breaks at this point
   2395        tv_dict_extend(d, d2, 'force', 'E704: Funcref variable name must start with a capital: tr')
   2396        tv_dict_extend(d, d3, 'force', 'E704: Funcref variable name must start with a capital: tr')
   2397        tv_dict_extend(d, d4, 'force', 'E461: Illegal variable name: TEST:THIS')
   2398        tv_dict_extend(d, d5, 'force', 'E705: Variable name conflicts with existing function: Test')
   2399        tv_dict_extend(d, d6, 'force', 'E705: Variable name conflicts with existing function: Test')
   2400        eq({}, dct2tbl(d))
   2401        d.dv_scope = lib.VAR_SCOPE
   2402        tv_dict_extend(d, d4, 'force', 'E461: Illegal variable name: TEST:THIS')
   2403        eq({}, dct2tbl(d))
   2404        tv_dict_extend(d, d2, 'force')
   2405        eq({ tr = f_lua }, dct2tbl(d))
   2406        tv_dict_extend(d, d3, 'force')
   2407        eq({ tr = p_lua }, dct2tbl(d))
   2408        tv_dict_extend(d, d5, 'force')
   2409        eq({ tr = p_lua, Test = f_lua }, dct2tbl(d))
   2410        tv_dict_extend(d, d6, 'force')
   2411        eq({ tr = p_lua, Test = p_lua }, dct2tbl(d))
   2412      end)
   2413      itp('cares about locks and read-only items', function()
   2414        local d_lua = { tv_locked = 1, tv_fixed = 2, di_ro = 3, di_ro_sbx = 4 }
   2415        local d = dict(d_lua)
   2416        local dis = dict_items(d)
   2417        dis.tv_locked.di_tv.v_lock = lib.VAR_LOCKED
   2418        dis.tv_fixed.di_tv.v_lock = lib.VAR_FIXED
   2419        dis.di_ro.di_flags = bit.bor(dis.di_ro.di_flags, lib.DI_FLAGS_RO)
   2420        dis.di_ro_sbx.di_flags = bit.bor(dis.di_ro_sbx.di_flags, lib.DI_FLAGS_RO_SBX)
   2421        lib.sandbox = true
   2422        local d1 = dict({ tv_locked = 41 })
   2423        local d2 = dict({ tv_fixed = 42 })
   2424        local d3 = dict({ di_ro = 43 })
   2425        local d4 = dict({ di_ro_sbx = 44 })
   2426        tv_dict_extend(d, d1, 'force', 'E741: Value is locked: extend() argument')
   2427        tv_dict_extend(d, d2, 'force', 'E742: Cannot change value of extend() argument')
   2428        tv_dict_extend(d, d3, 'force', 'E46: Cannot change read-only variable "extend() argument"')
   2429        tv_dict_extend(
   2430          d,
   2431          d4,
   2432          'force',
   2433          'E794: Cannot set variable in the sandbox: "extend() argument"'
   2434        )
   2435        eq(d_lua, dct2tbl(d))
   2436        lib.sandbox = false
   2437        tv_dict_extend(d, d4, 'force')
   2438        d_lua.di_ro_sbx = 44
   2439        eq(d_lua, dct2tbl(d))
   2440      end)
   2441    end)
   2442    describe('equal()', function()
   2443      local function tv_dict_equal(d1, d2, ic)
   2444        return lib.tv_dict_equal(d1, d2, ic or false)
   2445      end
   2446      itp('works', function()
   2447        eq(true, tv_dict_equal(nil, nil))
   2448        local d1 = dict()
   2449        alloc_log:check({ a.dict(d1) })
   2450        eq(1, d1.dv_refcount)
   2451        eq(true, tv_dict_equal(nil, d1))
   2452        eq(true, tv_dict_equal(d1, nil))
   2453        eq(true, tv_dict_equal(d1, d1))
   2454        eq(1, d1.dv_refcount)
   2455        alloc_log:check({})
   2456        local d_upper = dict({ a = 'TEST' })
   2457        local dis_upper = dict_items(d_upper)
   2458        local d_lower = dict({ a = 'test' })
   2459        local dis_lower = dict_items(d_lower)
   2460        local d_kupper_upper = dict({ A = 'TEST' })
   2461        local dis_kupper_upper = dict_items(d_kupper_upper)
   2462        local d_kupper_lower = dict({ A = 'test' })
   2463        local dis_kupper_lower = dict_items(d_kupper_lower)
   2464        alloc_log:clear_tmp_allocs()
   2465        alloc_log:check({
   2466          a.dict(d_upper),
   2467          a.di(dis_upper.a),
   2468          a.str(dis_upper.a.di_tv.vval.v_string),
   2469 
   2470          a.dict(d_lower),
   2471          a.di(dis_lower.a),
   2472          a.str(dis_lower.a.di_tv.vval.v_string),
   2473 
   2474          a.dict(d_kupper_upper),
   2475          a.di(dis_kupper_upper.A),
   2476          a.str(dis_kupper_upper.A.di_tv.vval.v_string),
   2477 
   2478          a.dict(d_kupper_lower),
   2479          a.di(dis_kupper_lower.A),
   2480          a.str(dis_kupper_lower.A.di_tv.vval.v_string),
   2481        })
   2482        eq(true, tv_dict_equal(d_upper, d_upper))
   2483        eq(true, tv_dict_equal(d_upper, d_upper, true))
   2484        eq(false, tv_dict_equal(d_upper, d_lower, false))
   2485        eq(true, tv_dict_equal(d_upper, d_lower, true))
   2486        eq(true, tv_dict_equal(d_kupper_upper, d_kupper_lower, true))
   2487        eq(false, tv_dict_equal(d_kupper_upper, d_lower, true))
   2488        eq(false, tv_dict_equal(d_kupper_upper, d_upper, true))
   2489        alloc_log:check({})
   2490      end)
   2491    end)
   2492    describe('copy()', function()
   2493      local function tv_dict_copy(...)
   2494        return ffi.gc(lib.tv_dict_copy(...), lib.tv_dict_unref)
   2495      end
   2496      itp('copies NULL correctly', function()
   2497        eq(nil, lib.tv_dict_copy(nil, nil, true, 0))
   2498        eq(nil, lib.tv_dict_copy(nil, nil, false, 0))
   2499        eq(nil, lib.tv_dict_copy(nil, nil, true, 1))
   2500        eq(nil, lib.tv_dict_copy(nil, nil, false, 1))
   2501      end)
   2502      itp('copies dict correctly without converting items', function()
   2503        do
   2504          local v = {
   2505            a = { ['«'] = '»' },
   2506            b = { '„' },
   2507            ['1'] = 1,
   2508            ['«»'] = '“',
   2509            ns = null_string,
   2510            nl = null_list,
   2511            nd = null_dict,
   2512          }
   2513          local d_tv = lua2typvalt(v)
   2514          local d = d_tv.vval.v_dict
   2515          local dis = dict_items(d)
   2516          alloc_log:clear()
   2517 
   2518          eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
   2519          eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
   2520          local d_copy1 = tv_dict_copy(nil, d, false, 0)
   2521          eq(2, dis.a.di_tv.vval.v_dict.dv_refcount)
   2522          eq(2, dis.b.di_tv.vval.v_list.lv_refcount)
   2523          local dis_copy1 = dict_items(d_copy1)
   2524          eq(dis.a.di_tv.vval.v_dict, dis_copy1.a.di_tv.vval.v_dict)
   2525          eq(dis.b.di_tv.vval.v_list, dis_copy1.b.di_tv.vval.v_list)
   2526          eq(v, dct2tbl(d_copy1))
   2527          alloc_log:clear()
   2528          lib.tv_dict_free(ffi.gc(d_copy1, nil))
   2529          alloc_log:clear()
   2530 
   2531          eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
   2532          eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
   2533          local d_deepcopy1 = tv_dict_copy(nil, d, true, 0)
   2534          neq(nil, d_deepcopy1)
   2535          eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
   2536          eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
   2537          local dis_deepcopy1 = dict_items(d_deepcopy1)
   2538          neq(dis.a.di_tv.vval.v_dict, dis_deepcopy1.a.di_tv.vval.v_dict)
   2539          neq(dis.b.di_tv.vval.v_list, dis_deepcopy1.b.di_tv.vval.v_list)
   2540          eq(v, dct2tbl(d_deepcopy1))
   2541          alloc_log:clear()
   2542        end
   2543        collectgarbage()
   2544      end)
   2545      itp('copies dict correctly and converts items', function()
   2546        local vc = vimconv_alloc()
   2547        -- UTF-8 ↔ latin1 conversions need no iconv
   2548        eq(OK, lib.convert_setup(vc, to_cstr('utf-8'), to_cstr('latin1')))
   2549 
   2550        local v = {
   2551          a = { ['«'] = '»' },
   2552          b = { '„' },
   2553          ['1'] = 1,
   2554          ['«»'] = '“',
   2555          ns = null_string,
   2556          nl = null_list,
   2557          nd = null_dict,
   2558        }
   2559        local d_tv = lua2typvalt(v)
   2560        local d = d_tv.vval.v_dict
   2561        local dis = dict_items(d)
   2562        alloc_log:clear()
   2563 
   2564        eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
   2565        eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
   2566        local d_deepcopy1 = tv_dict_copy(vc, d, true, 0)
   2567        neq(nil, d_deepcopy1)
   2568        eq(1, dis.a.di_tv.vval.v_dict.dv_refcount)
   2569        eq(1, dis.b.di_tv.vval.v_list.lv_refcount)
   2570        local dis_deepcopy1 = dict_items(d_deepcopy1)
   2571        neq(dis.a.di_tv.vval.v_dict, dis_deepcopy1.a.di_tv.vval.v_dict)
   2572        neq(dis.b.di_tv.vval.v_list, dis_deepcopy1.b.di_tv.vval.v_list)
   2573        eq({
   2574          a = { ['\171'] = '\187' },
   2575          b = { '\191' },
   2576          ['1'] = 1,
   2577          ['\171\187'] = '\191',
   2578          ns = null_string,
   2579          nl = null_list,
   2580          nd = null_dict,
   2581        }, dct2tbl(d_deepcopy1))
   2582        alloc_log:clear_tmp_allocs()
   2583        alloc_log:clear()
   2584      end)
   2585      itp('returns different/same containers with(out) copyID', function()
   2586        local d_inner_tv = lua2typvalt({})
   2587        local d_tv = lua2typvalt({ a = d_inner_tv, b = d_inner_tv })
   2588        eq(3, d_inner_tv.vval.v_dict.dv_refcount)
   2589        local d = d_tv.vval.v_dict
   2590        local dis = dict_items(d)
   2591        eq(dis.a.di_tv.vval.v_dict, dis.b.di_tv.vval.v_dict)
   2592 
   2593        local d_copy1 = tv_dict_copy(nil, d, true, 0)
   2594        local dis_copy1 = dict_items(d_copy1)
   2595        neq(dis_copy1.a.di_tv.vval.v_dict, dis_copy1.b.di_tv.vval.v_dict)
   2596        eq({ a = {}, b = {} }, dct2tbl(d_copy1))
   2597 
   2598        local d_copy2 = tv_dict_copy(nil, d, true, 2)
   2599        local dis_copy2 = dict_items(d_copy2)
   2600        eq(dis_copy2.a.di_tv.vval.v_dict, dis_copy2.b.di_tv.vval.v_dict)
   2601        eq({ a = {}, b = {} }, dct2tbl(d_copy2))
   2602 
   2603        eq(3, d_inner_tv.vval.v_dict.dv_refcount)
   2604      end)
   2605      itp('works with self-referencing dict with copyID', function()
   2606        local d_tv = lua2typvalt({})
   2607        local d = d_tv.vval.v_dict
   2608        eq(1, d.dv_refcount)
   2609        lib.tv_dict_add_dict(d, 'test', 4, d)
   2610        eq(2, d.dv_refcount)
   2611 
   2612        local d_copy1 = tv_dict_copy(nil, d, true, 2)
   2613        eq(2, d_copy1.dv_refcount)
   2614        local v = {}
   2615        v.test = v
   2616        eq(v, dct2tbl(d_copy1))
   2617 
   2618        lib.tv_dict_clear(d)
   2619        eq(1, d.dv_refcount)
   2620 
   2621        lib.tv_dict_clear(d_copy1)
   2622        eq(1, d_copy1.dv_refcount)
   2623      end)
   2624    end)
   2625    describe('set_keys_readonly()', function()
   2626      itp('works', function()
   2627        local d = dict({ a = true })
   2628        local dis = dict_items(d)
   2629        alloc_log:check({ a.dict(d), a.di(dis.a) })
   2630        eq(0, bit.band(dis.a.di_flags, lib.DI_FLAGS_RO))
   2631        eq(0, bit.band(dis.a.di_flags, lib.DI_FLAGS_FIX))
   2632        lib.tv_dict_set_keys_readonly(d)
   2633        alloc_log:check({})
   2634        eq(lib.DI_FLAGS_RO, bit.band(dis.a.di_flags, lib.DI_FLAGS_RO))
   2635        eq(lib.DI_FLAGS_FIX, bit.band(dis.a.di_flags, lib.DI_FLAGS_FIX))
   2636      end)
   2637    end)
   2638  end)
   2639  describe('tv', function()
   2640    describe('alloc', function()
   2641      describe('list ret()', function()
   2642        itp('works', function()
   2643          local rettv = typvalt(lib.VAR_UNKNOWN)
   2644          local l = lib.tv_list_alloc_ret(rettv, 0)
   2645          eq(empty_list, typvalt2lua(rettv))
   2646          eq(rettv.vval.v_list, l)
   2647        end)
   2648      end)
   2649      describe('dict ret()', function()
   2650        itp('works', function()
   2651          local rettv = typvalt(lib.VAR_UNKNOWN)
   2652          lib.tv_dict_alloc_ret(rettv)
   2653          eq({}, typvalt2lua(rettv))
   2654        end)
   2655      end)
   2656    end)
   2657    local function defalloc()
   2658      return {}
   2659    end
   2660    describe('clear()', function()
   2661      itp('works', function()
   2662        local function deffrees(alloc_rets)
   2663          local ret = {}
   2664          for i = #alloc_rets, 1, -1 do
   2665            ret[#alloc_rets - i + 1] = alloc_rets:freed(i)
   2666          end
   2667          return ret
   2668        end
   2669        alloc_log:check({})
   2670        lib.tv_clear(nil)
   2671        alloc_log:check({})
   2672        local ll = {}
   2673        local ll_l = nil
   2674        ll[1] = ll
   2675        local dd = {}
   2676        local dd_d = nil
   2677        dd.dd = dd
   2678        for _, v in ipairs({
   2679          { nil_value },
   2680          {
   2681            null_string,
   2682            nil,
   2683            function()
   2684              return { a.freed(alloc_log.null) }
   2685            end,
   2686          },
   2687          { 0 },
   2688          { int(0) },
   2689          { true },
   2690          { false },
   2691          {
   2692            'true',
   2693            function(tv)
   2694              return { a.str(tv.vval.v_string) }
   2695            end,
   2696          },
   2697          {
   2698            {},
   2699            function(tv)
   2700              return { a.dict(tv.vval.v_dict) }
   2701            end,
   2702          },
   2703          {
   2704            empty_list,
   2705            function(tv)
   2706              return { a.list(tv.vval.v_list) }
   2707            end,
   2708          },
   2709          {
   2710            ll,
   2711            function(tv)
   2712              ll_l = tv.vval.v_list
   2713              return { a.list(tv.vval.v_list), a.li(tv.vval.v_list.lv_first) }
   2714            end,
   2715            defalloc,
   2716          },
   2717          {
   2718            dd,
   2719            function(tv)
   2720              dd_d = tv.vval.v_dict
   2721              return { a.dict(tv.vval.v_dict), a.di(first_di(tv.vval.v_dict)) }
   2722            end,
   2723            defalloc,
   2724          },
   2725        }) do
   2726          local tv = lua2typvalt(v[1])
   2727          local alloc_rets = {}
   2728          alloc_log:check(get_alloc_rets((v[2] or defalloc)(tv), alloc_rets))
   2729          lib.tv_clear(tv)
   2730          alloc_log:check((v[3] or deffrees)(alloc_rets))
   2731        end
   2732        eq(1, ll_l.lv_refcount)
   2733        eq(1, dd_d.dv_refcount)
   2734      end)
   2735    end)
   2736    describe('copy()', function()
   2737      itp('works', function()
   2738        local function strallocs(tv)
   2739          return { a.str(tv.vval.v_string) }
   2740        end
   2741        for _, v in ipairs({
   2742          { nil_value },
   2743          { null_string },
   2744          { 0 },
   2745          { int(0) },
   2746          { true },
   2747          { false },
   2748          {
   2749            {},
   2750            function(tv)
   2751              return { a.dict(tv.vval.v_dict) }
   2752            end,
   2753            nil,
   2754            function(from, to)
   2755              eq(2, to.vval.v_dict.dv_refcount)
   2756              eq(to.vval.v_dict, from.vval.v_dict)
   2757            end,
   2758          },
   2759          {
   2760            empty_list,
   2761            function(tv)
   2762              return { a.list(tv.vval.v_list) }
   2763            end,
   2764            nil,
   2765            function(from, to)
   2766              eq(2, to.vval.v_list.lv_refcount)
   2767              eq(to.vval.v_list, from.vval.v_list)
   2768            end,
   2769          },
   2770          {
   2771            'test',
   2772            strallocs,
   2773            strallocs,
   2774            function(from, to)
   2775              neq(to.vval.v_string, from.vval.v_string)
   2776            end,
   2777          },
   2778        }) do
   2779          local from = lua2typvalt(v[1])
   2780          alloc_log:check((v[2] or defalloc)(from))
   2781          local to = typvalt(lib.VAR_UNKNOWN)
   2782          lib.tv_copy(from, to)
   2783          local res = v[1]
   2784          eq(res, typvalt2lua(to))
   2785          alloc_log:check((v[3] or defalloc)(to))
   2786          if v[4] then
   2787            v[4](from, to)
   2788          end
   2789        end
   2790      end)
   2791    end)
   2792    describe('item_lock()', function()
   2793      itp('does not alter VAR_PARTIAL', function()
   2794        local p_tv = lua2typvalt({
   2795          [type_key] = func_type,
   2796          value = 'tr',
   2797          dict = {},
   2798        })
   2799        lib.tv_item_lock(p_tv, -1, true, false)
   2800        eq(lib.VAR_UNLOCKED, p_tv.vval.v_partial.pt_dict.dv_lock)
   2801      end)
   2802      itp('does not change VAR_FIXED values', function()
   2803        local d_tv = lua2typvalt({})
   2804        local l_tv = lua2typvalt(empty_list)
   2805        alloc_log:clear()
   2806        d_tv.v_lock = lib.VAR_FIXED
   2807        d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED
   2808        l_tv.v_lock = lib.VAR_FIXED
   2809        l_tv.vval.v_list.lv_lock = lib.VAR_FIXED
   2810        lib.tv_item_lock(d_tv, 1, true, false)
   2811        lib.tv_item_lock(l_tv, 1, true, false)
   2812        eq(lib.VAR_FIXED, d_tv.v_lock)
   2813        eq(lib.VAR_FIXED, l_tv.v_lock)
   2814        eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock)
   2815        eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
   2816        lib.tv_item_lock(d_tv, 1, false, false)
   2817        lib.tv_item_lock(l_tv, 1, false, false)
   2818        eq(lib.VAR_FIXED, d_tv.v_lock)
   2819        eq(lib.VAR_FIXED, l_tv.v_lock)
   2820        eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock)
   2821        eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
   2822        alloc_log:check({})
   2823      end)
   2824      itp('works with NULL values', function()
   2825        local l_tv = lua2typvalt(null_list)
   2826        local d_tv = lua2typvalt(null_dict)
   2827        local s_tv = lua2typvalt(null_string)
   2828        alloc_log:clear()
   2829        lib.tv_item_lock(l_tv, 1, true, false)
   2830        lib.tv_item_lock(d_tv, 1, true, false)
   2831        lib.tv_item_lock(s_tv, 1, true, false)
   2832        eq(null_list, typvalt2lua(l_tv))
   2833        eq(null_dict, typvalt2lua(d_tv))
   2834        eq(null_string, typvalt2lua(s_tv))
   2835        eq(lib.VAR_LOCKED, d_tv.v_lock)
   2836        eq(lib.VAR_LOCKED, l_tv.v_lock)
   2837        eq(lib.VAR_LOCKED, s_tv.v_lock)
   2838        alloc_log:check({})
   2839      end)
   2840    end)
   2841    describe('islocked()', function()
   2842      itp('works with NULL values', function()
   2843        local l_tv = lua2typvalt(null_list)
   2844        local d_tv = lua2typvalt(null_dict)
   2845        eq(false, lib.tv_islocked(l_tv))
   2846        eq(false, lib.tv_islocked(d_tv))
   2847      end)
   2848      itp('works', function()
   2849        local tv = lua2typvalt()
   2850        local d_tv = lua2typvalt({})
   2851        local l_tv = lua2typvalt(empty_list)
   2852        alloc_log:clear()
   2853        eq(false, lib.tv_islocked(tv))
   2854        eq(false, lib.tv_islocked(l_tv))
   2855        eq(false, lib.tv_islocked(d_tv))
   2856        d_tv.vval.v_dict.dv_lock = lib.VAR_LOCKED
   2857        l_tv.vval.v_list.lv_lock = lib.VAR_LOCKED
   2858        eq(true, lib.tv_islocked(l_tv))
   2859        eq(true, lib.tv_islocked(d_tv))
   2860        tv.v_lock = lib.VAR_LOCKED
   2861        d_tv.v_lock = lib.VAR_LOCKED
   2862        l_tv.v_lock = lib.VAR_LOCKED
   2863        eq(true, lib.tv_islocked(tv))
   2864        eq(true, lib.tv_islocked(l_tv))
   2865        eq(true, lib.tv_islocked(d_tv))
   2866        d_tv.vval.v_dict.dv_lock = lib.VAR_UNLOCKED
   2867        l_tv.vval.v_list.lv_lock = lib.VAR_UNLOCKED
   2868        eq(true, lib.tv_islocked(tv))
   2869        eq(true, lib.tv_islocked(l_tv))
   2870        eq(true, lib.tv_islocked(d_tv))
   2871        tv.v_lock = lib.VAR_FIXED
   2872        d_tv.v_lock = lib.VAR_FIXED
   2873        l_tv.v_lock = lib.VAR_FIXED
   2874        eq(false, lib.tv_islocked(tv))
   2875        eq(false, lib.tv_islocked(l_tv))
   2876        eq(false, lib.tv_islocked(d_tv))
   2877        d_tv.vval.v_dict.dv_lock = lib.VAR_LOCKED
   2878        l_tv.vval.v_list.lv_lock = lib.VAR_LOCKED
   2879        eq(true, lib.tv_islocked(l_tv))
   2880        eq(true, lib.tv_islocked(d_tv))
   2881        d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED
   2882        l_tv.vval.v_list.lv_lock = lib.VAR_FIXED
   2883        eq(false, lib.tv_islocked(l_tv))
   2884        eq(false, lib.tv_islocked(d_tv))
   2885        alloc_log:check({})
   2886      end)
   2887    end)
   2888    describe('check_lock()', function()
   2889      local function tv_check_lock(lock, name, name_len, emsg)
   2890        return check_emsg(function()
   2891          return lib.value_check_lock(lock, name, name_len)
   2892        end, emsg)
   2893      end
   2894      itp('works', function()
   2895        eq(false, tv_check_lock(lib.VAR_UNLOCKED, 'test', 3))
   2896        eq(true, tv_check_lock(lib.VAR_LOCKED, 'test', 3, 'E741: Value is locked: tes'))
   2897        eq(true, tv_check_lock(lib.VAR_FIXED, 'test', 3, 'E742: Cannot change value of tes'))
   2898        eq(true, tv_check_lock(lib.VAR_LOCKED, nil, 0, 'E741: Value is locked'))
   2899        eq(true, tv_check_lock(lib.VAR_FIXED, nil, 0, 'E742: Cannot change value'))
   2900        eq(true, tv_check_lock(lib.VAR_LOCKED, nil, lib.kTVCstring, 'E741: Value is locked'))
   2901        eq(
   2902          true,
   2903          tv_check_lock(lib.VAR_FIXED, 'test', lib.kTVCstring, 'E742: Cannot change value of test')
   2904        )
   2905      end)
   2906    end)
   2907    describe('equal()', function()
   2908      itp('compares empty and NULL lists correctly', function()
   2909        local l = lua2typvalt(empty_list)
   2910        local l2 = lua2typvalt(empty_list)
   2911        local nl = lua2typvalt(null_list)
   2912 
   2913        -- NULL lists are equal to empty lists
   2914        eq(true, lib.tv_equal(l, nl, true))
   2915        eq(true, lib.tv_equal(nl, l, false))
   2916 
   2917        -- NULL lists are equal themselves
   2918        eq(true, lib.tv_equal(nl, nl, true))
   2919        eq(true, lib.tv_equal(nl, nl, false))
   2920 
   2921        -- As well as empty lists
   2922        eq(true, lib.tv_equal(l, l, true))
   2923        eq(true, lib.tv_equal(l, l2, false))
   2924        eq(true, lib.tv_equal(l2, l, false))
   2925        eq(true, lib.tv_equal(l2, l2, true))
   2926      end)
   2927      itp('compares lists correctly when case is not ignored', function()
   2928        local l1 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' })
   2929        local l2 = lua2typvalt({ 'abc', { 1, 2, 'Abc' } })
   2930        local l3 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'Def' })
   2931        local l4 = lua2typvalt({ 'abc', { 1, 2, 'Abc', 4 }, 'def' })
   2932        local l5 = lua2typvalt({ 'Abc', { 1, 2, 'Abc' }, 'def' })
   2933        local l6 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' })
   2934        local l7 = lua2typvalt({ 'abc', { 1, 2, 'abc' }, 'def' })
   2935        local l8 = lua2typvalt({ 'abc', nil, 'def' })
   2936        local l9 = lua2typvalt({ 'abc', { 1, 2, nil }, 'def' })
   2937 
   2938        eq(true, lib.tv_equal(l1, l1, false))
   2939        eq(false, lib.tv_equal(l1, l2, false))
   2940        eq(false, lib.tv_equal(l1, l3, false))
   2941        eq(false, lib.tv_equal(l1, l4, false))
   2942        eq(false, lib.tv_equal(l1, l5, false))
   2943        eq(true, lib.tv_equal(l1, l6, false))
   2944        eq(false, lib.tv_equal(l1, l7, false))
   2945        eq(false, lib.tv_equal(l1, l8, false))
   2946        eq(false, lib.tv_equal(l1, l9, false))
   2947      end)
   2948      itp('compares lists correctly when case is ignored', function()
   2949        local l1 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' })
   2950        local l2 = lua2typvalt({ 'abc', { 1, 2, 'Abc' } })
   2951        local l3 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'Def' })
   2952        local l4 = lua2typvalt({ 'abc', { 1, 2, 'Abc', 4 }, 'def' })
   2953        local l5 = lua2typvalt({ 'Abc', { 1, 2, 'Abc' }, 'def' })
   2954        local l6 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' })
   2955        local l7 = lua2typvalt({ 'abc', { 1, 2, 'abc' }, 'def' })
   2956        local l8 = lua2typvalt({ 'abc', nil, 'def' })
   2957        local l9 = lua2typvalt({ 'abc', { 1, 2, nil }, 'def' })
   2958 
   2959        eq(true, lib.tv_equal(l1, l1, true))
   2960        eq(false, lib.tv_equal(l1, l2, true))
   2961        eq(true, lib.tv_equal(l1, l3, true))
   2962        eq(false, lib.tv_equal(l1, l4, true))
   2963        eq(true, lib.tv_equal(l1, l5, true))
   2964        eq(true, lib.tv_equal(l1, l6, true))
   2965        eq(true, lib.tv_equal(l1, l7, true))
   2966        eq(false, lib.tv_equal(l1, l8, true))
   2967        eq(false, lib.tv_equal(l1, l9, true))
   2968      end)
   2969      local function tv_equal(d1, d2, ic)
   2970        return lib.tv_equal(d1, d2, ic or false)
   2971      end
   2972      itp('works with dictionaries', function()
   2973        local nd = lua2typvalt(null_dict)
   2974        eq(true, tv_equal(nd, nd))
   2975        alloc_log:check({})
   2976        local d1 = lua2typvalt({})
   2977        alloc_log:check({ a.dict(d1.vval.v_dict) })
   2978        eq(1, d1.vval.v_dict.dv_refcount)
   2979        eq(true, tv_equal(nd, d1))
   2980        eq(true, tv_equal(d1, nd))
   2981        eq(true, tv_equal(d1, d1))
   2982        eq(1, d1.vval.v_dict.dv_refcount)
   2983        alloc_log:check({})
   2984        local d_upper = lua2typvalt({ a = 'TEST' })
   2985        local dis_upper = dict_items(d_upper.vval.v_dict)
   2986        local d_lower = lua2typvalt({ a = 'test' })
   2987        local dis_lower = dict_items(d_lower.vval.v_dict)
   2988        local d_kupper_upper = lua2typvalt({ A = 'TEST' })
   2989        local dis_kupper_upper = dict_items(d_kupper_upper.vval.v_dict)
   2990        local d_kupper_lower = lua2typvalt({ A = 'test' })
   2991        local dis_kupper_lower = dict_items(d_kupper_lower.vval.v_dict)
   2992        alloc_log:clear_tmp_allocs()
   2993        alloc_log:check({
   2994          a.dict(d_upper.vval.v_dict),
   2995          a.di(dis_upper.a),
   2996          a.str(dis_upper.a.di_tv.vval.v_string),
   2997 
   2998          a.dict(d_lower.vval.v_dict),
   2999          a.di(dis_lower.a),
   3000          a.str(dis_lower.a.di_tv.vval.v_string),
   3001 
   3002          a.dict(d_kupper_upper.vval.v_dict),
   3003          a.di(dis_kupper_upper.A),
   3004          a.str(dis_kupper_upper.A.di_tv.vval.v_string),
   3005 
   3006          a.dict(d_kupper_lower.vval.v_dict),
   3007          a.di(dis_kupper_lower.A),
   3008          a.str(dis_kupper_lower.A.di_tv.vval.v_string),
   3009        })
   3010        eq(true, tv_equal(d_upper, d_upper))
   3011        eq(true, tv_equal(d_upper, d_upper, true))
   3012        eq(false, tv_equal(d_upper, d_lower, false))
   3013        eq(true, tv_equal(d_upper, d_lower, true))
   3014        eq(true, tv_equal(d_kupper_upper, d_kupper_lower, true))
   3015        eq(false, tv_equal(d_kupper_upper, d_lower, true))
   3016        eq(false, tv_equal(d_kupper_upper, d_upper, true))
   3017        alloc_log:check({})
   3018      end)
   3019    end)
   3020    describe('check', function()
   3021      describe('str_or_nr()', function()
   3022        itp('works', function()
   3023          local tv = typvalt()
   3024          local mem = lib.xmalloc(1)
   3025          tv.vval.v_list = mem -- Should crash when actually accessed
   3026          alloc_log:clear()
   3027          for _, v in ipairs({
   3028            { lib.VAR_NUMBER, nil },
   3029            { lib.VAR_FLOAT, 'E805: Expected a Number or a String, Float found' },
   3030            { lib.VAR_PARTIAL, 'E703: Expected a Number or a String, Funcref found' },
   3031            { lib.VAR_FUNC, 'E703: Expected a Number or a String, Funcref found' },
   3032            { lib.VAR_LIST, 'E745: Expected a Number or a String, List found' },
   3033            { lib.VAR_DICT, 'E728: Expected a Number or a String, Dictionary found' },
   3034            { lib.VAR_SPECIAL, 'E5300: Expected a Number or a String' },
   3035            { lib.VAR_UNKNOWN, 'E685: Internal error: tv_check_str_or_nr(UNKNOWN)' },
   3036          }) do
   3037            local typ = v[1]
   3038            local emsg = v[2]
   3039            local ret = true
   3040            if emsg then
   3041              ret = false
   3042            end
   3043            tv.v_type = typ
   3044            eq(
   3045              ret,
   3046              check_emsg(function()
   3047                return lib.tv_check_str_or_nr(tv)
   3048              end, emsg)
   3049            )
   3050            if emsg then
   3051              alloc_log:clear()
   3052            else
   3053              alloc_log:check({})
   3054            end
   3055          end
   3056        end)
   3057      end)
   3058      describe('num()', function()
   3059        itp('works', function()
   3060          local tv = typvalt()
   3061          local mem = lib.xmalloc(1)
   3062          tv.vval.v_list = mem -- Should crash when actually accessed
   3063          alloc_log:clear()
   3064          for _, v in ipairs({
   3065            { lib.VAR_NUMBER, nil },
   3066            { lib.VAR_FLOAT, 'E805: Using a Float as a Number' },
   3067            { lib.VAR_PARTIAL, 'E703: Using a Funcref as a Number' },
   3068            { lib.VAR_FUNC, 'E703: Using a Funcref as a Number' },
   3069            { lib.VAR_LIST, 'E745: Using a List as a Number' },
   3070            { lib.VAR_DICT, 'E728: Using a Dictionary as a Number' },
   3071            { lib.VAR_SPECIAL, nil },
   3072            { lib.VAR_UNKNOWN, 'E685: using an invalid value as a Number' },
   3073          }) do
   3074            local typ = v[1]
   3075            local emsg = v[2]
   3076            local ret = true
   3077            if emsg then
   3078              ret = false
   3079            end
   3080            tv.v_type = typ
   3081            eq(
   3082              ret,
   3083              check_emsg(function()
   3084                return lib.tv_check_num(tv)
   3085              end, emsg)
   3086            )
   3087            if emsg then
   3088              alloc_log:clear()
   3089            else
   3090              alloc_log:check({})
   3091            end
   3092          end
   3093        end)
   3094      end)
   3095      describe('str()', function()
   3096        itp('works', function()
   3097          local tv = typvalt()
   3098          local mem = lib.xmalloc(1)
   3099          tv.vval.v_list = mem -- Should crash when actually accessed
   3100          alloc_log:clear()
   3101          for _, v in ipairs({
   3102            { lib.VAR_NUMBER, nil },
   3103            { lib.VAR_FLOAT, nil },
   3104            { lib.VAR_PARTIAL, 'E729: Using a Funcref as a String' },
   3105            { lib.VAR_FUNC, 'E729: Using a Funcref as a String' },
   3106            { lib.VAR_LIST, 'E730: Using a List as a String' },
   3107            { lib.VAR_DICT, 'E731: Using a Dictionary as a String' },
   3108            { lib.VAR_BOOL, nil },
   3109            { lib.VAR_SPECIAL, nil },
   3110            { lib.VAR_UNKNOWN, 'E908: Using an invalid value as a String' },
   3111          }) do
   3112            local typ = v[1]
   3113            local emsg = v[2]
   3114            local ret = true
   3115            if emsg then
   3116              ret = false
   3117            end
   3118            tv.v_type = typ
   3119            eq(
   3120              ret,
   3121              check_emsg(function()
   3122                return lib.tv_check_str(tv)
   3123              end, emsg)
   3124            )
   3125            if emsg then
   3126              alloc_log:clear()
   3127            else
   3128              alloc_log:check({})
   3129            end
   3130          end
   3131        end)
   3132      end)
   3133    end)
   3134    describe('get', function()
   3135      describe('number()', function()
   3136        itp('works', function()
   3137          for _, v in ipairs({
   3138            { lib.VAR_NUMBER, { v_number = 42 }, nil, 42 },
   3139            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, 100500 },
   3140            { lib.VAR_FLOAT, { v_float = 42.53 }, 'E805: Using a Float as a Number', 0 },
   3141            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E703: Using a Funcref as a Number', 0 },
   3142            { lib.VAR_FUNC, { v_string = NULL }, 'E703: Using a Funcref as a Number', 0 },
   3143            { lib.VAR_LIST, { v_list = NULL }, 'E745: Using a List as a Number', 0 },
   3144            { lib.VAR_DICT, { v_dict = NULL }, 'E728: Using a Dictionary as a Number', 0 },
   3145            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 0 },
   3146            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 1 },
   3147            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 0 },
   3148            { lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0 },
   3149          }) do
   3150            -- Using to_cstr, cannot free with tv_clear
   3151            local tv = ffi.gc(typvalt(v[1], v[2]), nil)
   3152            alloc_log:check({})
   3153            local emsg = v[3]
   3154            local ret = v[4]
   3155            eq(
   3156              ret,
   3157              check_emsg(function()
   3158                return lib.tv_get_number(tv)
   3159              end, emsg)
   3160            )
   3161            if emsg then
   3162              alloc_log:clear()
   3163            else
   3164              alloc_log:check({})
   3165            end
   3166          end
   3167        end)
   3168      end)
   3169      describe('number_chk()', function()
   3170        itp('works', function()
   3171          for _, v in ipairs({
   3172            { lib.VAR_NUMBER, { v_number = 42 }, nil, 42 },
   3173            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, 100500 },
   3174            { lib.VAR_FLOAT, { v_float = 42.53 }, 'E805: Using a Float as a Number', 0 },
   3175            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E703: Using a Funcref as a Number', 0 },
   3176            { lib.VAR_FUNC, { v_string = NULL }, 'E703: Using a Funcref as a Number', 0 },
   3177            { lib.VAR_LIST, { v_list = NULL }, 'E745: Using a List as a Number', 0 },
   3178            { lib.VAR_DICT, { v_dict = NULL }, 'E728: Using a Dictionary as a Number', 0 },
   3179            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 0 },
   3180            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 1 },
   3181            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 0 },
   3182            { lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0 },
   3183          }) do
   3184            -- Using to_cstr, cannot free with tv_clear
   3185            local tv = ffi.gc(typvalt(v[1], v[2]), nil)
   3186            alloc_log:check({})
   3187            local emsg = v[3]
   3188            local ret = { v[4], not not emsg }
   3189            eq(
   3190              ret,
   3191              check_emsg(function()
   3192                local err = ffi.new('bool[1]', { false })
   3193                local res = lib.tv_get_number_chk(tv, err)
   3194                return { res, err[0] }
   3195              end, emsg)
   3196            )
   3197            if emsg then
   3198              alloc_log:clear()
   3199            else
   3200              alloc_log:check({})
   3201            end
   3202          end
   3203        end)
   3204      end)
   3205      describe('lnum()', function()
   3206        itp('works', function()
   3207          for _, v in ipairs({
   3208            { lib.VAR_NUMBER, { v_number = 42 }, nil, 42 },
   3209            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, 100500 },
   3210            { lib.VAR_STRING, { v_string = to_cstr('.') }, nil, 46 },
   3211            { lib.VAR_FLOAT, { v_float = 42.53 }, 'E805: Using a Float as a Number', -1 },
   3212            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E703: Using a Funcref as a Number', -1 },
   3213            { lib.VAR_FUNC, { v_string = NULL }, 'E703: Using a Funcref as a Number', -1 },
   3214            { lib.VAR_LIST, { v_list = NULL }, 'E745: Using a List as a Number', -1 },
   3215            { lib.VAR_DICT, { v_dict = NULL }, 'E728: Using a Dictionary as a Number', -1 },
   3216            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 0 },
   3217            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 1 },
   3218            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 0 },
   3219            { lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', -1 },
   3220          }) do
   3221            lib.curwin.w_cursor.lnum = 46
   3222            -- Using to_cstr, cannot free with tv_clear
   3223            local tv = ffi.gc(typvalt(v[1], v[2]), nil)
   3224            alloc_log:check({})
   3225            local emsg = v[3]
   3226            local ret = v[4]
   3227            eq(
   3228              ret,
   3229              check_emsg(function()
   3230                return lib.tv_get_lnum(tv)
   3231              end, emsg)
   3232            )
   3233            if emsg then
   3234              alloc_log:clear()
   3235            else
   3236              alloc_log:check({})
   3237            end
   3238          end
   3239        end)
   3240      end)
   3241      describe('float()', function()
   3242        itp('works', function()
   3243          for _, v in ipairs({
   3244            { lib.VAR_NUMBER, { v_number = 42 }, nil, 42 },
   3245            {
   3246              lib.VAR_STRING,
   3247              { v_string = to_cstr('100500') },
   3248              'E892: Using a String as a Float',
   3249              0,
   3250            },
   3251            { lib.VAR_FLOAT, { v_float = 42.53 }, nil, 42.53 },
   3252            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E891: Using a Funcref as a Float', 0 },
   3253            { lib.VAR_FUNC, { v_string = NULL }, 'E891: Using a Funcref as a Float', 0 },
   3254            { lib.VAR_LIST, { v_list = NULL }, 'E893: Using a List as a Float', 0 },
   3255            { lib.VAR_DICT, { v_dict = NULL }, 'E894: Using a Dictionary as a Float', 0 },
   3256            {
   3257              lib.VAR_SPECIAL,
   3258              { v_special = lib.kSpecialVarNull },
   3259              'E907: Using a special value as a Float',
   3260              0,
   3261            },
   3262            {
   3263              lib.VAR_BOOL,
   3264              { v_bool = lib.kBoolVarTrue },
   3265              'E362: Using a boolean value as a Float',
   3266              0,
   3267            },
   3268            {
   3269              lib.VAR_BOOL,
   3270              { v_bool = lib.kBoolVarFalse },
   3271              'E362: Using a boolean value as a Float',
   3272              0,
   3273            },
   3274            { lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_float(UNKNOWN)', 0 },
   3275          }) do
   3276            -- Using to_cstr, cannot free with tv_clear
   3277            local tv = ffi.gc(typvalt(v[1], v[2]), nil)
   3278            alloc_log:check({})
   3279            local emsg = v[3]
   3280            local ret = v[4]
   3281            eq(
   3282              ret,
   3283              check_emsg(function()
   3284                return lib.tv_get_float(tv)
   3285              end, emsg)
   3286            )
   3287            if emsg then
   3288              alloc_log:clear()
   3289            else
   3290              alloc_log:check({})
   3291            end
   3292          end
   3293        end)
   3294      end)
   3295 
   3296      local function test_string_fn(input, fn)
   3297        for _, v in ipairs(input) do
   3298          -- Using to_cstr in place of Neovim allocated string, cannot
   3299          -- tv_clear() that.
   3300          local tv = ffi.gc(typvalt(v[1], v[2]), nil)
   3301          alloc_log:check({})
   3302          local emsg = v[3]
   3303          local ret = v[4]
   3304          eq(
   3305            ret,
   3306            check_emsg(function()
   3307              local res, buf = fn(tv)
   3308              if
   3309                tv.v_type == lib.VAR_NUMBER
   3310                or tv.v_type == lib.VAR_FLOAT
   3311                or tv.v_type == lib.VAR_SPECIAL
   3312                or tv.v_type == lib.VAR_BOOL
   3313              then
   3314                eq(buf, res)
   3315              else
   3316                neq(buf, res)
   3317              end
   3318              if res ~= nil then
   3319                return ffi.string(res)
   3320              else
   3321                return nil
   3322              end
   3323            end, emsg)
   3324          )
   3325          if emsg then
   3326            alloc_log:clear()
   3327          elseif tv.v_type == lib.VAR_FLOAT then
   3328            alloc_log:check({ a.freed(alloc_log.null), a.freed(alloc_log.null) })
   3329          else
   3330            alloc_log:check({})
   3331          end
   3332        end
   3333      end
   3334      describe('string()', function()
   3335        itp('works', function()
   3336          local buf = lib.tv_get_string(lua2typvalt(int(1)))
   3337          local buf_chk = lib.tv_get_string_chk(lua2typvalt(int(1)))
   3338          neq(buf, buf_chk)
   3339          test_string_fn({
   3340            { lib.VAR_NUMBER, { v_number = 42 }, nil, '42' },
   3341            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, '100500' },
   3342            { lib.VAR_FLOAT, { v_float = 42.53 }, nil, '42.53' },
   3343            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E729: Using a Funcref as a String', '' },
   3344            { lib.VAR_FUNC, { v_string = NULL }, 'E729: Using a Funcref as a String', '' },
   3345            { lib.VAR_LIST, { v_list = NULL }, 'E730: Using a List as a String', '' },
   3346            { lib.VAR_DICT, { v_dict = NULL }, 'E731: Using a Dictionary as a String', '' },
   3347            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 'v:null' },
   3348            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 'v:true' },
   3349            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 'v:false' },
   3350            { lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', '' },
   3351          }, function(tv)
   3352            return lib.tv_get_string(tv), buf
   3353          end)
   3354        end)
   3355      end)
   3356      describe('string_chk()', function()
   3357        itp('works', function()
   3358          local buf = lib.tv_get_string_chk(lua2typvalt(int(1)))
   3359          test_string_fn({
   3360            { lib.VAR_NUMBER, { v_number = 42 }, nil, '42' },
   3361            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, '100500' },
   3362            { lib.VAR_FLOAT, { v_float = 42.53 }, nil, '42.53' },
   3363            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E729: Using a Funcref as a String', nil },
   3364            { lib.VAR_FUNC, { v_string = NULL }, 'E729: Using a Funcref as a String', nil },
   3365            { lib.VAR_LIST, { v_list = NULL }, 'E730: Using a List as a String', nil },
   3366            { lib.VAR_DICT, { v_dict = NULL }, 'E731: Using a Dictionary as a String', nil },
   3367            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 'v:null' },
   3368            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 'v:true' },
   3369            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 'v:false' },
   3370            { lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', nil },
   3371          }, function(tv)
   3372            return lib.tv_get_string_chk(tv), buf
   3373          end)
   3374        end)
   3375      end)
   3376      describe('string_buf()', function()
   3377        itp('works', function()
   3378          test_string_fn({
   3379            { lib.VAR_NUMBER, { v_number = 42 }, nil, '42' },
   3380            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, '100500' },
   3381            { lib.VAR_FLOAT, { v_float = 42.53 }, nil, '42.53' },
   3382            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E729: Using a Funcref as a String', '' },
   3383            { lib.VAR_FUNC, { v_string = NULL }, 'E729: Using a Funcref as a String', '' },
   3384            { lib.VAR_LIST, { v_list = NULL }, 'E730: Using a List as a String', '' },
   3385            { lib.VAR_DICT, { v_dict = NULL }, 'E731: Using a Dictionary as a String', '' },
   3386            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 'v:null' },
   3387            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 'v:true' },
   3388            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 'v:false' },
   3389            { lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', '' },
   3390          }, function(tv)
   3391            local buf = ffi.new('char[?]', lib.NUMBUFLEN, { 0 })
   3392            return lib.tv_get_string_buf(tv, buf), buf
   3393          end)
   3394        end)
   3395      end)
   3396      describe('string_buf_chk()', function()
   3397        itp('works', function()
   3398          test_string_fn({
   3399            { lib.VAR_NUMBER, { v_number = 42 }, nil, '42' },
   3400            { lib.VAR_STRING, { v_string = to_cstr('100500') }, nil, '100500' },
   3401            { lib.VAR_FLOAT, { v_float = 42.53 }, nil, '42.53' },
   3402            { lib.VAR_PARTIAL, { v_partial = NULL }, 'E729: Using a Funcref as a String', nil },
   3403            { lib.VAR_FUNC, { v_string = NULL }, 'E729: Using a Funcref as a String', nil },
   3404            { lib.VAR_LIST, { v_list = NULL }, 'E730: Using a List as a String', nil },
   3405            { lib.VAR_DICT, { v_dict = NULL }, 'E731: Using a Dictionary as a String', nil },
   3406            { lib.VAR_SPECIAL, { v_special = lib.kSpecialVarNull }, nil, 'v:null' },
   3407            { lib.VAR_BOOL, { v_bool = lib.kBoolVarTrue }, nil, 'v:true' },
   3408            { lib.VAR_BOOL, { v_bool = lib.kBoolVarFalse }, nil, 'v:false' },
   3409            { lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', nil },
   3410          }, function(tv)
   3411            local buf = ffi.new('char[?]', lib.NUMBUFLEN, { 0 })
   3412            return lib.tv_get_string_buf_chk(tv, buf), buf
   3413          end)
   3414        end)
   3415      end)
   3416    end)
   3417  end)
   3418 end)