/* SessionCommandFunction(Test) - function test of command dispatch via SessionCommand facade Copyright (C) Lumiera.org 2017, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****************************************************/ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" extern "C" { #include "common/interfaceregistry.h" } #include "proc/control/proc-dispatcher.hpp" #include "proc/control/command-def.hpp" #include "gui/ctrl/command-handler.hpp" #include "gui/interact/invocation-trail.hpp" #include "lib/format-obj.hpp" #include "lib/format-cout.hpp" //////////TODO #include "lib/symbol.hpp" #include "lib/util.hpp" //#include #include namespace proc { namespace control { namespace test { // using std::function; // using std::rand; using lib::test::randTime; using gui::interact::InvocationTrail; using gui::ctrl::CommandHandler; using lib::diff::GenNode; using lib::diff::Rec; using lib::time::Time; using lib::time::TimeVar; using lib::time::Duration; using lib::time::Offset; using lib::time::FSecs; using lib::Symbol; using util::isnil; using util::toString; using std::string; namespace { // test fixture... /* === mock operation to be dispatched as command === */ const Symbol COMMAND_ID{"test.dispatch.function.command"}; TimeVar testCommandState = randTime(); void operate (Duration dur, Offset offset, int factor) { testCommandState += Offset(dur) + offset*factor; } Time capture (Duration, Offset, int) { return testCommandState; } void undoIt (Duration, Offset, int, Time oldState) { testCommandState = oldState; } }//(End) test fixture // typedef shared_ptr PCommandImpl; // typedef HandlingPattern const& HaPatt; #define __DELAY__ usleep(10000); /******************************************************************************************//** * @test verify integrated functionality of command dispatch through the SessionCommand facade. * - operate lifecycle of the supporting components, * similar to activating the »session subsystem« * - generate command messages similar to what is received from the UI-Bus * - us the handler mechanism from gui::ctrl::CoreService to talk to the facade * - have a specially rigged command function to observe invocation * - wait for the session loop thread to dispatch this command * - verify that commands are really executed single-threaded * * @see proc::SessionSubsystem * @see ProcDispatcher * @see CommandQueue_test * @see AbstractTangible_test::invokeCommand() */ class SessionCommandFunction_test : public Test { //------------------FIXTURE public: SessionCommandFunction_test() { CommandDef (COMMAND_ID) .operation (operate) .captureUndo (capture) .undoOperation (undoIt) ; } ~SessionCommandFunction_test() { Command::remove (COMMAND_ID); } //-------------(End)FIXTURE virtual void run (Arg) { lumiera_interfaceregistry_init(); lumiera::throwOnError(); startDispatcher(); perform_simpleInvocation(); // perform_messageInvocation(); // perform_massivelyParallel(); stopDispatcher(); lumiera_interfaceregistry_destroy(); } /** @test start the session loop thread, similar * to what the »session subsystem« does * @note we are _not_ actually starting the subsystem * @see facade.cpp */ void startDispatcher() { CHECK (not ProcDispatcher::instance().isRunning()); ProcDispatcher::instance().start ([&] (string* problemMessage) { CHECK (isnil (*problemMessage)); thread_has_ended = true; }); CHECK (ProcDispatcher::instance().isRunning()); CHECK (not thread_has_ended); } bool thread_has_ended{false}; void stopDispatcher() { CHECK (ProcDispatcher::instance().isRunning()); ProcDispatcher::instance().requestStop(); __DELAY__ CHECK (not ProcDispatcher::instance().isRunning()); CHECK (thread_has_ended); } void perform_simpleInvocation() { string cmdID {COMMAND_ID}; Rec arguments {Duration(15,10), Time(500,0), -1}; CHECK (not Command(COMMAND_ID).canExec()); SessionCommand::facade().bindArg (cmdID, arguments); CHECK (Command(COMMAND_ID).canExec()); Time prevState = testCommandState; SessionCommand::facade().invoke(cmdID); __DELAY__ CHECK (testCommandState - prevState == Time(0, 1)); // execution added 1500ms -1*500ms == 1sec } /** @test invoke a command in the same way as CoreService does * when handling command messages from the UI-Bus * - use the help of an InvocationTrail, similar to what the * [generic UI element](\ref gui::model::Tangible) does * - generate a argument binding message * - generate a "bang!" message */ void perform_messageInvocation() { // this happens "somewhere" in the UI interaction control framework InvocationTrail invoTrail{Command(COMMAND_ID)}; // this happens within some tangible UI element (widget / controller) GenNode argumentBindingMessage = invoTrail.bindMsg (Rec{Duration(25,10), Time(500,0), -2}); GenNode commandTriggerMessage = invoTrail.bangMsg (); CHECK (argumentBindingMessage.idi == commandTriggerMessage.idi); // this happens, when CoreService receives command messages from UI-Bus CommandHandler handler1{argumentBindingMessage}; argumentBindingMessage.data.accept(handler1); // handler is a visitor for the message payload CHECK (Command::canExec(COMMAND_ID)); CHECK (not Command::canUndo(COMMAND_ID)); Time prevState = testCommandState; // now handling the message to trigger execution CommandHandler handler2{commandTriggerMessage}; commandTriggerMessage.data.accept(handler2); __DELAY__ CHECK (Command::canUndo(COMMAND_ID)); CHECK (testCommandState - prevState == Time(500, 1)); // execution added 2500ms -2*500ms == 1.5sec } /** @test verify that commands are properly enqueued * and executed one by one * - create several threads to send random command messages * - verify that, after executing all commands, the internal * state variable reflects the result of a proper * sequential calculation and summation */ void perform_massivelyParallel() { UNIMPLEMENTED ("verify sequentialisation by queue"); } }; /** Register this test class... */ LAUNCHER (SessionCommandFunction_test, "function controller"); }}} // namespace proc::control::test