--- /dev/null
+/* -*-c++-*-
+ *
+ * Copyright (C) 2013 James Turner
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <simgear_config.h>
+#endif
+
+#include "StateMachine.hxx"
+
+#include <cassert>
+#include <set>
+#include <boost/foreach.hpp>
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/structure/SGBinding.hxx>
+#include <simgear/props/condition.hxx>
+#include <simgear/timing/timestamp.hxx>
+#include <simgear/structure/exception.hxx>
+
+namespace simgear
+{
+
+typedef std::vector<StateMachine::State_ptr> StatePtrVec;
+
+static void readBindingList(SGPropertyNode* desc, const std::string& name,
+ SGPropertyNode* root, SGBindingList& result)
+{
+ BOOST_FOREACH(SGPropertyNode* b, desc->getChildren(name)) {
+ SGBinding* bind = new SGBinding;
+ bind->read(b, root);
+ result.push_back(bind);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+class StateMachine::State::StatePrivate
+{
+public:
+ std::string _name;
+ SGBindingList _updateBindings,
+ _entryBindings,
+ _exitBindings;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+class StateMachine::Transition::TransitionPrivate
+{
+public:
+ std::string _name;
+ SGBindingList _bindings;
+ std::set<State*> _sourceStates; ///< weak refs to source states
+ State* _target;
+ SGSharedPtr<SGCondition> _condition;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+class StateMachine::StateMachinePrivate : public SGPropertyChangeListener
+{
+public:
+ StateMachinePrivate(StateMachine* p) : _p(p) { }
+
+ void computeEligibleTransitions()
+ {
+ _eligible.clear();
+ BOOST_FOREACH(Transition_ptr t, _transitions) {
+ if (t->applicableForState(_currentState)) {
+ _eligible.push_back(t.ptr());
+ }
+ }
+ }
+
+ StateMachine* _p;
+ State_ptr _currentState;
+ StatePtrVec _states;
+ std::vector<Transition_ptr> _transitions;
+ std::vector<Transition*> _eligible;
+ SGTimeStamp _timeInState;
+
+ bool _listenerLockout; ///< block our listener when self-updating props
+ virtual void valueChanged(SGPropertyNode* changed)
+ {
+ if (_listenerLockout) {
+ return;
+ }
+
+ if (changed == _currentStateIndex) {
+ State_ptr s = _p->stateByIndex(changed->getIntValue());
+ _p->changeToState(s);
+ } else if (changed == _currentStateName) {
+ _p->changeToStateName(changed->getStringValue());
+ }
+ }
+
+// exposed properties
+ SGPropertyNode_ptr _root;
+ SGPropertyNode_ptr _currentStateIndex;
+ SGPropertyNode_ptr _currentStateName;
+ SGPropertyNode_ptr _timeInStateProp;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+StateMachine::State::State(const std::string& aName) :
+ d(new StatePrivate)
+{
+ d->_name = aName;
+}
+
+StateMachine::State::~State()
+{
+}
+
+std::string StateMachine::State::name() const
+{
+ return d->_name;
+}
+
+void StateMachine::State::update()
+{
+ fireBindingList(d->_updateBindings);
+}
+
+void StateMachine::State::fireEntryBindings()
+{
+ fireBindingList(d->_entryBindings);
+}
+
+void StateMachine::State::fireExitBindings()
+{
+ fireBindingList(d->_exitBindings);
+}
+
+void StateMachine::State::addUpdateBinding(SGBinding* aBinding)
+{
+ d->_updateBindings.push_back(aBinding);
+}
+
+void StateMachine::State::addEntryBinding(SGBinding* aBinding)
+{
+ d->_entryBindings.push_back(aBinding);
+}
+
+void StateMachine::State::addExitBinding(SGBinding* aBinding)
+{
+ d->_exitBindings.push_back(aBinding);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+StateMachine::Transition::Transition(const std::string& aName, State* aTarget) :
+ d(new TransitionPrivate)
+{
+ assert(aTarget);
+ d->_name = aName;
+ d->_target = aTarget;
+}
+
+StateMachine::Transition::~Transition()
+{
+}
+
+StateMachine::State* StateMachine::Transition::target() const
+{
+ return d->_target;
+}
+
+void StateMachine::Transition::addSourceState(State* aSource)
+{
+ if (aSource == d->_target) { // should this be disallowed outright?
+ SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source");
+ }
+
+ d->_sourceStates.insert(aSource);
+}
+
+bool StateMachine::Transition::applicableForState(State* aCurrent) const
+{
+ return d->_sourceStates.count(aCurrent);
+}
+
+bool StateMachine::Transition::evaluate() const
+{
+ return d->_condition->test();
+}
+
+void StateMachine::Transition::fireBindings()
+{
+ fireBindingList(d->_bindings);
+}
+
+std::string StateMachine::Transition::name() const
+{
+ return d->_name;
+}
+
+void StateMachine::Transition::setTriggerCondition(SGCondition* aCondition)
+{
+ d->_condition = aCondition;
+}
+
+void StateMachine::Transition::addBinding(SGBinding* aBinding)
+{
+ d->_bindings.push_back(aBinding);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+StateMachine::StateMachine() :
+ d(new StateMachinePrivate(this))
+{
+ d->_root = new SGPropertyNode();
+}
+
+StateMachine::~StateMachine()
+{
+
+}
+
+void StateMachine::init()
+{
+
+
+ d->_currentStateIndex = d->_root->getChild("current-index", 0, true);
+ d->_currentStateIndex->setIntValue(0);
+
+ d->_currentStateName = d->_root->getChild("current-name", 0, true);
+ d->_currentStateName->setStringValue("");
+
+ d->_currentStateIndex->addChangeListener(d.get());
+ d->_currentStateName->addChangeListener(d.get());
+
+ d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true);
+ d->_timeInStateProp->setIntValue(0);
+
+ // TODO go to default state if found
+ d->computeEligibleTransitions();
+
+}
+
+void StateMachine::shutdown()
+{
+ d->_currentStateIndex->removeChangeListener(d.get());
+ d->_currentStateName->removeChangeListener(d.get());
+
+}
+
+void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans)
+{
+ d->_currentState->fireExitBindings();
+
+// fire bindings before we change the state, hmmmm
+ if (aTrans) {
+ aTrans->fireBindings();
+ }
+
+ // update our private state and properties
+ d->_listenerLockout = true;
+ d->_currentState = aState;
+ d->_timeInState.stamp();
+ d->_currentStateName->setStringValue(d->_currentState->name());
+ d->_currentStateIndex->setIntValue(indexOfState(aState));
+ d->_timeInStateProp->setIntValue(0);
+ d->_listenerLockout = false;
+
+ // fire bindings
+ d->_currentState->fireEntryBindings();
+ d->_currentState->update();
+
+ d->computeEligibleTransitions();
+}
+
+void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent)
+{
+ assert(aState != NULL);
+ if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) {
+ throw sg_exception("Requested change to state not in machine");
+ }
+
+ if (aOnlyIfDifferent && (aState == d->_currentState)) {
+ return;
+ }
+
+ innerChangeState(aState, NULL);
+}
+
+void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDifferent)
+{
+ State_ptr st = findStateByName(aName);
+ if (!st) {
+ throw sg_range_exception("unknown state:" + aName);
+ }
+
+ changeToState(st, aOnlyIfDifferent);
+}
+
+StateMachine::State_ptr StateMachine::state() const
+{
+ return d->_currentState;
+}
+
+SGPropertyNode* StateMachine::root()
+{
+ return d->_root;
+}
+
+void StateMachine::update(double aDt)
+{
+ // do this first, for triggers which depend on time in current state
+ // (spring-loaded transitions)
+ d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec());
+
+ Transition_ptr trigger;
+
+ BOOST_FOREACH(Transition* trans, d->_eligible) {
+ if (trans->evaluate()) {
+ if (trigger != Transition_ptr()) {
+ SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! "
+ << trans->name() << " or " << trigger->name());
+ }
+
+ trigger = trans;
+ }
+ }
+
+ if (trigger != Transition_ptr()) {
+ SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name());
+ innerChangeState(trigger->target(), trigger);
+ }
+
+ d->_currentState->update();
+}
+
+StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const
+{
+ BOOST_FOREACH(State_ptr sp, d->_states) {
+ if (sp->name() == aName) {
+ return sp;
+ }
+ }
+
+ SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName);
+ return State_ptr();
+}
+
+StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const
+{
+ if (aIndex >= d->_states.size()) {
+ throw sg_range_exception("invalid state index, out of bounds");
+ }
+
+ return d->_states[aIndex];
+}
+
+int StateMachine::indexOfState(State_ptr aState) const
+{
+ StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState);
+ if (it == d->_states.end()) {
+ return -1;
+ }
+
+ return it - d->_states.begin();
+}
+
+StateMachine::State_ptr StateMachine::createState(const std::string& aName)
+{
+ if (findStateByName(aName) != NULL) {
+ throw sg_range_exception("duplicate state name");
+ }
+
+ State_ptr st = new State(aName);
+ addState(st);
+ return st;
+}
+
+StateMachine::Transition_ptr
+StateMachine::createTransition(const std::string& aName, State_ptr aTarget)
+{
+ Transition_ptr t = new Transition(aName, aTarget);
+ addTransition(t);
+ return t;
+}
+
+StateMachine* StateMachine::createFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
+{
+ StateMachine* sm = new StateMachine;
+
+
+ BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) {
+ std::string nm = stateDesc->getStringValue("name");
+ State_ptr st(new State(nm));
+
+ readBindingList(stateDesc, "enter", root, st->d->_updateBindings);
+ readBindingList(stateDesc, "exit", root, st->d->_entryBindings);
+ readBindingList(stateDesc, "update", root, st->d->_exitBindings);
+
+ sm->addState(st);
+ } // of states iteration
+
+ BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) {
+ std::string nm = tDesc->getStringValue("name");
+ State_ptr target = sm->findStateByName(tDesc->getStringValue("target"));
+
+ SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition"));
+
+ Transition_ptr t(new Transition(nm, target));
+ t->setTriggerCondition(cond);
+
+ BOOST_FOREACH(SGPropertyNode* src, desc->getChildren("source")) {
+ State_ptr srcState = sm->findStateByName(src->getStringValue());
+ t->addSourceState(srcState);
+ }
+
+ readBindingList(tDesc, "binding", root, t->d->_bindings);
+
+ sm->addTransition(t);
+ } // of states iteration
+
+ return sm;
+}
+
+void StateMachine::addState(State_ptr aState)
+{
+ bool wasEmpty = d->_states.empty();
+ d->_states.push_back(aState);
+ if (wasEmpty) {
+ d->_currentState = aState;
+ }
+}
+
+void StateMachine::addTransition(Transition_ptr aTrans)
+{
+ d->_transitions.push_back(aTrans);
+}
+
+} // of namespace simgear
--- /dev/null
+/* -*-c++-*-
+ *
+ * Copyright (C) 2013 James Turner
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SIMGEAR_STATE_MACHINE_H
+#define SIMGEAR_STATE_MACHINE_H
+
+#include <memory>
+
+#include <simgear/structure/SGReferenced.hxx>
+#include <simgear/structure/SGSharedPtr.hxx>
+
+// forward decls
+class SGPropertyNode;
+class SGBinding;
+class SGCondition;
+
+namespace simgear
+{
+
+class StateMachine : public SGReferenced
+{
+public:
+ StateMachine();
+ virtual ~StateMachine();
+
+ class State : public SGReferenced
+ {
+ public:
+ virtual ~State();
+
+ std::string name() const;
+
+ void addUpdateBinding(SGBinding* aBinding);
+ void addEntryBinding(SGBinding* aBinding);
+ void addExitBinding(SGBinding* aBinding);
+
+ private:
+ friend class StateMachine;
+
+ State(const std::string& name);
+
+ void fireExitBindings();
+ void fireEntryBindings();
+
+ void update();
+
+ class StatePrivate;
+ std::auto_ptr<StatePrivate> d;
+ };
+
+ class Transition : public SGReferenced
+ {
+ public:
+ virtual ~Transition();
+
+ std::string name() const;
+
+ /**
+ * The state we end in, after this transition fires
+ */
+ State* target() const;
+
+ /**
+ * Add a state in which this transition is eligible to fire
+ */
+ void addSourceState(State* aSource);
+
+ /**
+ * Specify the transition trigger condition. Takes ownership
+ */
+ void setTriggerCondition(SGCondition* aCondition);
+
+
+ void addBinding(SGBinding* aBinding);
+ private:
+ friend class StateMachine;
+
+ Transition(const std::string& aName, State* aTarget);
+
+ /**
+ * predicate to determine if this transition can fire given a
+ * current state.
+ */
+ bool applicableForState(State* aCurrent) const;
+
+ /**
+ * test if the transition should fire, based on current state
+ */
+ bool evaluate() const;
+
+ void fireBindings();
+
+ class TransitionPrivate;
+ std::auto_ptr<TransitionPrivate> d;
+ };
+
+ typedef SGSharedPtr<State> State_ptr;
+ typedef SGSharedPtr<Transition> Transition_ptr;
+
+ /**
+ * create a state machine from a property list description
+ */
+ static StateMachine* createFromPlist(SGPropertyNode* desc, SGPropertyNode* root);
+
+ SGPropertyNode* root();
+
+ void init();
+ void shutdown();
+
+ void update(double dt);
+
+ State_ptr state() const;
+
+ /**
+ * public API to force a change to a particular state.
+ * @param aOnlyIfDifferent - only make a transition if the new state is
+ * different from the current state. Otherwise, the existing state will
+ * be exited and re-entered.
+ */
+ void changeToState(State_ptr aState, bool aOnlyIfDifferent=true);
+
+ /// wrapper to change state by looking up a name
+ void changeToStateName(const std::string& aName, bool aOnlyIfDifferent=true);
+
+ State_ptr findStateByName(const std::string& aName) const;
+
+ State_ptr stateByIndex(unsigned int aIndex) const;
+
+ int indexOfState(State_ptr aState) const;
+
+ // programatic creation
+ State_ptr createState(const std::string& aName);
+ Transition_ptr createTransition(const std::string& aName, State_ptr aTarget);
+private:
+ void addState(State_ptr aState);
+ void addTransition(Transition_ptr aTrans);
+
+ void innerChangeState(State_ptr aState, Transition_ptr aTrans);
+
+ class StateMachinePrivate;
+ std::auto_ptr<StateMachinePrivate> d;
+};
+
+typedef SGSharedPtr<StateMachine> StateMachine_ptr;
+
+} // of simgear namespace
+
+#endif // of SIMGEAR_STATE_MACHINE_H
--- /dev/null
+#ifdef HAVE_CONFIG_H
+# include <simgear_config.h>
+#endif
+
+#ifdef NDEBUG
+// Always enable DEBUG mode in test application, otherwise "assert" test
+// statements have no effect and don't actually test anything (catch 17 ;-) ).
+#undef NDEBUG
+#endif
+
+#include <simgear/compiler.h>
+
+#include <iostream>
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+
+#include "StateMachine.hxx"
+
+#include <simgear/structure/SGBinding.hxx>
+#include <simgear/structure/exception.hxx>
+#include <simgear/props/condition.hxx>
+#include <simgear/props/props.hxx>
+#include <simgear/props/props_io.hxx>
+#include <simgear/structure/commands.hxx>
+
+using std::string;
+using std::cout;
+using std::cerr;
+using std::endl;
+
+// SGCondition subclass we can trivially manipulate from test code.
+class DummyCondition : public SGCondition
+{
+public:
+ DummyCondition(): _state(false) { }
+
+ virtual bool test() const
+ {
+ return _state;
+ }
+
+ bool _state;
+};
+
+static int dummy_cmd_state = 0;
+
+bool dummyCommand(const SGPropertyNode* arg)
+{
+ ++dummy_cmd_state;
+ return true;
+}
+
+#define COMPARE(a, b) \
+ if ((a) != (b)) { \
+ cerr << "failed:" << #a << " != " << #b << endl; \
+ cerr << "\tgot:'" << a << "'" << endl; \
+ exit(1); \
+ }
+
+#define VERIFY(a) \
+ if (!(a)) { \
+ cerr << "failed:" << #a << endl; \
+ exit(1); \
+ }
+
+using namespace simgear;
+
+#define BUILD_MACHINE_1() \
+ StateMachine_ptr sm(new StateMachine); \
+ StateMachine::State_ptr stateA = sm->createState("a"); \
+ StateMachine::State_ptr stateB = sm->createState("b"); \
+ StateMachine::State_ptr stateC = sm->createState("c"); \
+ \
+ DummyCondition* trigger1 = new DummyCondition; \
+ StateMachine::Transition_ptr t1 = sm->createTransition(">b", stateB); \
+ t1->addSourceState(stateA); \
+ t1->setTriggerCondition(trigger1); \
+ \
+ DummyCondition* trigger2 = new DummyCondition; \
+ StateMachine::Transition_ptr t2 = sm->createTransition(">c", stateC); \
+ t2->addSourceState(stateB); \
+ t2->setTriggerCondition(trigger2); \
+ \
+ DummyCondition* trigger3 = new DummyCondition; \
+ StateMachine::Transition_ptr t3 = sm->createTransition(">a", stateA); \
+ t3->addSourceState(stateC); \
+ t3->addSourceState(stateB); \
+ t3->setTriggerCondition(trigger3); \
+ sm->init();
+
+void testBasic()
+{
+ BUILD_MACHINE_1();
+////////////////////////////////////////////
+ COMPARE(sm->state()->name(), "a");
+
+ COMPARE(sm->indexOfState(stateA), 0);
+ COMPARE(sm->findStateByName("c"), stateC);
+
+ sm->changeToState(stateC);
+ COMPARE(sm->state(), stateC);
+
+ trigger3->_state = true;
+ sm->update(1.0);
+ COMPARE(sm->state()->name(), "a");
+ trigger3->_state = false;
+
+ trigger1->_state = true;
+ sm->update(1.0);
+ trigger1->_state = false;
+ COMPARE(sm->state()->name(), "b");
+
+ trigger3->_state = true;
+ sm->update(1.0);
+ COMPARE(sm->state()->name(), "a");
+ trigger3->_state = false;
+
+ trigger1->_state = true;
+ sm->update(1.0);
+ trigger1->_state = false;
+ COMPARE(sm->state()->name(), "b");
+
+ trigger2->_state = true;
+ sm->update(1.0);
+ trigger2->_state = false;
+ COMPARE(sm->state()->name(), "c");
+
+//////////////////////////////////////////
+ COMPARE(sm->root()->getIntValue("current-index"), 2);
+ COMPARE(sm->root()->getStringValue("current-name"), string("c"));
+
+ sm->root()->setStringValue("current-name", "b");
+ COMPARE(sm->state()->name(), "b");
+
+////////////////////////////////////////
+ COMPARE(sm->findStateByName("foo"), NULL);
+ COMPARE(sm->indexOfState(StateMachine::State_ptr()), -1);
+
+ COMPARE(sm->stateByIndex(1), stateB);
+
+ try {
+ sm->stateByIndex(44);
+ VERIFY(false && "should have raised an exception");
+ } catch (sg_exception& e){
+ // expected!
+ }
+}
+
+void testBindings()
+{
+ SGCommandMgr* cmdMgr = SGCommandMgr::instance();
+ cmdMgr->addCommand("dummy", dummyCommand);
+ BUILD_MACHINE_1();
+
+ t2->addBinding(new SGBinding("dummy"));
+
+ stateA->addEntryBinding(new SGBinding("dummy"));
+ stateA->addExitBinding(new SGBinding("dummy"));
+ stateC->addEntryBinding(new SGBinding("dummy"));
+
+////////////////////////
+ COMPARE(sm->state()->name(), "a");
+ trigger1->_state = true;
+ sm->update(1.0);
+ trigger1->_state = false;
+ COMPARE(sm->state()->name(), "b");
+ COMPARE(dummy_cmd_state, 1); // exit state A
+
+ trigger2->_state = true;
+ sm->update(1.0);
+ trigger2->_state = false;
+ COMPARE(dummy_cmd_state, 3); // fire transition 2, enter state C
+
+ dummy_cmd_state = 0;
+ sm->changeToState(stateA);
+ COMPARE(dummy_cmd_state, 1); // enter state A
+ trigger1->_state = true;
+ sm->update(1.0);
+ trigger1->_state = false;
+ COMPARE(dummy_cmd_state, 2); // exit state A
+
+////////////////////////
+ t3->addBinding(new SGBinding("dummy"));
+ t3->addBinding(new SGBinding("dummy"));
+ t3->addBinding(new SGBinding("dummy"));
+
+ sm->changeToStateName("b");
+ dummy_cmd_state = 0;
+ trigger3->_state = true;
+ sm->update(1.0);
+ trigger3->_state = false;
+ COMPARE(dummy_cmd_state, 4); // three transition bindings, enter A
+}
+
+void testParse()
+{
+ const char* xml = "<?xml version=\"1.0\"?>"
+ "<PropertyList>"
+ "<state>"
+ "<name>one</name>"
+ "</state>"
+ "<state>"
+ "<name>two</name>"
+ "</state>"
+ "</PropertyList>";
+
+ SGPropertyNode* desc = new SGPropertyNode;
+ readProperties(xml, strlen(xml), desc);
+
+ SGPropertyNode_ptr root(new SGPropertyNode);
+ StateMachine_ptr sm = StateMachine::createFromPlist(desc, root);
+
+ VERIFY(sm->findStateByName("one") != NULL);
+ VERIFY(sm->findStateByName("two") != NULL);
+}
+
+int main(int argc, char* argv[])
+{
+ testBasic();
+ testBindings();
+ testParse();
+ cout << __FILE__ << ": All tests passed" << endl;
+ return EXIT_SUCCESS;
+}
+