Merge pull request #126 from nathanhourt/api-optionals

Add API support for optional arguments
This commit is contained in:
Nathan Hourt 2019-05-14 06:52:07 -05:00 committed by GitHub
commit 6bee7ff30b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 289 additions and 257 deletions

View file

@ -30,7 +30,6 @@ script:
- 'which build-wrapper-linux-x86-64 && build-wrapper-linux-x86-64 --out-dir bw-output make -j 2 || make -j 2'
- set -o pipefail
- tests/run-parallel-tests.sh tests/all_tests
- "tests/api 2>&1 | grep -vE 'callback result 9|remote_calc->add. 4, 5 .: 9|set callback|] \\.$'"
- tests/hmac_test 2>&1 | cat
- tests/ecc_test README.md 2>&1 | cat
- 'find CMakeFiles/fc.dir -type d | while read d; do gcov -o "$d" "${d/CMakeFiles*.dir/./}"/*.cpp; done >/dev/null'

View file

@ -13,13 +13,91 @@
#endif
namespace fc {
struct identity_member {
namespace detail {
/// This metafunction determines whether its template argument is an instantiation of fc::optional
template<typename T> struct is_optional : public std::false_type {};
template<typename T> struct is_optional<fc::optional<T>> : public std::true_type {};
/// This metafunction determines whether all of its template arguments are instantiations of fc::optional
template<typename... Ts> struct all_optionals;
template<> struct all_optionals<> : public std::true_type {};
template<typename T, typename... Ts> struct all_optionals<T, Ts...> : public std::false_type {};
template<typename T, typename... Ts> struct all_optionals<fc::optional<T>, Ts...> : public all_optionals<Ts...> {};
/// A wrapper of std::function allowing callers to omit the last several arguments if those arguments are
/// fc::optional types. i.e. given a function taking (int, double, bool, fc::optional<string>, fc::optional<char>),
/// whereas normally the last two arguments must be provided, this template allows them to be omitted.
/// Note that this only applies to trailing optional arguments, i.e. given a callable taking
/// (fc::optional<int>, int, fc::optional<int>), only the last argument can be omitted.
///
/// A discussion of how exactly this works is available here:
/// https://github.com/bitshares/bitshares-fc/pull/126#issuecomment-490566060
template<typename R, typename... Parameters>
struct optionals_callable : public std::function<R(Parameters...)> {
using std::function<R(Parameters...)>::operator();
template<typename... CutList>
struct short_pack {};
/// This metafunction removes the first several types from a variadic parameter pack of types.
/// The first parameter is the number of arguments to remove from the beginning of the pack.
/// All subsequent parameters are types in the list to be cut
/// The result pack_cutter<...>::type is a short_pack<RemainingTypes>
template<unsigned RemoveCount, typename... Types>
struct pack_cutter;
template<unsigned RemoveCount, typename, typename... Types>
struct pack_cutter_impl;
template<typename... Types>
struct pack_cutter_impl<0, void, Types...> {
static_assert(all_optionals<Types...>::value, "All omitted arguments must correspond to optional parameters.");
using type = short_pack<Types...>;
};
template<unsigned RemoveCount, typename T, typename... Types>
struct pack_cutter_impl<RemoveCount, std::enable_if_t<RemoveCount != 0>, T, Types...>
: public pack_cutter_impl<RemoveCount - 1, void, Types...> {};
template<unsigned RemoveCount, typename... Types>
struct pack_cutter : public pack_cutter_impl<RemoveCount, void, Types...> {};
template<unsigned RemoveCount, typename... Types>
using pack_cutter_t = typename pack_cutter<RemoveCount, Types...>::type;
template<typename F, typename... OptionalTypes>
R call_function(F&& f, short_pack<OptionalTypes...>) {
return f(OptionalTypes()...);
}
/// Overload the function call operator, enabled if the caller provides fewer arguments than there are parameters.
/// Pads out the provided arguments with default-constructed optionals, checking that they are indeed optional types
template<class... Args>
std::enable_if_t<sizeof...(Args) < sizeof...(Parameters), R> operator()(Args... args) {
// Partially apply with the arguments provided
auto partial_function = [this, &args...](auto&&... rest) {
return (*this)(std::forward<decltype(args)>(args)..., std::move(rest)...);
};
// Cut the provided arguments' types out of the Parameters list, and store the rest in a dummy type
pack_cutter_t<sizeof...(Args), std::decay_t<Parameters>...> dummy;
// Pass the partially applied function and the dummy type to another function which can deduce the optional
// types and call the function with default instantiations of those types
return call_function(std::move(partial_function), dummy);
}
};
}
// This is no longer used and probably no longer can be used without generalizing the infrastructure around it, but I
// kept it because it is informative.
// struct identity_member {
// template<typename R, typename C, typename P, typename... Args>
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
// template<typename R, typename C, typename P, typename... Args>
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
// };
/// Used as the Transform template parameter for APIs, this type has two main purposes: first, it reads the argument
/// list and return type of a method into template parameters; and second, it uses those types in conjunction with the
/// optionals_callable template above to create a function pointer which supports optional arguments.
struct identity_member_with_optionals {
template<typename R, typename C, typename P, typename... Args>
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...) );
template<typename R, typename C, typename P, typename... Args>
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...)const );
};
template< typename Interface, typename Transform >
struct vtable : public std::enable_shared_from_this<vtable<Interface,Transform>>
{ private: vtable(); };
@ -57,13 +135,13 @@ namespace fc {
// defined in api_connection.hpp
template< typename T >
api<T, identity_member> as();
api<T, identity_member_with_optionals> as();
};
typedef std::shared_ptr< api_base > api_ptr;
class api_connection;
template<typename Interface, typename Transform = identity_member >
template<typename Interface, typename Transform = identity_member_with_optionals >
class api : public api_base {
public:
typedef vtable<Interface,Transform> vtable_type;

View file

@ -50,8 +50,12 @@ namespace fc { namespace http {
void on_connection( const on_connection_handler& handler);
void listen( uint16_t port );
void listen( const fc::ip::endpoint& ep );
uint16_t get_listening_port();
void start_accept();
void stop_listening();
void close();
private:
friend class detail::websocket_server_impl;
std::unique_ptr<detail::websocket_server_impl> my;
@ -83,6 +87,9 @@ namespace fc { namespace http {
websocket_connection_ptr connect( const std::string& uri );
websocket_connection_ptr secure_connect( const std::string& uri );
void close();
void synchronous_close();
private:
std::unique_ptr<detail::websocket_client_impl> my;
std::unique_ptr<detail::websocket_tls_client_impl> smy;

View file

@ -33,7 +33,9 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )
{
return [=]( Args... args ) { return f( a0, args... ); };
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
// captured as optional<bool>(false).
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
}
template<typename R>
R call_generic( const std::function<R()>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth = 1 )
@ -44,9 +46,11 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
{
FC_ASSERT( a0 != e );
bool optional_args = all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
FC_ASSERT( a0 != e || optional_args );
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
}
template<typename R, typename ... Args>
@ -137,7 +141,9 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )const
{
return [=]( Args... args ) { return f( a0, args... ); };
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
// captured as optional<bool>(false).
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
}
template<typename R>
@ -172,9 +178,11 @@ namespace fc {
template<typename R, typename Arg0, typename ... Args>
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
{
FC_ASSERT( a0 != e, "too few arguments passed to method" );
bool optional_args = detail::all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
FC_ASSERT( a0 != e || optional_args, "too few arguments passed to method" );
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
}
struct api_visitor

View file

@ -214,7 +214,6 @@ namespace fc { namespace http {
auto current_con = _connections.find(hdl);
assert( current_con != _connections.end() );
wdump(("server")(msg->get_payload()));
//std::cerr<<"recv: "<<msg->get_payload()<<"\n";
auto payload = msg->get_payload();
std::shared_ptr<websocket_connection> con = current_con->second;
++_pending_messages;
@ -290,7 +289,7 @@ namespace fc { namespace http {
if( _server.is_listening() )
_server.stop_listening();
if( _connections.size() )
if ( _connections.size() )
_closed = new fc::promise<void>();
auto cpy_con = _connections;
@ -431,6 +430,7 @@ namespace fc { namespace http {
typedef websocket_client_type::connection_ptr websocket_client_connection_type;
typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type;
using websocketpp::connection_hdl;
class websocket_client_impl
{
@ -484,6 +484,7 @@ namespace fc { namespace http {
websocket_client_type _client;
websocket_connection_ptr _connection;
std::string _uri;
fc::optional<connection_hdl> _hdl;
};
@ -625,13 +626,29 @@ namespace fc { namespace http {
}
void websocket_server::listen( const fc::ip::endpoint& ep )
{
my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) );
my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) );
}
uint16_t websocket_server::get_listening_port()
{
websocketpp::lib::asio::error_code ec;
return my->_server.get_local_endpoint(ec).port();
}
void websocket_server::start_accept() {
my->_server.start_accept();
my->_server.start_accept();
}
void websocket_server::stop_listening()
{
my->_server.stop_listening();
}
void websocket_server::close()
{
for (auto& connection : my->_connections)
my->_server.close(connection.first, websocketpp::close::status::normal, "Goodbye");
}
@ -678,6 +695,7 @@ namespace fc { namespace http {
my->_connected = fc::promise<void>::ptr( new fc::promise<void>("websocket::connect") );
my->_client.set_open_handler( [=]( websocketpp::connection_hdl hdl ){
my->_hdl = hdl;
auto con = my->_client.get_con_from_hdl(hdl);
my->_connection = std::make_shared<detail::websocket_connection_impl<detail::websocket_client_connection_type>>( con );
my->_closed = fc::promise<void>::ptr( new fc::promise<void>("websocket::closed") );
@ -717,7 +735,20 @@ namespace fc { namespace http {
smy->_client.connect(con);
smy->_connected->wait();
return smy->_connection;
} FC_CAPTURE_AND_RETHROW( (uri) ) }
} FC_CAPTURE_AND_RETHROW( (uri) ) }
void websocket_client::close()
{
if (my->_hdl)
my->_client.close(*my->_hdl, websocketpp::close::status::normal, "Goodbye");
}
void websocket_client::synchronous_close()
{
close();
if (my->_closed)
my->_closed->wait();
}
websocket_connection_ptr websocket_tls_client::connect( const std::string& uri )
{ try {

View file

@ -1,7 +1,3 @@
add_executable( api api.cpp )
target_link_libraries( api fc )
if( ECC_IMPL STREQUAL secp256k1 )
add_executable( blind all_tests.cpp crypto/blind.cpp )
target_link_libraries( blind fc )
@ -55,5 +51,6 @@ add_executable( all_tests all_tests.cpp
utf8_test.cpp
variant_test.cpp
logging_tests.cpp
api_tests.cpp
)
target_link_libraries( all_tests fc )

View file

@ -1,191 +0,0 @@
#include <fc/api.hpp>
#include <fc/log/logger.hpp>
#include <fc/rpc/api_connection.hpp>
#include <fc/rpc/websocket_api.hpp>
class calculator
{
public:
int32_t add( int32_t a, int32_t b ); // not implemented
int32_t sub( int32_t a, int32_t b ); // not implemented
void on_result( const std::function<void(int32_t)>& cb );
void on_result2( const std::function<void(int32_t)>& cb, int test );
};
FC_API( calculator, (add)(sub)(on_result)(on_result2) )
class login_api
{
public:
fc::api<calculator> get_calc()const
{
FC_ASSERT( calc );
return *calc;
}
fc::optional<fc::api<calculator>> calc;
std::set<std::string> test( const std::string&, const std::string& ) { return std::set<std::string>(); }
};
FC_API( login_api, (get_calc)(test) );
using namespace fc;
class some_calculator
{
public:
int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; }
int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; }
void on_result( const std::function<void(int32_t)>& cb ) { wlog( "set callback" ); _cb = cb; return ; }
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
std::function<void(int32_t)> _cb;
};
class variant_calculator
{
public:
double add( fc::variant a, fc::variant b ) { return a.as_double()+b.as_double(); }
double sub( fc::variant a, fc::variant b ) { return a.as_double()-b.as_double(); }
void on_result( const std::function<void(int32_t)>& cb ) { wlog("set callback"); _cb = cb; return ; }
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
std::function<void(int32_t)> _cb;
};
using namespace fc::http;
using namespace fc::rpc;
#define MAX_DEPTH 10
int main( int argc, char** argv )
{
try {
fc::api<calculator> calc_api( std::make_shared<some_calculator>() );
fc::http::websocket_server server;
server.on_connection([&]( const websocket_connection_ptr& c ){
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
auto login = std::make_shared<login_api>();
login->calc = calc_api;
wsc->register_api(fc::api<login_api>(login));
c->set_session_data( wsc );
});
server.listen( 8090 );
server.start_accept();
for( uint32_t i = 0; i < 5000; ++i )
{
try {
fc::http::websocket_client client;
auto con = client.connect( "ws://localhost:8090" );
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
auto remote_login_api = apic->get_remote_api<login_api>();
auto remote_calc = remote_login_api->get_calc();
remote_calc->on_result( []( uint32_t r ) { elog( "callback result ${r}", ("r",r) ); } );
wdump((remote_calc->add( 4, 5 )));
} catch ( const fc::exception& e )
{
edump((e.to_detail_string()));
}
}
wlog( "exit scope" );
}
catch( const fc::exception& e )
{
edump((e.to_detail_string()));
}
wlog( "returning now..." );
return 0;
some_calculator calc;
variant_calculator vcalc;
fc::api<calculator> api_calc( &calc );
fc::api<calculator> api_vcalc( &vcalc );
fc::api<calculator> api_nested_calc( api_calc );
wdump( (api_calc->add(5,4)) );
wdump( (api_calc->sub(5,4)) );
wdump( (api_vcalc->add(5,4)) );
wdump( (api_vcalc->sub(5,4)) );
wdump( (api_nested_calc->sub(5,4)) );
wdump( (api_nested_calc->sub(5,4)) );
/*
variants v = { 4, 5 };
auto g = to_generic( api_calc->add );
auto r = call_generic( api_calc->add, v.begin(), v.end() );
wdump((r));
wdump( (g(v)) );
*/
/*
try {
fc::api_server server;
auto api_id = server.register_api( api_calc );
wdump( (api_id) );
auto result = server.call( api_id, "add", {4, 5} );
wdump( (result) );
} catch ( const fc::exception& e )
{
elog( "${e}", ("e",e.to_detail_string() ) );
}
ilog( "------------------ NESTED TEST --------------" );
try {
login_api napi_impl;
napi_impl.calc = api_calc;
fc::api<login_api> napi(&napi_impl);
fc::api_server server;
auto api_id = server.register_api( napi );
wdump( (api_id) );
auto result = server.call( api_id, "get_calc" );
wdump( (result) );
result = server.call( result.as_uint64(), "add", {4,5} );
wdump( (result) );
fc::api<api_server> serv( &server );
fc::api_client<login_api> apic( serv );
fc::api<login_api> remote_api = apic;
auto remote_calc = remote_api->get_calc();
int r = remote_calc->add( 4, 5 );
idump( (r) );
} catch ( const fc::exception& e )
{
elog( "${e}", ("e",e.to_detail_string() ) );
}
*/
ilog( "------------------ NESTED TEST --------------" );
try {
login_api napi_impl;
napi_impl.calc = api_calc;
fc::api<login_api> napi(&napi_impl);
auto client_side = std::make_shared<local_api_connection>(MAX_DEPTH);
auto server_side = std::make_shared<local_api_connection>(MAX_DEPTH);
server_side->set_remote_connection( client_side );
client_side->set_remote_connection( server_side );
server_side->register_api( napi );
fc::api<login_api> remote_api = client_side->get_remote_api<login_api>();
auto remote_calc = remote_api->get_calc();
int r = remote_calc->add( 4, 5 );
idump( (r) );
} catch ( const fc::exception& e )
{
elog( "${e}", ("e",e.to_detail_string() ) );
}
return 0;
}

138
tests/api_tests.cpp Normal file
View file

@ -0,0 +1,138 @@
#include <boost/test/unit_test.hpp>
#include <fc/api.hpp>
#include <fc/log/logger.hpp>
#include <fc/rpc/api_connection.hpp>
#include <fc/rpc/websocket_api.hpp>
class calculator
{
public:
int32_t add( int32_t a, int32_t b ); // not implemented
int32_t sub( int32_t a, int32_t b ); // not implemented
void on_result( const std::function<void(int32_t)>& cb );
void on_result2( const std::function<void(int32_t)>& cb, int test );
};
FC_API( calculator, (add)(sub)(on_result)(on_result2) )
class login_api
{
public:
fc::api<calculator> get_calc()const
{
FC_ASSERT( calc );
return *calc;
}
fc::optional<fc::api<calculator>> calc;
std::set<std::string> test( const std::string&, const std::string& ) { return std::set<std::string>(); }
};
FC_API( login_api, (get_calc)(test) );
class optionals_api
{
public:
std::string foo( const std::string& first, const fc::optional<std::string>& second,
const fc::optional<std::string>& third ) {
return fc::json::to_string(fc::variants{first, {second, 2}, {third, 2}});
}
};
FC_API( optionals_api, (foo) );
using namespace fc;
class some_calculator
{
public:
int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; }
int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; }
void on_result( const std::function<void(int32_t)>& cb ) { wlog( "set callback" ); _cb = cb; return ; }
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
std::function<void(int32_t)> _cb;
};
using namespace fc::http;
using namespace fc::rpc;
#define MAX_DEPTH 10
BOOST_AUTO_TEST_SUITE(api_tests)
BOOST_AUTO_TEST_CASE(login_test) {
try {
fc::api<calculator> calc_api( std::make_shared<some_calculator>() );
auto server = std::make_shared<fc::http::websocket_server>();
server->on_connection([&]( const websocket_connection_ptr& c ){
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
auto login = std::make_shared<login_api>();
login->calc = calc_api;
wsc->register_api(fc::api<login_api>(login));
c->set_session_data( wsc );
});
server->listen( 0 );
auto listen_port = server->get_listening_port();
server->start_accept();
auto client = std::make_shared<fc::http::websocket_client>();
auto con = client->connect( "ws://localhost:" + std::to_string(listen_port) );
server->stop_listening();
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
auto remote_login_api = apic->get_remote_api<login_api>();
auto remote_calc = remote_login_api->get_calc();
bool remote_triggered = false;
remote_calc->on_result( [&remote_triggered]( uint32_t r ) { remote_triggered = true; } );
BOOST_CHECK_EQUAL(remote_calc->add( 4, 5 ), 9);
BOOST_CHECK(remote_triggered);
client->synchronous_close();
server->close();
fc::usleep(fc::milliseconds(50));
client.reset();
server.reset();
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(optionals_test) {
try {
auto optionals = std::make_shared<optionals_api>();
fc::api<optionals_api> oapi(optionals);
BOOST_CHECK_EQUAL(oapi->foo("a"), "[\"a\",null,null]");
BOOST_CHECK_EQUAL(oapi->foo("a", "b"), "[\"a\",\"b\",null]");
BOOST_CHECK_EQUAL(oapi->foo("a", "b", "c"), "[\"a\",\"b\",\"c\"]");
BOOST_CHECK_EQUAL(oapi->foo("a", {}, "c"), "[\"a\",null,\"c\"]");
auto server = std::make_shared<fc::http::websocket_server>();
server->on_connection([&]( const websocket_connection_ptr& c ){
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
wsc->register_api(fc::api<optionals_api>(optionals));
c->set_session_data( wsc );
});
server->listen( 0 );
auto listen_port = server->get_listening_port();
server->start_accept();
auto client = std::make_shared<fc::http::websocket_client>();
auto con = client->connect( "ws://localhost:" + std::to_string(listen_port) );
server->stop_listening();
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
auto remote_optionals = apic->get_remote_api<optionals_api>();
BOOST_CHECK_EQUAL(remote_optionals->foo("a"), "[\"a\",null,null]");
BOOST_CHECK_EQUAL(remote_optionals->foo("a", "b"), "[\"a\",\"b\",null]");
BOOST_CHECK_EQUAL(remote_optionals->foo("a", "b", "c"), "[\"a\",\"b\",\"c\"]");
BOOST_CHECK_EQUAL(remote_optionals->foo("a", {}, "c"), "[\"a\",null,\"c\"]");
client->synchronous_close();
server->close();
fc::usleep(fc::milliseconds(50));
client.reset();
server.reset();
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -20,21 +20,8 @@ BOOST_AUTO_TEST_CASE(websocket_test)
});
});
bool listen_ok = false;
for( int i = 0; !listen_ok && i < 5; ++i )
{
port = std::rand() % 50000 + 10000;
try
{
server.listen( port );
listen_ok = true;
}
catch( std::exception& ignore )
{
// if the port is busy, listen() will throw a std::exception, do nothing here.
}
}
BOOST_REQUIRE( listen_ok );
server.listen( 0 );
port = server.get_listening_port();
server.start_accept();
@ -44,49 +31,27 @@ BOOST_AUTO_TEST_CASE(websocket_test)
echo = s;
});
c_conn->send_message( "hello world" );
fc::usleep( fc::seconds(1) );
fc::usleep( fc::milliseconds(100) );
BOOST_CHECK_EQUAL("echo: hello world", echo);
c_conn->send_message( "again" );
fc::usleep( fc::seconds(1) );
fc::usleep( fc::milliseconds(100) );
BOOST_CHECK_EQUAL("echo: again", echo);
s_conn->close(0, "test");
fc::usleep( fc::seconds(1) );
try {
c_conn->send_message( "again" );
BOOST_FAIL("expected assertion failure");
} catch (const fc::exception& e) {
//std::cerr << e.to_string() << "\n";
}
fc::usleep( fc::milliseconds(100) );
BOOST_CHECK_THROW(c_conn->send_message( "again" ), fc::exception);
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
c_conn->on_message_handler([&](const std::string& s){
echo = s;
});
c_conn->send_message( "hello world" );
fc::usleep( fc::seconds(1) );
fc::usleep( fc::milliseconds(100) );
BOOST_CHECK_EQUAL("echo: hello world", echo);
}
try {
c_conn->send_message( "again" );
BOOST_FAIL("expected assertion failure");
} catch (const fc::assert_exception& e) {
std::cerr << "Expected assertion failure : " << e.to_string() << "\n";
} catch (const fc::exception& e) {
BOOST_FAIL("Unexpected exception : " + e.to_string());
} catch (const std::exception& e) {
BOOST_FAIL("Unexpected exception : " + std::string(e.what()));
}
try {
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
BOOST_FAIL("expected fc::exception (fail to connect)");
} catch (const fc::exception& e) {
std::cerr << "Excepted fc::exception : " << e.to_string() << "\n";
} catch (const std::exception& e) {
BOOST_FAIL("Unexpected exception : " + std::string(e.what()));
}
BOOST_CHECK_THROW(c_conn->send_message( "again" ), fc::assert_exception);
BOOST_CHECK_THROW(client.connect( "ws://localhost:" + fc::to_string(port) ), fc::exception);
}
BOOST_AUTO_TEST_SUITE_END()