mirror of
https://github.com/neovim/neovim.git
synced 2024-12-29 14:41:06 -07:00
12b062b2c8
Used sed -r -i -e '/ helpers =/ s/$/\nlocal itp = helpers.gen_itp(it)/; s/^(\s*)it\(/\1itp(/' test/unit/**/*_spec.lua to alter all tests. Locally they all run fine now. Reasoning: 1. General: state from one test should not affect other tests. 2. Local: travis build is failing with something which may be an output of garbage collector. This should prevent state of the garbage collector from interferring as well.
391 lines
10 KiB
Lua
391 lines
10 KiB
Lua
local helpers = require("test.unit.helpers")
|
|
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('stdlib.h', './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 = ffi.typeof('garray_T[1]')
|
|
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)
|