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