marktree_spec.lua 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. local helpers = require("test.unit.helpers")(after_each)
  2. local itp = helpers.gen_itp(it)
  3. local ffi = helpers.ffi
  4. local eq = helpers.eq
  5. local ok = helpers.ok
  6. local lib = helpers.cimport("./src/nvim/marktree.h")
  7. local function tablelength(t)
  8. local count = 0
  9. for _ in pairs(t) do count = count + 1 end
  10. return count
  11. end
  12. local function pos_leq(a, b)
  13. return a[1] < b[1] or (a[1] == b[1] and a[2] <= b[2])
  14. end
  15. -- Checks that shadow and tree is consistent, and optionally
  16. -- return the order
  17. local function shadoworder(tree, shadow, iter, giveorder)
  18. ok(iter ~= nil)
  19. local status = lib.marktree_itr_first(tree, iter)
  20. local count = 0
  21. local pos2id, id2pos = {}, {}
  22. local last
  23. if not status and next(shadow) == nil then
  24. return pos2id, id2pos
  25. end
  26. repeat
  27. local mark = lib.marktree_itr_current(iter)
  28. local id = tonumber(mark.id)
  29. local spos = shadow[id]
  30. if (mark.pos.row ~= spos[1] or mark.pos.col ~= spos[2]) then
  31. error("invalid pos for "..id..":("..mark.pos.row..", "..mark.pos.col..") instead of ("..spos[1]..", "..spos[2]..")")
  32. end
  33. if lib.mt_right_test(mark) ~= spos[3] then
  34. error("invalid gravity for "..id..":("..mark.pos.row..", "..mark.pos.col..")")
  35. end
  36. if count > 0 then
  37. if not pos_leq(last, spos) then
  38. error("DISORDER")
  39. end
  40. end
  41. count = count + 1
  42. last = spos
  43. if giveorder then
  44. pos2id[count] = id
  45. id2pos[id] = count
  46. end
  47. until not lib.marktree_itr_next(tree, iter)
  48. local shadowlen = tablelength(shadow)
  49. if shadowlen ~= count then
  50. error("missed some keys? (shadow "..shadowlen..", tree "..count..")")
  51. end
  52. return id2pos, pos2id
  53. end
  54. local function shadowsplice(shadow, start, old_extent, new_extent)
  55. local old_end = {start[1] + old_extent[1],
  56. (old_extent[1] == 0 and start[2] or 0) + old_extent[2]}
  57. local new_end = {start[1] + new_extent[1],
  58. (new_extent[1] == 0 and start[2] or 0) + new_extent[2]}
  59. local delta = {new_end[1] - old_end[1], new_end[2] - old_end[2]}
  60. for _, pos in pairs(shadow) do
  61. if pos_leq(start, pos) then
  62. if pos_leq(pos, old_end) then
  63. -- delete region
  64. if pos[3] then -- right gravity
  65. pos[1], pos[2] = new_end[1], new_end[2]
  66. else
  67. pos[1], pos[2] = start[1], start[2]
  68. end
  69. else
  70. if pos[1] == old_end[1] then
  71. pos[2] = pos[2] + delta[2]
  72. end
  73. pos[1] = pos[1] + delta[1]
  74. end
  75. end
  76. end
  77. end
  78. local function dosplice(tree, shadow, start, old_extent, new_extent)
  79. lib.marktree_splice(tree, start[1], start[2], old_extent[1], old_extent[2], new_extent[1], new_extent[2])
  80. shadowsplice(shadow, start, old_extent, new_extent)
  81. end
  82. local last_id = nil
  83. local function put(tree, row, col, gravitate)
  84. last_id = last_id + 1
  85. local my_id = last_id
  86. lib.marktree_put_test(tree, my_id, row, col, gravitate);
  87. return my_id
  88. end
  89. describe('marktree', function()
  90. before_each(function()
  91. last_id = 0
  92. end)
  93. itp('works', function()
  94. local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
  95. local shadow = {}
  96. local iter = ffi.new("MarkTreeIter[1]")
  97. local iter2 = ffi.new("MarkTreeIter[1]")
  98. for i = 1,100 do
  99. for j = 1,100 do
  100. local gravitate = (i%2) > 0
  101. local id = put(tree, j, i, gravitate)
  102. ok(id > 0)
  103. eq(nil, shadow[id])
  104. shadow[id] = {j,i,gravitate}
  105. end
  106. -- checking every insert is too slow, but this is ok
  107. lib.marktree_check(tree)
  108. end
  109. -- ss = lib.mt_inspect_rec(tree)
  110. -- io.stdout:write(ffi.string(ss))
  111. -- io.stdout:flush()
  112. local id2pos, pos2id = shadoworder(tree, shadow, iter)
  113. eq({}, pos2id) -- not set if not requested
  114. eq({}, id2pos)
  115. for i,ipos in pairs(shadow) do
  116. local p = lib.marktree_lookup_ns(tree, -1, i, false, iter)
  117. eq(ipos[1], p.pos.row)
  118. eq(ipos[2], p.pos.col)
  119. local k = lib.marktree_itr_current(iter)
  120. eq(ipos[1], k.pos.row)
  121. eq(ipos[2], k.pos.col, ipos[1])
  122. lib.marktree_itr_next(tree, iter)
  123. -- TODO(bfredl): use id2pos to check neighbour?
  124. -- local k2 = lib.marktree_itr_current(iter)
  125. end
  126. for i,ipos in pairs(shadow) do
  127. lib.marktree_itr_get(tree, ipos[1], ipos[2], iter)
  128. local k = lib.marktree_itr_current(iter)
  129. eq(i, tonumber(k.id))
  130. eq(ipos[1], k.pos.row)
  131. eq(ipos[2], k.pos.col)
  132. end
  133. ok(lib.marktree_itr_first(tree, iter))
  134. local del = lib.marktree_itr_current(iter)
  135. lib.marktree_del_itr(tree, iter, false)
  136. shadow[tonumber(del.id)] = nil
  137. shadoworder(tree, shadow, iter)
  138. for _, ci in ipairs({0,-1,1,-2,2,-10,10}) do
  139. for i = 1,100 do
  140. lib.marktree_itr_get(tree, i, 50+ci, iter)
  141. local k = lib.marktree_itr_current(iter)
  142. local id = tonumber(k.id)
  143. eq(shadow[id][1], k.pos.row)
  144. eq(shadow[id][2], k.pos.col)
  145. lib.marktree_del_itr(tree, iter, false)
  146. shadow[id] = nil
  147. end
  148. lib.marktree_check(tree)
  149. shadoworder(tree, shadow, iter)
  150. end
  151. -- NB: this is quite rudimentary. We rely on
  152. -- functional tests exercising splicing quite a bit
  153. lib.marktree_check(tree)
  154. dosplice(tree, shadow, {2,2}, {0,5}, {1, 2})
  155. lib.marktree_check(tree)
  156. shadoworder(tree, shadow, iter)
  157. dosplice(tree, shadow, {30,2}, {30,5}, {1, 2})
  158. lib.marktree_check(tree)
  159. shadoworder(tree, shadow, iter)
  160. dosplice(tree, shadow, {5,3}, {0,2}, {0, 5})
  161. shadoworder(tree, shadow, iter)
  162. lib.marktree_check(tree)
  163. -- build then burn (HOORAY! HOORAY!)
  164. while next(shadow) do
  165. lib.marktree_itr_first(tree, iter)
  166. -- delete every other key for fun and profit
  167. while true do
  168. local k = lib.marktree_itr_current(iter)
  169. lib.marktree_del_itr(tree, iter, false)
  170. ok(shadow[tonumber(k.id)] ~= nil)
  171. shadow[tonumber(k.id)] = nil
  172. local stat = lib.marktree_itr_next(tree, iter)
  173. if not stat then
  174. break
  175. end
  176. end
  177. lib.marktree_check(tree)
  178. shadoworder(tree, shadow, iter2)
  179. end
  180. -- Check iterator validity for 2 specific edge cases:
  181. -- https://github.com/neovim/neovim/pull/14719
  182. lib.marktree_clear(tree)
  183. for i = 1,20 do
  184. put(tree, i, i, false)
  185. end
  186. lib.marktree_itr_get(tree, 10, 10, iter)
  187. lib.marktree_del_itr(tree, iter, false)
  188. eq(11, iter[0].node.key[iter[0].i].pos.col)
  189. lib.marktree_itr_get(tree, 11, 11, iter)
  190. lib.marktree_del_itr(tree, iter, false)
  191. eq(12, iter[0].node.key[iter[0].i].pos.col)
  192. end)
  193. end)