Lumiera  0.pre.03
»edit your freedom«
bus-term-test.cpp
Go to the documentation of this file.
1 /*
2  BusTerm(Test) - cover the building block of the UI-Bus
3 
4  Copyright (C)
5  2015, 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 
19 #include "lib/test/run.hpp"
20 #include "lib/test/test-helper.hpp"
21 #include "lib/thread.hpp"
22 #include "lib/sync.hpp"
23 #include "lib/sync-classlock.hpp"
24 #include "include/ui-protocol.hpp"
25 #include "stage/ctrl/bus-term.hpp"
28 #include "test/test-nexus.hpp"
29 #include "test/mock-elm.hpp"
30 #include "lib/diff/gen-node.hpp"
32 #include "lib/idi/entry-id.hpp"
33 #include "lib/iter-adapter-stl.hpp"
34 #include "lib/iter-stack.hpp"
35 #include "lib/call-queue.hpp"
36 #include "lib/format-string.hpp"
37 #include "lib/format-cout.hpp"
38 #include "lib/time/timevalue.hpp"
39 #include "lib/luid.h"
40 #include "lib/util.hpp"
41 
42 #include <boost/lexical_cast.hpp>
43 
44 using boost::lexical_cast;
45 
46 using lib::Sync;
47 using lib::ClassLock;
50 using lib::IterQueue;
51 using lib::IterStack;
52 using std::function;
53 using util::contains;
54 using util::isnil;
55 using util::_Fmt;
56 
57 
58 namespace stage {
59 namespace model{
60 namespace test {
61 
62  using lib::idi::EntryID;
71  using lib::diff::DiffStep;
72  using lib::diff::GenNode;
73  using lib::diff::MakeRec;
74  using lib::diff::Rec;
75  using lib::diff::Ref;
76  using lib::time::Time;
77  using lib::time::TimeSpan;
78  using lib::hash::LuidH;
79  using lib::test::EventLog;
80  using lib::CallQueue;
81 
82  using LERR_(UNBOUND_ARGUMENTS);
83  using LERR_(WRONG_TYPE);
84 
85  using ID = lib::idi::BareEntryID const&;
86 
87 
88  namespace {// test data...
89 
90  // --------random-diff-test------
91  uint const MAX_RAND_BORGS = 100; // stay below 400, verification export grows quadratic
92  uint const MAX_RAND_NUMBS = 500;
93  uint const MAX_RAND_DELAY = 5000; // throttle generation, since diff application is slower
94  // --------random-diff-test------
95 
96  int generator_instances = 0;
97  }
98 
99 
100 
101 
102 
103 
104  /**************************************************************************/
126  class BusTerm_test : public Test
127  {
128 
129  virtual void
130  run (Arg)
131  {
132  seedRand();
133 
137  replayStateMark();
139  clearStates();
140  pushDiff();
141  }
142 
143 
155  void
157  {
159  // our dummy will be linked with this identity
160  BareEntryID elmID = EntryID<MockElm>{"zeitgeist"};
161 
162  // Access the log on the Test-Nexus hub
163  EventLog nexusLog = stage::test::Nexus::startNewLog();
164  CHECK (nexusLog.ensureNot("zeitgeist"));
165 
166  MockElm mock(elmID);
167  CHECK (nexusLog.verifyCall("routeAdd").on("TestNexus").arg(elmID,"Tangible") // Note: invoked from ctor, so it is just a tangible at the moment
168  .beforeEvent("TestNexus", "added route to bID-zeitgeist"));
169 
170  EventLog elmLog = mock.getLog();
171  CHECK (elmLog.verifyCall("ctor").on(&mock)
172  .beforeEvent("create", "zeitgeist"));
173 
174 
175  // now verify there is indeed bidirectional connectivity...
176  CHECK (elmLog.ensureNot("expanded"));
177  CHECK (elmLog.ensureNot("doFlash"));
178  CHECK (nexusLog.ensureNot("zeitgeist").arg("expand"));
179  CHECK (nexusLog.ensureNot("zeitgeist").arg("Flash"));
180 
181  // invoke action on element to cause upstream message (with a "state mark")
182  mock.slotExpand();
183  CHECK (elmLog.verifyEvent("expanded"));
184  CHECK (nexusLog.verifyCall("note").on("TestNexus").arg(elmID, "GenNode-ID(\"expand\")-DataCap|«bool»|true"));
185 
186  // send a state mark down to the mock element
187  stage::test::Nexus::testUI().mark (elmID, GenNode(string{MARK_Flash}, 23));
188  CHECK (nexusLog.verifyCall("mark").on("TestNexus").arg(elmID, MARK_Flash)
189  .beforeEvent("TestNexus", "mark to bID-zeitgeist"));
190  CHECK (elmLog.verifyCall("doFlash").on("zeitgeist"));
191 
192 
193  // kill the zeitgeist and verify disconnection
194  mock.kill();
195  CHECK (elmLog.verifyEvent("destroy","zeitgeist"));
196  CHECK (nexusLog.verifyCall("routeDetach").on("TestNexus").arg(elmID)
197  .beforeEvent("TestNexus", "removed route to bID-zeitgeist"));
198 
199  stage::test::Nexus::testUI().mark (elmID, GenNode({MARK_Flash}, 88));
200  CHECK (nexusLog.verify("removed route to bID-zeitgeist")
201  .beforeCall("mark").on("TestNexus").arg(elmID, MARK_Flash)
202  .beforeEvent("warn","discarding mark to unknown bID-zeitgeist"));
203  CHECK (elmLog.ensureNot("Flash")
204  .afterEvent("destroy","zeitgeist"));
205 
206 
207  cout << "____Probe-Log_________________\n"
208  << util::join(elmLog, "\n")
209  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
210 
211  cout << "____Nexus-Log_________________\n"
212  << util::join(stage::test::Nexus::getLog(), "\n")
213  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
214  }
215 
216 
218  void
220  {
222  stage::test::Nexus::startNewLog();
223  Symbol cmd = stage::test::Nexus::prepareMockCmd<string, TimeSpan, LuidH>();
224 
225  MockElm mock("uiElm");
226 
227  // random command arguments...
228  string text {lib::test::randStr(12)};
229  TimeSpan clip (Time(1,2,3), lib::test::randTime());
230  LuidH luid;
231 
232  // we cannot invoke commands without binding required arguments
233  VERIFY_ERROR (WRONG_TYPE, mock.invoke(cmd) );
234 
235  // proper argument typing is ensured while dispatching the bind message.
236  VERIFY_ERROR (WRONG_TYPE, mock.invoke(cmd, Rec({"lalala"})) );
237 
238  // command can't be issued, since it's still unbound
239  CHECK (not Command::canExec(cmd));
240 
241 
242  mock.invoke (cmd, text, clip, luid);
243 
244  CHECK (Command::canExec(cmd));
245  CHECK (stage::test::Nexus::wasBound(cmd, text, clip, luid));
246  CHECK (not stage::test::Nexus::wasBound(cmd, "lololo"));
247  CHECK (stage::test::Nexus::wasInvoked(cmd));
248  CHECK (stage::test::Nexus::wasInvoked(cmd, text, clip, luid));
249  CHECK (not stage::test::Nexus::wasInvoked(cmd, " huh ", clip, luid));
250  CHECK (not stage::test::Nexus::wasInvoked(cmd, text, clip));
251 
252  // Mock commands are automatically unique
253  auto cmdX = stage::test::Nexus::prepareMockCmd<>();
254  auto cmdY = stage::test::Nexus::prepareMockCmd<>();
255  CHECK (cmd != cmdX);
256  CHECK (cmd != cmdY);
257 
258  CHECK (not stage::test::Nexus::wasInvoked(cmdX));
259  CHECK (not stage::test::Nexus::wasInvoked(cmdY));
260 
261  cout << "____Nexus-Log_________________\n"
262  << util::join(stage::test::Nexus::getLog(), "\n")
263  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
264 
265  stage::test::Nexus::setCommandHandler(); // deinstall custom command handler
266  }
267 
268 
272  void
274  {
276  stage::test::Nexus::startNewLog();
278 
279  MockElm mockA("alpha"); BareEntryID alpha = mockA.getID();
280  MockElm mockB("bravo"); BareEntryID bravo = mockB.getID();
281  MockElm mockC("charly"); BareEntryID charly = mockC.getID();
282 
283  mockA.slotExpand();
284 
285  mockB.slotExpand();
286  mockB.slotCollapse();
287 
288  CHECK (stateManager.currentState(alpha, "expand") == GenNode("expand", true ));
289  CHECK (stateManager.currentState(bravo, "expand") == GenNode("expand", false ));
290 
291  // handling of missing information
292  CHECK (stateManager.currentState(charly, "expand") == Ref::NO); // no data recorded yet
293  CHECK (stateManager.currentState(bravo, "extinct") == Ref::NO); // unknown property
294 
295  EntryID<MockElm> bruno("bruno");
296  CHECK (stateManager.currentState(bruno, "expand") == Ref::NO); // who knows bruno?
297 
298  mockC.slotExpand();
299  CHECK (stateManager.currentState(charly, "expand") == GenNode("expand", true ));
300 
301  // error states can be sticky
302  mockC.markErr("overinflated");
303  CHECK (stateManager.currentState(charly, "Error") == GenNode("Error", "overinflated"));
304 
305  mockC.reset();
306  CHECK (stateManager.currentState(charly, "expand") == Ref::NO); // back to void
307 
308 
309  cout << "____Nexus-Log_________________\n"
310  << util::join(stage::test::Nexus::getLog(), "\n")
311  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
312  }
313 
314 
316  void
318  {
320  StateManager& stateManager = stage::test::Nexus::getMockStateManager();
321 
322  MockElm mockA("alpha");
323  // no "bravo" this time
324  MockElm mockC("charly");
325 
326  CHECK (not mockA.isExpanded());
327  CHECK (not mockC.isTouched());
328 
329  BareEntryID alpha = mockA.getID();
330  stateManager.replayState (alpha, "expand");
331  CHECK (mockA.isExpanded());
332 
333  auto& uiBus = stage::test::Nexus::testUI();
334  uiBus.mark (mockA.getID(), GenNode{"expand", false});
335 
336  CHECK (not mockA.isExpanded());
337  CHECK (mockA.isTouched());
338 
339  stateManager.replayAllState ("expand");
340 
341  CHECK (mockA.isExpanded());
342  CHECK (not mockC.isExpanded());
343  CHECK (not mockC.isTouched());
344  }
345 
346 
348  void
350  {
352  EventLog nexusLog = stage::test::Nexus::startNewLog();
353 
354  MockElm mockA("alpha"); BareEntryID alpha = mockA.getID(); mockA.joinLog (nexusLog);
355  MockElm mockB("bravo"); BareEntryID bravo = mockB.getID(); mockB.joinLog (nexusLog);
356  MockElm mockC("charly"); BareEntryID charly = mockC.getID(); mockC.joinLog (nexusLog);
357 
358  auto& uiBus = stage::test::Nexus::testUI();
359 
360  CHECK (not mockA.isTouched());
361  CHECK (not mockB.isTouched());
362  CHECK (not mockC.isTouched());
363 
364  uiBus.mark (alpha, GenNode{"Message", "Centauri"});
365  uiBus.mark (bravo, GenNode{"Flash", true});
366  uiBus.mark (charly, GenNode{"Message", "Delta"});
367  uiBus.mark (charly, GenNode{"Error", "Echo"});
368 
369  CHECK ( mockA.isTouched());
370  CHECK (not mockB.isTouched());
371  CHECK ( mockC.isTouched());
372 
373  CHECK (not mockA.isError());
374  CHECK (not mockB.isError());
375  CHECK ( mockC.isError());
376 
377  CHECK ("Centauri" == mockA.getMessage());
378  CHECK ("Delta" == mockC.getMessage());
379 
380  CHECK ("Echo" == mockC.getError());
381 
382  // verify the message passing in the combined log...
383  CHECK (nexusLog.verifyEvent("create", "alpha")
384  .beforeCall("mark").on("TestNexus").arg("alpha", "Centauri") // bus API invoked
385  .beforeCall("doMsg").on("alpha").arg("Centauri") // handler on target invoked
386  .beforeEvent("mark", "Centauri") // target action activated
387  .beforeEvent("TestNexus","delivered mark to bID-alpha")); // dispatch done within UI-Bus
388 
389  CHECK (nexusLog.verifyEvent("TestNexus","delivered mark to bID-alpha")
390  .beforeCall("mark").on("TestNexus").arg("bravo", "GenNode-ID(\"Flash\")-DataCap|«bool»|true")
391  .beforeCall("doFlash").on("bravo")
392  .beforeEvent("TestNexus","delivered mark to bID-bravo"));
393 
394  // NOTE: calls are passed down synchronously, in one hop, and in sequence
395  CHECK (nexusLog.verifyEvent("TestNexus","delivered mark to bID-bravo")
396  .beforeCall("mark").on("TestNexus").arg("charly", "GenNode-ID(\"Message\")-DataCap|«string»|Delta")
397  .beforeCall("doMsg").on("charly").arg("Delta")
398  .beforeEvent("mark", "Delta").id("Message")
399  .beforeEvent("TestNexus","delivered mark to bID-charly")
400  .beforeCall("mark").on("TestNexus").arg("charly", "GenNode-ID(\"Error\")-DataCap|«string»|Echo")
401  .beforeCall("doErr").on("charly").arg("Echo")
402  .beforeEvent("mark", "Echo").id("Error")
403  .beforeEvent("TestNexus","delivered mark to bID-charly"));
404 
405 
406  // broadcast message
407  uiBus.markAll (GenNode{"Message", "Foxtrot"});
408  CHECK (not mockA.isError());
409  CHECK (not mockB.isError());
410  CHECK ( mockC.isError());
411  CHECK ( mockA.isTouched());
412  CHECK ( mockB.isTouched());
413  CHECK ( mockC.isTouched());
414  CHECK ("Foxtrot" == mockA.getMessage());
415  CHECK ("Foxtrot" == mockB.getMessage());
416  CHECK ("Foxtrot" == mockC.getMessage());
417  CHECK ( "" == mockA.getError());
418  CHECK ( "" == mockB.getError());
419  CHECK ( "Echo" == mockC.getError());
420 
421  CHECK (nexusLog.verifyEvent("mark", "Echo").id("Error")
422  .beforeCall("markAll").on("TestNexus").arg("Foxtrot")
423  .beforeEvent("Broadcast", "Foxtrot")
424  .beforeCall("mark").on("TestNexus").arg("bravo", "GenNode-ID(\"Message\")-DataCap|«string»|Foxtrot")
425  .beforeCall("doMsg").on("bravo").arg("Foxtrot")
426  .beforeEvent("TestNexus", "broadcasted mark to 3 terminals"));
427 
428  // the order of dispatch is unspecified,
429  // but we know a regular mark call sequence happens for each connected terminal
430  CHECK (nexusLog.verifyCall("markAll").on("TestNexus").arg("Foxtrot")
431  .beforeCall("mark").on("TestNexus").arg("alpha", "Foxtrot")
432  .beforeCall("doMsg").on("alpha").arg("Foxtrot")
433  .beforeEvent("TestNexus", "successfully broadcasted"));
434 
435  CHECK (nexusLog.verifyCall("markAll").on("TestNexus").arg("Foxtrot")
436  .beforeCall("mark").on("TestNexus").arg("bravo", "Foxtrot")
437  .beforeCall("doMsg").on("bravo").arg("Foxtrot")
438  .beforeEvent("TestNexus", "successfully broadcasted"));
439 
440  CHECK (nexusLog.verifyCall("markAll").on("TestNexus").arg("Foxtrot")
441  .beforeCall("mark").on("TestNexus").arg("charly", "Foxtrot")
442  .beforeCall("doMsg").on("charly").arg("Foxtrot")
443  .beforeEvent("TestNexus", "successfully broadcasted"));
444 
445 
446  cout << "____Nexus-Log_________________\n"
447  << util::join(nexusLog, "\n")
448  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
449  }
450 
451 
453  void
455  {
457  EventLog nexusLog = stage::test::Nexus::startNewLog();
458 
459  MockElm mockA("alpha"); BareEntryID alpha = mockA.getID(); mockA.joinLog (nexusLog);
460  MockElm mockB("bravo"); BareEntryID bravo = mockB.getID(); mockB.joinLog (nexusLog);
461  MockElm mockC("charly"); BareEntryID charly = mockC.getID(); mockC.joinLog (nexusLog);
462 
463  auto& uiBus = stage::test::Nexus::testUI();
464 
465  CHECK (not mockA.isTouched());
466  CHECK (not mockB.isTouched());
467  CHECK (not mockC.isTouched());
468 
469  mockB.slotExpand();
470  uiBus.mark (alpha, GenNode{"Message", "Centauri"});
471  uiBus.mark (charly, GenNode{"Message", "Delta"});
472  uiBus.mark (charly, GenNode{"Error", "Echo"});
473 
474  CHECK (mockB.isExpanded());
475  CHECK (mockC.isError());
476  CHECK ("Delta" == mockC.getMessage());
477  CHECK ("Centauri" == mockA.getMessage());
478 
479  // reset all notification messages
480  uiBus.markAll (GenNode{"clearMsg", true});
481  CHECK (mockB.isExpanded());
482  CHECK (mockC.isError());
483  CHECK (isnil (mockA.getMessage()));
484  CHECK (isnil (mockC.getMessage()));
485  CHECK ("Echo" == mockC.getError());
486 
487  uiBus.mark (bravo, GenNode{"Message", "miss"});
488  mockA.slotExpand();
489  mockA.slotCollapse();
490 
491  auto& stateManager = stage::test::Nexus::getMockStateManager();
492  CHECK (stateManager.currentState(alpha, "expand") == GenNode("expand", false ));
493  CHECK (stateManager.currentState(bravo, "expand") == GenNode("expand", true ));
494  CHECK (stateManager.currentState(charly,"expand") == Ref::NO);
495  CHECK (stateManager.currentState(charly, "Error") == GenNode("Error", "Echo")); // sticky error state was recorded
496 
497  // reset error state(s)
498  uiBus.markAll (GenNode{"clearErr", true});
499  CHECK (not mockA.isExpanded());
500  CHECK (mockB.isExpanded());
501  CHECK ("miss" == mockB.getMessage());
502  CHECK (not mockC.isError());
503 
504  CHECK (stateManager.currentState(alpha, "expand") == GenNode("expand", false ));
505  CHECK (stateManager.currentState(bravo, "expand") == GenNode("expand", true ));
506  CHECK (stateManager.currentState(charly,"expand") == Ref::NO);
507  CHECK (stateManager.currentState(charly, "Error") == Ref::NO); // sticky error state was cleared,
508  // because charly sent a clearErr state mark notification back
509 
510  // send global sweeping reset
511  uiBus.markAll (GenNode{"reset", true});
512 
513  CHECK (not mockA.isTouched());
514  CHECK (not mockB.isTouched());
515  CHECK (not mockC.isTouched());
516 
517  CHECK (not mockA.isExpanded());
518  CHECK (not mockB.isExpanded());
519 
520  CHECK (isnil (mockA.getMessage()));
521  CHECK (isnil (mockB.getMessage()));
522  CHECK (isnil (mockC.getMessage()));
523 
524  CHECK (stateManager.currentState(alpha, "expand") == Ref::NO);
525  CHECK (stateManager.currentState(bravo, "expand") == Ref::NO);
526  CHECK (stateManager.currentState(charly, "expand") == Ref::NO);
527  CHECK (stateManager.currentState(charly, "Error" ) == Ref::NO);
528 
529 
530  cout << "____Nexus-Log_________________\n"
531  << util::join(nexusLog, "\n")
532  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
533 
534  stage::test::Nexus::setStateMarkHandler(); // deinstall custom state mark handler
535  }
536 
537 
538 
539 
540 
555  void
557  {
559 
560  struct SessionThread
561  : Sync<>
562  , ThreadJoinable<>
563  {
564  // shared data
565  uint64_t borgChecksum_ = 0;
566  IterStack<uint> sessionBorgs_;
567 
568  // access to shared session data
569  void
570  scheduleBorg (uint id)
571  {
572  Lock sync{this};
573  borgChecksum_ += id;
574  sessionBorgs_.push(id);
575  }
576 
577  auto
578  dispatchBorgs()
579  {
580  Lock sync{this};
581  return dischargeToSnapshot (sessionBorgs_);
582  }
583 
584 
585 
594  struct BorgGenerator
597  , DiffSource
598  {
599  uint generatorID_;
600  SessionThread& theCube_;
601  IterQueue<DiffStep> steps_;
602 
603  BorgGenerator (SessionThread& motherShip, uint id)
604  : generatorID_{id}
605  , theCube_{motherShip}
606  {
608  ++generator_instances;
609  }
610  ~BorgGenerator()
611  {
613  --generator_instances;
614  }
615 
616 
617  /* == Interface IterSource<DiffStep> == */
618 
619  virtual DiffStep*
620  firstResult () override
621  {
622  REQUIRE (not steps_);
623  auto plannedBorgs = theCube_.dispatchBorgs();
624  uint max = plannedBorgs.size();
625  uint cur = 0;
626 
627  _Fmt borgName{"%d of %d ≺%03d.gen%03d≻"};
628 
629 
630  steps_.feed (after(Ref::ATTRIBS)); // important: retain all existing attributes
631  for (uint id : plannedBorgs) // Generate diff to inject a flock of Borg
632  {
633  GenNode borg = MakeRec().genNode(borgName % ++cur % max % id % generatorID_);
634  steps_.feed (ins(borg));
635  steps_.feed (mut(borg)); // open nested scope for this Borg
636  steps_.feed ( ins(GenNode{"borgID", int(id)}));
637  steps_.feed (emu(borg)); // close nested scope
638  }
639  steps_.feed (after(Ref::END)); // important: fast-forward and accept already existing Borgs
640 
641  return & *steps_; // the IterSource protocol requires us to return a ptr to current element
642  }
643 
644 
645  virtual void
646  nextResult (DiffStep*& pos) override
647  {
648  if (!pos) return;
649  if (steps_) ++steps_;
650  if (steps_)
651  pos = & *steps_; // pointer to current element
652  else
653  pos = NULL; // signal iteration end
654  }
655  };
656 
657 
661  SessionThread(function<void(DiffSource*)> notifyGUI)
662  : ThreadJoinable{"BusTerm_test: asynchronous diff mutation"
663  , [=]
664  {
665  uint cnt = randGen_.i(MAX_RAND_BORGS);
666  for (uint i=0; i<cnt; ++i)
667  {
668  uint delay = randGen_.i(MAX_RAND_DELAY);
669  uint id = randGen_.i(MAX_RAND_NUMBS);
670  usleep (delay);
671  scheduleBorg (id);
672  notifyGUI (new BorgGenerator{*this, i});
673  }
674  }}
675  { }
676 
677  private:
679  };
680 
681 
682 
683  EventLog nexusLog = stage::test::Nexus::startNewLog();
684 
685  // the simulated »GUI model«
686  // — to be infested by hosts of Borg sent via Diff-Message...
687  MockElm rootMock("alpha zero");
688  ID rootID = rootMock.getID();
689 
690  rootMock.attrib["α"] = "Quadrant";
691  CHECK ("Quadrant" == rootMock.attrib["α"]);
692  CHECK (isnil (rootMock.scope));
693 
694  CHECK (0 == generator_instances);
695 
696 
697  // The final part in the puzzle is to dispatch the diff messages into the UI
698  // In the real application, this operation is provided by the NotificationService.
699  // It has access to the UI-Bus, but has to ensure all bus operations are actually
700  // performed on the UI event thread.
701  auto& uiBus = stage::test::Nexus::testUI();
702  CallQueue uiDispatcher;
703  auto notifyGUI = [&](DiffSource* diffGenerator)
704  {
705  uiDispatcher.feed ([&, diffGenerator]()
706  {
707  // apply and consume diff message stored within closure
708  uiBus.change (rootID, MutationMessage{diffGenerator});
709  });
710  };
711 
712 
713 
714  //----start-multithreaded-mutation---
715  SessionThread session{notifyGUI};
716  usleep (2 * MAX_RAND_DELAY);
717  while (not isnil(uiDispatcher))
718  {
719  usleep (100);
720  uiDispatcher.invoke();
721  }
722  session.join();
723  //------end-multithreaded-mutation---
724 
725  // on rare occasions we (consumer thread)
726  // prematurely empty the queue...
727  while (not isnil(uiDispatcher))
728  {
729  uiDispatcher.invoke();
730  }
731 
732 
733  // now verify rootMock has been properly assimilated...
734  uint generatedBorgs = rootMock.scope.size();
735 
736  // root and all Borg child nodes are connected to the UI-Bus
737  CHECK (1 + generatedBorgs == stage::test::Nexus::size());
738 
739  uint64_t borgChecksum = 0;
740  for (uint i=0; i<generatedBorgs; ++i)
741  {
742  MockElm& borg = *rootMock.scope[i];
743  CHECK (contains (borg.attrib, "borgID"));
744  string borgID = borg.attrib["borgID"];
745  borgChecksum += lexical_cast<int> (borgID);
746  string childID = borg.getID().getSym();
747  CHECK (contains (childID, borgID));
748  CHECK (contains (childID, " of ")); // e.g. "3 of 5"
749 
750  CHECK (nexusLog.verifyCall("routeAdd").arg(rootMock.getID(), memLocation(rootMock)) // rootMock was attached to Nexus
751  .beforeCall("change") .argMatch(rootMock.getID(), // diff message sent via UI-Bus
752  "after.+?_ATTRIBS_.+?" // verify diff pattern generated for each Borg
753  "ins.+?"+childID+".+?"
754  "mut.+?"+childID+".+?"
755  "ins.+?borgID.+?"+borgID+".+?"
756  "emu.+?"+childID)
757  .beforeCall("routeAdd").arg(borg.getID(), memLocation(borg)) // Borg was inserted as child and attached to Nexus
758  .beforeEvent("applied diff to "+string(rootMock.getID()))
759  );
760  }
761 
762  CHECK (rootMock.attrib["α"] == "Quadrant"); // attribute alpha was preserved while injecting all those Borg
763 
764 
765  // sanity checks
766  CHECK (borgChecksum == session.borgChecksum_); // no Borgs got lost
767  CHECK (0 == generator_instances); // no generator instance leaks
768 
769 
770 
771  cout << "____Event-Log_________________\n"
772  << util::join(rootMock.getLog(), "\n")
773  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
774 
775  cout << "____Nexus-Log_________________\n"
776  << util::join(nexusLog, "\n")
777  << "\n───╼━━━━━━━━━╾────────────────"<<endl;
778  }
779 
780 
781  static string
782  memLocation (Tangible& uiElm)
783  {
784  return lib::idi::instanceTypeID (&uiElm);
785  }
786  };
787 
788 
790  LAUNCHER (BusTerm_test, "unit stage");
791 
792 
793 }}} // namespace stage::model::test
Facility for monitor object based locking.
Definition: sync.hpp:209
type erased baseclass for building a combined hash and symbolic ID.
Definition: entry-id.hpp:133
Variant of the standard case, requiring to wait and join() on the termination of this thread...
Definition: thread.hpp:668
Generic Message with an embedded diff, to describe changes to model elements.
void slotExpand()
Expand this element and remember the expanded state.
Definition: tangible.cpp:161
Automatically use custom string conversion in C++ stream output.
Constructor for a specially crafted &#39;ref GenNode&#39;.
Definition: gen-node.hpp:840
Hard wired key constants and basic definitions for communication with the GUI.
connection point at the UI-Bus.
Definition: bus-term.hpp:96
A Stack which can be popped by iterating.
Definition: iter-stack.hpp:113
A threadsafe queue for bound void(void) functors.
Definition: call-queue.hpp:49
Definition: run.hpp:40
static void setCommandHandler(CommandHandler=CommandHandler())
install a closure (custom handler function) to deal with any command invocations encountered in the t...
Definition: test-nexus.cpp:408
A fake UI backbone for investigations and unit testing.
void reset()
invoke the generic reset hook
Definition: tangible.cpp:65
Conveniently iterable stack and queue containers.
Any copy and copy construction prohibited.
Definition: nocopy.hpp:37
EventMatch & beforeCall(string match)
find a match for some function invocation after the current point of reference
Definition: event-log.cpp:438
Interface: a component to maintain persistent interface state.
Helper to log and verify the occurrence of events.
Definition: event-log.hpp:275
Front-end for printf-style string template interpolation.
typed symbolic and hash ID for asset-like position accounting.
Definition: entry-id.hpp:126
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
EventMatch & arg(ARGS const &...args)
refine filter to additionally require specific arguments
Definition: event-log.hpp:198
Opaque message to effect a structural change on a target, which is likewise only known in an abstract...
A front-end for using printf-style formatting.
Namespace of Session and user visible high-level objects.
Definition: sequence.hpp:65
A Queue which can be pulled by iterating.
Definition: iter-stack.hpp:188
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
SeedNucleus & seedFromDefaultGen()
draw seed another Generator from the default RandomSequencer
Definition: random.cpp:74
Lumiera unique object identifier.
Attachment point to the UI-Bus.
Object Monitor based synchronisation.
ThreadJoinable(string const &, FUN &&, ARGS &&...) -> ThreadJoinable< std::invoke_result_t< FUN, ARGS... >>
deduction guide: find out about result value to capture from a generic callable.
Token or Atom with distinct identity.
Definition: symbol.hpp:117
Iteration source interface to abstract a data source, which then can be accessed through IterAdapter ...
Definition: iter-source.hpp:79
static ctrl::StateManager & useMockStateManager()
install a standard handler for state mark messages, which is actually backed by a mock implementation...
Definition: test-nexus.cpp:553
EventMatch verifyCall(string match) const
start a query to match especially a function call
Definition: event-log.cpp:788
A Queue for function invocations, allowing them to be dispatched on demand.
EventMatch ensureNot(string match) const
start a query to ensure the given expression does not match.
Definition: event-log.cpp:805
static const Ref END
symbolic ID ref "_END_"
Definition: gen-node.hpp:862
#define MARK_TEST_FUN
Macro to mark the current test function in STDOUT.
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
Definition: event-log.cpp:417
Simplistic test class runner.
virtual bool mark(ID subject, GenNode const &mark)
route a state update or notification to the given subject.
Definition: ui-bus.cpp:168
void markErr(string error)
push an error state tag to the element
Definition: tangible.cpp:141
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:37
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
static bool wasInvoked(Symbol)
Test predicate: verify at least one actual invocation happened for the given commend, without matching any concrete arguments.
Definition: test-nexus.hpp:188
static bool wasBound(Symbol, ARGS const &...args)
Test predicate: verify by string match that the denoted command was actually bound against the given ...
Definition: test-nexus.hpp:150
ContentSnapshot< IT > dischargeToSnapshot(IT &ii)
Take a snapshot of the given LumieraIterator, which is thereby consumed.
Generic building block for tree shaped (meta)data structures.
Steam-Layer command frontend.
Convenience front-end to simplify and codify basic thread handling.
A special implementation of lib::Sync, where the storage of the object monitor is associated directly...
static void setStateMarkHandler(StateMarkHandler=StateMarkHandler())
similar to the custom command handler this hook allows to install a closure to intercept any "state m...
Definition: test-nexus.cpp:419
A collection of frequently used helper functions to support unit testing.
EventMatch & argMatch(ARGS const &...regExps)
refine filter to additionally cover all arguments with a series of regular expressions.
Definition: event-log.hpp:217
static const Ref NO
symbolic ID ref "_NO_"
Definition: gen-node.hpp:861
Interface: handling of persistent interface state.
void slotCollapse()
Collapse or minimise this element and remember the collapsed state.
Definition: tangible.cpp:173
string instanceTypeID(const TY *const obj)
designation of an distinct object instance
Definition: genfunc.hpp:116
A synchronisation protection guard employing a lock scoped to the parameter type as a whole...
static ctrl::BusTerm & testUI()
get a connection point to a UI backbone faked for test
Definition: test-nexus.cpp:366
Hash implementation based on a lumiera unique object id (LUID) When invoking the default ctor...
Mock UI element or controller.
Definition: mock-elm.hpp:99
static const Ref ATTRIBS
symbolic ID ref "_ATTRIBS_"
Definition: gen-node.hpp:865
Handle object representing a single Command instance to be used by client code.
Definition: command.hpp:115
EventMatch verify(string match) const
start a query to match for some substring.
Definition: event-log.cpp:752
Bare symbolic and hash ID used for accounting of asset like entries.
Interface common to all UI elements of relevance for the Lumiera application.
Definition: tangible.hpp:156
A time interval anchored at a specific point in time.
Definition: timevalue.hpp:573
EventMatch & on(string targetID)
refine filter to additionally match the &#39;this&#39; attribute
Definition: event-log.cpp:583
Preconfigured adapters for some STL container standard usage situations.
a family of time value like entities and their relationships.
object-like record of data.
Definition: record.hpp:141
A generic interface element instrumented for unit testing.
generic data element node within a tree
Definition: gen-node.hpp:222
bool contains(SEQ const &cont, typename SEQ::const_reference val)
shortcut for brute-force containment test in any sequential container
Definition: util.hpp:255