Lumiera  0.pre.03
»edit your freedom«
session-command-function-test.cpp
Go to the documentation of this file.
1 /*
2  SessionCommandFunction(Test) - function test of command dispatch via SessionCommand facade
3 
4  Copyright (C)
5  2017, 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 
14 
67 #include "lib/test/run.hpp"
68 #include "lib/test/test-helper.hpp"
69 extern "C" {
71 }
72 
76 #include "lib/typed-counter.hpp"
77 #include "lib/format-string.hpp"
78 #include "lib/sync-barrier.hpp"
79 #include "lib/thread.hpp"
80 #include "lib/symbol.hpp"
81 #include "lib/util.hpp"
82 
83 #include <boost/lexical_cast.hpp>
84 #include <chrono>
85 #include <string>
86 #include <vector>
87 #include <deque>
88 
89 
90 namespace steam {
91 namespace control {
92 namespace test {
93 
94 
95  using boost::lexical_cast;
96  using std::this_thread::sleep_for;
97  using std::chrono::microseconds;
98  using namespace std::chrono_literals;
100  using lib::test::randTime;
101  using lib::diff::GenNode;
102  using lib::diff::Rec;
103  using lib::time::Time;
104  using lib::time::TimeVar;
105  using lib::time::Duration;
106  using lib::time::Offset;
107  using lib::time::FSecs;
108  using lib::FamilyMember;
109  using lib::SyncBarrier;
110  using lib::Symbol;
111  using util::_Fmt;
112  using util::isnil;
113  using std::string;
114  using std::vector;
115  using std::deque;
116  using std::rand;
117 
118 
119  namespace { // test fixture...
120 
121  /* === parameters for multi-threaded stress test === */
122 
124  uint NUM_INVOC_PER_THRED = 10;
125  uint MAX_RAND_DELAY_us = 50;
126 
127  void
128  maybeOverride (uint& configSetting, Arg cmdline, uint paramNr)
129  {
130  if (paramNr < cmdline.size())
131  configSetting = lexical_cast<uint>(cmdline[paramNr]);
132  }
133 
134 
135  /* === mock operation to be dispatched as command === */
136 
137  const Symbol COMMAND_ID{"test.dispatch.function.command"};
138  const Symbol COMMAND_I1{"test.dispatch.function.command.instance-1"};
139  const Symbol COMMAND_I2{"test.dispatch.function.command.instance-2"};
140 
141  TimeVar testCommandState = randTime();
142 
143  void
144  operate (Duration dur, Offset offset, int factor)
145  {
146  testCommandState += Offset(dur) + offset*factor;
147  }
148 
149  Time
150  capture (Duration, Offset, int)
151  {
152  return testCommandState;
153  }
154 
155  void
156  undoIt (Duration, Offset, int, Time oldState)
157  {
158  testCommandState = oldState;
159  }
160 
161 
162  }//(End) test fixture
163 
164 
165 #define __DELAY__ sleep_for (20ms);
166 
167 
168 
169 
170 
171  /******************************************************************************************/
186  class SessionCommandFunction_test : public Test
187  {
188 
189  //------------------FIXTURE
190  public:
192  {
193  CommandDef (COMMAND_ID)
194  .operation (operate)
195  .captureUndo (capture)
196  .undoOperation (undoIt)
197  ;
198  Command(COMMAND_ID).storeDef(COMMAND_I1);
199  Command(COMMAND_ID).storeDef(COMMAND_I2);
200  }
202  {
203  Command::remove (COMMAND_ID);
204  Command::remove (COMMAND_I1);
205  Command::remove (COMMAND_I2);
206  }
207  //-------------(End)FIXTURE
208 
209 
210  virtual void
211  run (Arg args_for_stresstest)
212  {
215 
216  startDispatcher();
217  perform_simpleInvocation();
218  perform_messageInvocation();
219  perform_massivelyParallel(args_for_stresstest);
220  stopDispatcher();
221 
222  lumiera_interfaceregistry_destroy();
223  }
224 
225 
232  void
234  {
235  CHECK (not SteamDispatcher::instance().isRunning());
236 
237  SteamDispatcher::instance().start ([&] (string* problemMessage)
238  {
239  CHECK (isnil (*problemMessage));
240  thread_has_ended = true;
241  });
242 
243  CHECK (SteamDispatcher::instance().isRunning());
244  CHECK (not thread_has_ended);
245  }
246  bool thread_has_ended{false};
247 
248 
250  void
252  {
253  CHECK (SteamDispatcher::instance().isRunning());
254  SteamDispatcher::instance().requestStop();
255 
256  __DELAY__
257  CHECK (not SteamDispatcher::instance().isRunning());
258  CHECK (thread_has_ended);
259  }
260 
261 
263  void
265  {
266  string cmdID {COMMAND_I1};
267  Rec arguments {Duration(15,10), Time(500,0), -1};
268 
269  CHECK (not Command(COMMAND_I1).canExec());
270  SessionCommand::facade().bindArg (cmdID, arguments);
271  CHECK (Command(COMMAND_I1).canExec());
272 
273 
274  Time prevState = testCommandState;
275  SessionCommand::facade().invoke(cmdID);
276 
277  __DELAY__
278  CHECK (testCommandState - prevState == Time(0, 1)); // execution added 1500ms -1*500ms == 1sec
279  }
280 
281 
282 
290  void
292  {
293  // this happens within some tangible UI element (widget / controller)
294  GenNode commandMsg{string(COMMAND_I2), Rec{Duration(25,10), Time(500,0), -2}};
295  CHECK (commandMsg.idi.getSym() == string{COMMAND_I2});
296  CHECK (not Command::canExec(COMMAND_I2));
297  Time prevState = testCommandState;
298 
299  // this happens, when CoreService receives command messages from UI-Bus
300  SessionCommand::facade().trigger (commandMsg.idi.getSym(), commandMsg.data.get<Rec>());
301 
302  __DELAY__
303  CHECK (testCommandState - prevState == Time(FSecs(3,2))); // execution added 2500ms -2*500ms == 1.5sec
304  }
305 
306 
307 
315  void
316  perform_massivelyParallel (Arg args_for_stresstest)
317  {
318  seedRand();
319  maybeOverride (NUM_THREADS_DEFAULT, args_for_stresstest, 1);
320  maybeOverride (NUM_INVOC_PER_THRED, args_for_stresstest, 2);
321  maybeOverride (MAX_RAND_DELAY_us, args_for_stresstest, 3);
322 
323 
324  // we'll run several instances of the following thread....
325  class InvocationProducer
327  {
328  SyncBarrier& barrier_;
330  vector<string> cmdIDs_;
331  lib::Random random_;
332 
334 
335  Symbol
336  cmdID (uint j)
337  {
338  cmdIDs_.push_back (_Fmt("%s.thread-%02d.%d") % COMMAND_ID % id_ % j);
339  return cStr(cmdIDs_.back());
340  }
341 
342 
343  public:
344  InvocationProducer (SyncBarrier& trigger)
345  : barrier_{trigger}
346  , random_{defaultGen}
347  , thread_{"producer", [&]{ fabricateCommands(); }}
348  { }
349 
350  ~InvocationProducer()
351  {
352  thread_.join().maybeThrow();
353  for (auto& id : cmdIDs_)
354  Command::remove (cStr(id));
355  }
356 
357  private:
358  void
359  fabricateCommands()
360  {
361  barrier_.sync(); // barrier to unleash all threads together
362 
363  for (uint j=0; j<NUM_INVOC_PER_THRED; ++j)
364  {
365  auto cmd = Command(COMMAND_ID).storeDef(cmdID(j));
366 
367  __randomDelay();
368  sendCommandMessage (GenNode{string{cmd.getID()}, Rec{Duration(7*id_, 2), Time(500,0), -int(j)}});
369  }
370  }
371 
372  static void
373  sendCommandMessage (GenNode msg)
374  {
375  SessionCommand::facade().trigger (msg.idi.getSym(), msg.data.get<Rec>());
376  }
377 
378  void
379  __randomDelay()
380  {
381  if (not MAX_RAND_DELAY_us) return;
382  sleep_for (microseconds (1 + random_.i(MAX_RAND_DELAY_us))); // random delay varying in steps of 1µs
383  }
384  };
385 
386  /* == controlling code in main thread == */
387  Time prevState = testCommandState;
388 
389  FSecs expectedOffset{0};
390  for (uint i=0; i<NUM_THREADS_DEFAULT; ++i)
391  for (uint j=0; j<NUM_INVOC_PER_THRED; ++j)
392  expectedOffset += FSecs(i*7,2) - FSecs(j,2);
393 
394  // fire up several threads to issue commands in parallel...
395  SyncBarrier trigger{NUM_THREADS_DEFAULT + 1};
396  deque<InvocationProducer> producerThreads;
397  for (uint i=0; i<NUM_THREADS_DEFAULT; ++i)
398  producerThreads.emplace_back (trigger);
399 
400  // start concurrent execution
401  trigger.sync();
402 
403  // give the producer threads some head start...
404  sleep_for (microseconds (MAX_RAND_DELAY_us * NUM_INVOC_PER_THRED / 2));
405  __DELAY__
406 
407  // stop the dispatching to cause the queue to build up...
408  SteamDispatcher::instance().deactivate();
409  SteamDispatcher::instance().awaitDeactivation();
410 
411  __DELAY__
412  SteamDispatcher::instance().activate();
413 
414  __DELAY__
415  while (not SteamDispatcher::instance().empty());
416 
417  __DELAY__
418  CHECK (testCommandState - prevState == Time(expectedOffset));
419  }// Note: leaving this scope blocks for joining all producer threads
420  };
421 
422 
424  LAUNCHER (SessionCommandFunction_test, "function controller");
425 
426 
427 }}} // namespace steam::control::test
Helper class used solely for defining a Command-Object.
a mutable time value, behaving like a plain number, allowing copy and re-accessing ...
Definition: timevalue.hpp:232
static lib::Depend< SteamDispatcher > instance
storage for Singleton access
Variant of the standard case, requiring to wait and join() on the termination of this thread...
Definition: thread.hpp:668
Creating series of type-based contexts.
Global access point to invoke commands and cause edit operations within the Session.
CStr cStr(std::string const &rendered)
convenience shortcut: forced conversion to c-String via string.
Definition: symbol.hpp:59
Definition: run.hpp:40
int i(uint bound=_iBOUND())
drop-in replacement for rand() % bound
Definition: random.hpp:205
Any copy and copy construction prohibited.
Definition: nocopy.hpp:37
Front-end for printf-style string template interpolation.
void throwOnError()
Check the lumiera error state, which maybe was set by C-code.
Definition: error.hpp:233
Dispatch and execute mutation operations on the High-level model.
Command storeDef(Symbol newCmdID) const
create a clone definition
Definition: command.cpp:183
Steam-Layer implementation namespace root.
A front-end for using printf-style formatting.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
static lib::Depend< SessionCommand > facade
static storage for the facade access front-end
Token or Atom with distinct identity.
Definition: symbol.hpp:117
Marker types to indicate a literal string and a Symbol.
Simplistic test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
lib::Result< RES > join()
put the caller into a blocking wait until this thread has terminated
Definition: thread.hpp:685
Convenience front-end to simplify and codify basic thread handling.
boost::rational< int64_t > FSecs
rational representation of fractional seconds
Definition: timevalue.hpp:220
A collection of frequently used helper functions to support unit testing.
void lumiera_interfaceregistry_init(void)
Initialise the interface registry.
Global registry for interfaces (extension points).
A one time N-fold mutual synchronisation barrier.
Offset measures a distance in time.
Definition: timevalue.hpp:358
Handle object representing a single Command instance to be used by client code.
Definition: command.hpp:115
Duration is the internal Lumiera time metric.
Definition: timevalue.hpp:468
Utility to produce member IDs for objects belonging to a "Family", as defined by a distinguishing typ...
lib::time::Time randTime()
create a random but not insane Time value between 1s ...
Actually defining a command and binding it to execution parameters.
object-like record of data.
Definition: record.hpp:141
Random defaultGen
a global default RandomSequencer for mundane purposes
Definition: random.cpp:70
Major public Interface to the Session subsystem of Lumiera GUI.
generic data element node within a tree
Definition: gen-node.hpp:222
A N-fold synchronisation latch using yield-wait until fulfilment.