mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			391 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			391 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
local helpers = require("test.unit.helpers")(after_each)
 | 
						|
local itp = helpers.gen_itp(it)
 | 
						|
 | 
						|
local cimport = helpers.cimport
 | 
						|
local internalize = helpers.internalize
 | 
						|
local eq = helpers.eq
 | 
						|
local neq = helpers.neq
 | 
						|
local ffi = helpers.ffi
 | 
						|
local to_cstr = helpers.to_cstr
 | 
						|
local NULL = helpers.NULL
 | 
						|
 | 
						|
local garray = cimport('./src/nvim/garray.h')
 | 
						|
 | 
						|
local itemsize = 14
 | 
						|
local growsize = 95
 | 
						|
 | 
						|
-- define a basic interface to garray. We could make it a lot nicer by
 | 
						|
-- constructing a class wrapper around garray. It could for example associate
 | 
						|
-- ga_clear_strings to the underlying garray cdata if the garray is a string
 | 
						|
-- array. But for now I estimate that that kind of magic might make testing
 | 
						|
-- less "transparant" (i.e.: the interface would become quite different as to
 | 
						|
-- how one would use it from C.
 | 
						|
 | 
						|
-- accessors
 | 
						|
local ga_len = function(garr)
 | 
						|
  return garr[0].ga_len
 | 
						|
end
 | 
						|
 | 
						|
local ga_maxlen = function(garr)
 | 
						|
  return garr[0].ga_maxlen
 | 
						|
end
 | 
						|
 | 
						|
local ga_itemsize = function(garr)
 | 
						|
  return garr[0].ga_itemsize
 | 
						|
end
 | 
						|
 | 
						|
local ga_growsize = function(garr)
 | 
						|
  return garr[0].ga_growsize
 | 
						|
end
 | 
						|
 | 
						|
local ga_data = function(garr)
 | 
						|
  return garr[0].ga_data
 | 
						|
end
 | 
						|
 | 
						|
-- derived accessors
 | 
						|
local ga_size = function(garr)
 | 
						|
  return ga_len(garr) * ga_itemsize(garr)
 | 
						|
end
 | 
						|
 | 
						|
local ga_maxsize = function(garr)  -- luacheck: ignore
 | 
						|
  return ga_maxlen(garr) * ga_itemsize(garr)
 | 
						|
end
 | 
						|
 | 
						|
local ga_data_as_bytes = function(garr)
 | 
						|
  return ffi.cast('uint8_t *', ga_data(garr))
 | 
						|
end
 | 
						|
 | 
						|
local ga_data_as_strings = function(garr)
 | 
						|
  return ffi.cast('char **', ga_data(garr))
 | 
						|
end
 | 
						|
 | 
						|
local ga_data_as_ints = function(garr)
 | 
						|
  return ffi.cast('int *', ga_data(garr))
 | 
						|
end
 | 
						|
 | 
						|
-- garray manipulation
 | 
						|
local ga_init = function(garr, itemsize_, growsize_)
 | 
						|
  return garray.ga_init(garr, itemsize_, growsize_)
 | 
						|
end
 | 
						|
 | 
						|
local ga_clear = function(garr)
 | 
						|
  return garray.ga_clear(garr)
 | 
						|
end
 | 
						|
 | 
						|
local ga_clear_strings = function(garr)
 | 
						|
  assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *'))
 | 
						|
  return garray.ga_clear_strings(garr)
 | 
						|
end
 | 
						|
 | 
						|
local ga_grow = function(garr, n)
 | 
						|
  return garray.ga_grow(garr, n)
 | 
						|
end
 | 
						|
 | 
						|
local ga_concat = function(garr, str)
 | 
						|
  return garray.ga_concat(garr, to_cstr(str))
 | 
						|
end
 | 
						|
 | 
						|
local ga_append = function(garr, b)
 | 
						|
  if type(b) == 'string' then
 | 
						|
    return garray.ga_append(garr, string.byte(b))
 | 
						|
  else
 | 
						|
    return garray.ga_append(garr, b)
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
local ga_concat_strings = function(garr)
 | 
						|
  return internalize(garray.ga_concat_strings(garr))
 | 
						|
end
 | 
						|
 | 
						|
local ga_concat_strings_sep = function(garr, sep)
 | 
						|
  return internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep)))
 | 
						|
