comparison spec/util_smqueue_spec.lua @ 14198:73903352de86

util.smqueue: Refactor checkpoint logic and add tests The previous checkpoint logic used recursion, and the code was more complex than needed due to maintenance of the self._next_checkpoint field. This switches to a loop, which should be a bit more efficient, partly because there are fewer calls (although it was a tail call) and because table lookups can now be cached between each execution. _call_checkpoints() is now called unconditionally for every ack, which is simpler though adds a function call, it should be more than balanced out by the other changes.
author Matthew Wild <mwild1@gmail.com>
date Tue, 26 May 2026 15:36:36 +0100
parents 6163c8b17ea9
children
comparison
equal deleted inserted replaced
14197:3b26b8772fb1 14198:73903352de86
79 local t = q:table(); 79 local t = q:table();
80 assert.same({}, t); 80 assert.same({}, t);
81 end 81 end
82 end) 82 end)
83 end) 83 end)
84
85 describe("#add_checkpoint", function()
86 it("fires the callback when ack reaches the checkpoint", function()
87 local q = smqueue.new(10);
88 for i = 1, 4 do q:push(i); end
89 local called = false;
90 q:add_checkpoint(function() called = true; end);
91 assert.falsy(called, "should not fire before ack");
92 q:ack(4);
93 assert.truthy(called, "should fire when ack reaches checkpoint");
94 end)
95
96 it("fires when ack exceeds the checkpoint", function()
97 local q = smqueue.new(10);
98 for i = 1, 4 do q:push(i); end
99 local called = false;
100 q:add_checkpoint(function() called = true; end);
101 for i = 5, 8 do q:push(i); end
102 q:ack(7); -- acks past the checkpoint at 4
103 assert.truthy(called);
104 end)
105
106 it("does not fire for acks below the checkpoint threshold", function()
107 local q = smqueue.new(10);
108 for i = 1, 5 do q:push(i); end
109 local called = false;
110 q:add_checkpoint(function() called = true; end);
111 q:ack(4);
112 assert.falsy(called, "should not fire when ack is below checkpoint");
113 end)
114
115 it("fires the callback with optional data argument", function()
116 local q = smqueue.new(10);
117 for i = 1, 4 do q:push(i); end
118 local received_data;
119 q:add_checkpoint(function(d) received_data = d; end, "hello");
120 q:ack(4);
121 assert.equal("hello", received_data);
122 end)
123
124 it("fires multiple checkpoints in a single ack call", function()
125 local q = smqueue.new(10);
126 for i = 1, 3 do q:push(i); end
127 local fired = {};
128 q:add_checkpoint(function() fired[#fired+1] = "first"; end);
129 q:push(4);
130 q:add_checkpoint(function() fired[#fired+1] = "second-1"; end);
131 q:add_checkpoint(function() fired[#fired+1] = "second-2"; end);
132 q:push(5);
133 q:add_checkpoint(function() fired[#fired+1] = "third"; end);
134 q:ack(5); -- should call all checkpoints, in order
135 assert.same({ "first", "second-1", "second-2", "third" }, fired);
136 end)
137
138 it("fires checkpoints in order across multiple acks", function()
139 local q = smqueue.new(10);
140 for i = 1, 3 do q:push(i); end
141 local order = {};
142 q:add_checkpoint(function() order[#order+1] = 1; end);
143 q:push(4);
144 q:add_checkpoint(function() order[#order+1] = 2; end);
145 q:push(5);
146 q:add_checkpoint(function() order[#order+1] = 3; end);
147 q:ack(3); -- fires only first
148 assert.same({ 1 }, order);
149 q:ack(5); -- fires second and third
150 assert.same({ 1, 2, 3 }, order);
151 end)
152
153 it("fires each checkpoint exactly once", function()
154 local q = smqueue.new(10);
155 for i = 1, 4 do q:push(i); end
156 local count = 0;
157 q:add_checkpoint(function() count = count + 1; end);
158 q:ack(4);
159 q:ack(4); -- duplicate ack
160 for i = 5, 8 do q:push(i); end
161 q:ack(8);
162 assert.equal(1, count);
163 end)
164 end)
84 end); 165 end);