Lumiera  0.pre.03
»edit your freedom«
dispatcher-looper-test.cpp
1 /*
2  DispatcherLooper(Test) - verify loop control and timing functionality of SteamDispatcher
3 
4  Copyright (C) Lumiera.org
5  2016, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
23 
24 #include "lib/test/run.hpp"
25 #include "steam/control/looper.hpp"
26 
27 #include <chrono>
28 #include <thread>
29 
30 
31 namespace steam {
32 namespace control {
33 namespace test {
34 
35  using std::this_thread::sleep_for;
36  using namespace std::chrono_literals;
37 
38 
39  namespace { // test fixture...
40 
41  using Dur = std::chrono::duration<double, std::milli>;
42 
47  const uint EXPECTED_BUILDER_DELAY_ms = 50;
48 
49  bool
50  isFast (milliseconds timeoutDelay)
51  {
52  return timeoutDelay < Dur{1.2 * EXPECTED_BUILDER_DELAY_ms}
53  and 0ms < timeoutDelay;
54  }
55 
56  bool
57  isSlow (milliseconds timeoutDelay)
58  {
59  return timeoutDelay >= Dur{1.2 * EXPECTED_BUILDER_DELAY_ms};
60  }
61 
62  bool
63  isDisabled (milliseconds timeoutDelay)
64  {
65  return 0ms == timeoutDelay;
66  }
67 
68 
75  struct Setup
76  {
77  bool has_commands_in_queue = false;
78 
79  Looper
80  install()
81  {
82  return Looper([&](){ return has_commands_in_queue; });
83  }
84  };
85 
86  }//(End) test fixture
87 
88 
89 
90 
91 
92 
93 
94  /******************************************************************************/
104  class DispatcherLooper_test : public Test
105  {
106 
107 
108  virtual void
109  run (Arg)
110  {
111  verifyBasics();
112  verifyShutdown();
113  verifyWakeupActivity();
114  verifyShutdown_stops_processing();
115  verifyDisabling_stops_processing();
116  verifyBuilderStart();
117  }
118 
119 
120  void
121  verifyBasics()
122  {
123  Setup setup;
124  Looper looper = setup.install();
125 
126  CHECK (not looper.isDying());
127  CHECK (looper.shallLoop());
128  CHECK (not looper.runBuild());
129  CHECK (isDisabled (looper.getTimeout()));
130 
131  setup.has_commands_in_queue = true;
132  CHECK (looper.requireAction());
133 
134  milliseconds timeout = looper.getTimeout();
135  CHECK (10ms < timeout, "configured idle timeout %2l to short", timeout.count());
136  CHECK (timeout < 800ms, "configured idle timeout %3l to long", timeout.count());
137  }
138 
139 
140  void
141  verifyShutdown()
142  {
143  Setup setup;
144  Looper looper = setup.install();
145 
146  CHECK (not looper.isDying());
147  CHECK (looper.shallLoop());
148 
149  looper.triggerShutdown();
150  CHECK (looper.isDying());
151  CHECK (not looper.shallLoop());
152  }
153 
154 
155  void
156  verifyWakeupActivity()
157  {
158  Setup setup;
159  Looper looper = setup.install();
160 
161  CHECK (not looper.isDying());
162  CHECK (looper.shallLoop());
163 
164  CHECK (not looper.requireAction());
165  CHECK (not looper.isWorking());
166  CHECK ( looper.isIdle());
167 
168  setup.has_commands_in_queue = true;
169 
170  CHECK ( looper.requireAction());
171  CHECK ( looper.isWorking());
172  CHECK (not looper.isIdle());
173  CHECK (looper.shallLoop());
174 
175  setup.has_commands_in_queue = false;
176  looper.markStateProcessed(); // after command processing
177  CHECK (not looper.requireAction()); // stops immediate work state
178  CHECK ( looper.useTimeout()); // but still performs timeout
179  CHECK (not looper.isWorking());
180  CHECK (not looper.isIdle()); // still need to run the builder
181 
182  looper.markStateProcessed(); // second round-trip, after builder run
183 
184  CHECK (not looper.requireAction());
185  CHECK (not looper.isWorking());
186  CHECK ( looper.isIdle());
187  CHECK (looper.shallLoop());
188 
189  looper.triggerShutdown();
190 
191  CHECK (not looper.shallLoop());
192 
193  CHECK ( looper.requireAction());
194  CHECK (not looper.isWorking());
195  CHECK (not looper.isIdle());
196  }
197 
198 
199  void
200  verifyShutdown_stops_processing()
201  {
202  Setup setup;
203  Looper looper = setup.install();
204 
205  CHECK (not looper.isDying());
206  CHECK (looper.shallLoop());
207 
208  CHECK (not looper.requireAction());
209  CHECK (not looper.isWorking());
210  CHECK ( looper.isIdle());
211 
212  setup.has_commands_in_queue = true;
213 
214  CHECK ( looper.requireAction());
215  CHECK ( looper.isWorking());
216  CHECK (not looper.isIdle());
217  CHECK ( looper.shallLoop());
218  CHECK (not looper.isDying());
219 
220  looper.triggerShutdown();
221 
222  CHECK ( looper.requireAction());
223  CHECK (not looper.isWorking());
224  CHECK (not looper.isIdle());
225  CHECK (not looper.shallLoop());
226  CHECK ( looper.isDying());
227 
228  setup.has_commands_in_queue = false;
229 
230  CHECK ( looper.requireAction());
231  CHECK (not looper.isWorking());
232  CHECK (not looper.isIdle());
233  CHECK (not looper.shallLoop());
234  CHECK ( looper.isDying());
235 
236  setup.has_commands_in_queue = true;
237 
238  CHECK ( looper.requireAction());
239  CHECK (not looper.isWorking());
240  CHECK (not looper.isIdle());
241  CHECK (not looper.shallLoop());
242  CHECK ( looper.isDying());
243  }
244 
245 
246  void
247  verifyDisabling_stops_processing()
248  {
249  Setup setup;
250  Looper looper = setup.install();
251 
252  CHECK (not looper.requireAction());
253  CHECK (not looper.isDisabled());
254  CHECK (not looper.isWorking());
255  CHECK ( looper.isIdle());
256  CHECK ( looper.shallLoop());
257  CHECK (not looper.isDying());
258 
259  setup.has_commands_in_queue = true; // normal operation: pending commands will be processed
260 
261  CHECK ( looper.requireAction()); // ..causes wake-up
262  CHECK (not looper.isDisabled());
263  CHECK ( looper.isWorking());
264  CHECK (not looper.isIdle());
265  CHECK ( looper.shallLoop());
266  CHECK (not looper.isDying());
267 
268  looper.enableProcessing(false); // disable processing
269 
270  CHECK (not looper.requireAction());
271  CHECK ( looper.isDisabled());
272  CHECK (not looper.isWorking());
273  CHECK (not looper.isIdle());
274  CHECK ( looper.shallLoop());
275  CHECK (not looper.isDying());
276 
277  setup.has_commands_in_queue = false; // while disabled, state of the command queue has no effect
278 
279  CHECK (not looper.requireAction());
280  CHECK ( looper.isDisabled());
281  CHECK (not looper.isWorking());
282  CHECK (not looper.isIdle());
283  CHECK ( looper.shallLoop());
284  CHECK (not looper.isDying());
285 
286  setup.has_commands_in_queue = true;
287 
288  CHECK (not looper.requireAction());
289  CHECK ( looper.isDisabled());
290  CHECK (not looper.isWorking());
291  CHECK (not looper.isIdle());
292  CHECK ( looper.shallLoop());
293  CHECK (not looper.isDying());
294 
295  looper.enableProcessing(); // resume normal operation
296 
297  CHECK ( looper.requireAction());
298  CHECK (not looper.isDisabled());
299  CHECK ( looper.isWorking());
300  CHECK (not looper.isIdle());
301  CHECK ( looper.shallLoop());
302  CHECK (not looper.isDying());
303 
304  looper.enableProcessing(false); // disable again
305 
306  CHECK (not looper.requireAction());
307  CHECK ( looper.isDisabled());
308  CHECK (not looper.isWorking());
309  CHECK (not looper.isIdle());
310  CHECK ( looper.shallLoop());
311  CHECK (not looper.isDying());
312 
313  looper.triggerShutdown(); // wake-up for shutdown even from disabled state
314 
315  CHECK ( looper.requireAction());
316  CHECK ( looper.isDisabled());
317  CHECK (not looper.isWorking());
318  CHECK (not looper.isIdle());
319  CHECK (not looper.shallLoop());
320  CHECK ( looper.isDying());
321  }
322 
323 
333  void
335  {
336  Setup setup;
337  Looper looper = setup.install();
338 
339  CHECK (not looper.requireAction());
340  CHECK (not looper.isDisabled());
341  CHECK (not looper.isWorking());
342  CHECK (not looper.runBuild());
343  CHECK ( looper.isIdle());
344 
345  setup.has_commands_in_queue = true; // regular command processing
346 
347  CHECK ( looper.requireAction());
348  CHECK (not looper.isDisabled());
349  CHECK ( looper.isWorking());
350  CHECK (not looper.runBuild());
351  CHECK (not looper.isIdle());
352 
353  looper.markStateProcessed(); // at least one command has been handled
354 
355  CHECK ( looper.requireAction());
356  CHECK (not looper.isDisabled());
357  CHECK ( looper.isWorking());
358  CHECK (not looper.runBuild()); // ...note: build not yet triggered
359  CHECK (not looper.isIdle());
360 
361  CHECK (isSlow (looper.getTimeout()));
362 
363 
364  looper.markStateProcessed(); // next processing round: further command(s) processed,
365  // yet still more commands pending...
366  CHECK ( looper.requireAction());
367  CHECK (not looper.isDisabled());
368  CHECK ( looper.isWorking());
369  CHECK (not looper.runBuild()); // ...build still postponed
370  CHECK (not looper.isIdle());
371 
372  sleep_for (800ms); // let's assume we did command processing for a long time...
373 
374  CHECK ( looper.requireAction());
375  CHECK (not looper.isDisabled());
376  CHECK ( looper.isWorking());
377  CHECK ( looper.runBuild()); // ...after some time of command processing, a build run is forced
378  CHECK (not looper.isIdle());
379 
380  looper.markStateProcessed(); // and when the builder run is confirmed...
381 
382  CHECK ( looper.requireAction());
383  CHECK (not looper.isDisabled());
384  CHECK ( looper.isWorking());
385  CHECK (not looper.runBuild()); // ...we are back to normal working state (build postponed)
386  CHECK (not looper.isIdle());
387 
388 
389  setup.has_commands_in_queue = false; // now emptied our queue
390 
391  CHECK (not looper.requireAction());
392  CHECK (not looper.isDisabled());
393  CHECK (not looper.isWorking());
394  CHECK ( looper.runBuild()); // ...note: now build will be triggered
395  CHECK (not looper.isIdle());
396 
397  CHECK (isFast (looper.getTimeout())); // ...but only after a short wait period,
398  // since not looper.requireAction()
399 
400 
401  looper.markStateProcessed(); // next processing round: invoked builder,
402  // and no more commands commands pending...
403  CHECK (not looper.requireAction());
404  CHECK (not looper.isDisabled());
405  CHECK (not looper.isWorking());
406  CHECK (not looper.runBuild()); // ...note: now done with building
407  CHECK ( looper.isIdle());
408 
409  CHECK (isDisabled(looper.getTimeout())); // ...now Dispatcher is idle and goes to sleep
410 
411 
412  setup.has_commands_in_queue = true; // next command pending
413 
414  CHECK ( looper.requireAction()); // return to work mode
415  CHECK (not looper.isDisabled());
416  CHECK ( looper.isWorking());
417  CHECK (not looper.runBuild());
418  CHECK (not looper.isIdle());
419 
420  setup.has_commands_in_queue = false; // now let's assume command has been processed
421  looper.markStateProcessed(); // and queue is empty again
422 
423  CHECK (not looper.requireAction());
424  CHECK (not looper.isDisabled());
425  CHECK (not looper.isWorking());
426  CHECK ( looper.runBuild());
427  CHECK (not looper.isIdle());
428 
429  CHECK (isFast (looper.getTimeout())); // now build *would* be triggered after short timeout, but..
430 
431 
432  looper.enableProcessing(false); // disable processing
433 
434  CHECK (not looper.requireAction());
435  CHECK ( looper.isDisabled());
436  CHECK (not looper.isWorking());
437  CHECK (not looper.runBuild()); // ...note: dirty state hidden by disabled state
438  CHECK (not looper.isIdle());
439 
440  CHECK (isDisabled (looper.getTimeout()));
441 
442 
443  looper.enableProcessing(true); // enable back
444 
445  CHECK (not looper.requireAction());
446  CHECK (not looper.isDisabled());
447  CHECK (not looper.isWorking());
448  CHECK ( looper.runBuild()); // ...note: dirty state revealed again
449  CHECK (not looper.isIdle());
450 
451  CHECK (isFast (looper.getTimeout()));
452 
453  looper.enableProcessing(false); // disable processing
454  looper.markStateProcessed(); // let's assume builder was running and is now finished
455 
456  CHECK (not looper.requireAction());
457  CHECK ( looper.isDisabled());
458  CHECK (not looper.isWorking());
459  CHECK (not looper.runBuild()); // ...note: dirty state not obvious
460  CHECK (not looper.isIdle());
461 
462  CHECK (isDisabled (looper.getTimeout()));
463 
464 
465  looper.enableProcessing(true); // enable back
466  // NOTE special twist: it's unclear, if builder was triggered before the disabled state...
467  CHECK (isFast (looper.getTimeout())); // ...and thus we remain in dirty state
468 
469  CHECK (not looper.requireAction());
470  CHECK (not looper.isDisabled());
471  CHECK (not looper.isWorking());
472  CHECK ( looper.runBuild()); // so the builder will be triggered (possibly a second time) after a short timeout
473  CHECK (not looper.isIdle());
474 
475  looper.markStateProcessed(); // and after one round-trip the builder was running and is now finished
476 
477  CHECK (not looper.requireAction());
478  CHECK (not looper.isDisabled());
479  CHECK (not looper.isWorking());
480  CHECK (not looper.runBuild());
481  CHECK ( looper.isIdle()); // ...system is in idle state now and waits until triggered externally
482 
483  CHECK (isDisabled (looper.getTimeout()));
484 
485 
486  setup.has_commands_in_queue = true; // more commands again -> wake up
487  looper.markStateProcessed(); // ...and let's assume one command has already been processed
488 
489  CHECK ( looper.requireAction());
490  CHECK (not looper.isDisabled());
491  CHECK ( looper.isWorking());
492  CHECK (not looper.runBuild());
493  CHECK (not looper.isIdle());
494 
495  looper.triggerShutdown(); // request shutdown...
496 
497  CHECK ( looper.requireAction());
498  CHECK ( looper.isDisabled());
499  CHECK (not looper.isWorking());
500  CHECK (not looper.runBuild());
501  CHECK (not looper.isIdle());
502 
503  CHECK (isDisabled (looper.getTimeout()));
504 
505 
506  setup.has_commands_in_queue = false; // and even when done with all commands...
507  looper.markStateProcessed();
508 
509  CHECK (isDisabled (looper.getTimeout()));
510  CHECK (not looper.shallLoop()); // we remain disabled and break out of the loop
511 
512  CHECK ( looper.requireAction());
513  CHECK ( looper.isDisabled());
514  CHECK (not looper.isWorking());
515  CHECK (not looper.runBuild()); // ...note: still no need for builder run, since in shutdown
516  CHECK (not looper.isIdle());
517 
518  }
519  };
520 
521 
523  LAUNCHER (DispatcherLooper_test, "unit controller");
524 
525 
526 }}} // namespace steam::control::test
Definition: Setup.py:1
Definition: run.hpp:49
Steam-Layer implementation namespace root.
bool shallLoop() const
state fusion to control looping
Definition: looper.hpp:206
bool requireAction()
state fusion to control (timed) wait
Definition: looper.hpp:189
Implementation building block of SteamDispatcher to control waiting and timing.
Simple test class runner.
void markStateProcessed()
invoking this function signals that all consequences of past state changes have been processed and ar...
Definition: looper.hpp:174
auto setup(FUN &&workFun)
Helper: setup a Worker-Pool configuration for the test.
Encapsulated control logic for the session thread loop.
Definition: looper.hpp:114