Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
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"
69extern "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
90namespace steam {
91namespace control {
92namespace 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;
101 using lib::diff::GenNode;
102 using lib::diff::Rec;
103 using lib::time::Time;
104 using lib::time::TimeVar;
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
126
127 void
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
142
143 void
144 operate (Duration dur, Offset offset, int factor)
145 {
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 {
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
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());
245 }
246 bool thread_has_ended{false};
247
248
250 void
252 {
253 CHECK (SteamDispatcher::instance().isRunning());
254 SteamDispatcher::instance().requestStop();
255
257 CHECK (not SteamDispatcher::instance().isRunning());
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
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
303 CHECK (testCommandState - prevState == Time(FSecs(3,2))); // execution added 2500ms -2*500ms == 1.5sec
304 }
305
306
307
315 void
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....
327 {
328 SyncBarrier& barrier_;
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:
345 : barrier_{trigger}
346 , random_{defaultGen}
347 , thread_{"producer", [&]{ fabricateCommands(); }}
348 { }
349
351 {
352 thread_.join().maybeThrow();
353 for (auto& id : cmdIDs_)
354 Command::remove (cStr(id));
355 }
356
357 private:
358 void
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
368 sendCommandMessage (GenNode{string{cmd.getID()}, Rec{Duration(7*id_, 2), Time(500,0), -int(j)}});
369 }
370 }
371
372 static void
374 {
375 SessionCommand::facade().trigger (msg.idi.getSym(), msg.data.get<Rec>());
376 }
377
378 void
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};
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));
406
407 // stop the dispatching to cause the queue to build up...
408 SteamDispatcher::instance().deactivate();
409 SteamDispatcher::instance().awaitDeactivation();
410
412 SteamDispatcher::instance().activate();
413
415 while (not SteamDispatcher::instance().empty());
416
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
Utility to produce member IDs for objects belonging to a "Family", as defined by a distinguishing typ...
Token or Atom with distinct identity.
Definition symbol.hpp:120
A one time N-fold mutual synchronisation barrier.
Variant of the standard case, requiring to wait and join() on the termination of this thread.
Definition thread.hpp:670
lib::Result< RES > join()
put the caller into a blocking wait until this thread has terminated
Definition thread.hpp:685
object-like record of data.
Definition record.hpp:142
Duration is the internal Lumiera time metric.
Offset measures a distance in time.
a mutable time value, behaving like a plain number, allowing copy and re-accessing
Lumiera's internal time value datatype.
Helper class used solely for defining a Command-Object.
auto operation(FUN operation_to_define)
Handle object representing a single Command instance to be used by client code.
Definition command.hpp:120
static bool remove(Symbol cmdID)
Definition command.cpp:241
Command storeDef(Symbol newCmdID) const
create a clone definition
Definition command.cpp:183
Global access point to invoke commands and cause edit operations within the Session.
static lib::Depend< SessionCommand > facade
static storage for the facade access front-end
static lib::Depend< SteamDispatcher > instance
storage for Singleton access
Any copy and copy construction prohibited.
Definition nocopy.hpp:38
A front-end for using printf-style formatting.
Actually defining a command and binding it to execution parameters.
#define __DELAY__
Front-end for printf-style string template interpolation.
unsigned int uint
Definition integral.hpp:29
void lumiera_interfaceregistry_init(void)
Initialise the interface registry.
void lumiera_interfaceregistry_destroy(void)
Global registry for interfaces (extension points).
enable_if_c< Cond::value, T >::type enable_if
SFINAE helper to control the visibility of specialisations and overloads.
Definition meta/util.hpp:87
lib::time::Time randTime()
create a random but not insane Time value between 1s ... 10min + 500ms
boost::rational< int64_t > FSecs
rational representation of fractional seconds
void throwOnError()
Check the lumiera error state, which maybe was set by C-code.
Definition error.hpp:234
Steam-Layer implementation namespace root.
Test runner and basic definitions for tests.
bool isnil(lib::time::Duration const &dur)
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
Major public Interface to the Session subsystem of Lumiera GUI.
Dispatch and execute mutation operations on the High-level model.
generic data element node within a tree
Definition gen-node.hpp:224
Marker types to indicate a literal string and a Symbol.
CStr cStr(std::string const &rendered)
convenience shortcut: forced conversion to c-String via string.
Definition symbol.hpp:60
A N-fold synchronisation latch using yield-wait until fulfilment.
A collection of frequently used helper functions to support unit testing.
Convenience front-end to simplify and codify basic thread handling.
Creating series of type-based contexts.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...