end
 | 
						|
 | 
						|
local ga_remove_duplicate_strings = function(garr)
 | 
						|
  return garray.ga_remove_duplicate_strings(garr)
 | 
						|
end
 | 
						|
 | 
						|
-- derived manipulators
 | 
						|
local ga_set_len = function(garr, len)
 | 
						|
  assert.is_true(len <= ga_maxlen(garr))
 | 
						|
  garr[0].ga_len = len
 | 
						|
end
 | 
						|
 | 
						|
local ga_inc_len = function(garr, by)
 | 
						|
  return ga_set_len(garr, ga_len(garr) + by)
 | 
						|
end
 | 
						|
 | 
						|
-- custom append functions
 | 
						|
-- not the C ga_append, which only works for bytes
 | 
						|
local ga_append_int = function(garr, it)
 | 
						|
  assert.is_true(ga_itemsize(garr) == ffi.sizeof('int'))
 | 
						|
  ga_grow(garr, 1)
 | 
						|
  local data = ga_data_as_ints(garr)
 | 
						|
  data[ga_len(garr)] = it
 | 
						|
  return ga_inc_len(garr, 1)
 | 
						|
end
 | 
						|
 | 
						|
local ga_append_string = function(garr, it)
 | 
						|
  assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *'))
 | 
						|
  -- make a non-garbage collected string and copy the lua string into it,
 | 
						|
  -- TODO(aktau): we should probably call xmalloc here, though as long as
 | 
						|
  -- xmalloc is based on malloc it should work.
 | 
						|
  local mem = ffi.C.malloc(string.len(it) + 1)
 | 
						|
  ffi.copy(mem, it)
 | 
						|
  ga_grow(garr, 1)
 | 
						|
  local data = ga_data_as_strings(garr)
 | 
						|
  data[ga_len(garr)] = mem
 | 
						|
  return ga_inc_len(garr, 1)
 | 
						|
end
 | 
						|
 | 
						|
local ga_append_strings = function(garr, ...)
 | 
						|
  local prevlen = ga_len(garr)
 | 
						|
  local len = select('#', ...)
 | 
						|
  for i = 1, len do
 | 
						|
    ga_append_string(garr, select(i, ...))
 | 
						|
  end
 | 
						|
  return eq(prevlen + len, ga_len(garr))
 | 
						|
end
 | 
						|
 | 
						|
local ga_append_ints = function(garr, ...)
 | 
						|
  local prevlen = ga_len(garr)
 | 
						|
  local len = select('#', ...)
 | 
						|
  for i = 1, len do
 | 
						|
    ga_append_int(garr, select(i, ...))
 | 
						|
  end
 | 
						|
  return eq(prevlen + len, ga_len(garr))
 | 
						|
end
 | 
						|
 | 
						|
-- enhanced constructors
 | 
						|
local garray_ctype = function(...) return ffi.typeof('garray_T[1]')(...) end
 | 
						|
local new_garray = function()
 | 
						|
  local garr = garray_ctype()
 | 
						|
  return ffi.gc(garr, ga_clear)
 | 
						|
end
 | 
						|
 | 
						|
local new_string_garray = function()
 | 
						|
  local garr = garray_ctype()
 | 
						|
  ga_init(garr, ffi.sizeof("unsigned char *"), 1)
 | 
						|
  return ffi.gc(garr, ga_clear_strings)
 | 
						|
end
 | 
						|
 | 
						|
local randomByte = function()
 | 
						|
  return ffi.cast('uint8_t', math.random(0, 255))
 | 
						|
end
 | 
						|
 | 
						|
-- scramble the data in a garray
 | 
						|
