neovim/test/unit/garray_spec.lua
2014-08-31 14:50:49 +02:00

392 lines
10 KiB
Lua

local helpers = require("test.unit.helpers")
local cimport = helpers.cimport
local internalize = helpers.internalize
local eq = helpers.eq
local neq = helpers.neq
local ffi = helpers.ffi
local lib = helpers.lib
local cstr = helpers.cstr
local to_cstr = helpers.to_cstr
local NULL = helpers.NULL
local garray = cimport('./src/nvim/garray.h')
-- define a basic interface to garray. We could make it a lot nicer by
-- constructing a moonscript 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
function ga_len(garr)
return garr[0].ga_len
end
function ga_maxlen(garr)
return garr[0].ga_maxlen
end
function ga_itemsize(garr)
return garr[0].ga_itemsize
end
function ga_growsize(garr)
return garr[0].ga_growsize
end
function ga_data(garr)
return garr[0].ga_data
end
-- derived accessors
function ga_size(garr)
return ga_len(garr) * ga_itemsize(garr)
end
function ga_maxsize(garr)
return ga_maxlen(garr) * ga_itemsize(garr)
end
function ga_data_as_bytes(garr)
return ffi.cast('uint8_t *', ga_data(garr))
end
function ga_data_as_strings(garr)
return ffi.cast('char **', ga_data(garr))
end
function ga_data_as_ints(garr)
return ffi.cast('int *', ga_data(garr))
end
-- garray manipulation
function ga_init(garr, itemsize, growsize)
return garray.ga_init(garr, itemsize, growsize)
end
function ga_clear(garr)
return garray.ga_clear(garr)
end
function ga_clear_strings(garr)
assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *'))
return garray.ga_clear_strings(garr)
end
function ga_grow(garr, n)
return garray.ga_grow(garr, n)
end
function ga_concat(garr, str)
return garray.ga_concat(garr, to_cstr(str))
end
function ga_append(garr, b)
if type(b) == 'string' then
return garray.ga_append(garr, string.byte(b))
else
return garray.ga_append(garr, b)
end
end
function ga_concat_strings(garr)
return internalize(garray.ga_concat_strings(garr))
end
function ga_concat_strings_sep(garr, sep)
return internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep)))
end
function ga_remove_duplicate_strings(garr)
return garray.ga_remove_duplicate_strings(garr)
end
-- derived manipulators
function ga_set_len(garr, len)
assert.is_true(len <= ga_maxlen(garr))
garr[0].ga_len = len
end
function ga_inc_len(garr, by)
return ga_set_len(garr, ga_len(garr) + 1)
end
-- custom append functions
-- not the C ga_append, which only works for bytes
function ga_append_int(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
function ga_append_string(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
function ga_append_strings(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
function ga_append_ints(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 = ffi.typeof('garray_T[1]')
function new_garray()
local garr = garray_ctype()
return ffi.gc(garr, ga_clear)
end
function new_string_garray()
local garr = garray_ctype()
ga_init(garr, ffi.sizeof("char_u *"), 1)
return ffi.gc(garr, ga_clear_strings)
end
function randomByte()
return ffi.cast('uint8_t', math.random(0, 255))
end
-- scramble the data in a garray
function ga_scramble(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()
local itemsize = 14
local growsize = 95
describe('ga_init', function()
it('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 new_and_grow
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
it('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)
it('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)
it('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()
it('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()
it('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)
it('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)
it('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()
it('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 i = 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)
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()
it('returns an empty string when concatenating an empty array', function()
test_concat_fn({ }, ga_concat_strings)
end)
it('can concatenate a non-empty array', function()
test_concat_fn({
'oh',
'my',
'neovim'
}, ga_concat_strings)
end)
end)
describe('ga_concat_strings_sep', function()
it('returns an empty string when concatenating an empty array', function()
test_concat_fn({ }, ga_concat_strings_sep, '---')
end)
it('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()
it('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)