diff --git a/Builds/VisualStudio2012/RippleD.vcxproj b/Builds/VisualStudio2012/RippleD.vcxproj
index cd304a991..a553ceb50 100644
--- a/Builds/VisualStudio2012/RippleD.vcxproj
+++ b/Builds/VisualStudio2012/RippleD.vcxproj
@@ -778,6 +778,12 @@
true
+
+ true
+ true
+ true
+ true
+
true
true
@@ -1501,6 +1507,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Builds/VisualStudio2012/RippleD.vcxproj.filters b/Builds/VisualStudio2012/RippleD.vcxproj.filters
index c4b8552b5..c9578065f 100644
--- a/Builds/VisualStudio2012/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2012/RippleD.vcxproj.filters
@@ -151,6 +151,9 @@
{1d9bc26d-d76e-4fd4-a737-b968e31e614b}
+
+ {129c51dc-e885-4023-999b-e133d800fb80}
+
@@ -864,6 +867,9 @@
[1] Ripple\ripple_core\validator
+
+ [1] Ripple\ripple_core\test
+
@@ -1685,6 +1691,36 @@
[1] Ripple\ripple_core\validator
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
+
+ [1] Ripple\ripple_core\test
+
diff --git a/modules/ripple_core/ripple_core.cpp b/modules/ripple_core/ripple_core.cpp
index e01ad2999..3299d9d62 100644
--- a/modules/ripple_core/ripple_core.cpp
+++ b/modules/ripple_core/ripple_core.cpp
@@ -13,6 +13,7 @@
#include "beast/modules/beast_core/system/BeforeBoost.h" // must come first
#include
#include
+#include
// For NodeStore backends
#include "beast/modules/beast_db/beast_db.h"
@@ -46,6 +47,18 @@ namespace ripple
#include "node/NodeStore.cpp"
#include "node/NodeObject.cpp"
+# include "test/Results.h"
+# include "test/SimplePayload.h"
+# include "test/MessageType.h"
+# include "test/ConnectionType.h"
+# include "test/PeerType.h"
+# include "test/NetworkType.h"
+# include "test/StateBase.h"
+# include "test/PeerLogicBase.h"
+# include "test/InitPolicy.h"
+# include "test/ConfigType.h"
+#include "test/TestOverlay.cpp"
+
# include "validator/Validator.h"
#include "validator/Validator.cpp"
# include "validator/ValidatorSourceStrings.h"
diff --git a/modules/ripple_core/test/ConfigType.h b/modules/ripple_core/test/ConfigType.h
new file mode 100644
index 000000000..088e9b232
--- /dev/null
+++ b/modules/ripple_core/test/ConfigType.h
@@ -0,0 +1,44 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_CONFIGTYPE_H_INCLUDED
+#define RIPPLE_CORE_TEST_CONFIGTYPE_H_INCLUDED
+
+/** A simulated peer to peer network for unit tests. */
+namespace TestOverlay
+{
+
+/** Combines Params with standard Config requirements for test objects. */
+template <
+ class Params,
+ template class StateType = StateBase,
+ template class PeerLogicType = PeerLogicBase
+>
+class ConfigType
+{
+public:
+ // These defaults can be overridden in
+ // Params simply by adding declarations to it.
+
+ static int64 const randomSeedValue = 42;
+
+ typedef std::size_t SizeType;
+
+ typedef SimplePayload Payload;
+
+ typedef StateType State;
+ typedef MessageType Message;
+ typedef NetworkType Network;
+
+ typedef PeerType Peer;
+ typedef PeerLogicType PeerLogic;
+
+ typedef NoInitPolicy InitPolicy;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/ConnectionType.h b/modules/ripple_core/test/ConnectionType.h
new file mode 100644
index 000000000..78156a151
--- /dev/null
+++ b/modules/ripple_core/test/ConnectionType.h
@@ -0,0 +1,173 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_CONNECTIONTYPE_H_INCLUDED
+#define RIPPLE_CORE_TEST_CONNECTIONTYPE_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/** A connection between two nodes. */
+template
+class ConnectionType : public Config
+{
+public:
+ using typename Config::Peer;
+ using typename Config::Message;
+
+ typedef std::vector Messages;
+ typedef typename Config::State::UniqueID UniqueID;
+ typedef boost::unordered_set MessageTable;
+
+ /** Create the 'no connection' object. */
+ ConnectionType ()
+ : m_peer (nullptr)
+ {
+ }
+
+ ConnectionType (Peer& peer, bool inbound)
+ : m_peer (&peer)
+ , m_inbound (inbound)
+ {
+ }
+
+ ConnectionType (ConnectionType const& other)
+ : m_peer (other.m_peer)
+ , m_inbound (other.m_inbound)
+ {
+ }
+
+ ConnectionType& operator= (ConnectionType const& other)
+ {
+ m_peer = other.m_peer;
+ m_inbound = other.m_inbound;
+ }
+
+ /** Returns `true` if there is no connection. */
+ bool empty () const
+ {
+ return m_peer == nullptr;
+ }
+
+ /** Returns `true` if this is an inbound connection.
+ If there is no connection, the return value is undefined.
+ */
+ bool inbound () const
+ {
+ return m_inbound;
+ }
+
+ /** Returns the peer on the other end.
+ If there is no connection, the return value is undefined.
+ */
+ /** @{ */
+ Peer& peer ()
+ {
+ return *m_peer;
+ }
+
+ Peer const& peer () const
+ {
+ return *m_peer;
+ }
+ /** @} */
+
+ /** Returns a container with the current step's incoming messages. */
+ /** @{ */
+ Messages& messages ()
+ {
+ return m_messages;
+ }
+
+ Messages const& messages () const
+ {
+ return m_messages;
+ }
+ /** @} */
+
+ /** Returns a container with the next step's incoming messages.
+ During each step, peers process the current step's message
+ list, but post new messages to the pending messages list.
+ This way, new messages will always process in the next step
+ and not the current one.
+ */
+ /** @{ */
+ Messages& pending ()
+ {
+ return m_pending;
+ }
+
+ Messages const& pending () const
+ {
+ return m_pending;
+ }
+ /** @} */
+
+
+ //--------------------------------------------------------------------------
+
+ /** A UnaryPredicate that always returns true. */
+ class Any
+ {
+ public:
+ bool operator() (ConnectionType const&) const
+ {
+ return true;
+ }
+ };
+
+ //--------------------------------------------------------------------------
+
+ /** A UnaryPredicate that returns `true` if the peer matches. */
+ class IsPeer
+ {
+ public:
+ explicit IsPeer (Peer const& peer)
+ : m_peer (&peer)
+ {
+ }
+
+ bool operator() (ConnectionType const& connection) const
+ {
+ return &connection.peer () == m_peer;
+ }
+
+ private:
+ Peer const* m_peer;
+ };
+
+ //--------------------------------------------------------------------------
+
+ /** A UnaryPredicate that returns `true` if the peer does not match. */
+ class IsNotPeer
+ {
+ public:
+ explicit IsNotPeer (Peer const& peer)
+ : m_peer (&peer)
+ {
+ }
+
+ bool operator() (ConnectionType const& connection) const
+ {
+ return &connection.peer () != m_peer;
+ }
+
+ private:
+ Peer const* m_peer;
+ };
+
+ //--------------------------------------------------------------------------
+
+private:
+ Peer* m_peer;
+ bool m_inbound;
+ Messages m_messages;
+ Messages m_pending;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/InitPolicy.h b/modules/ripple_core/test/InitPolicy.h
new file mode 100644
index 000000000..a57c57ecb
--- /dev/null
+++ b/modules/ripple_core/test/InitPolicy.h
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_INITPOLICY_H_INCLUDED
+#define RIPPLE_CORE_TEST_INITPOLICY_H_INCLUDED
+
+/** A simulated peer to peer network for unit tests. */
+namespace TestOverlay
+{
+
+//------------------------------------------------------------------------------
+//
+// InitPolicy
+//
+// This is called during construction to form the network.
+//
+
+/** InitPolicy which does nothing. */
+class NoInitPolicy
+{
+public:
+ template
+ void operator() (Network& network)
+ {
+ }
+};
+
+//------------------------------------------------------------------------------
+
+/** Init policy for a pre-built connected network. */
+template
+class PremadeInitPolicy
+{
+public:
+ static int const numberOfPeers = NumberOfPeers;
+ static int const outgoingConnectionsPerPeer = OutgoingConnectionsPerPeer;
+
+ template
+ void operator() (Network& network)
+ {
+ typedef typename Network::Peer Peer;
+ typedef typename Network::Peers Peers;
+ typedef typename Network::Config Config;
+ typedef typename Config::SizeType SizeType;
+
+ for (SizeType i = 0; i < numberOfPeers; ++i)
+ network.createPeer ();
+
+ Peers& peers (network.peers ());
+ for (SizeType i = 0; i < numberOfPeers; ++i)
+ {
+ Peer& peer (*peers [i]);
+ for (SizeType j = 0; j < outgoingConnectionsPerPeer; ++j)
+ {
+ for (;;)
+ {
+ SizeType k (network.state ().random ().nextInt (numberOfPeers));
+ if (peer.connect_to (*peers [k]))
+ break;
+ }
+ }
+ }
+ }
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/MessageType.h b/modules/ripple_core/test/MessageType.h
new file mode 100644
index 000000000..c724c5d6d
--- /dev/null
+++ b/modules/ripple_core/test/MessageType.h
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_MESSAGETYPE_H_INCLUDED
+#define RIPPLE_CORE_TEST_MESSAGETYPE_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/** A message sent between peers. */
+template
+class MessageType : public Config
+{
+public:
+ typedef typename Config::State::UniqueID UniqueID;
+ using typename Config::Payload;
+
+ MessageType ()
+ : m_id (0)
+ {
+ }
+
+ MessageType (UniqueID id, Payload payload)
+ : m_id (id)
+ , m_payload (payload)
+ {
+ }
+
+ MessageType (MessageType const& other)
+ : m_id (other.m_id)
+ , m_payload (other.m_payload)
+ {
+ }
+
+ MessageType& operator= (MessageType const& other)
+ {
+ m_id = other.m_id;
+ m_payload = other.m_payload;
+ return *this;
+ }
+
+ UniqueID id () const
+ {
+ return m_id;
+ }
+
+ Payload payload () const
+ {
+ return m_payload;
+ }
+
+private:
+ UniqueID m_id;
+ Payload m_payload;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/NetworkType.h b/modules/ripple_core/test/NetworkType.h
new file mode 100644
index 000000000..a92e3b678
--- /dev/null
+++ b/modules/ripple_core/test/NetworkType.h
@@ -0,0 +1,130 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_NETWORKTYPE_H_INCLUDED
+#define RIPPLE_CORE_TEST_NETWORKTYPE_H_INCLUDED
+
+namespace TestOverlay
+{
+
+template
+class NetworkType : public ConfigParam
+{
+public:
+ typedef ConfigParam Config;
+
+ using typename Config::SizeType;
+ using typename Config::State;
+ using typename Config::Peer;
+
+ typedef std::vector > Peers;
+
+ NetworkType ()
+ : m_steps (0)
+ {
+ typename Config::InitPolicy () (*this);
+ }
+
+ /** Return the number of steps taken in the simulation. */
+ SizeType steps () const
+ {
+ return m_steps;
+ }
+
+ /** Return the size of the network measured in peers. */
+ SizeType size () const
+ {
+ return m_peers.size ();
+ }
+
+ /** Retrieve the state information associated with the Config. */
+ State& state ()
+ {
+ return m_state;
+ }
+
+ /** Create new Peer. */
+ Peer& createPeer ()
+ {
+ Peer* peer (new Peer (*this));
+ m_peers.push_back (peer);
+ return *peer;
+ }
+
+ /** Retrieve the container holding the set of peers. */
+ Peers& peers ()
+ {
+ return m_peers;
+ }
+
+ /** Run the network for 1 iteration. */
+ Results step ()
+ {
+ Results results;
+ for (typename Peers::iterator iter = m_peers.begin ();
+ iter!= m_peers.end (); ++iter)
+ (*iter)->pre_step ();
+ for (typename Peers::iterator iter = m_peers.begin ();
+ iter!= m_peers.end (); ++iter)
+ (*iter)->step ();
+ ++results.steps;
+ ++m_steps;
+ for (typename Peers::iterator iter = m_peers.begin ();
+ iter!= m_peers.end (); ++iter)
+ {
+ Peer& peer (**iter);
+ peer.post_step ();
+ results = results + peer.results();
+ peer.results() = Results();
+ }
+ return results;
+ }
+
+ /** Run the network until a condition is met.
+ Requirements:
+ p (*this) is well-formed and returns bool.
+ */
+ template
+ Results step_until (Predicate p)
+ {
+ Results results;
+ while (! p (*this))
+ results += step ();
+ return results;
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** A UnaryPredicate that returns true after # steps have passed. */
+ class Steps
+ {
+ public:
+ explicit Steps (SizeType steps)
+ : m_steps (steps)
+ {
+ }
+
+ bool operator() (NetworkType const&)
+ {
+ if (m_steps == 0)
+ return true;
+ --m_steps;
+ return false;
+ }
+
+ private:
+ SizeType m_steps;
+ };
+
+private:
+ State m_state;
+ SizeType m_steps;
+ Peers m_peers;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/PeerLogicBase.h b/modules/ripple_core/test/PeerLogicBase.h
new file mode 100644
index 000000000..bfed1fce8
--- /dev/null
+++ b/modules/ripple_core/test/PeerLogicBase.h
@@ -0,0 +1,69 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_PEERLOGICBASE_H_INCLUDED
+#define RIPPLE_CORE_TEST_PEERLOGICBASE_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/** Base class for all PeerLogic implementations.
+ This provides stubs for all necessary functions, although
+ they don't actually do anything.
+*/
+template
+class PeerLogicBase : public Config
+{
+public:
+ typedef typename Config::Peer Peer;
+ typedef typename Peer::Connection Connection;
+ typedef typename Connection::Message Message;
+
+ explicit PeerLogicBase (Peer& peer)
+ : m_peer (peer)
+ {
+ }
+
+ /** Return the Peer associated with this logic. */
+ /** @{ */
+ Peer& peer ()
+ {
+ return m_peer;
+ }
+
+ Peer const& peer () const
+ {
+ return m_peer;
+ }
+ /** @} */
+
+ // Called to process a message
+ void receive (Connection const& c, Message const& m)
+ {
+ }
+
+ // Called before taking a step
+ void pre_step ()
+ {
+ }
+
+ // Called during a step
+ void step ()
+ {
+ }
+
+ // Called after a step is taken
+ void post_step ()
+ {
+ }
+
+private:
+ Peer& m_peer;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/PeerType.h b/modules/ripple_core/test/PeerType.h
new file mode 100644
index 000000000..99023ca07
--- /dev/null
+++ b/modules/ripple_core/test/PeerType.h
@@ -0,0 +1,283 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_PEERTYPE_H_INCLUDED
+#define RIPPLE_CORE_TEST_PEERTYPE_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/** A peer in the overlay network. */
+template
+class PeerType
+ : public Config
+ , public Uncopyable
+{
+public:
+ using typename Config::Peer;
+ using typename Config::Payload;
+ using typename Config::PeerLogic;
+ using typename Config::Message;
+ using typename Config::Network;
+ typedef typename Config::State::UniqueID UniqueID;
+ typedef ConnectionType Connection;
+ typedef std::vector Connections;
+ typedef boost::unordered_set MessageTable;
+
+ explicit PeerType (Network& network)
+ : m_network (network)
+ , m_id (network.state().nextPeerID())
+ , m_logic (*this)
+ {
+ }
+
+ /** Return the pending Results data associated with this peer. */
+ /** @{ */
+ Results& results ()
+ {
+ return m_results;
+ }
+
+ Results const& results () const
+ {
+ return m_results;
+ }
+ /** @} */
+
+ /** Return the unique ID associated with this peer. */
+ UniqueID id () const
+ {
+ return m_id;
+ }
+
+ /** Return the network this peer belongs to. */
+ /** @{ */
+ Network& network ()
+ {
+ return m_network;
+ }
+
+ Network const& network () const
+ {
+ return m_network;
+ }
+ /** @} */
+
+ /** Return the container holding active connections. */
+ /** @{ */
+ Connections& connections ()
+ {
+ return m_connections;
+ }
+
+ Connections const& connections () const
+ {
+ return m_connections;
+ }
+ /** @} */
+
+ /** Return the container holding the message ids seen by this peer. */
+ /** @{ */
+ MessageTable& msg_table ()
+ {
+ return m_msg_table;
+ }
+
+ MessageTable const& msg_table () const
+ {
+ return m_msg_table;
+ }
+ /** @} */
+
+ /** Establish an outgoing connection to peer.
+ @return `true` if the peer is not us and not connected already.
+ */
+ bool connect_to (Peer& peer)
+ {
+ if (&peer == this)
+ return false;
+ typename Connections::iterator const iter (std::find_if (
+ connections().begin(), connections().end (),
+ typename Connection::IsPeer (peer)));
+ if (iter != connections().end())
+ return false;
+ check_postcondition (std::find_if (peer.connections().begin(),
+ peer.connections().end(),
+ typename Connection::IsPeer (*this))
+ == peer.connections().end ());
+ connections().push_back (Connection (peer, false));
+ peer.connections().push_back (Connection (*this, true));
+ return true;
+ }
+
+ /** Disconnect from a peer.
+ @return `true` if the peer was found and disconnected.
+ */
+ bool disconnect (Peer& peer)
+ {
+ if (&peer == this)
+ return false;
+ typename Connections::iterator const iter1 (std::find_if (
+ connections().begin(), connections().end (),
+ typename Connection::IsPeer (peer)));
+ if (iter1 == connections().end())
+ return false;
+ typename Connections::iterator const iter2 (std::find_if (
+ peer.connections().begin(), peer.connections().end (),
+ typename Connection::IsPeer (*this)));
+ check_postcondition (iter2 != peer.connections().end());
+ connections().erase (iter1);
+ peer.connections().erase (iter2);
+ return true;
+ }
+
+ //--------------------------------------------------------------------------
+
+ /** Send a new message to a specific connection.
+ A new message with an unused id is created with the given payload.
+ */
+ void send (Peer& peer, Payload const& payload)
+ {
+ Message const m (network().state().nextMessageID(), payload);
+ check_postcondition (msg_table().insert (m.id()).second);
+ check_postcondition (send_to (peer,
+ Message (network().state().nextMessageID(),
+ payload)));
+ }
+
+ /** Send a message to a specific connection.
+ The message already has an id and associated payload.
+ */
+ bool send (Peer& peer, Message const& m)
+ {
+ return send_to (peer, m);
+ }
+
+ /** Send a new message to all connections.
+ A new message with an unused id is created with the given payload.
+ */
+ void send_all (Payload const& payload)
+ {
+ Message const m (network().state().nextMessageID(), payload);
+ check_postcondition (msg_table().insert (m.id()).second);
+ check_postcondition (send_all_if (m,
+ typename Connection::Any ()));
+ };
+
+ /** Send a message to all connections.
+ The message already has an id and associated payload.
+ */
+ bool send_all (Message const& m)
+ {
+ return send_all_if (m,
+ typename Connection::Any ());
+ };
+
+ /** Create a new message and send it to each connection that passes the predicate.
+ Predicate is a UnaryPredicate that takes a Connection parameter.
+ A new message with an unused id is created with the given payload.
+ */
+ template
+ void send_all_if (Payload const& payload, Predicate p)
+ {
+ Message const m (network().state().nextMessageID(), payload);
+ check_postcondition (msg_table().insert (m.id()).second);
+ check_postcondition (send_all_if (m, p));
+ }
+
+ /** Send an existing message to all connections that pass the predicate.
+ @return `true` if at least one message was sent.
+ */
+ template
+ bool send_all_if (Message const& m, Predicate p)
+ {
+ bool sent = false;
+ for (typename Connections::iterator iter (connections().begin());
+ iter != connections().end(); ++iter)
+ if (p (*iter))
+ sent = send_to (iter->peer(), m) || sent;
+ return sent;
+ }
+
+private:
+ // Low level send function, everything goes through this.
+ // Returns true if the message was sent.
+ //
+ bool send_to (Peer& peer, Message const& m)
+ {
+ // already seen it?
+ if (peer.msg_table().count(m.id()) != 0)
+ {
+ ++results().dropped;
+ return false;
+ }
+ typename Connections::iterator const iter (std::find_if (
+ peer.connections().begin(), peer.connections().end (),
+ typename Connection::IsPeer (*this)));
+ check_postcondition (iter != peer.connections().end());
+ check_postcondition (peer.msg_table().insert(m.id()).second);
+ iter->pending().push_back (m);
+ ++results().sent;
+ return true;
+ }
+
+public:
+ //--------------------------------------------------------------------------
+
+ /** Called once on each Peer object before every iteration. */
+ void pre_step ()
+ {
+ m_logic.pre_step ();
+ }
+
+ /** Called once on each Peer object during every iteration. */
+ void step ()
+ {
+ // Call logic with current messages
+ for (typename Connections::iterator iter (connections().begin());
+ iter != connections().end(); ++iter)
+ {
+ Connection& c (*iter);
+ for (typename Connection::Messages::iterator iter (
+ c.messages().begin()); iter != c.messages().end(); ++iter)
+ {
+ Message const& m (*iter);
+ check_precondition (msg_table().count (m.id()) == 1);
+ m_logic.receive (c, m);
+ ++results().received;
+ }
+ }
+
+ m_logic.step ();
+ }
+
+ /** Called once on each Peer object after every iteration. */
+ void post_step ()
+ {
+ // Move pending messages to current messages
+ for (typename Connections::iterator iter (connections().begin());
+ iter != connections().end(); ++iter)
+ {
+ Connection& c (*iter);
+ c.messages().clear ();
+ c.messages().swap (c.pending());
+ }
+
+ m_logic.post_step ();
+ }
+
+private:
+ Results m_results;
+ Network& m_network;
+ UniqueID const m_id;
+ Connections m_connections;
+ MessageTable m_msg_table;
+ PeerLogic m_logic; // must come last
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/Results.h b/modules/ripple_core/test/Results.h
new file mode 100644
index 000000000..64b4d429d
--- /dev/null
+++ b/modules/ripple_core/test/Results.h
@@ -0,0 +1,78 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_RESULTS_H_INCLUDED
+#define RIPPLE_CORE_TEST_RESULTS_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/** Accumulates statistics on one or more simulation steps. */
+struct Results
+{
+ Results ()
+ : steps (0)
+ , sent (0)
+ , received (0)
+ , dropped (0)
+ {
+ }
+
+ Results (Results const& other)
+ : steps (other.steps)
+ , sent (other.sent)
+ , received (other.received)
+ , dropped (other.dropped)
+ {
+ }
+
+ Results& operator= (Results const& other)
+ {
+ steps = other.steps;
+ sent = other.sent;
+ received = other.received;
+ dropped = other.dropped;
+ return *this;
+ }
+
+ String toString () const
+ {
+ String s;
+ s = "steps(" + String::fromNumber (steps) + ")"
+ + ", sent(" + String::fromNumber (sent) + ")"
+ + ", received(" + String::fromNumber (received) + ")"
+ + ", dropped(" + String::fromNumber (dropped) + ")";
+ return s;
+ }
+
+ Results& operator+= (Results const& other)
+ {
+ steps += other.steps;
+ sent += other.sent;
+ received += other.received;
+ dropped += other.dropped;
+ return *this;
+ }
+
+ Results operator+ (Results const& other)
+ {
+ Results results;
+ results.steps = steps + other.steps;
+ results.sent = sent + other.sent;
+ results.received = received + other.received;
+ results.dropped = dropped + other.dropped;
+ return results;
+ }
+
+ int steps;
+ int sent;
+ int received;
+ int dropped;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/SimplePayload.h b/modules/ripple_core/test/SimplePayload.h
new file mode 100644
index 000000000..e4148349e
--- /dev/null
+++ b/modules/ripple_core/test/SimplePayload.h
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_SIMPLEPAYLOAD_H_INCLUDED
+#define RIPPLE_CORE_TEST_SIMPLEPAYLOAD_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/** A simple message payload. */
+class SimplePayload
+{
+public:
+ SimplePayload ()
+ {
+ }
+
+ SimplePayload (int what, String data = String::empty, int hops = 0)
+ : m_hops (hops)
+ , m_what (what)
+ , m_data (data)
+ {
+ }
+
+ SimplePayload (SimplePayload const& other)
+ : m_hops (other.m_hops)
+ , m_what (other.m_what)
+ , m_data (other.m_data)
+ {
+ }
+
+ SimplePayload& operator= (SimplePayload const& other)
+ {
+ m_hops = other.m_hops;
+ m_what = other.m_what;
+ m_data = other.m_data;
+ return *this;
+ }
+
+ SimplePayload withHop () const
+ {
+ return SimplePayload (m_what, m_data, m_hops + 1);
+ }
+
+ int hops () const
+ {
+ return m_hops;
+ }
+
+ int what () const
+ {
+ return m_what;
+ }
+
+ String data () const
+ {
+ return m_data;
+ }
+
+private:
+ int m_hops;
+ int m_what;
+ String m_data;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/StateBase.h b/modules/ripple_core/test/StateBase.h
new file mode 100644
index 000000000..3fec6d9b2
--- /dev/null
+++ b/modules/ripple_core/test/StateBase.h
@@ -0,0 +1,53 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_CORE_TEST_STATEBASE_H_INCLUDED
+#define RIPPLE_CORE_TEST_STATEBASE_H_INCLUDED
+
+namespace TestOverlay
+{
+
+/* Base class for state information used by test objects. */
+template
+class StateBase
+{
+public:
+ // Identifies messages and peers.
+ // Always starts at 1 and increases incrementally.
+ //
+ typedef uint64 UniqueID;
+
+ StateBase ()
+ : m_random (Params::randomSeedValue)
+ , m_peerID (0)
+ , m_messageID (0)
+ {
+ }
+
+ Random& random ()
+ {
+ return m_random;
+ }
+
+ UniqueID nextPeerID ()
+ {
+ return ++m_peerID;
+ }
+
+ UniqueID nextMessageID ()
+ {
+ return ++m_messageID;
+ }
+
+private:
+ Random m_random;
+ UniqueID m_peerID;
+ UniqueID m_messageID;
+};
+
+}
+
+#endif
diff --git a/modules/ripple_core/test/TestOverlay.cpp b/modules/ripple_core/test/TestOverlay.cpp
new file mode 100644
index 000000000..b1fa48fd1
--- /dev/null
+++ b/modules/ripple_core/test/TestOverlay.cpp
@@ -0,0 +1,133 @@
+//------------------------------------------------------------------------------
+/*
+ Copyright (c) 2011-2013, OpenCoin, Inc.
+*/
+//==============================================================================
+
+namespace TestOverlay
+{
+
+class Tests : public UnitTest
+{
+public:
+ template
+ class SeenState : public StateBase
+ {
+ public:
+ SeenState ()
+ : m_seen (0)
+ {
+ }
+
+ void increment ()
+ {
+ ++m_seen;
+ }
+
+ int seen () const
+ {
+ return m_seen;
+ }
+
+ private:
+ int m_seen;
+ };
+
+ //--------------------------------------------------------------------------
+
+ template
+ class PeerLogic : public PeerLogicBase
+ {
+ public:
+ typedef PeerLogicBase Base;
+ typedef typename Config::Payload Payload;
+ typedef typename Base::Connection Connection;
+ typedef typename Base::Peer Peer;
+ typedef typename Base::Message Message;
+ typedef typename Config::SizeType SizeType;
+
+ explicit PeerLogic (Peer& peer)
+ : PeerLogicBase (peer)
+ {
+ }
+
+ ~PeerLogic ()
+ {
+ }
+
+ void step ()
+ {
+ if (this->peer().id () == 1)
+ {
+ if (this->peer().network().steps() == 0)
+ {
+ this->peer().network().state().increment();
+ this->peer().send_all (Payload (1));
+ }
+ }
+ }
+
+ void receive (Connection const& c, Message const& m)
+ {
+ if (this->peer().id () != 1)
+ {
+ this->peer().network().state().increment();
+ this->peer().send_all_if (Message (m.id(),
+ m.payload().withHop ()),
+ typename Connection::IsNotPeer (c.peer()));
+ }
+ }
+ };
+
+ //--------------------------------------------------------------------------
+
+ struct Params : ConfigType <
+ Params,
+ SeenState,
+ PeerLogic
+ >
+ {
+ typedef PremadeInitPolicy <250, 3> InitPolicy;
+ };
+
+ typedef Params::Network Network;
+
+ //--------------------------------------------------------------------------
+
+ void testCreation ()
+ {
+ beginTestCase ("create");
+
+ Network network;
+
+ Results result;
+ for (int i = 0; result.received < 249 && i < 100; ++i)
+ {
+ String s =
+ String ("step #") + String::fromNumber (
+ network.steps()) + " ";
+ result += network.step ();
+ s << result.toString ();
+ logMessage (s);
+ }
+
+ int const seen (network.state().seen());
+
+ String s = "Seen = " + String::fromNumber (seen);
+ logMessage (s);
+ pass ();
+ }
+
+ void runTest ()
+ {
+ testCreation ();
+ }
+
+ Tests () : UnitTest ("TestOverlay", "ripple", runManual)
+ {
+ }
+};
+
+static Tests tests;
+
+}