garray_spec.lua (10053B)
1 local t = require('test.unit.testutil') 2 local itp = t.gen_itp(it) 3 4 local cimport = t.cimport 5 local internalize = t.internalize 6 local eq = t.eq 7 local neq = t.neq 8 local ffi = t.ffi 9 local to_cstr = t.to_cstr 10 local NULL = t.NULL 11 12 local garray = cimport('./src/nvim/garray.h') 13 14 local itemsize = 14 15 local growsize = 95 16 17 -- define a basic interface to garray. We could make it a lot nicer by 18 -- constructing a class wrapper around garray. It could for example associate 19 -- ga_clear_strings to the underlying garray cdata if the garray is a string 20 -- array. But for now I estimate that that kind of magic might make testing 21 -- less "transparent" (i.e.: the interface would become quite different as to 22 -- how one would use it from C. 23 24 -- accessors 25 local ga_len = function(garr) 26 return garr[0].ga_len 27 end 28 29 local ga_maxlen = function(garr) 30 return garr[0].ga_maxlen 31 end 32 33 local ga_itemsize = function(garr) 34 return garr[0].ga_itemsize 35 end 36 37 local ga_growsize = function(garr) 38 return garr[0].ga_growsize 39 end 40 41 local ga_data = function(garr) 42 return garr[0].ga_data 43 end 44 45 -- derived accessors 46 local ga_size = function(garr) 47 return ga_len(garr) * ga_itemsize(garr) 48 end 49 50 local ga_maxsize = function(garr) -- luacheck: ignore 51 return ga_maxlen(garr) * ga_itemsize(garr) 52 end 53 54 local ga_data_as_bytes = function(garr) 55 return ffi.cast('uint8_t *', ga_data(garr)) 56 end 57 58 local ga_data_as_strings = function(garr) 59 return ffi.cast('char **', ga_data(garr)) 60 end 61 62 local ga_data_as_ints = function(garr) 63 return ffi.cast('int *', ga_data(garr)) 64 end 65 66 -- garray manipulation 67 local ga_init = function(garr, itemsize_, growsize_) 68 return garray.ga_init(garr, itemsize_, growsize_) 69 end 70 71 local ga_clear = function(garr) 72 return garray.ga_clear(garr) 73 end 74 75 local ga_clear_strings = function(garr) 76 assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) 77 return garray.ga_clear_strings(garr) 78 end 79 80 local ga_grow = function(garr, n) 81 return garray.ga_grow(garr, n) 82 end 83 84 local ga_concat = function(garr, str) 85 return garray.ga_concat(garr, to_cstr(str)) 86 end 87 88 local ga_append = function(garr, b) 89 if type(b) == 'string' then 90 return garray.ga_append(garr, string.byte(b)) 91 else 92 return garray.ga_append(garr, b) 93 end 94 end 95 96 local ga_concat_strings = function(garr, sep) 97 return internalize(garray.ga_concat_strings(garr, to_cstr(sep))) 98 end 99 100 local ga_remove_duplicate_strings = function(garr) 101 return garray.ga_remove_duplicate_strings(garr) 102 end 103 104 -- derived manipulators 105 local ga_set_len = function(garr, len) 106 assert.is_true(len <= ga_maxlen(garr)) 107 garr[0].ga_len = len 108 end 109 110 local ga_inc_len = function(garr, by) 111 return ga_set_len(garr, ga_len(garr) + by) 112 end 113 114 -- custom append functions 115 -- not the C ga_append, which only works for bytes 116 local ga_append_int = function(garr, it) 117 assert.is_true(ga_itemsize(garr) == ffi.sizeof('int')) 118 ga_grow(garr, 1) 119 local data = ga_data_as_ints(garr) 120 data[ga_len(garr)] = it 121 return ga_inc_len(garr, 1) 122 end 123 124 local ga_append_string = function(garr, it) 125 assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) 126 -- make a non-garbage collected string and copy the lua string into it, 127 -- TODO(aktau): we should probably call xmalloc here, though as long as 128 -- xmalloc is based on malloc it should work. 129 local mem = ffi.C.malloc(string.len(it) + 1) 130 ffi.copy(mem, it) 131 ga_grow(garr, 1) 132 local data = ga_data_as_strings(garr) 133 data[ga_len(garr)] = mem 134 return ga_inc_len(garr, 1) 135 end 136 137 local ga_append_strings = function(garr, ...) 138 local prevlen = ga_len(garr) 139 local len = select('#', ...) 140 for i = 1, len do 141 ga_append_string(garr, select(i, ...)) 142 end 143 return eq(prevlen + len, ga_len(garr)) 144 end 145 146 local ga_append_ints = function(garr, ...) 147 local prevlen = ga_len(garr) 148 local len = select('#', ...) 149 for i = 1, len do 150 ga_append_int(garr, select(i, ...)) 151 end 152 return eq(prevlen + len, ga_len(garr)) 153 end 154 155 -- enhanced constructors 156 local garray_ctype = function(...) 157 return ffi.typeof('garray_T[1]')(...) 158 end 159 local new_garray = function() 160 local garr = garray_ctype() 161 return ffi.gc(garr, ga_clear) 162 end 163 164 local new_string_garray = function() 165 local garr = garray_ctype() 166 ga_init(garr, ffi.sizeof('unsigned char *'), 1) 167 return ffi.gc(garr, ga_clear_strings) 168 end 169 170 local randomByte = function() 171 return ffi.cast('uint8_t', math.random(0, 255)) 172 end 173 174 -- scramble the data in a garray 175 local ga_scramble = function(garr) 176 local size, bytes = ga_size(garr), ga_data_as_bytes(garr) 177 for i = 0, size - 1 do 178 bytes[i] = randomByte() 179 end 180 end 181 182 describe('garray', function() 183 describe('ga_init', function() 184 itp('initializes the values of the garray', function() 185 local garr = new_garray() 186 ga_init(garr, itemsize, growsize) 187 eq(0, ga_len(garr)) 188 eq(0, ga_maxlen(garr)) 189 eq(growsize, ga_growsize(garr)) 190 eq(itemsize, ga_itemsize(garr)) 191 eq(NULL, ga_data(garr)) 192 end) 193 end) 194 195 describe('ga_grow', function() 196 local function new_and_grow(itemsize_, growsize_, req) 197 local garr = new_garray() 198 ga_init(garr, itemsize_, growsize_) 199 eq(0, ga_size(garr)) -- should be 0 at first 200 eq(NULL, ga_data(garr)) -- should be NULL 201 ga_grow(garr, req) -- add space for `req` items 202 return garr 203 end 204 205 itp('grows by growsize items if num < growsize', function() 206 itemsize = 16 207 growsize = 4 208 local grow_by = growsize - 1 209 local garr = new_and_grow(itemsize, growsize, grow_by) 210 neq(NULL, ga_data(garr)) -- data should be a ptr to memory 211 eq(growsize, ga_maxlen(garr)) -- we requested LESS than growsize, so... 212 end) 213 214 itp('grows by num items if num > growsize', function() 215 itemsize = 16 216 growsize = 4 217 local grow_by = growsize + 1 218 local garr = new_and_grow(itemsize, growsize, grow_by) 219 neq(NULL, ga_data(garr)) -- data should be a ptr to memory 220 eq(grow_by, ga_maxlen(garr)) -- we requested MORE than growsize, so... 221 end) 222 223 itp('does not grow when nothing is requested', function() 224 local garr = new_and_grow(16, 4, 0) 225 eq(NULL, ga_data(garr)) 226 eq(0, ga_maxlen(garr)) 227 end) 228 end) 229 230 describe('ga_clear', function() 231 itp('clears an already allocated array', function() 232 -- allocate and scramble an array 233 local garr = garray_ctype() 234 ga_init(garr, itemsize, growsize) 235 ga_grow(garr, 4) 236 ga_set_len(garr, 4) 237 ga_scramble(garr) 238 239 -- clear it and check 240 ga_clear(garr) 241 eq(NULL, ga_data(garr)) 242 eq(0, ga_maxlen(garr)) 243 eq(0, ga_len(garr)) 244 end) 245 end) 246 247 describe('ga_append', function() 248 itp('can append bytes', function() 249 -- this is the actual ga_append, the others are just emulated lua 250 -- versions 251 local garr = new_garray() 252 ga_init(garr, ffi.sizeof('uint8_t'), 1) 253 ga_append(garr, 'h') 254 ga_append(garr, 'e') 255 ga_append(garr, 'l') 256 ga_append(garr, 'l') 257 ga_append(garr, 'o') 258 ga_append(garr, 0) 259 local bytes = ga_data_as_bytes(garr) 260 eq('hello', ffi.string(bytes)) 261 end) 262 263 itp('can append integers', function() 264 local garr = new_garray() 265 ga_init(garr, ffi.sizeof('int'), 1) 266 local input = { 267 -20, 268 94, 269 867615, 270 90927, 271 86, 272 } 273 ga_append_ints(garr, unpack(input)) 274 local ints = ga_data_as_ints(garr) 275 for i = 0, #input - 1 do 276 eq(input[i + 1], ints[i]) 277 end 278 end) 279 280 itp('can append strings to a growing array of strings', function() 281 local garr = new_string_garray() 282 local input = { 283 'some', 284 'str', 285 '\r\n\r●●●●●●,,,', 286 'hmm', 287 'got it', 288 } 289 ga_append_strings(garr, unpack(input)) 290 -- check that we can get the same strings out of the array 291 local strings = ga_data_as_strings(garr) 292 for i = 0, #input - 1 do 293 eq(input[i + 1], ffi.string(strings[i])) 294 end 295 end) 296 end) 297 298 describe('ga_concat', function() 299 itp('concatenates the parameter to the growing byte array', function() 300 local garr = new_garray() 301 ga_init(garr, ffi.sizeof('char'), 1) 302 local str = 'ohwell●●' 303 local loop = 5 304 for _ = 1, loop do 305 ga_concat(garr, str) 306 end 307 308 -- ga_concat does NOT append the NUL in the src string to the 309 -- destination, you have to do that manually by calling something like 310 -- ga_append(gar, '\0'). I'ts always used like that in the vim 311 -- codebase. I feel that this is a bit of an unnecesesary 312 -- micro-optimization. 313 ga_append(garr, 0) 314 local result = ffi.string(ga_data_as_bytes(garr)) 315 eq(string.rep(str, loop), result) 316 end) 317 end) 318 319 local function test_concat_fn(input, sep) 320 local garr = new_string_garray() 321 ga_append_strings(garr, unpack(input)) 322 eq(table.concat(input, sep), ga_concat_strings(garr, sep)) 323 end 324 325 describe('ga_concat_strings', function() 326 itp('returns an empty string when concatenating an empty array', function() 327 test_concat_fn({}, ',') 328 test_concat_fn({}, '---') 329 end) 330 331 itp('can concatenate a non-empty array', function() 332 test_concat_fn({ 333 'oh', 334 'my', 335 'neovim', 336 }, ',') 337 test_concat_fn({ 338 'oh', 339 'my', 340 'neovim', 341 }, '-●●-') 342 end) 343 end) 344 345 describe('ga_remove_duplicate_strings', function() 346 itp('sorts and removes duplicate strings', function() 347 local garr = new_string_garray() 348 local input = { 349 'ccc', 350 'aaa', 351 'bbb', 352 'ddd●●', 353 'aaa', 354 'bbb', 355 'ccc', 356 'ccc', 357 'ddd●●', 358 } 359 local sorted_dedup_input = { 360 'aaa', 361 'bbb', 362 'ccc', 363 'ddd●●', 364 } 365 ga_append_strings(garr, unpack(input)) 366 ga_remove_duplicate_strings(garr) 367 eq(#sorted_dedup_input, ga_len(garr)) 368 local strings = ga_data_as_strings(garr) 369 for i = 0, #sorted_dedup_input - 1 do 370 eq(sorted_dedup_input[i + 1], ffi.string(strings[i])) 371 end 372 end) 373 end) 374 end)