diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 468675d1e..b2e407700 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -3406,16 +3406,24 @@
+
+
True
True
+
+ True
+ True
+
True
True
+
+
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 4ca49f8f7..8822874aa 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -3915,15 +3915,24 @@
ripple\shamap
+
+ ripple\test
+
ripple\test
ripple\test\impl
+
+ ripple\test\impl
+
ripple\test\impl
+
+ ripple\test
+
ripple\test
diff --git a/src/ripple/app/tests/Transaction_ordering_test.cpp b/src/ripple/app/tests/Transaction_ordering_test.cpp
index c897a6e0a..7536213da 100644
--- a/src/ripple/app/tests/Transaction_ordering_test.cpp
+++ b/src/ripple/app/tests/Transaction_ordering_test.cpp
@@ -74,13 +74,11 @@ struct Transaction_ordering_test : public beast::unit_test::suite
{
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
- expect(result.first == rpcSUCCESS);
- expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
+ expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
{
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
- expect(result.first == rpcSUCCESS);
- expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
+ expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
@@ -112,13 +110,11 @@ struct Transaction_ordering_test : public beast::unit_test::suite
{
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
- expect(result.first == rpcSUCCESS);
- expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
+ expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
{
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
- expect(result.first == rpcSUCCESS);
- expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
+ expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
@@ -160,8 +156,7 @@ struct Transaction_ordering_test : public beast::unit_test::suite
for (auto i = 0; i < 5; ++i)
{
auto const result = env.rpc("tx", to_string(tx[i].stx->getTransactionID()));
- expect(result.first == rpcSUCCESS);
- expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
+ expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
}
}
diff --git a/src/ripple/test/AbstractClient.h b/src/ripple/test/AbstractClient.h
new file mode 100644
index 000000000..d11c7a700
--- /dev/null
+++ b/src/ripple/test/AbstractClient.h
@@ -0,0 +1,58 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2016 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED
+#define RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED
+
+#include
+
+namespace ripple {
+namespace test {
+
+/* Abstract Ripple Client interface.
+
+ This abstracts the transport layer, allowing
+ commands to be submitted to a rippled server.
+*/
+class AbstractClient
+{
+public:
+ virtual ~AbstractClient() = default;
+
+ /** Submit a command synchronously.
+
+ The arguments to the function and the returned JSON
+ are in a normalized format, the same whether the client
+ is using the JSON-RPC over HTTP/S or WebSocket transport.
+
+ @param cmd The command to execute
+ @param params Json::Value of null or object type
+ with zero or more key/value pairs.
+ @return The server response in normalized format.
+ */
+ virtual
+ Json::Value
+ invoke(std::string const& cmd,
+ Json::Value const& params = {}) = 0;
+};
+
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/test/JSONRPCClient.h b/src/ripple/test/JSONRPCClient.h
new file mode 100644
index 000000000..28ce3543e
--- /dev/null
+++ b/src/ripple/test/JSONRPCClient.h
@@ -0,0 +1,37 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2016 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#ifndef RIPPLE_TEST_HTTPCLIENT_H_INCLUDED
+#define RIPPLE_TEST_HTTPCLIENT_H_INCLUDED
+
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+/** Returns a client using JSON-RPC over HTTP/S. */
+std::unique_ptr
+makeJSONRPCClient(Config const& cfg);
+
+} // test
+} // ripple
+
+#endif
diff --git a/src/ripple/test/impl/JSONRPCClient.cpp b/src/ripple/test/impl/JSONRPCClient.cpp
new file mode 100644
index 000000000..2137dc218
--- /dev/null
+++ b/src/ripple/test/impl/JSONRPCClient.cpp
@@ -0,0 +1,164 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2016 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+class JSONRPCClient : public AbstractClient
+{
+ static
+ boost::asio::ip::tcp::endpoint
+ getEndpoint(BasicConfig const& cfg)
+ {
+ auto& log = std::cerr;
+ ParsedPort common;
+ parse_Port (common, cfg["server"], log);
+ for (auto const& name : cfg.section("server").values())
+ {
+ if (! cfg.exists(name))
+ continue;
+ ParsedPort pp;
+ parse_Port(pp, cfg[name], log);
+ if(pp.protocol.count("http") == 0)
+ continue;
+ using boost::asio::ip::address_v4;
+ if(*pp.ip == address_v4{0x00000000})
+ *pp.ip = address_v4{0x7f000001};
+ return { *pp.ip, *pp.port };
+ }
+ throw std::runtime_error("Missing HTTP port");
+ }
+
+ template
+ static
+ std::string
+ buffer_string (ConstBufferSequence const& b)
+ {
+ using namespace boost::asio;
+ std::string s;
+ s.resize(buffer_size(b));
+ buffer_copy(buffer(&s[0], s.size()), b);
+ return s;
+ }
+
+ boost::asio::io_service ios_;
+ boost::asio::ip::tcp::socket stream_;
+ boost::asio::streambuf bin_;
+ beast::asio::streambuf bout_;
+
+public:
+ explicit
+ JSONRPCClient(Config const& cfg)
+ : stream_(ios_)
+ {
+ stream_.connect(getEndpoint(cfg));
+ }
+
+ ~JSONRPCClient() override
+ {
+ //stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
+ //stream_.close();
+ }
+
+ /*
+ Return value is an Object type with up to three keys:
+ status
+ error
+ result
+ */
+ Json::Value
+ invoke(std::string const& cmd,
+ Json::Value const& params) override
+ {
+ std::string s;
+ {
+ Json::Value jr;
+ jr["method"] = cmd;
+ if(params)
+ {
+ Json::Value& ja = jr["params"] = Json::arrayValue;
+ ja.append(params);
+ }
+ s = to_string(jr);
+ }
+
+ using namespace boost::asio;
+ using namespace std::string_literals;
+ std::string r;
+ r =
+ "POST / HTTP/1.1\r\n"
+ "Host: me\r\n"
+ "Connection: Keep-Alive\r\n"s +
+ "Content-Type: application/json; charset=UTF-8\r\n"s +
+ "Content-Length: " + std::to_string(s.size()) + "\r\n"
+ "\r\n";
+ write(stream_, buffer(r));
+ write(stream_, buffer(s));
+
+ read_until(stream_, bin_, "\r\n\r\n");
+ beast::asio::streambuf body;
+ beast::http::message m;
+ beast::http::parser p(
+ [&](void const* data, std::size_t size)
+ {
+ body.commit(buffer_copy(
+ body.prepare(size), const_buffer(data, size)));
+ }, m, false);
+
+ for(;;)
+ {
+ auto const result = p.write(bin_.data());
+ if (result.first)
+ throw result.first;
+ bin_.consume(result.second);
+ if(p.complete())
+ break;
+ bin_.commit(stream_.read_some(
+ bin_.prepare(1024)));
+ }
+
+ Json::Reader jr;
+ Json::Value jv;
+ jr.parse(buffer_string(body.data()), jv);
+ if(jv["result"].isMember("error"))
+ jv["error"] = jv["result"]["error"];
+ if(jv["result"].isMember("status"))
+ jv["status"] = jv["result"]["status"];
+ return jv;
+ }
+};
+
+std::unique_ptr
+makeJSONRPCClient(Config const& cfg)
+{
+ return std::make_unique(cfg);
+}
+
+} // test
+} // ripple
diff --git a/src/ripple/test/jtx/Env.h b/src/ripple/test/jtx/Env.h
index 0e330b0ce..99a70d361 100644
--- a/src/ripple/test/jtx/Env.h
+++ b/src/ripple/test/jtx/Env.h
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -36,7 +37,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -99,6 +99,7 @@ private:
std::unique_ptr owned;
ManualTimeKeeper* timeKeeper;
std::thread thread;
+ std::unique_ptr client;
AppBundle (std::unique_ptr config);
AppBundle (Application* app_);
@@ -161,7 +162,6 @@ public:
{
}
-
Application&
app()
{
@@ -185,6 +185,22 @@ public:
return app().timeKeeper().now();
}
+ /** Returns the connected client. */
+ AbstractClient&
+ client()
+ {
+ return *bundle_.client;
+ }
+
+ /** Execute an RPC command.
+
+ The command is examined and used to build
+ the correct JSON as per the arguments.
+ */
+ template
+ Json::Value
+ rpc(std::string const& cmd, Args&&... args);
+
/** Returns the current ledger.
This is a non-modifiable snapshot of the
@@ -423,11 +439,6 @@ public:
std::shared_ptr
meta();
- /** Execute a client command */
- template
- std::pair
- rpc (Arg&& arg0, Args&&... args);
-
private:
void
fund (bool setDefaultRipple,
@@ -534,6 +545,9 @@ protected:
uint256 txid_;
TER ter_ = tesSUCCESS;
+ Json::Value
+ do_rpc(std::vector const& args);
+
void
autofill_sig (JTx& jt);
@@ -628,16 +642,13 @@ protected:
AccountID, Account> map_;
};
-//------------------------------------------------------------------------------
-
-template
-std::pair
-Env::rpc (Arg&& arg0, Args&&... args)
+template
+Json::Value
+Env::rpc(std::string const& cmd, Args&&... args)
{
- std::vector v({ std::forward(arg0),
- std::forward(args)... });
- return rpcClient(v,
- app().config(), app().logs());
+ std::vector vs{cmd,
+ std::forward(args)...};
+ return do_rpc(vs);
}
} // jtx
diff --git a/src/ripple/test/jtx/impl/Env.cpp b/src/ripple/test/jtx/impl/Env.cpp
index af8b72db4..f7aed9ece 100644
--- a/src/ripple/test/jtx/impl/Env.cpp
+++ b/src/ripple/test/jtx/impl/Env.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -37,6 +38,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -54,26 +56,29 @@ namespace ripple {
namespace test {
void
-setupConfigForUnitTests (Config& config)
+setupConfigForUnitTests (Config& cfg)
{
- config.overwrite (ConfigSection::nodeDatabase (), "type", "memory");
- config.overwrite (ConfigSection::nodeDatabase (), "path", "main");
-
- config.deprecatedClearSection (ConfigSection::importNodeDatabase ());
- config.legacy("database_path", "");
-
- config.RUN_STANDALONE = true;
- config.QUIET = true;
- config.SILENT = true;
- config["server"].append("port_peer");
- config["port_peer"].set("ip", "127.0.0.1");
- config["port_peer"].set("port", "8080");
- config["port_peer"].set("protocol", "peer");
- config["server"].append("port_admin");
- config["port_admin"].set("ip", "127.0.0.1");
- config["port_admin"].set("port", "8081");
- config["port_admin"].set("protocol", "http");
- config["port_admin"].set("admin", "127.0.0.1");
+ cfg.overwrite (ConfigSection::nodeDatabase (), "type", "memory");
+ cfg.overwrite (ConfigSection::nodeDatabase (), "path", "main");
+ cfg.deprecatedClearSection (ConfigSection::importNodeDatabase ());
+ cfg.legacy("database_path", "");
+ cfg.RUN_STANDALONE = true;
+ cfg.QUIET = true;
+ cfg.SILENT = true;
+ cfg["server"].append("port_peer");
+ cfg["port_peer"].set("ip", "127.0.0.1");
+ cfg["port_peer"].set("port", "8080");
+ cfg["port_peer"].set("protocol", "peer");
+ cfg["server"].append("port_http");
+ cfg["port_http"].set("ip", "127.0.0.1");
+ cfg["port_http"].set("port", "8081");
+ cfg["port_http"].set("protocol", "http");
+ cfg["port_http"].set("admin", "127.0.0.1");
+ cfg["server"].append("port_ws");
+ cfg["port_ws"].set("ip", "127.0.0.1");
+ cfg["port_ws"].set("port", "8082");
+ cfg["port_ws"].set("protocol", "ws");
+ cfg["port_ws"].set("admin", "127.0.0.1");
}
//------------------------------------------------------------------------------
@@ -102,10 +107,13 @@ Env::AppBundle::AppBundle(std::unique_ptr config)
app->doStart();
thread = std::thread(
[&](){ app->run(); });
+
+ client = makeJSONRPCClient(app->config());
}
Env::AppBundle::~AppBundle()
{
+ client.reset();
app->signalStop();
thread.join();
}
@@ -131,8 +139,8 @@ Env::close(NetClock::time_point closeTime,
app().getOPs().acceptLedger(consensusDelay);
else
{
- auto const result = rpc("ledger_accept");
- test.expect(result.first == rpcSUCCESS);
+ rpc("ledger_accept");
+ // VFALCO No error check?
}
bundle_.timeKeeper->set(
closed()->info().closeTime);
@@ -274,11 +282,10 @@ Env::submit (JTx const& jt)
txid_ = jt.stx->getTransactionID();
Serializer s;
jt.stx->add(s);
- auto const result = rpc("submit", strHex(s.slice()));
- if (result.first == rpcSUCCESS &&
- result.second["result"].isMember("engine_result_code"))
+ auto const jr = rpc("submit", strHex(s.slice()));
+ if (jr["result"].isMember("engine_result_code"))
ter_ = static_cast(
- result.second["result"]["engine_result_code"].asInt());
+ jr["result"]["engine_result_code"].asInt());
else
ter_ = temINVALID;
didApply = isTesSuccess(ter_) || isTecClaim(ter_);
@@ -408,6 +415,14 @@ Env::applyFlags() const
return flags;
}
+Json::Value
+Env::do_rpc(std::vector const& args)
+{
+ auto const jv = cmdLineToJSONRPC(args, journal);
+ return client().invoke(jv["method"].asString(),
+ jv["params"][0U]);
+}
+
} // jtx
} // test
diff --git a/src/ripple/unity/test.cpp b/src/ripple/unity/test.cpp
index 90dc452e6..4ad8fc39a 100644
--- a/src/ripple/unity/test.cpp
+++ b/src/ripple/unity/test.cpp
@@ -48,4 +48,5 @@
#include
#include
+#include
#include