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) Lumiera.org
5  2008, 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 
30 #include "lib/test/run.hpp"
31 #include "lib/test/test-helper.hpp"
32 #include "common/subsys.hpp"
34 #include "common/option.hpp"
35 
36 #include "lib/symbol.hpp"
37 #include "lib/thread.hpp"
38 #include "lib/sync-barrier.hpp"
39 #include "lib/query-util.hpp"
40 #include "lib/format-cout.hpp"
41 #include "lib/error.hpp"
42 #include "lib/util.hpp"
43 #include "lib/sync.hpp"
44 
45 #include <memory>
46 #include <atomic>
47 #include <chrono>
48 
49 using util::isnil;
50 using test::Test;
51 using lib::Literal;
53 using lib::Thread;
54 using std::unique_ptr;
55 using std::atomic_bool;
56 using std::this_thread::sleep_for;
57 using std::chrono::milliseconds;
58 
59 
60 namespace lumiera {
61 namespace test {
62 
63  namespace { // private test classes and data...
64 
65  using lib::query::extractID;
66 
69  const uint MAX_RUNNING_TIME_ms = 80;
70  const uint MIN_RUNNING_TIME_ms = 20;
71 
74  const uint TICK_DURATION_ms = 5;
75 
82 
85  lumiera::Option dummyOpt (dummyArgs);
86 
88  LUMIERA_ERROR_DEFINE( TEST, "simulated failure.");
89 
90  using LERR_(LOGIC);
91  using LERR_(STATE);
92 
93 
94 
95 
102  class MockSys
103  : public lumiera::Subsys
104  {
105  const string id_;
106  const string spec_;
107 
108  atomic_bool isUp_{false};
109  atomic_bool didRun_{false};
110  atomic_bool started_{false};
111  atomic_bool termRequest_{false};
112  int running_duration_{0};
113 
114  lib::SyncBarrier barrier_{};
115  unique_ptr<Thread> thread_{};
116 
117  bool
119  {
120  string startSpec (extractID ("start",spec_));
121  return "true" ==startSpec
122  or "fail" ==startSpec
123  or "throw"==startSpec;
124  }
125 
126 
127  bool
128  start (lumiera::Option&, Subsys::SigTerm termination) override
129  {
130  CHECK (not (isUp_ or started_ or didRun_), "attempt to start %s twice!", cStr(*this));
131 
132  string startSpec (extractID ("start",spec_));
133  CHECK (not isnil (startSpec));
134 
135  if ("true"==startSpec) //----simulate successful subsystem start
136  {
137  CHECK (not started_);
138 
139  // start »Subsystem operation« in a dedicated thread....
140  thread_.reset (new Thread{id_, &MockSys::run, this, termination});
141  barrier_.sync(); //---run-status handshake
142 
143  CHECK (started_);
144  }
145  else
146  if ("fail"==startSpec) //---not starting, incorrectly reporting success
147  return true;
148  else
149  if ("throw"==startSpec) //---starting flounders
150  throw error::Fatal("simulated failure to start the subsystem", LUMIERA_ERROR_TEST);
151 
152  return started_;
153  }
154 
155  void
156  triggerShutdown () noexcept override
157  {
158  // note: *not* locking here...
159  termRequest_ = true;
160 
161  INFO (test, "triggerShutdown() --> %s....", cStr(*this));
162  }
163 
164  bool
165  checkRunningState () noexcept override
166  {
167  // note: *not* locking here...
168  return isUp_;
169  }
170 
171 
180  void
181  run (Subsys::SigTerm termination)
182  {
183  string runSpec (extractID ("run",spec_));
184  CHECK (not isnil (runSpec));
185 
186  // run-status handshake
187  started_ = true;
188  isUp_ = ("true"==runSpec || "throw"==runSpec);
189  didRun_ = ("false"!=runSpec); // includes "fail" and "throw"
190 
191  // coordinate startup with controlling thread
192  barrier_.sync();
193 
194  if (isUp_) //-------------actually enter running state for some time
195  {
196  running_duration_ = MIN_RUNNING_TIME_ms;
197  running_duration_ += (rand() % (MAX_RUNNING_TIME_ms - MIN_RUNNING_TIME_ms));
198 
199  INFO (test, "thread %s now running....", cStr(*this));
200 
201  while (not shouldTerminate())
202  {
203  sleep_for (milliseconds{TICK_DURATION_ms});
204  running_duration_ -= TICK_DURATION_ms;
205  }
206 
207  INFO (test, "thread %s about to terminate...", cStr(*this));
208  isUp_ = false;
209  }
210 
211  if ("fail" ==runSpec) return; // terminate without further notice
212  if ("true" ==runSpec) termination(0); // signal regular termination
213  if ("throw"==runSpec)
214  {
215  Error problemIndicator("simulated Problem terminating subsystem",LUMIERA_ERROR_TEST);
216  lumiera_error(); // reset error state....
217  // Note: in real life this actually
218  // would be an catched exception!
219  string problemReport (problemIndicator.what());
220  termination (&problemReport);
221  }
222 
223  }
224 
225 
226  bool
227  shouldTerminate ()
228  {
229  return termRequest_ || running_duration_ <= 0;
230  }
231 
232 
233 
234  public:
235  MockSys(Literal id, Literal spec)
236  : id_(id)
237  , spec_(spec)
238  { }
239 
240  ~MockSys() { }
241 
242  operator string () const { return "MockSys(\""+id_+"\")"; }
243 
244  bool didRun () const { return didRun_; }
245  };
246 
247 
248 
249  } // (End) test classes and data....
250 
251 
252 
253 
254 
255 
256 
257 
258 
259 
260  /**********************************************************************/
272  class SubsystemRunner_test : public Test
273  {
274 
275  virtual void
276  run (Arg)
277  {
278  singleSubsys_complete_cycle();
279  singleSubsys_start_failure();
280  singleSubsys_emegency_exit();
281 
282  dependentSubsys_complete_cycle();
283  dependentSubsys_start_failure();
284  }
285 
286 
287  void
288  singleSubsys_complete_cycle()
289  {
290  cout << "-----singleSubsys_complete_cycle-----\n";
291 
292  MockSys unit ("one", "start(true), run(true).");
293  SubsystemRunner runner(dummyOpt);
294  CHECK (not unit.isRunning());
295  CHECK (not unit.didRun());
296 
297  runner.maybeRun (unit);
298  bool emergency = runner.wait();
299 
300  CHECK (not emergency);
301  CHECK (not unit.isRunning());
302  CHECK (unit.didRun());
303  }
304 
305 
315  void
317  {
318  cout << "-----singleSubsys_start_failure-----\n";
319 
320  MockSys unit1 ("U1", "start(false), run(false).");
321  MockSys unit2 ("U2", "start(throw), run(false).");
322  MockSys unit3 ("U3", "start(fail), run(false)."); // simulates incorrect behaviour
323  MockSys unit4 ("U4", "start(true), run(fail)." ); // simulates failure immediately after start
324  SubsystemRunner runner(dummyOpt);
325 
326  runner.maybeRun (unit1); // this one doesn't start at all, which isn't considered an error
327  CHECK (not unit1.didRun());
328 
329  VERIFY_ERROR (TEST, runner.maybeRun (unit2) );
330  VERIFY_ERROR (LOGIC, runner.maybeRun (unit3) ); // incorrect behaviour trapped
331  VERIFY_ERROR (LOGIC, runner.maybeRun (unit4) ); // detected that the subsystem didn't come up
332 
333  sleep_for (milliseconds{DELAY_FOR_FLOUNDERING_THRAD_ms}); // preempt to allow unit4 to go away
334  runner.wait();
335 
336  CHECK (not unit1.isRunning());
337  CHECK (not unit2.isRunning());
338  CHECK (not unit3.isRunning());
339  CHECK (not unit4.isRunning());
340  CHECK (not unit1.didRun());
341  CHECK (not unit2.didRun());
342  CHECK (not unit3.didRun());
343  CHECK (unit4.didRun()); // ...but it failed immediately
344  }
345 
346 
347  void
348  singleSubsys_emegency_exit()
349  {
350  cout << "-----singleSubsys_emegency_exit-----\n";
351 
352  MockSys unit ("one", "start(true), run(throw).");
353  SubsystemRunner runner(dummyOpt);
354 
355  runner.maybeRun (unit);
356  bool emergency = runner.wait();
357 
358  CHECK (emergency == true); // emergency state was propagated
359  CHECK (not unit.isRunning());
360  CHECK (unit.didRun());
361  }
362 
363 
364  void
365  dependentSubsys_complete_cycle()
366  {
367  cout << "-----dependentSubsys_complete_cycle-----\n";
368 
369  MockSys unit1 ("U1", "start(true), run(true).");
370  MockSys unit2 ("U2", "start(true), run(true).");
371  MockSys unit3 ("U3", "start(true), run(true).");
372  MockSys unit4 ("U4", "start(true), run(true).");
373  unit2.depends (unit1);
374  unit4.depends (unit3);
375  unit4.depends (unit1);
376  unit3.depends (unit2);
377  SubsystemRunner runner(dummyOpt);
378 
379  runner.maybeRun (unit4);
380  CHECK (unit1.isRunning());
381  CHECK (unit2.isRunning());
382  CHECK (unit3.isRunning());
383  CHECK (unit4.isRunning());
384 
385  bool emergency = runner.wait();
386 
387  CHECK (not emergency);
388  CHECK (not unit1.isRunning());
389  CHECK (not unit2.isRunning());
390  CHECK (not unit3.isRunning());
391  CHECK (not unit4.isRunning());
392  CHECK (unit1.didRun());
393  CHECK (unit2.didRun());
394  CHECK (unit3.didRun());
395  CHECK (unit4.didRun());
396  }
397 
398 
399  void
400  dependentSubsys_start_failure()
401  {
402  cout << "-----dependentSubsys_start_failure-----\n";
403 
404  MockSys unit1 ("U1", "start(true), run(true).");
405  MockSys unit2 ("U2", "start(true), run(true).");
406  MockSys unit3 ("U3", "start(false),run(false)."); // note
407  MockSys unit4 ("U4", "start(true), run(true).");
408  unit2.depends (unit1);
409  unit4.depends (unit3);
410  unit4.depends (unit1);
411  unit3.depends (unit2);
412  SubsystemRunner runner(dummyOpt);
413 
414  VERIFY_ERROR (STATE, runner.maybeRun (unit4) ); // failure to bring up prerequisites is detected
415  CHECK ( unit1.isRunning());
416  CHECK ( unit2.isRunning());
417  CHECK (not unit3.isRunning());
418  // shutdown has been triggered for unit4, but may require some time
419 
420  bool emergency = runner.wait();
421 
422  CHECK (not emergency); // no problems with the subsystems actually running...
423  CHECK (not unit1.isRunning());
424  CHECK (not unit2.isRunning());
425  CHECK (not unit3.isRunning());
426  CHECK (not unit4.isRunning());
427  CHECK ( unit1.didRun());
428  CHECK ( unit2.didRun());
429  CHECK (not unit3.didRun());
430  // can't say for sure if unit4 actually did run
431  }
432  };
433 
434 
435 
437  LAUNCHER (SubsystemRunner_test, "function common");
438 
439 
440 
441 }} // 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:70
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:68
Definition: run.hpp:49
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:85
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.
#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:77
Object Monitor based synchronisation.
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:199
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:101
Abstract Base Class for all testcases.
Definition: run.hpp:62
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.
Simple 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:124
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:113
A thin convenience wrapper to simplify thread-handling.
Definition: thread.hpp:656
Abstraction of the usual int argc, int** argv-Commandline, to be able to treat it as a vector of stri...
Definition: cmdline.hpp:57
Interface and Base definition for all Lumiera Exceptions.
Definition: error.hpp:71
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:80