local ga_scramble = function(garr)
 | 
						|
  local size, bytes = ga_size(garr), ga_data_as_bytes(garr)
 | 
						|
  for i = 0, size - 1 do
 | 
						|
    bytes[i] = randomByte()
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
describe('garray', function()
 | 
						|
 | 
						|
  describe('ga_init', function()
 | 
						|
    itp('initializes the values of the garray', function()
 | 
						|
      local garr = new_garray()
 | 
						|
      ga_init(garr, itemsize, growsize)
 | 
						|
      eq(0, ga_len(garr))
 | 
						|
      eq(0, ga_maxlen(garr))
 | 
						|
      eq(growsize, ga_growsize(garr))
 | 
						|
      eq(itemsize, ga_itemsize(garr))
 | 
						|
      eq(NULL, ga_data(garr))
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('ga_grow', function()
 | 
						|
    local function new_and_grow(itemsize_, growsize_, req)
 | 
						|
      local garr = new_garray()
 | 
						|
      ga_init(garr, itemsize_, growsize_)
 | 
						|
      eq(0, ga_size(garr))         -- should be 0 at first
 | 
						|
      eq(NULL, ga_data(garr))      -- should be NULL
 | 
						|
      ga_grow(garr, req)           -- add space for `req` items
 | 
						|
      return garr
 | 
						|
    end
 | 
						|
 | 
						|
    itp('grows by growsize items if num < growsize', function()
 | 
						|
      itemsize = 16
 | 
						|
      growsize = 4
 | 
						|
      local grow_by = growsize - 1
 | 
						|
      local garr = new_and_grow(itemsize, growsize, grow_by)
 | 
						|
      neq(NULL, ga_data(garr))       -- data should be a ptr to memory
 | 
						|
      eq(growsize, ga_maxlen(garr))  -- we requested LESS than growsize, so...
 | 
						|
    end)
 | 
						|
 | 
						|
    itp('grows by num items if num > growsize', function()
 | 
						|
      itemsize = 16
 | 
						|
      growsize = 4
 | 
						|
      local grow_by = growsize + 1
 | 
						|
      local garr = new_and_grow(itemsize, growsize, grow_by)
 | 
						|
      neq(NULL, ga_data(garr))       -- data should be a ptr to memory
 | 
						|
      eq(grow_by, ga_maxlen(garr))   -- we requested MORE than growsize, so...
 | 
						|
    end)
 | 
						|
 | 
						|
    itp('does not grow when nothing is requested', function()
 | 
						|
      local garr = new_and_grow(16, 4, 0)
 | 
						|
      eq(NULL, ga_data(garr))
 | 
						|
      eq(0, ga_maxlen(garr))
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('ga_clear', function()
 | 
						|
    itp('clears an already allocated array', function()
 | 
						|
      -- allocate and scramble an array
 | 
						|
      local garr = garray_ctype()
 | 
						|
      ga_init(garr, itemsize, growsize)
 | 
						|
      ga_grow(garr, 4)
 | 
						|
      ga_set_len(garr, 4)
 | 
						|
      ga_scramble(garr)
 | 
						|
 | 
						|
      -- clear it and check
 | 
						|
      ga_clear(garr)
 | 
						|
      eq(NULL, ga_data(garr))
 | 
						|
      eq(0, ga_maxlen(garr))
 | 
						|
      eq(0, ga_len(garr))
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('ga_append', function()
 | 
						|
    itp('can append bytes', function()
 | 
						|
      -- this is the actual ga_append, the others are just emulated lua
 | 
						|
      -- versions
 | 
						|
      local garr = new_garray()
 | 
						|
      ga_init(garr, ffi.sizeof("uint8_t"), 1)
 | 
						|
      ga_append(garr, 'h')
 | 
						|
      ga_append(garr, 'e')
 | 
						|
      ga_append(garr, 'l')
 | 
						|
      ga_append(garr, 'l')
 | 
						|
      ga_append(garr, 'o')
 | 
						|
      ga_append(garr, 0)
 | 
						|
      local bytes = ga_data_as_bytes(garr)
 | 
						|
      eq('hello', ffi.string(bytes))
 | 
						|
    end)
 | 
						|
 | 
						|
    itp('can append integers', function()
 | 
						|
      local garr = new_garray()
 | 
						|
      ga_init(garr, ffi.sizeof("int"), 1)
 | 
						|
      local input = {
 | 
						|
        -20,
 | 
						|
        94,
 | 
						|
        867615,
 | 
						|
        90927,
 | 
						|
        86
 | 
						|
      }
 | 
						|
      ga_append_ints(garr, unpack(input))
 | 
						|
      local ints = ga_data_as_ints(garr)
 | 
						|
      for i = 0, #input - 1 do
 | 
						|
        eq(input[i + 1], ints[i])
 | 
						|
      end
 | 
						|
    end)
 | 
						|
 | 
						|
    itp('can append strings to a growing array of strings', function()
 | 
						|
      local garr = new_string_garray()
 | 
						|
      local input = {
 | 
						|
        "some",
 | 
						|
        "str",
 | 
						|
        "\r\n\r●●●●●●,,,",
 | 
						|
        "hmm",
 | 
						|
        "got it"
 | 
						|
      }
 | 
						|
      ga_append_strings(garr, unpack(input))
 | 
						|
      -- check that we can get the same strings out of the array
 | 
						|
      local strings = ga_data_as_strings(garr)
 | 
						|
      for i = 0, #input - 1 do
 | 
						|
        eq(input[i + 1], ffi.string(strings[i]))
 | 
						|
      end
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('ga_concat', function()
 | 
						|
    itp('concatenates the parameter to the growing byte array', function()
 | 
						|
      local garr = new_garray()
 | 
						|
      ga_init(garr, ffi.sizeof("char"), 1)
 | 
						|
      local str = "ohwell●●"
 | 
						|
      local loop = 5
 | 
						|
      for _ = 1, loop do
 | 
						|
        ga_concat(garr, str)
 | 
						|
      end
 | 
						|
 | 
						|
      -- ga_concat does NOT append the NUL in the src string to the
 | 
						|
      -- destination, you have to do that manually by calling something like
 | 
						|
      -- ga_append(gar, '\0'). I'ts always used like that in the vim
 | 
						|
      -- codebase. I feel that this is a bit of an unnecesesary
 | 
						|
      -- micro-optimization.
 | 
						|
      ga_append(garr, 0)
 | 
						|
      local result = ffi.string(ga_data_as_bytes(garr))
 | 
						|
      eq(string.rep(str, loop), result)
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  local function test_concat_fn(input, fn, sep)
 | 
						|
    local garr = new_string_garray()
 | 
						|
    ga_append_strings(garr, unpack(input))
 | 
						|
    if sep == nil then
 | 
						|
      eq(table.concat(input, ','), fn(garr))
 | 
						|
    else
 | 
						|
      eq(table.concat(input, sep), fn(garr, sep))
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe('ga_concat_strings', function()
 | 
						|
    itp('returns an empty string when concatenating an empty array', function()
 | 
						|
      test_concat_fn({ }, ga_concat_strings)
 | 
						|
    end)
 | 
						|
 | 
						|
    itp('can concatenate a non-empty array', function()
 | 
						|
      test_concat_fn({
 | 
						|
        'oh',
 | 
						|
        'my',
 | 
						|
        'neovim'
 | 
						|
      }, ga_concat_strings)
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('ga_concat_strings_sep', function()
 | 
						|
    itp('returns an empty string when concatenating an empty array', function()
 | 
						|
      test_concat_fn({ }, ga_concat_strings_sep, '---')
 | 
						|
    end)
 | 
						|
 | 
						|
    itp('can concatenate a non-empty array', function()
 | 
						|
      local sep = '-●●-'
 | 
						|
      test_concat_fn({
 | 
						|
        'oh',
 | 
						|
        'my',
 | 
						|
        'neovim'
 | 
						|
      }, ga_concat_strings_sep, sep)
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
 | 
						|
  describe('ga_remove_duplicate_strings', function()
 | 
						|
    itp('sorts and removes duplicate strings', function()
 | 
						|
      local garr = new_string_garray()
 | 
						|
      local input = {
 | 
						|
        'ccc',
 | 
						|
        'aaa',
 | 
						|
        'bbb',
 | 
						|
        'ddd●●',
 | 
						|
        'aaa',
 | 
						|
        'bbb',
 | 
						|
        'ccc',
 | 
						|
        'ccc',
 | 
						|
        'ddd●●'
 | 
						|
      }
 | 
						|
      local sorted_dedup_input = {
 | 
						|
        'aaa',
 | 
						|
        'bbb',
 | 
						|
        'ccc',
 | 
						|
        'ddd●●'
 | 
						|
      }
 | 
						|
      ga_append_strings(garr, unpack(input))
 | 
						|
      ga_remove_duplicate_strings(garr)
 | 
						|
      eq(#sorted_dedup_input, ga_len(garr))
 | 
						|
      local strings = ga_data_as_strings(garr)
 | 
						|
      for i = 0, #sorted_dedup_input - 1 do
 | 
						|
        eq(sorted_dedup_input[i + 1], ffi.string(strings[i]))
 | 
						|
      end
 | 
						|
    end)
 | 
						|
  end)
 | 
						|
end)
 |