Command handling pattern? test-driven brainstorming
This commit is contained in:
parent
843f44795e
commit
e9b95e47cf
6 changed files with 359 additions and 9 deletions
|
|
@ -96,6 +96,7 @@ namespace control {
|
|||
{ SYNC
|
||||
, SYNC_THROW
|
||||
, ASYNC
|
||||
, DUMMY
|
||||
|
||||
, NUM_IDS
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,13 +35,6 @@ out: RESET...undoIt\(time=00:..:....00\)----memento-:START...doIt\( Time=00:..:.
|
|||
END
|
||||
|
||||
|
||||
TEST "Command functor and UNDO functor" CommandMutation_test <<END
|
||||
out: empty placeholder closure: Closure\(0\)
|
||||
out: param values: Closure\(23\)
|
||||
out: saved state: 11
|
||||
END
|
||||
|
||||
|
||||
TEST "build argument accepting function" AcceptArgumentTuple_test <<END
|
||||
out: sizeof\( .+control.+TestClass.+ \) = 1
|
||||
out: sizeof\( .+control.+TestClass.+lumiera.Time.+ \) = (12)|(16)
|
||||
|
|
@ -50,6 +43,13 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Command functor and UNDO functor" CommandMutation_test <<END
|
||||
out: empty placeholder closure: Closure\(0\)
|
||||
out: param values: Closure\(23\)
|
||||
out: saved state: 11
|
||||
END
|
||||
|
||||
|
||||
TEST "Memento wiring and storage" MementoTie_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
|
@ -60,6 +60,16 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
PLANNED "handling pattern basics" HandlingPatternBasics_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "handling patterns" HandlingPatternStandardImpl_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Command usage aspects I" CommandUse1_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ test_components_SOURCES = \
|
|||
$(testcomponents_srcdir)/proc/control/command-registry-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/control/argument-tuple-accept-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/control/memento-tie-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/control/handling-pattern-basics-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/control/handling-pattern-standard-impl-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/engine/buff-table-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/engine/node-basic-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/engine/node-fabrication-test.cpp \
|
||||
|
|
|
|||
194
tests/components/proc/control/handling-pattern-basics-test.cpp
Normal file
194
tests/components/proc/control/handling-pattern-basics-test.cpp
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
HandlingPatternBasics(Test) - verify elementary operation of a command handling pattern
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2009, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
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"
|
||||
#include "proc/control/command.hpp"
|
||||
#include "proc/control/command-impl.hpp"
|
||||
#include "proc/control/command-registry.hpp"
|
||||
#include "proc/control/argument-erasure.hpp"
|
||||
#include "proc/control/handling-pattern.hpp"
|
||||
//#include "lib/lumitime.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
#include "proc/control/test-dummy-commands.hpp"
|
||||
|
||||
//#include <tr1/functional>
|
||||
//#include <boost/ref.hpp>
|
||||
//#include <boost/format.hpp>
|
||||
//#include <iostream>
|
||||
#include <cstdlib>
|
||||
//#include <string>
|
||||
|
||||
|
||||
namespace control {
|
||||
namespace test {
|
||||
|
||||
|
||||
// using boost::format;
|
||||
// using boost::str;
|
||||
//using lumiera::Time;
|
||||
//using util::contains;
|
||||
using std::tr1::function;
|
||||
// using std::tr1::bind;
|
||||
// using std::string;
|
||||
using std::rand;
|
||||
//using std::cout;
|
||||
//using std::endl;
|
||||
// using lib::test::showSizeof;
|
||||
// using util::isSameObject;
|
||||
// using util::contains;
|
||||
using lib::Symbol;
|
||||
|
||||
|
||||
|
||||
//using lumiera::typelist::BuildTupleAccessor;
|
||||
// using lumiera::error::LUMIERA_ERROR_EXTERNAL;
|
||||
|
||||
namespace { // test data and helpers...
|
||||
|
||||
Symbol TEST_CMD = "test.command1.handling";
|
||||
HandlingPattern::ID TEST_PATTERN = HandlingPattern::DUMMY;
|
||||
}
|
||||
|
||||
typedef shared_ptr<CommandImpl> PCommandImpl;
|
||||
typedef HandlingPattern const& HaPatt;
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************
|
||||
* @test operate and verify a simple dummy command handling pattern.
|
||||
* interface. Add/remove a command instance to the index, allocate an
|
||||
* CommandImpl frame and verify it is removed properly on ref count zero.
|
||||
* @note this test covers mainly the behaviour of a handling pattern as a concept,
|
||||
* not so much the behaviour of the (standard) handling pattern implementations.
|
||||
*
|
||||
* @see HandlingPattern
|
||||
* @see CommandRegistry
|
||||
* @see command.hpp
|
||||
* @see command-basic-test.cpp
|
||||
*/
|
||||
class HandlingPatternBasics_test : public Test
|
||||
{
|
||||
|
||||
uint cnt_inst;
|
||||
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
CommandRegistry& registry = CommandRegistry::instance();
|
||||
ASSERT (®istry);
|
||||
|
||||
cnt_inst = registry.instance_count();
|
||||
|
||||
{
|
||||
PCommandImpl pCom = buildTestCommand(registry);
|
||||
checkExec (pCom);
|
||||
checkUndo (pCom);
|
||||
}
|
||||
|
||||
ASSERT (cnt_inst == registry.instance_count());
|
||||
}
|
||||
|
||||
|
||||
/** create a command implementation frame usable for tests.
|
||||
* This simulates what normally happens within a CommandDef.
|
||||
* The created CommandImpl isn't registered, and thus will
|
||||
* just go away when the smart-ptr leaves scope.
|
||||
*/
|
||||
PCommandImpl
|
||||
buildTestCommand (CommandRegistry& registry)
|
||||
{
|
||||
|
||||
typedef void Sig_oper(int);
|
||||
typedef long Sig_capt(int);
|
||||
typedef void Sig_undo(int,long);
|
||||
|
||||
function<Sig_oper> o_Fun (command1::operate);
|
||||
function<Sig_capt> c_Fun (command1::capture);
|
||||
function<Sig_undo> u_Fun (command1::undoIt);
|
||||
|
||||
ASSERT (o_Fun && c_Fun && u_Fun);
|
||||
|
||||
// when the CommandDef is complete, it issues the
|
||||
// allocation call to the registry behind the scenes....
|
||||
|
||||
PCommandImpl pImpl = registry.newCommandImpl(o_Fun,c_Fun,u_Fun);
|
||||
ASSERT (pImpl);
|
||||
ASSERT (*pImpl);
|
||||
return pImpl;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
checkExec (PCommandImpl com)
|
||||
{
|
||||
ASSERT (com);
|
||||
ASSERT (!com->canExec());
|
||||
|
||||
typedef Types<int> ArgType;
|
||||
const int ARGU (1 + rand() % 1000);
|
||||
Tuple<ArgType> tuple(ARGU);
|
||||
TypedArguments<Tuple<ArgType> > arg(tuple);
|
||||
com->setArguments(arg);
|
||||
|
||||
ASSERT (com->canExec());
|
||||
ASSERT (!com->canUndo());
|
||||
command1::check_ = 0;
|
||||
|
||||
HaPatt patt = HandlingPattern::get(TEST_PATTERN);
|
||||
ExecResult res = patt.invoke(*com, TEST_CMD);
|
||||
|
||||
ASSERT (res);
|
||||
ASSERT (ARGU == command1::check_);
|
||||
ASSERT (com->canUndo());
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
checkUndo (PCommandImpl com)
|
||||
{
|
||||
ASSERT (com);
|
||||
ASSERT (com->canExec());
|
||||
ASSERT (com->canUndo());
|
||||
|
||||
ASSERT (command1::check_ > 0);
|
||||
|
||||
HaPatt ePatt = HandlingPattern::get(TEST_PATTERN);
|
||||
HaPatt uPatt = ePatt.howtoUNDO();
|
||||
ExecResult res = uPatt.invoke(*com, TEST_CMD);
|
||||
|
||||
ASSERT (res);
|
||||
ASSERT (command1::check_ == 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (HandlingPatternBasics_test, "function controller");
|
||||
|
||||
|
||||
}} // namespace control::test
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
HandlingPatternStandardImpl(Test) - cover the provided standard command handling patterns
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2009, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
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"
|
||||
//#include "proc/asset/media.hpp"
|
||||
//#include "proc/mobject/session.hpp"
|
||||
//#include "proc/mobject/session/edl.hpp"
|
||||
//#include "proc/mobject/session/testclip.hpp"
|
||||
//#include "proc/mobject/test-dummy-mobject.hpp"
|
||||
//#include "lib/p.hpp"
|
||||
//#include "proc/mobject/placement.hpp"
|
||||
//#include "proc/mobject/placement-index.hpp"
|
||||
//#include "proc/mobject/explicitplacement.hpp"
|
||||
#include "proc/control/command.hpp"
|
||||
#include "proc/control/command-impl.hpp"
|
||||
#include "proc/control/command-registry.hpp"
|
||||
//#include "proc/control/command-def.hpp"
|
||||
//#include "lib/lumitime.hpp"
|
||||
//#include "lib/symbol.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
#include "proc/control/test-dummy-commands.hpp"
|
||||
|
||||
//#include <tr1/functional>
|
||||
//#include <boost/ref.hpp>
|
||||
//#include <boost/format.hpp>
|
||||
//#include <iostream>
|
||||
//#include <cstdlib>
|
||||
//#include <string>
|
||||
|
||||
|
||||
namespace control {
|
||||
namespace test {
|
||||
|
||||
|
||||
// using boost::format;
|
||||
// using boost::str;
|
||||
//using lumiera::Time;
|
||||
//using util::contains;
|
||||
using std::tr1::function;
|
||||
// using std::tr1::bind;
|
||||
// using std::string;
|
||||
//using std::rand;
|
||||
//using std::cout;
|
||||
//using std::endl;
|
||||
// using lib::test::showSizeof;
|
||||
// using util::isSameObject;
|
||||
// using util::contains;
|
||||
|
||||
// using session::test::TestClip;
|
||||
// using lib::Symbol;
|
||||
// using lumiera::P;
|
||||
|
||||
|
||||
//using lumiera::typelist::BuildTupleAccessor;
|
||||
// using lumiera::error::LUMIERA_ERROR_EXTERNAL;
|
||||
|
||||
namespace { // test data and helpers...
|
||||
|
||||
// Symbol TEST_CMD = "test.command1.1";
|
||||
// Symbol TEST_CMD2 = "test.command1.2";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* @test verify correct behaviour of all the command handling patterns
|
||||
* provided by the default configuration of a lumiera session.
|
||||
* - executing quasi synchronous
|
||||
* - the throw-on-error variant
|
||||
* - background execution
|
||||
* - ...?
|
||||
* @todo define and code those handling patterns; Ticket #210
|
||||
*
|
||||
* @see HandlingPattern
|
||||
* @see CommandRegistry
|
||||
* @see command.hpp
|
||||
* @see command-basic-test.cpp
|
||||
*/
|
||||
class HandlingPatternStandardImpl_test : public Test
|
||||
{
|
||||
|
||||
uint cnt_inst;
|
||||
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
CommandRegistry& registry = CommandRegistry::instance();
|
||||
ASSERT (®istry);
|
||||
|
||||
cnt_inst = registry.instance_count();
|
||||
|
||||
UNIMPLEMENTED ("unit test to cover the standard command handling patterns");
|
||||
|
||||
ASSERT (cnt_inst == registry.instance_count());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (HandlingPatternStandardImpl_test, "function controller");
|
||||
|
||||
|
||||
}} // namespace control::test
|
||||
|
|
@ -995,7 +995,7 @@ While generally there is //no limitation// on the number and type of parameters,
|
|||
Usually, parameters should be passed //by value// &mdash; with the exception of target object(s), which are typically bound as MObjectRef, causing them to be resolved at commad execution time (late binding).
|
||||
</pre>
|
||||
</div>
|
||||
<div title="CommandHandling" modifier="Ichthyostega" modified="200907212323" created="200906072048" tags="SessionLogic spec draft decision design img" changecount="26">
|
||||
<div title="CommandHandling" modifier="Ichthyostega" modified="200909291418" created="200906072048" tags="SessionLogic spec draft decision design img" changecount="27">
|
||||
<pre>Organising any ''mutating'' operations executable by the user (via GUI) by means of the [[command pattern|http://en.wikipedia.org/wiki/Command_pattern]] can be considered //state of the art//&nbsp; today. First of all, it allows to discern the specific implementation operations to be called on one or several objects within the HighLevelModel from the operation requested by the user, the latter being rather a concept. A command can be labeled clearly, executed under controlled circumstances, allowing transactional behaviour.
|
||||
|
||||
|
||||
|
|
@ -1014,7 +1014,24 @@ While the usual »Memento« implementation might automatically capture the whole
|
|||
A command may be [[defined|CommandDefinition]] completely from scratch, or it might just serve as a CommandPrototype with specific targets and parameters. The command could then be serialised and later be recovered and re-bound with the parameters, but usually it will be handed over to the ProcDispatcher, pending execution. When ''invoking'', the handling sequence is to [[log the command|SessionStorage]], then call the ''undo capture function'', followed from calling the actual ''operation function''. After success, the logging and [[undo registration|UndoManager]] is completed. In any case, finally the ''result signal'' (a functor previously stored within the command) is emitted.
|
||||
|
||||
By design, commands are single-serving value objects; executing an operation repeatedly requires creating a collection of command objects, one for each invocation. While nothing prevents you from invoking the command operation functor several times, each invocation will overwrite the undo state captrued by the previous invocation. Thus, each command instance should bes seen as the promise (or later the trace) of a single operation execution. In a similar vein, the undo capturing should be defined as to be self sufficient, so that invoking just the undo functor of a single command performes any necessary steps to restore the situation found before invoking the corresponding mutation functor &mdash; of course only //with respect to the topic covered by this command.// So, while commands provide a lot of flexibility and allow to do a multitude of things, certainly there is an intended CommandLifecycle.
|
||||
&rarr; more on possible [[command usage scenarios|CommandUsage]]</pre>
|
||||
&rarr; more on possible [[command usage scenarios|CommandUsage]]
|
||||
&rarr; more details regarding [[command implementation|CommandImpl]]
|
||||
</pre>
|
||||
</div>
|
||||
<div title="CommandImpl" modifier="Ichthyostega" modified="200909291434" created="200909291424" tags="spec impl" changecount="9">
|
||||
<pre>Commands are separated in a handle (the {{{control::Command}}}-object), to be used by the client code, and an implementation level, which is managed transparently behind the stages. Client code is assumed to build a CommandDefinition at some point, and from then on to access the command ''by ID'', yielding the command handle.
|
||||
Binding of arguments, invocation and UNDO all are accessible through this frontend.
|
||||
|
||||
!Infrastructure
|
||||
To support this handling scheme, some infrastructure is in place:
|
||||
* a command registry maintains the ID &harr; Command relation.
|
||||
* indirectly, through a custom alloctaor, the registry is also involved into allocation of the command implementation frame
|
||||
* this implementation frame combines
|
||||
** an operation mutation and an undo mutation
|
||||
** a closure, implemented through an argument holder
|
||||
** an undo state capturing mechanism, based on a capturing function provided on definition
|
||||
* performing the actual execution is delegated to a handling pattern object, accessed by name.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="CommandLifecycle" modifier="Ichthyostega" modified="200907240010" created="200907210135" tags="SessionLogic spec draft design img" changecount="10">
|
||||
<pre>[<img[Structure of Commands|uml/fig135173.png]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue