Lumiera  0.pre.03
»edit your freedom«
subsystem-runner-test.cpp
Go to the documentation of this file.
1 /*
2  SubsystemRunner(Test) - validate starting and stopping of dependent subsystems
3 
4  Copyright (C)
5  2008, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
21 #include "lib/test/run.hpp"
22 #include "lib/test/test-helper.hpp"
23 #include "common/subsys.hpp"
25 #include "common/option.hpp"
26 
27 #include "lib/symbol.hpp"
28 #include "lib/thread.hpp"
29 #include "lib/sync-barrier.hpp"
30 #include "lib/query-util.hpp"
31 #include "lib/format-cout.hpp"
32 #include "lib/error.hpp"
33 #include "lib/util.hpp"
34 #include "lib/sync.hpp"
35 
36 #include <memory>
37 #include <atomic>
38 #include <chrono>
39 
40 using util::isnil;
41 using test::Test;
42 using lib::Literal;
44 using lib::Thread;
45 using std::unique_ptr;
46 using std::atomic_bool;
47 using std::this_thread::sleep_for;
48 using std::chrono::milliseconds;
49 
50 
51 namespace lumiera {
52 namespace test {
53 
54  namespace { // private test classes and data...
55 
56  using lib::query::extractID;
57 
60  const uint MAX_RUNNING_TIME_ms = 80;
61  const uint MIN_RUNNING_TIME_ms = 20;
62 
63  inline int
64  draw_rand_runtime()
65  {
66  return MIN_RUNNING_TIME_ms
67  + rani (MAX_RUNNING_TIME_ms - MIN_RUNNING_TIME_ms);
68  }
69 
72  const uint TICK_DURATION_ms = 5;
73 
80 
83  lumiera::Option dummyOpt (dummyArgs);
84 
86  LUMIERA_ERROR_DEFINE( TEST, "simulated failure.");
87 
88  using LERR_(LOGIC);
89  using LERR_(STATE);
90 
91 
92 
93 
100  class MockSys
101  : public lumiera::Subsys
102  {
103  const string id_;
104  const string spec_;
105 
106  atomic_bool isUp_{false};
107  atomic_bool didRun_{false};
108  atomic_bool started_{false};
109  atomic_bool termRequest_{false};
110  int running_duration_{0};
111  const int TIME_GOAL{draw_rand_runtime()};
112 
113  lib::SyncBarrier barrier_{};
114  unique_ptr<Thread> thread_{};
115 
116  bool
118  {
119  string startSpec (extractID ("start",spec_));
120  return "true" ==startSpec
121  or "fail" ==startSpec
122  or "throw"==startSpec;
123  }
124 
125 
126  bool
127  start (lumiera::Option&, Subsys::SigTerm termination) override
128  {
129  CHECK (not (isUp_ or started_ or didRun_), "attempt to start %s twice!", cStr(*this));
130 
131  string startSpec (extractID ("start",spec_));
132  CHECK (not isnil (startSpec));
133 
134  if ("true"==startSpec) //----simulate successful subsystem start
135  {
136  CHECK (not started_);
137 
138  // start »Subsystem operation« in a dedicated thread....
139  thread_.reset (new Thread{id_, &MockSys::run, this, termination});
140  barrier_.sync(); //---run-status handshake
141 
142  CHECK (started_);
143  }
144  else
145  if ("fail"==startSpec) //---not starting, incorrectly reporting success
146  return true;
147  else
148  if ("throw"==startSpec) //---starting flounders
149  throw error::Fatal("simulated failure to start the subsystem", LUMIERA_ERROR_TEST);
150 
151  return started_;
152  }
153 
154  void
155  triggerShutdown () noexcept override
156  {
157  // note: *not* locking here...
158  termRequest_ = true;
159 
160  INFO (test, "triggerShutdown() --> %s....", cStr(*this));
161  }
162 
163  bool
164  checkRunningState () noexcept override
165  {
166  // note: *not* locking here...
167  return isUp_;
168  }
169 
170 
179  void
180  run (Subsys::SigTerm termination)
181  {
182  string runSpec (extractID ("run",spec_));
183  CHECK (not isnil (runSpec));
184 
185  // run-status handshake
186  started_ = true;
187  isUp_ = ("true"==runSpec || "throw"==runSpec);
188  didRun_ = ("false"!=runSpec); // includes "fail" and "throw"
189 
190  // coordinate startup with controlling thread
191  barrier_.sync();
192 
193  if (isUp_) //-------------actually enter running state for some time
194  {
195  running_duration_ = TIME_GOAL; // prepared when creating instance
196 
197  INFO (test, "thread %s now running....", cStr(*this));
198 
199  while (not shouldTerminate())
200  {
201  sleep_for (milliseconds{TICK_DURATION_ms});
202  running_duration_ -= TICK_DURATION_ms;
203  }
204 
205  INFO (test, "thread %s about to terminate...", cStr(*this));
206  isUp_ = false;
207  }
208 
209  if ("fail" ==runSpec) return; // terminate without further notice
210  if ("true" ==runSpec) termination(0); // signal regular termination
211  if ("throw"==runSpec)
212  {
213  Error problemIndicator("simulated Problem terminating subsystem",LUMIERA_ERROR_TEST);
214  lumiera_error(); // reset error state....
215  // Note: in real life this actually
216  // would be an catched exception!
217  string problemReport (problemIndicator.what());
218  termination (&problemReport);
219  }
220 
221  }
222 
223 
224  bool
225  shouldTerminate ()
226  {
227  return termRequest_ || running_duration_ <= 0;
228  }
229 
230 
231 
232  public:
233  MockSys(Literal id, Literal spec)
234  : id_(id)
235  , spec_(spec)
236  { }
237 
238  ~MockSys() { }
239 
240  operator string () const { return "MockSys(\""+id_+"\")"; }
241 
242  bool didRun () const { return didRun_; }
243  };
244 
245 
246 
247  } // (End) test classes and data....
248 
249 
250 
251 
252 
253 
254 
255 
256 
257 
258  /**********************************************************************/
270  class SubsystemRunner_test : public Test
271  {
272 
273  virtual void
274  run (Arg)
275  {
276  seedRand();
277  singleSubsys_complete_cycle();
278  singleSubsys_start_failure();
279  singleSubsys_emegency_exit();
280 
281  dependentSubsys_complete_cycle();
282  dependentSubsys_start_failure();
283  }
284 
285 
286  void
287  singleSubsys_complete_cycle()
288  {
289  cout << "-----singleSubsys_complete_cycle-----\n";
290 
291  MockSys unit ("one", "start(true), run(true).");
292  SubsystemRunner runner(dummyOpt);
293  CHECK (not unit.isRunning());
294  CHECK (not unit.didRun());
295 
296  runner.maybeRun (unit);
297  bool emergency = runner.wait();
298 
299  CHECK (not emergency);
300  CHECK (not unit.isRunning());
301  CHECK (unit.didRun());
302  }
303 
304 
314  void
316  {
317  cout << "-----singleSubsys_start_failure-----\n";
318 
319  MockSys unit1 ("U1", "start(false), run(false).");
320  MockSys unit2 ("U2", "start(throw), run(false).");
321  MockSys unit3 ("U3", "start(fail), run(false)."); // simulates incorrect behaviour
322  MockSys unit4 ("U4", "start(true), run(fail)." ); // simulates failure immediately after start
323  SubsystemRunner runner(dummyOpt);
324 
325  runner.maybeRun (unit1); // this one doesn't start at all, which isn't considered an error
326  CHECK (not unit1.didRun());
327 
328  VERIFY_ERROR (TEST, runner.maybeRun (unit2) );
329  VERIFY_ERROR (LOGIC, runner.maybeRun (unit3) ); // incorrect behaviour trapped
330  VERIFY_ERROR (LOGIC, runner.maybeRun (unit4) ); // detected that the subsystem didn't come up
331 
332  sleep_for (milliseconds{DELAY_FOR_FLOUNDERING_THRAD_ms}); // preempt to allow unit4 to go away
333  runner.wait();
334 
335  CHECK (not unit1.isRunning());
336  CHECK (not unit2.isRunning());
337  CHECK (not unit3.isRunning());
338  CHECK (not unit4.isRunning());
339  CHECK (not unit1.didRun());
340  CHECK (not unit2.didRun());
341  CHECK (not unit3.didRun());
342  CHECK (unit4.didRun()); // ...but it failed immediately
343  }
344 
345 
346  void
347  singleSubsys_emegency_exit()
348  {
349  cout << "-----singleSubsys_emegency_exit-----\n";
350 
351  MockSys unit ("one", "start(true), run(throw).");
352  SubsystemRunner runner(dummyOpt);
353 
354  runner.maybeRun (unit);
355  bool emergency = runner.wait();
356 
357  CHECK (emergency == true); // emergency state was propagated
358  CHECK (not unit.isRunning());
359  CHECK (unit.didRun());
360  }
361 
362 
363  void
364  dependentSubsys_complete_cycle()
365  {
366  cout << "-----dependentSubsys_complete_cycle-----\n";
367 
368  MockSys unit1 ("U1", "start(true), run(true).");
369  MockSys unit2 ("U2", "start(true), run(true).");
370  MockSys unit3 ("U3", "start(true), run(true).");
371  MockSys unit4 ("U4", "start(true), run(true).");
372  unit2.depends (unit1);
373  unit4.depends (unit3);
374  unit4.depends (unit1);
375  unit3.depends (unit2);
376  SubsystemRunner runner(dummyOpt);
377 
378  runner.maybeRun (unit4);
379  CHECK (unit1.isRunning());
380  CHECK (unit2.isRunning());
381  CHECK (unit3.isRunning());
382  CHECK (unit4.isRunning());
383 
384  bool emergency = runner.wait();
385 
386  CHECK (not emergency);
387  CHECK (not unit1.isRunning());
388  CHECK (not unit2.isRunning());
389  CHECK (not unit3.isRunning());
390  CHECK (not unit4.isRunning());
391  CHECK (unit1.didRun());
392  CHECK (unit2.didRun());
393  CHECK (unit3.didRun());
394  CHECK (unit4.didRun());
395  }
396 
397 
398  void
399  dependentSubsys_start_failure()
400  {
401  cout << "-----dependentSubsys_start_failure-----\n";
402 
403  MockSys unit1 ("U1", "start(true), run(true).");
404  MockSys unit2 ("U2", "start(true), run(true).");
405  MockSys unit3 ("U3", "start(false),run(false)."); // note
406  MockSys unit4 ("U4", "start(true), run(true).");
407  unit2.depends (unit1);
408  unit4.depends (unit3);
409  unit4.depends (unit1);
410  unit3.depends (unit2);
411  SubsystemRunner runner(dummyOpt);
412 
413  VERIFY_ERROR (STATE, runner.maybeRun (unit4) ); // failure to bring up prerequisites is detected
414  CHECK ( unit1.isRunning());
415  CHECK ( unit2.isRunning());
416  CHECK (not unit3.isRunning());
417  // shutdown has been triggered for unit4, but may require some time
418 
419  bool emergency = runner.wait();
420 
421  CHECK (not emergency); // no problems with the subsystems actually running...
422  CHECK (not unit1.isRunning());
423  CHECK (not unit2.isRunning());
424  CHECK (not unit3.isRunning());
425  CHECK (not unit4.isRunning());
426  CHECK ( unit1.didRun());
427  CHECK ( unit2.didRun());
428  CHECK (not unit3.didRun());
429  // can't say for sure if unit4 actually did run
430  }
431  };
432 
433 
434 
436  LAUNCHER (SubsystemRunner_test, "function common");
437 
438 
439 
440 }} // namespace lumiera::test
bool start(lumiera::Option &, Subsys::SigTerm termination) override
attempt to bring up this subsystem up.
Dependencies and lifecycle of a partially independent Subsystem of the Application.
Definition: subsys.hpp:61
Utilities to support working with predicate queries.
const uint TICK_DURATION_ms
the "running" subsystem checks for a shutdown request every XX milliseconds
Automatically use custom string conversion in C++ stream output.
void triggerShutdown() noexcept override
initiate termination of this subsystem.
CStr cStr(std::string const &rendered)
convenience shortcut: forced conversion to c-String via string.
Definition: symbol.hpp:59
Definition: run.hpp:40
const uint MAX_RUNNING_TIME_ms
limit for the randomly selected duration of subsystem&#39;s running phase (milliseconds) ...
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:76
virtual CStr what() const noexcept override
std::exception interface : yield a diagnostic message
const uint DELAY_FOR_FLOUNDERING_THRAD_ms
due to a shortcoming of this test fixture, a floundering subsystem continues to run for a short time ...
void run(Subsys::SigTerm termination)
executes in a separate thread and simulates a "running" subsystem.
int rani(uint bound=_iBOUND())
Definition: random.hpp:135
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
Frontend for handling the Lumiera application commandline arguments.
Definition: option.hpp:68
Object Monitor based synchronisation.
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:190
string extractID(Symbol sym, const string &termString)
(preliminary) helper: instead of really parsing and evaluating the terms, just do a regular expressio...
Definition: query-util.cpp:92
Abstract Base Class for all testcases.
Definition: run.hpp:53
Marker types to indicate a literal string and a Symbol.
Implementation helper for managing execution of a collection of subsystems, which may depend on one a...
Describing dependencies and lifecycle of the application&#39;s primary parts.
Simplistic test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
bool shouldStart(lumiera::Option &) override
query application option state to determine if this subsystem should be activated.
lumiera_err lumiera_error(void)
Get and clear current error state.
Definition: error-state.c:115
front-end for handling the commandline arguments.
Convenience front-end to simplify and codify basic thread handling.
bool checkRunningState() noexcept override
whether this subsystem is actually operational.
A collection of frequently used helper functions to support unit testing.
lib::Cmdline dummyArgs("")
dummy options just to be ignored
Lumiera error handling (C++ interface).
A one time N-fold mutual synchronisation barrier.
Manage execution of the independent Subsystems of the Lumiera application.
Lumiera public interface.
Definition: advice.cpp:104
A thin convenience wrapper to simplify thread-handling.
Definition: thread.hpp:648
Abstraction of the usual int argc, int** argv-Commandline, to be able to treat it as a vector of stri...
Definition: cmdline.hpp:48
Interface and Base definition for all Lumiera Exceptions.
Definition: error.hpp:62
A N-fold synchronisation latch using yield-wait until fulfilment.
#define LUMIERA_ERROR_DEFINE(err, msg)
Definition and initialisation of an error constant.
Definition: error.h:71