1 local async
= require
"util.async";
3 describe("util.async", function()
7 require
"util.logger".add_simple_sink(print);
9 print = function () end
12 local function mock_watchers(event_log
)
13 local function generic_logging_watcher(name
)
15 table.insert(event_log
, { name
= name
, n
= select("#", ...)-1, select(2, ...) });
18 return setmetatable(mock
{
19 ready
= generic_logging_watcher("ready");
20 waiting
= generic_logging_watcher("waiting");
21 error = generic_logging_watcher("error");
23 __index
= function (_
, event
)
24 -- Unexpected watcher called
25 assert(false, "unexpected watcher called: "..event
);
30 local function new(func
)
32 local spy_func
= spy
.new(func
);
33 return async
.runner(spy_func
, mock_watchers(event_log
)), spy_func
, event_log
;
35 describe("#runner", function()
36 it("should work", function()
37 local r
= new(function (item
) assert(type(item
) == "number") end);
42 it("should be ready after creation", function ()
43 local r
= new(function () end);
44 assert.equal(r
.state
, "ready");
47 it("should do nothing if the queue is empty", function ()
49 local r
= new(function () did_run
= true end);
51 assert.equal(r
.state
, "ready");
52 assert.is_nil(did_run
);
54 assert.is_true(did_run
);
57 it("should support queuing work items without running", function ()
59 local r
= new(function () did_run
= true end);
61 assert.equal(r
.state
, "ready");
62 assert.is_nil(did_run
);
64 assert.is_true(did_run
);
67 it("should support queuing multiple work items", function ()
69 local r
, s
= new(function (item
) last_item
= item
; end);
73 assert.equal(r
.state
, "ready");
75 assert.equal(r
.state
, "ready");
76 assert.spy(s
).was
.called(3);
77 assert.equal(last_item
, "world");
80 it("should support all simple data types", function ()
82 local r
, s
= new(function (item
) last_item
= item
; end);
83 local values
= { {}, 123, "hello", true, false };
87 assert.equal(r
.state
, "ready");
89 assert.equal(r
.state
, "ready");
90 assert.spy(s
).was
.called(#values
);
92 assert.spy(s
).was
.called_with(values
[i
]);
94 assert.equal(last_item
, values
[#values
]);
97 it("should work with no parameters", function ()
99 local r
= async
.runner();
100 local f
= spy
.new(function () item
= "success"; end);
102 assert.spy(f
).was
.called();
103 assert.equal(item
, "success");
106 it("supports a default error handler", function ()
108 local r
= async
.runner();
109 local f
= spy
.new(function () error("test error"); end);
110 assert.error_matches(function ()
113 assert.spy(f
).was
.called();
114 assert.equal(item
, "fail");
117 describe("#errors", function ()
118 describe("should notify", function ()
119 local last_processed_item
, last_error
;
121 r
= async
.runner(function (item
)
122 if item
== "error" then
123 error({ e
= "test error" });
125 last_processed_item
= item
;
127 ready
= function () end;
128 waiting
= function () end;
129 error = function (runner
, err
)
130 assert.equal(r
, runner
);
135 -- Simple item, no error
137 assert.equal(r
.state
, "ready");
138 assert.equal(last_processed_item
, "hello");
139 assert.spy(r
.watchers
.ready
).was_not
.called();
140 assert.spy(r
.watchers
.error).was_not
.called();
142 -- Trigger an error inside the runner
143 assert.equal(last_error
, nil);
145 test("the correct watcher functions", function ()
146 -- Only the error watcher should have been called
147 assert.spy(r
.watchers
.ready
).was_not
.called();
148 assert.spy(r
.watchers
.waiting
).was_not
.called();
149 assert.spy(r
.watchers
.error).was
.called(1);
151 test("with the correct error", function ()
152 -- The error watcher state should be correct, to
153 -- demonstrate the error was passed correctly
154 assert.is_table(last_error
);
155 assert.equal(last_error
.e
, "test error");
158 assert.equal(r
.state
, "ready");
159 assert.equal(last_processed_item
, "hello");
163 local last_processed_item
, last_error
;
166 r
= async
.runner(function (item
)
167 if item
== "error" then
168 error({ e
= "test error" });
169 elseif item
== "wait" then
170 wait
, done
= async
.waiter();
172 error({ e
= "post wait error" });
174 last_processed_item
= item
;
176 ready
= function () end;
177 waiting
= function () end;
178 error = function (runner
, err
)
179 assert.equal(r
, runner
);
184 randomize(false); --luacheck: ignore 113/randomize
186 it("should not be fatal to the runner", function ()
188 assert.equal(r
.state
, "ready");
189 assert.spy(r
.watchers
.ready
).was_not
.called();
190 assert.equal(last_processed_item
, "world");
192 it("should work despite a #waiter", function ()
193 -- This test covers an important case where a runner
194 -- throws an error while being executed outside of the
195 -- main loop. This happens when it was blocked ('waiting'),
196 -- and then released (via a call to done()).
199 assert.equal(r
.state
, "waiting");
200 assert.spy(r
.watchers
.waiting
).was
.called(1);
202 -- At this point an error happens (state goes error->ready)
203 assert.equal(r
.state
, "ready");
204 assert.spy(r
.watchers
.error).was
.called(1);
205 assert.spy(r
.watchers
.ready
).was
.called(1);
206 assert.is_table(last_error
);
207 assert.equal(last_error
.e
, "post wait error");
209 r
:run("hello again");
210 assert.spy(r
.watchers
.ready
).was
.called(1);
211 assert.spy(r
.watchers
.waiting
).was
.called(1);
212 assert.spy(r
.watchers
.error).was
.called(1);
213 assert.equal(r
.state
, "ready");
214 assert.equal(last_processed_item
, "hello again");
218 it("should continue to process work items", function ()
220 local runner
, runner_func
= new(function (item
)
221 if item
== "error" then
226 runner
:enqueue("one");
227 runner
:enqueue("error");
228 runner
:enqueue("two");
230 assert.equal(runner
.state
, "ready");
231 assert.spy(runner_func
).was
.called(3);
232 assert.spy(runner
.watchers
.error).was
.called(1);
233 assert.spy(runner
.watchers
.ready
).was
.called(0);
234 assert.spy(runner
.watchers
.waiting
).was
.called(0);
235 assert.equal(last_item
, "two");
238 it("should continue to process work items during resume", function ()
239 local wait
, done
, last_item
;
240 local runner
, runner_func
= new(function (item
)
241 if item
== "wait-error" then
242 wait
, done
= async
.waiter();
248 runner
:enqueue("one");
249 runner
:enqueue("wait-error");
250 runner
:enqueue("two");
253 assert.equal(runner
.state
, "ready");
254 assert.spy(runner_func
).was
.called(3);
255 assert.spy(runner
.watchers
.error).was
.called(1);
256 assert.spy(runner
.watchers
.waiting
).was
.called(1);
257 assert.spy(runner
.watchers
.ready
).was
.called(1);
258 assert.equal(last_item
, "two");
262 describe("#waiter", function()
263 it("should error outside of async context", function ()
264 assert.has_error(function ()
268 it("should work", function ()
271 local r
= new(function (item
)
272 assert(type(item
) == "number")
274 wait
, done
= async
.waiter();
280 assert(r
.state
== "ready");
282 assert(r
.state
== "ready");
284 assert(r
.state
== "waiting");
286 assert(r
.state
== "ready");
287 --for k, v in ipairs(l) do print(k,v) end
290 it("should work", function ()
294 local r
= new(function (item
)
295 assert(type(item
) == "number")
296 assert(item
== last_item
+ 1);
299 wait
, done
= async
.waiter();
305 assert(r
.state
== "ready");
307 assert(r
.state
== "ready");
309 assert(r
.state
== "waiting");
311 assert(r
.state
== "waiting");
313 assert(r
.state
== "ready");
314 --for k, v in ipairs(l) do print(k,v) end
316 it("should work", function ()
320 local r
= new(function (item
)
321 assert(type(item
) == "number")
322 assert((item
== last_item
+ 1) or item
== 3);
325 wait
, done
= async
.waiter();
331 assert(r
.state
== "ready");
333 assert(r
.state
== "ready");
336 assert(r
.state
== "waiting");
338 assert(r
.state
== "waiting");
340 assert(r
.state
== "waiting");
342 assert(r
.state
== "waiting");
347 assert(r
.state
== "waiting");
351 assert(r
.state
== "ready");
352 --for k, v in ipairs(l) do print(k,v) end
354 it("should work", function ()
358 local r
= new(function (item
)
359 assert(type(item
) == "number")
360 assert((item
== last_item
+ 1) or item
== 3);
363 wait
, done
= async
.waiter();
369 assert(r
.state
== "ready");
371 assert(r
.state
== "ready");
374 assert(r
.state
== "waiting");
376 assert(r
.state
== "waiting");
381 assert(r
.state
== "waiting");
385 assert(r
.state
== "ready");
387 assert(r
.state
== "ready");
389 assert(r
.state
== "ready");
390 --for k, v in ipairs(l) do print(k,v) end
392 it("should work with multiple runners in parallel", function ()
393 -- Now with multiple runners
396 local last_item1
= 0;
397 local r1
= new(function (item
)
398 assert(type(item
) == "number")
399 assert((item
== last_item1
+ 1) or item
== 3);
402 wait1
, done1
= async
.waiter();
408 local last_item2
= 0;
409 local r2
= new(function (item
)
410 assert(type(item
) == "number")
411 assert((item
== last_item2
+ 1) or item
== 3);
414 wait2
, done2
= async
.waiter();
420 assert(r1
.state
== "ready");
422 assert(r1
.state
== "ready");
425 assert(r1
.state
== "waiting");
427 assert(r1
.state
== "waiting");
430 assert(r1
.state
== "waiting");
431 assert(r2
.state
== "ready");
434 assert(r1
.state
== "waiting");
435 assert(r2
.state
== "ready");
438 assert(r1
.state
== "waiting");
439 assert(r2
.state
== "waiting");
443 assert(r1
.state
== "waiting");
444 assert(r2
.state
== "waiting");
448 assert(r1
.state
== "waiting");
449 assert(r2
.state
== "ready");
454 assert(r1
.state
== "waiting");
458 assert(r1
.state
== "ready");
460 assert(r1
.state
== "ready");
462 assert(r1
.state
== "ready");
463 --for k, v in ipairs(l1) do print(k,v) end
465 it("should work work with multiple runners in parallel", function ()
468 local last_item1
= 0;
469 local r1
= new(function (item
)
470 print("r1 processing ", item
);
471 assert(type(item
) == "number")
472 assert((item
== last_item1
+ 1) or item
== 3);
475 wait1
, done1
= async
.waiter();
481 local last_item2
= 0;
482 local r2
= new(function (item
)
483 print("r2 processing ", item
);
484 assert.is_number(item
);
485 assert((item
== last_item2
+ 1) or item
== 3);
488 wait2
, done2
= async
.waiter();
494 assert.equal(r1
.state
, "ready");
496 assert.equal(r1
.state
, "ready");
499 assert.equal(r1
.state
, "ready");
502 assert.equal(r1
.state
, "waiting");
503 r1
:run(5); -- Will error, when we get to it
504 assert.equal(r1
.state
, "waiting");
506 assert.equal(r1
.state
, "ready");
508 assert.equal(r1
.state
, "waiting");
511 assert.equal(r1
.state
, "waiting");
512 assert.equal(r2
.state
, "ready");
515 assert.equal(r1
.state
, "waiting");
516 assert.equal(r2
.state
, "ready");
519 assert.equal(r1
.state
, "waiting");
520 assert.equal(r2
.state
, "waiting");
523 assert.equal(r1
.state
, "waiting");
524 assert.equal(r2
.state
, "ready");
527 assert.equal(r1
.state
, "waiting");
528 assert.equal(r2
.state
, "waiting");
531 assert.equal(r1
.state
, "waiting");
532 assert.equal(r2
.state
, "ready");
535 assert.equal(r1
.state
, "waiting");
536 assert.equal(r2
.state
, "ready");
540 assert.equal(r1
.state
, "ready");
542 assert.equal(r1
.state
, "ready");
544 assert.equal(r1
.state
, "ready");
547 it("should support multiple done() calls", function ()
548 local processed_item
;
550 local r
, rf
= new(function (item
)
551 wait
, done
= async
.waiter(4);
553 processed_item
= item
;
558 assert.equal(r
.state
, "waiting");
559 assert.is_nil(processed_item
);
562 assert.equal(r
.state
, "ready");
563 assert.equal(processed_item
, "test");
564 assert.spy(r
.watchers
.error).was_not
.called();
567 it("should not allow done() to be called more than specified", function ()
568 local processed_item
;
570 local r
, rf
= new(function (item
)
571 wait
, done
= async
.waiter(4);
573 processed_item
= item
;
579 assert.has_error(done
);
580 assert.equal(r
.state
, "ready");
581 assert.equal(processed_item
, "test");
582 assert.spy(r
.watchers
.error).was_not
.called();
585 it("should allow done() to be called before wait()", function ()
586 local processed_item
;
587 local r
, rf
= new(function (item
)
588 local wait
, done
= async
.waiter();
591 processed_item
= item
;
594 assert.equal(processed_item
, "test");
595 assert.equal(r
.state
, "ready");
596 -- Since the observable state did not change,
597 -- the watchers should not have been called
598 assert.spy(r
.watchers
.waiting
).was_not
.called();
599 assert.spy(r
.watchers
.ready
).was_not
.called();
603 describe("#ready()", function ()
604 it("should return false outside an async context", function ()
605 assert.falsy(async
.ready());
607 it("should return true inside an async context", function ()
608 local r
= new(function ()
609 assert.truthy(async
.ready());
612 assert.spy(r
.func
).was
.called();
613 assert.spy(r
.watchers
.error).was_not
.called();