Lumiera  0.pre.03
»edit your freedom«
test-nexus.cpp
Go to the documentation of this file.
1 /*
2  test::Nexus - implementation base for test user interface backbone
3 
4  Copyright (C) Lumiera.org
5  2015, 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 
46 #include "lib/error.hpp"
47 #include "lib/symbol.hpp"
48 #include "lib/itertools.hpp"
49 #include "test/test-nexus.hpp"
50 #include "lib/test/event-log.hpp"
52 #include "stage/ctrl/nexus.hpp"
55 #include "lib/diff/gen-node.hpp"
56 #include "lib/idi/entry-id.hpp"
57 #include "lib/idi/genfunc.hpp"
58 #include "lib/depend.hpp"
59 #include "lib/format-string.hpp"
60 #include "lib/format-cout.hpp"
61 
62 #include <string>
63 #include <deque>
64 
65 using std::string;
66 
67 using lib::Symbol;
68 using lib::append_all;
70 using lib::diff::Rec;
71 using lib::diff::GenNode;
72 using lib::diff::DataCap;
82 using util::_Fmt;
83 
84 namespace stage {
85 namespace test{
86 
87 
88  namespace { // internal details
89 
90  using BusHub = stage::ctrl::Nexus;
91 
101  class TestNexus
102  : public BusHub
103  {
104  EventLog log_{this};
105 
106  using CommandHandler = test::Nexus::CommandHandler;
107  using StateMarkHandler = test::Nexus::StateMarkHandler;
108 
109  CommandHandler commandHandler_;
110  StateMarkHandler stateMarkHandler_;
111 
112 
113 
114  virtual void
115  act (GenNode const& command)
116  {
117  log_.call (this, "act", command);
118  commandHandler_(command);
119  log_.event("TestNexus", _Fmt("bind and trigger command \"%s\"%s")
120  % command.idi.getSym()
121  % command.data.get<Rec>());
122  }
123 
124  virtual void
125  note (ID subject, GenNode const& mark) override
126  {
127  log_.call (this, "note", subject, mark);
128  stateMarkHandler_(subject, mark);
129  log_.event("TestNexus", _Fmt("processed note from %s |%s") % subject % mark);
130  }
131 
132  virtual bool
133  mark (ID subject, GenNode const& mark) override
134  {
135  log_.call(this, "mark", subject, mark);
136  if (BusHub::mark (subject, mark))
137  {
138  log_.event ("TestNexus", _Fmt("delivered mark to %s |%s") % subject % mark);
139  return true;
140  }
141  else
142  {
143  log_.warn (_Fmt("discarding mark to unknown %s |%s") % subject % mark);
144  return false;
145  }
146  }
147 
148  virtual size_t
149  markAll (GenNode const& mark) override
150  {
151  log_.call(this, "markAll", mark);
152  log_.event("Broadcast", _Fmt("Broadcast mark(\"%s\"): %s") % mark.idi.getSym() % mark.data);
153  size_t cnt = BusHub::markAll (mark);
154  log_.event("TestNexus", _Fmt("successfully broadcasted mark to %d terminals") % cnt);
155  return cnt;
156  }
157 
158  virtual bool
159  change (ID subject, MutationMessage&& diff) override
160  {
161  string diffSeqLog = diff.updateDiagnostics(); // snapshot of generated diff sequence
162  log_.call (this, "change", subject, diffSeqLog);
163  if (BusHub::change (subject, move(diff)))
164  {
165  log_.event ("TestNexus", _Fmt("applied diff to %s |%s") % subject % diffSeqLog);
166  return true;
167  }
168  else
169  {
170  log_.warn (_Fmt("disregarding change/diff to unknown %s |%s") % subject % diffSeqLog);
171  return false;
172  }
173  }
174 
175  virtual BusTerm&
176  routeAdd (ID identity, Tangible& newNode) override
177  {
178  log_.call (this, "routeAdd", identity, instanceTypeID(&newNode));
179  BusHub::routeAdd (identity, newNode);
180  log_.event("TestNexus", _Fmt("added route to %s |%s| table-size=%2d")
181  % identity
182  % instanceTypeID(&newNode)
183  % BusHub::size());
184  return *this;
185  }
186 
187  virtual void
188  routeDetach (ID node) noexcept override
189  {
190  log_.call (this, "routeDetach", node);
191  BusHub::routeDetach (node);
192  log_.event("TestNexus", _Fmt("removed route to %s | table-size=%2d") % node % BusHub::size());
193  }
194 
195  virtual operator string() const
196  {
197  return getID().getSym()+"."+instanceTypeID(this);
198  }
199 
200 
201  public:
202  TestNexus()
203  : BusHub(*this, lib::idi::EntryID<TestNexus>("mock-UI"))
204  {
205  installCommandHandler();
206  installStateMarkHandler();
207  }
208 
209  // standard copy operations
210 
211 
212  EventLog&
213  getLog()
214  {
215  return log_;
216  }
217 
218  void
219  installCommandHandler (CommandHandler newHandler =CommandHandler())
220  {
221  if (newHandler)
222  commandHandler_ = newHandler;
223  else
224  commandHandler_ =
225  [=](GenNode const& cmd)
226  {
227  log_.warn(_Fmt("NOT handling command-message %s in test-mode") % cmd);
228  };
229  }
230 
231  void
232  installStateMarkHandler (StateMarkHandler newHandler =StateMarkHandler())
233  {
234  if (newHandler)
235  stateMarkHandler_ = newHandler;
236  else
237  stateMarkHandler_ =
238  [=](ID subject, GenNode const& mark)
239  {
240  log_.warn(_Fmt("NOT handling state-mark %s passed from %s in test-mode")
241  % mark % subject);
242  };
243  }
244  };
245 
249 
250 
251 
252 
260  : public BusTerm
261  {
262 
263  EventLog&
264  log()
265  {
266  return testNexus().getLog();
267  }
268 
269 
270 
271  /* ==== defunct re-implementation of the BusTerm interface ==== */
272 
273  virtual void
274  act (GenNode const& command)
275  {
276  log().call(this, "act", command);
277  log().error ("sent command invocation to ZombieNexus");
278  cerr << "Command " << command << " -> ZombieNexus" <<endl;
279  }
280 
281  virtual void
282  note (ID subject, GenNode const& mark) override
283  {
284  log().call(this, "note", subject, mark);
285  log().error ("sent note message to ZombieNexus");
286  cerr << "note message "<< mark
287  << " FROM:" << subject
288  << " -> ZombieNexus" <<endl;
289  }
290 
291  virtual bool
292  mark (ID subject, GenNode const& mark) override
293  {
294  log().call(this, "mark", subject, mark);
295  log().error ("request to deliver mark message via ZombieNexus");
296  cerr << "mark message -> ZombieNexus" <<endl;
297  return false;
298  }
299 
300  virtual size_t
301  markAll (GenNode const& mark) override
302  {
303  log().call(this, "markAll", mark);
304  log().error ("request to broadcast to all Zombies");
305  cerr << "broadcast message -> ZombieNexus" <<endl;
306  return 0;
307  }
308 
309  virtual bool
310  change (ID subject, MutationMessage&& diff) override
311  {
312  log().call(this, "change", subject, diff);
313  log().error ("request to apply a diff message via ZombieNexus");
314  cerr << "change diff -> ZombieNexus" <<endl;
315  return false;
316  }
317 
318  virtual BusTerm&
319  routeAdd (ID identity, Tangible& newNode) override
320  {
321  log().call(this, "routeAdd", identity, newNode);
322  log().error ("attempt to connect against ZombieNexus");
323  cerr << "connect("<< identity <<" -> ZombieNexus" <<endl;
324  return *this;
325  }
326 
327  virtual void
328  routeDetach (ID node) noexcept override
329  {
330  log().call(this, "routeDetach", node);
331  log().error ("disconnect from ZombieNexus");
332  cerr << "disconnect("<< node <<" -> ZombieNexus" <<endl;
333  }
334 
335  virtual operator string() const
336  {
337  return getID().getSym()+"."+instanceTypeID(this);
338  }
339 
340 
341  public:
346  ZombieNexus(string formerID, BusTerm& homeland)
347  : BusTerm(lib::idi::EntryID<ZombieNexus>("defunct-"+formerID), homeland)
348  { }
349 
350  explicit
351  ZombieNexus()
352  : ZombieNexus{"zombieland", *this}
353  { }
354 
355  ~ZombieNexus()
356  {
357  cerr << this->getID().getSym() << ": Zombies never die" << endl;
358  }
359  };
360 
361 
362 
363  lib::Depend<ZombieNexus> zombieNexus;
364 
365  }//(End) internal details
366 
367 
368 
369 
376  {
377  return testNexus();
378  }
379 
380  lib::test::EventLog const&
381  Nexus::getLog()
382  {
383  return testNexus().getLog();
384  }
385 
386  lib::test::EventLog const&
387  Nexus::startNewLog()
388  {
389  return testNexus().getLog().clear();
390  }
391 
392  size_t
393  Nexus::size()
394  {
395  return testNexus().size();
396  }
397 
398 
416  void
417  Nexus::setCommandHandler (CommandHandler newHandler)
418  {
419  testNexus().installCommandHandler (newHandler);
420  }
421 
427  void
428  Nexus::setStateMarkHandler(StateMarkHandler newHandler)
429  {
430  testNexus().installStateMarkHandler (newHandler);
431  }
432 
433 
434  namespace { // install a diagnostic dummy-command-handler
435 
444  : public HandlingPattern
445  {
446  mutable EventLog log_;
447  Command command_;
448 
449 
450 
451  /* ==== HandlingPattern - Interface ==== */
452 
453  void
454  performExec (CommandImpl& command) const override
455  {
456  log_.call ("MockHandlingPattern", "exec", command);
457  command.invokeCapture();
458  command.invokeOperation();
459  }
460 
461  void
462  performUndo (CommandImpl& command) const override
463  {
464  log_.call ("MockHandlingPattern", "undo", command);
465  command.invokeUndo();
466  }
467 
468  bool
469  isValid() const override
470  {
471  return true;
472  }
473 
474 
475  public:
476  SimulatedCommandHandler (GenNode const& cmdMsg)
477  : log_(Nexus::getLog())
478  , command_(retrieveCommand(cmdMsg))
479  {
480  log_.event("TestNexus", "HANDLING Command-Message for "+string(command_));
481 
482  Rec const& argData{cmdMsg.data.get<Rec>()};
483  log_.call ("TestNexus", "bind-command", enumerate(argData));
484  command_.bindArg (argData);
485 
486  log_.call ("TestNexus", "exec-command", command_);
487  if (command_.exec (*this))
488  log_.event("TestNexus", "SUCCESS handling "+command_.getID());
489  else
490  log_.warn(_Fmt("FAILED to handle command-message %s in test-mode") % cmdMsg);
491  }
492 
493  private:
494  EventLog::ArgSeq
495  enumerate (Rec const& argData)
496  {
497  EventLog::ArgSeq strings;
498  strings.reserve (argData.childSize());
499  append_all (transformIterator (childData (argData.scope())
500  , util::toString<DataCap>)
501  ,strings);
502  return strings;
503  }
504 
505  static Command
506  retrieveCommand (GenNode const& cmdMsg)
507  {
508  Symbol cmdID {cmdMsg.idi.getSym().c_str()};
509  return Command::get (cmdID);
510  }
511  };
512 
513  }//(End)diagnostic dummy-command-handler
514 
515  void
516  Nexus::prepareDiagnosticCommandHandler()
517  {
518  testNexus().installCommandHandler(
519  [](GenNode const& cmdMsg)
520  {
521  SimulatedCommandHandler{cmdMsg};
522  });
523  }
524 
525 
526 
527 
528 
529 
530 
531  namespace { // install a diagnostic dummy-command-handler
532 
533  using ID = lib::idi::BareEntryID;
534 
536  : public StateRecorder
537  {
538 
539  public:
542  { }
543 
544  using StateManager::clearState;
545  };
546 
548 
549  }//(End)diagnostic mock-state-manager
550 
551 
563  {
564  // discard possible leftover
565  // from previous test installations
566  stateManager().clearState();
567 
568  testNexus().installStateMarkHandler(
569  [&](ID const& elementID, lib::diff::GenNode const& stateMark)
570  {
571  stateManager().recordState (elementID, stateMark);
572  });
573 
574  return getMockStateManager();
575  }
576 
578  Nexus::getMockStateManager()
579  {
580  return stateManager();
581  }
582 
583 
584 
585 
586 
587 
592  void
594  {
595  string lateName = doomed.getID().getSym();
596  doomed.~BusTerm();
597  testNexus().getLog().destroy (lateName);
598 
599  static_assert (sizeof(BusTerm) >= sizeof(ZombieNexus), "Zombie overflow");
600  new(&doomed) ZombieNexus{lateName, zombieNexus()};
601  testNexus().getLog().event(lateName + " successfully zombificated.");
602  }
603 
604 }} // namespace stage::test
virtual bool change(ID subject, MutationMessage &&diff) override
direct a mutation message towards the indicated Tangible.
Definition: test-nexus.cpp:159
type erased baseclass for building a combined hash and symbolic ID.
Definition: entry-id.hpp:142
Simple map based implementation of the PresentationStateManager interface.
Generic Message with an embedded diff, to describe changes to model elements.
EventLog & event(string text)
log some text as event
Definition: event-log.cpp:685
Automatically use custom string conversion in C++ stream output.
connection point at the UI-Bus.
Definition: bus-term.hpp:105
EventLog & warn(string text)
Log a warning entry.
Definition: event-log.cpp:719
Support for verifying the occurrence of events from unit tests.
Definition: run.hpp:49
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:417
virtual BusTerm & routeAdd(ID identity, Tangible &newNode) override
Definition: test-nexus.cpp:319
A fake UI backbone for investigations and unit testing.
Helper to log and verify the occurrence of events.
Definition: event-log.hpp:284
Front-end for printf-style string template interpolation.
typed symbolic and hash ID for asset-like position accounting.
Definition: entry-id.hpp:135
virtual void note(ID subject, GenNode const &mark) override
capture and record a "state mark" for later replay for restoring UI state.
Definition: test-nexus.cpp:282
virtual ~BusTerm()
this is an interface
Definition: ui-bus.cpp:92
ZombieNexus(string formerID, BusTerm &homeland)
fabricate a "dead terminal", marked as deceased, viciously connected to itself.
Definition: test-nexus.cpp:346
Opaque message to effect a structural change on a target, which is likewise only known in an abstract...
virtual void routeDetach(ID node) noexcept override
deactivate and remove a down-link route.
Definition: test-nexus.cpp:188
A front-end for using printf-style formatting.
Access point to singletons and other kinds of dependencies designated by type.
Definition: depend.hpp:289
Implementation namespace for support and library code.
Generic functions to build identification schemes.
virtual bool change(ID subject, MutationMessage &&diff) override
alter and reshape the designated subject by applying the given diff message.
Definition: test-nexus.cpp:310
Token or Atom with distinct identity.
Definition: symbol.hpp:126
lib::Depend< TestNexus > testNexus
singleton instance of the [TestNexus] used for rigging unit tests
Definition: test-nexus.cpp:248
static ctrl::StateManager & useMockStateManager()
install a standard handler for state mark messages, which is actually backed by a mock implementation...
Definition: test-nexus.cpp:562
virtual size_t markAll(GenNode const &mark) override
broadcast a notification to all connected terminal nodes.
Definition: test-nexus.cpp:149
Marker types to indicate a literal string and a Symbol.
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:46
EventLog & call(string target, string function)
Log occurrence of a function call with no arguments.
Definition: event-log.cpp:699
Implementation of the PresentationStateManager interface through associative (key-value) store...
virtual void act(GenNode const &command)
prepare or trigger invocation of a command.
Definition: test-nexus.cpp:274
Generic building block for tree shaped (meta)data structures.
Steam-Layer command frontend.
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:428
Interface: handling of persistent interface state.
Central hub of the UI-Bus.
Definition: nexus.hpp:76
Singleton services and Dependency Injection.
static void zombificate(ctrl::BusTerm &)
kill the given [BusTerm] and implant a dead terminal in place
Definition: test-nexus.cpp:593
virtual bool mark(ID subject, GenNode const &mark) override
route mark messages down to the individual Tangible.
Definition: test-nexus.cpp:133
virtual void act(GenNode const &command)
prepare or trigger invocation of a command.
Definition: test-nexus.cpp:115
Lumiera error handling (C++ interface).
virtual void note(ID subject, GenNode const &mark) override
capture and record a "state mark" for later replay for restoring UI state.
Definition: test-nexus.cpp:125
string instanceTypeID(const TY *const obj)
designation of an distinct object instance
Definition: genfunc.hpp:125
static ctrl::BusTerm & testUI()
get a connection point to a UI backbone faked for test
Definition: test-nexus.cpp:375
virtual void routeDetach(ID node) noexcept override
Definition: test-nexus.cpp:328
Handle object representing a single Command instance to be used by client code.
Definition: command.hpp:124
Bare symbolic and hash ID used for accounting of asset like entries.
Steam-Layer Command implementation.
Helpers for working with iterators based on the pipeline model.
Interface common to all UI elements of relevance for the Lumiera application.
Definition: tangible.hpp:165
auto transformIterator(IT const &src, FUN processingFunc)
Build a TransformIter: convenience free function shortcut, picking up the involved types automaticall...
Definition: itertools.hpp:797
object-like record of data.
Definition: record.hpp:150
virtual bool mark(ID subject, GenNode const &mark) override
route a state update or notification to the given subject.
Definition: test-nexus.cpp:292
virtual BusTerm & routeAdd(ID identity, Tangible &newNode) override
add a new down-link connection to the routing table
Definition: test-nexus.cpp:176
Interface: Operation Skeleton how to invoke or undo a command.
Core hub and routing table of the UI-Bus.
generic data element node within a tree
Definition: gen-node.hpp:231
virtual size_t markAll(GenNode const &mark) override
broadcast a notification message to all currently connected bus terminals.
Definition: test-nexus.cpp:301