Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
2,230 additions
and
1,732 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,391 @@ | ||
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 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) |
Oops, something went wrong.