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) Lumiera.org
5  2017, 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 
76 #include "lib/test/run.hpp"
77 #include "lib/test/test-helper.hpp"
78 extern "C" {
80 }
81 
85 #include "lib/typed-counter.hpp"
86 #include "lib/format-string.hpp"
87 #include "lib/sync-barrier.hpp"
88 #include "lib/thread.hpp"
89 #include "lib/symbol.hpp"
90 #include "lib/util.hpp"
91 
92 #include <boost/lexical_cast.hpp>
93 #include <chrono>
94 #include <string>
95 #include <vector>
96 #include <deque>
97 
98 
99 namespace steam {
100 namespace control {
101 namespace test {
102 
103 
104  using boost::lexical_cast;
105  using std::this_thread::sleep_for;
106  using std::chrono::microseconds;
107  using namespace std::chrono_literals;
109  using lib::test::randTime;
110  using lib::diff::GenNode;
111  using lib::diff::Rec;
112  using lib::time::Time;
113  using lib::time::TimeVar;
114  using lib::time::Duration;
115  using lib::time::Offset;
116  using lib::time::FSecs;
117  using lib::FamilyMember;
118  using lib::SyncBarrier;
119  using lib::Symbol;
120  using util::_Fmt;
121  using util::isnil;
122  using std::string;
123  using std::vector;
124  using std::deque;
125  using std::rand;
126 
127 
128  namespace { // test fixture...
129 
130  /* === parameters for multi-threaded stress test === */
131 
133  uint NUM_INVOC_PER_THRED = 10;
134  uint MAX_RAND_DELAY_us = 50;
135 
136  void
137  maybeOverride (uint& configSetting, Arg cmdline, uint paramNr)
138  {
139  if (paramNr < cmdline.size())
140  configSetting = lexical_cast<uint>(cmdline[paramNr]);
141  }
142 
143 
144  /* === mock operation to be dispatched as command === */
145 
146  const Symbol COMMAND_ID{"test.dispatch.function.command"};
147  const Symbol COMMAND_I1{"test.dispatch.function.command.instance-1"};
148  const Symbol COMMAND_I2{"test.dispatch.function.command.instance-2"};
149 
150  TimeVar testCommandState = randTime();
151 
152  void
153  operate (Duration dur, Offset offset, int factor)
154  {
155  testCommandState += Offset(dur) + offset*factor;
156  }
157 
158  Time
159  capture (Duration, Offset, int)
160  {
161  return testCommandState;
162  }
163 
164  void
165  undoIt (Duration, Offset, int, Time oldState)
166  {
167  testCommandState = oldState;
168  }
169 
170 
171  }//(End) test fixture
172 
173 
174 #define __DELAY__ sleep_for (20ms);
175 
176 
177 
178 
179 
180  /******************************************************************************************/
195  class SessionCommandFunction_test : public Test
196  {
197 
198  //------------------FIXTURE
199  public:
201  {
202  CommandDef (COMMAND_ID)
203  .operation (operate)
204  .captureUndo (capture)
205  .undoOperation (undoIt)
206  ;
207  Command(COMMAND_ID).storeDef(COMMAND_I1);
208  Command(COMMAND_ID).storeDef(COMMAND_I2);
209  }
211  {
212  Command::remove (COMMAND_ID);
213  Command::remove (COMMAND_I1);
214  Command::remove (COMMAND_I2);
215  }
216  //-------------(End)FIXTURE
217 
218 
219  virtual void
220  run (Arg args_for_stresstest)
221  {
224 
225  startDispatcher();
226  perform_simpleInvocation();
227  perform_messageInvocation();
228  perform_massivelyParallel(args_for_stresstest);
229  stopDispatcher();
230 
231  lumiera_interfaceregistry_destroy();
232  }
233 
234 
241  void
243  {
244  CHECK (not SteamDispatcher::instance().isRunning());
245 
246  SteamDispatcher::instance().start ([&] (string* problemMessage)
247  {
248  CHECK (isnil (*problemMessage));
249  thread_has_ended = true;
250  });
251 
252  CHECK (SteamDispatcher::instance().isRunning());
253  CHECK (not thread_has_ended);
254  }
255  bool thread_has_ended{false};
256 
257 
259  void
261  {
262  CHECK (SteamDispatcher::instance().isRunning());
263  SteamDispatcher::instance().requestStop();
264 
265  __DELAY__
266  CHECK (not SteamDispatcher::instance().isRunning());
267  CHECK (thread_has_ended);
268  }
269 
270 
272  void
274  {
275  string cmdID {COMMAND_I1};
276  Rec arguments {Duration(15,10), Time(500,0), -1};
277 
278  CHECK (not Command(COMMAND_I1).canExec());
279  SessionCommand::facade().bindArg (cmdID, arguments);
280  CHECK (Command(COMMAND_I1).canExec());
281 
282 
283  Time prevState = testCommandState;
284  SessionCommand::facade().invoke(cmdID);
285 
286  __DELAY__
287  CHECK (testCommandState - prevState == Time(0, 1)); // execution added 1500ms -1*500ms == 1sec
288  }
289 
290 
291 
299  void
301  {
302  // this happens within some tangible UI element (widget / controller)
303  GenNode commandMsg{string(COMMAND_I2), Rec{Duration(25,10), Time(500,0), -2}};
304  CHECK (commandMsg.idi.getSym() == string{COMMAND_I2});
305  CHECK (not Command::canExec(COMMAND_I2));
306  Time prevState = testCommandState;
307 
308  // this happens, when CoreService receives command messages from UI-Bus
309  SessionCommand::facade().trigger (commandMsg.idi.getSym(), commandMsg.data.get<Rec>());
310 
311  __DELAY__
312  CHECK (testCommandState - prevState == Time(FSecs(3,2))); // execution added 2500ms -2*500ms == 1.5sec
313  }
314 
315 
316 
324  void
325  perform_massivelyParallel(Arg args_for_stresstest)
326  {
327  maybeOverride(NUM_THREADS_DEFAULT, args_for_stresstest, 1);
328  maybeOverride(NUM_INVOC_PER_THRED, args_for_stresstest, 2);
329  maybeOverride(MAX_RAND_DELAY_us, args_for_stresstest, 3);
330 
331 
332  // we'll run several instances of the following thread....
333  class InvocationProducer
335  {
336  SyncBarrier& barrier_;
338  vector<string> cmdIDs_;
339 
341 
342  Symbol
343  cmdID(uint j)
344  {
345  cmdIDs_.push_back (_Fmt("%s.thread-%02d.%d") % COMMAND_ID % id_ % j);
346  return cStr(cmdIDs_.back());
347  }
348 
349 
350  public:
351  InvocationProducer (SyncBarrier& trigger)
352  : barrier_{trigger}
353  , thread_{"producer", [&]{ fabricateCommands(); }}
354  { }
355 
356  ~InvocationProducer()
357  {
358  thread_.join().maybeThrow();
359  for (auto& id : cmdIDs_)
360  Command::remove (cStr(id));
361  }
362 
363  private:
364  void
365  fabricateCommands()
366  {
367  barrier_.sync(); // barrier to unleash all threads together
368 
369  for (uint j=0; j<NUM_INVOC_PER_THRED; ++j)
370  {
371  auto cmd = Command(COMMAND_ID).storeDef(cmdID(j));
372 
373  __randomDelay();
374  sendCommandMessage (GenNode{string{cmd.getID()}, Rec{Duration(7*id_, 2), Time(500,0), -int(j)}});
375  }
376  }
377 
378  static void
379  sendCommandMessage(GenNode msg)
380  {
381  SessionCommand::facade().trigger (msg.idi.getSym(), msg.data.get<Rec>());
382  }
383 
384  static void
385  __randomDelay()
386  {
387  if (not MAX_RAND_DELAY_us) return;
388  sleep_for (microseconds (1 + rand() % MAX_RAND_DELAY_us)); // random delay varying in steps of 1µs
389  }
390  };
391 
392  /* == controlling code in main thread == */
393  Time prevState = testCommandState;
394 
395  FSecs expectedOffset{0};
396  for (uint i=0; i<NUM_THREADS_DEFAULT; ++i)
397  for (uint j=0; j<NUM_INVOC_PER_THRED; ++j)
398  expectedOffset += FSecs(i*7,2) - FSecs(j,2);
399 
400  // fire up several threads to issue commands in parallel...
401  SyncBarrier trigger{NUM_THREADS_DEFAULT + 1};
402  deque<InvocationProducer> producerThreads;
403  for (uint i=0; i<NUM_THREADS_DEFAULT; ++i)
404  producerThreads.emplace_back (trigger);
405 
406  // start concurrent execution
407  trigger.sync();
408 
409  // give the producer threads some head start...
410  sleep_for (microseconds (MAX_RAND_DELAY_us * NUM_INVOC_PER_THRED / 2));
411  __DELAY__
412 
413  // stop the dispatching to cause the queue to build up...
414  SteamDispatcher::instance().deactivate();
415  SteamDispatcher::instance().awaitDeactivation();
416 
417  __DELAY__
418  SteamDispatcher::instance().activate();
419 
420  __DELAY__
421  while (not SteamDispatcher::instance().empty());
422 
423  __DELAY__
424  CHECK (testCommandState - prevState == Time(expectedOffset));
425  }// Note: leaving this scope blocks for joining all producer threads
426  };
427 
428 
430  LAUNCHER (SessionCommandFunction_test, "function controller");
431 
432 
433 }}} // 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:241
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:676
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:68
Definition: run.hpp:49
Any copy and copy construction prohibited.
Definition: nocopy.hpp:46
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:242
Dispatch and execute mutation operations on the High-level model.
Command storeDef(Symbol newCmdID) const
create a clone definition
Definition: command.cpp:192
Steam-Layer implementation namespace root.
A front-end for using printf-style formatting.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:308
static lib::Depend< SessionCommand > facade
static storage for the facade access front-end
Token or Atom with distinct identity.
Definition: symbol.hpp:126
Marker types to indicate a literal string and a Symbol.
Simple 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:693
Convenience front-end to simplify and codify basic thread handling.
boost::rational< int64_t > FSecs
rational representation of fractional seconds
Definition: timevalue.hpp:229
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:367
Handle object representing a single Command instance to be used by client code.
Definition: command.hpp:124
Duration is the internal Lumiera time metric.
Definition: timevalue.hpp:477
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:150
Major public Interface to the Session subsystem of Lumiera GUI.
generic data element node within a tree
Definition: gen-node.hpp:231
A N-fold synchronisation latch using yield-wait until fulfilment.