diff options
-rw-r--r-- | test/unit/garray_spec.lua | 391 | ||||
-rw-r--r-- | test/unit/garray_spec.moon | 271 |
2 files changed, 391 insertions, 271 deletions
diff --git a/test/unit/garray_spec.lua b/test/unit/garray_spec.lua new file mode 100644 index 0000000000..ab38176c41 --- /dev/null +++ b/test/unit/garray_spec.lua @@ -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 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) diff --git a/test/unit/garray_spec.moon b/test/unit/garray_spec.moon deleted file mode 100644 index 5d4dbe690c..0000000000 --- a/test/unit/garray_spec.moon +++ /dev/null @@ -1,271 +0,0 @@ -{:cimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :NULL} = require 'test.unit.helpers' - -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 -ga_len = (garr) -> - garr[0].ga_len -ga_maxlen = (garr) -> - garr[0].ga_maxlen -ga_itemsize = (garr) -> - garr[0].ga_itemsize -ga_growsize = (garr) -> - garr[0].ga_growsize -ga_data = (garr) -> - garr[0].ga_data - --- derived accessors -ga_size = (garr) -> - ga_len(garr) * ga_itemsize(garr) -ga_maxsize = (garr) -> - ga_maxlen(garr) * ga_itemsize(garr) -ga_data_as_bytes = (garr) -> - ffi.cast('uint8_t *', ga_data(garr)) -ga_data_as_strings = (garr) -> - ffi.cast('char **', ga_data(garr)) -ga_data_as_ints = (garr) -> - ffi.cast('int *', ga_data(garr)) - --- garray manipulation -ga_init = (garr, itemsize, growsize) -> - garray.ga_init(garr, itemsize, growsize) -ga_clear = (garr) -> - garray.ga_clear(garr) -ga_clear_strings = (garr) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) - garray.ga_clear_strings(garr) -ga_grow = (garr, n) -> - garray.ga_grow(garr, n) -ga_concat = (garr, str) -> - garray.ga_concat(garr, to_cstr(str)) -ga_append = (garr, b) -> - if type(b) == 'string' - garray.ga_append(garr, string.byte(b)) - else - garray.ga_append(garr, b) -ga_concat_strings = (garr) -> - internalize(garray.ga_concat_strings(garr)) -ga_concat_strings_sep = (garr, sep) -> - internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep))) -ga_remove_duplicate_strings = (garr) -> - garray.ga_remove_duplicate_strings(garr) - --- derived manipulators -ga_set_len = (garr, len) -> - assert.is_true(len <= ga_maxlen(garr)) - garr[0].ga_len = len -ga_inc_len = (garr, by) -> - ga_set_len(garr, ga_len(garr) + 1) - --- custom append functions --- not the C ga_append, which only works for bytes -ga_append_int = (garr, it) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('int')) - - ga_grow(garr, 1) - data = ga_data_as_ints(garr) - data[ga_len(garr)] = it - ga_inc_len(garr, 1) -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. - mem = ffi.C.malloc(string.len(it) + 1) - ffi.copy(mem, it) - - ga_grow(garr, 1) - data = ga_data_as_strings(garr) - data[ga_len(garr)] = mem - ga_inc_len(garr, 1) -ga_append_strings = (garr, ...) -> - prevlen = ga_len(garr) - len = select('#', ...) - for i = 1, len - ga_append_string(garr, select(i, ...)) - eq prevlen + len, ga_len(garr) -ga_append_ints = (garr, ...) -> - prevlen = ga_len(garr) - len = select('#', ...) - for i = 1, len - ga_append_int(garr, select(i, ...)) - eq prevlen + len, ga_len(garr) - --- enhanced constructors -garray_ctype = ffi.typeof('garray_T[1]') -new_garray = -> - garr = garray_ctype() - ffi.gc(garr, ga_clear) -new_string_garray = -> - garr = garray_ctype() - ga_init(garr, ffi.sizeof("char_u *"), 1) - ffi.gc(garr, ga_clear_strings) - -randomByte = -> - ffi.cast('uint8_t', math.random(0, 255)) - --- scramble the data in a garray -ga_scramble = (garr) -> - size, bytes = ga_size(garr), ga_data_as_bytes(garr) - - for i = 0, size - 1 - bytes[i] = randomByte() - -describe 'garray', -> - itemsize = 14 - growsize = 95 - - describe 'ga_init', -> - it 'initializes the values of the garray', -> - 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) - - describe 'ga_grow', -> - new_and_grow = (itemsize, growsize, req) -> - 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 - - garr - - it 'grows by growsize items if num < growsize', -> - itemsize = 16 - growsize = 4 - grow_by = growsize - 1 - 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... - - it 'grows by num items if num > growsize', -> - itemsize = 16 - growsize = 4 - grow_by = growsize + 1 - 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... - - it 'does not grow when nothing is requested', -> - garr = new_and_grow(16, 4, 0) - eq NULL, ga_data(garr) - eq 0, ga_maxlen(garr) - - describe 'ga_clear', -> - it 'clears an already allocated array', -> - -- allocate and scramble an array - 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) - - describe 'ga_append', -> - it 'can append bytes', -> - -- this is the actual ga_append, the others are just emulated lua - -- versions - 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) - bytes = ga_data_as_bytes(garr) - eq 'hello', ffi.string(bytes) - - it 'can append integers', -> - garr = new_garray() - ga_init(garr, ffi.sizeof("int"), 1) - input = {-20, 94, 867615, 90927, 86} - ga_append_ints(garr, unpack(input)) - - ints = ga_data_as_ints(garr) - for i = 0, #input - 1 - eq input[i+1], ints[i] - - it 'can append strings to a growing array of strings', -> - garr = new_string_garray() - 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 - strings = ga_data_as_strings(garr) - for i = 0, #input - 1 - eq input[i+1], ffi.string(strings[i]) - - describe 'ga_concat', -> - it 'concatenates the parameter to the growing byte array', -> - garr = new_garray() - ga_init(garr, ffi.sizeof("char"), 1) - - str = "ohwell●●" - loop = 5 - for i = 1, loop - ga_concat(garr, str) - - -- 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) - - result = ffi.string(ga_data_as_bytes(garr)) - eq string.rep(str, loop), result - - test_concat_fn = (input, fn, sep) -> - garr = new_string_garray() - ga_append_strings(garr, unpack(input)) - if sep == nil - eq table.concat(input, ','), fn(garr) - else - eq table.concat(input, sep), fn(garr, sep) - - describe 'ga_concat_strings', -> - it 'returns an empty string when concatenating an empty array', -> - test_concat_fn({}, ga_concat_strings) - it 'can concatenate a non-empty array', -> - test_concat_fn({'oh', 'my', 'neovim'}, ga_concat_strings) - - describe 'ga_concat_strings_sep', -> - it 'returns an empty string when concatenating an empty array', -> - test_concat_fn({}, ga_concat_strings_sep, '---') - it 'can concatenate a non-empty array', -> - sep = '-●●-' - test_concat_fn({'oh', 'my', 'neovim'}, ga_concat_strings_sep, sep) - - describe 'ga_remove_duplicate_strings', -> - it 'sorts and removes duplicate strings', -> - garr = new_string_garray() - input = {'ccc', 'aaa', 'bbb', 'ddd●●', 'aaa', 'bbb', 'ccc', 'ccc', 'ddd●●'} - 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) - - strings = ga_data_as_strings(garr) - for i = 0, #sorted_dedup_input - 1 - eq sorted_dedup_input[i+1], ffi.string(strings[i]) |