garray_spec.lua 10 KB

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