diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /fnet/src/tests/frt |
Publish
Diffstat (limited to 'fnet/src/tests/frt')
25 files changed, 2339 insertions, 0 deletions
diff --git a/fnet/src/tests/frt/memorytub/.gitignore b/fnet/src/tests/frt/memorytub/.gitignore new file mode 100644 index 00000000000..e61f6585695 --- /dev/null +++ b/fnet/src/tests/frt/memorytub/.gitignore @@ -0,0 +1,6 @@ +*.core +.depend +Makefile +core +memorytub_test +fnet_memorytub_test_app diff --git a/fnet/src/tests/frt/memorytub/CMakeLists.txt b/fnet/src/tests/frt/memorytub/CMakeLists.txt new file mode 100644 index 00000000000..1b6aa6e778b --- /dev/null +++ b/fnet/src/tests/frt/memorytub/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(fnet_memorytub_test_app + SOURCES + memorytub.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_memorytub_test_app COMMAND fnet_memorytub_test_app) diff --git a/fnet/src/tests/frt/memorytub/DESC b/fnet/src/tests/frt/memorytub/DESC new file mode 100644 index 00000000000..b075fd81de5 --- /dev/null +++ b/fnet/src/tests/frt/memorytub/DESC @@ -0,0 +1 @@ +Test the memorytub class. diff --git a/fnet/src/tests/frt/memorytub/FILES b/fnet/src/tests/frt/memorytub/FILES new file mode 100644 index 00000000000..58e3592a833 --- /dev/null +++ b/fnet/src/tests/frt/memorytub/FILES @@ -0,0 +1 @@ +memorytub.cpp diff --git a/fnet/src/tests/frt/memorytub/memorytub.cpp b/fnet/src/tests/frt/memorytub/memorytub.cpp new file mode 100644 index 00000000000..a6be26bc860 --- /dev/null +++ b/fnet/src/tests/frt/memorytub/memorytub.cpp @@ -0,0 +1,124 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/fnet/frt/frt.h> + +//--------------------------------------------------------------- + +enum { + SMALL_ALLOCS = 90, + BIG_ALLOCS = 10, + ALLOCS = SMALL_ALLOCS + BIG_ALLOCS, + SMALL_SIZE = 407, + BIG_SIZE = 40700, + NOID = 99999 +}; + +//--------------------------------------------------------------- + +struct Fixture { + FRT_MemoryTub _tub; + char *_res[ALLOCS]; + uint32_t _i; + uint32_t _j; + Fixture() : _tub(), _res(), _i(0), _j(0) {} + bool overlap(char *start1, char *end1, + char *start2, char *end2); + bool inTub(char *pt, char *end); + bool notInTub(char *pt, char *end); +}; + +//--------------------------------------------------------------- + +bool +Fixture::overlap(char *start1, char *end1, + char *start2, char *end2) +{ + if (start1 == end1) + return false; + + if (start2 == end2) + return false; + + if (start2 >= start1 && start2 < end1) + return true; + + if (end2 > start1 && end2 <= end1) + return true; + + if (start1 >= start2 && start1 < end2) + return true; + + if (end1 > start2 && end1 <= end2) + return true; + + return false; +} + + +bool +Fixture::inTub(char *pt, char *end) +{ + for (char *p = pt; p < end; p++) + if (!_tub.InTub(p)) + return false; + return true; +} + + +bool +Fixture::notInTub(char *pt, char *end) +{ + for (char *p = pt; p < end; p++) + if (_tub.InTub(p)) + return false; + return true; +} + +//--------------------------------------------------------------- + +TEST_F("memory tub", Fixture()) { + for(f1._i = 0; f1._i < ALLOCS; f1._i++) + f1._res[f1._i] = NULL; + f1._i = NOID; + f1._j = NOID; + + EXPECT_TRUE(!f1._tub.InTub(&f1._tub)); + EXPECT_TRUE((uint32_t)SMALL_SIZE < (uint32_t)FRT_MemoryTub::ALLOC_LIMIT); + EXPECT_TRUE((uint32_t)BIG_SIZE > (uint32_t)FRT_MemoryTub::ALLOC_LIMIT); + EXPECT_TRUE((SMALL_SIZE * SMALL_ALLOCS) + > (FRT_MemoryTub::FIXED_SIZE + FRT_MemoryTub::CHUNK_SIZE)); + TEST_FLUSH(); + + for (f1._i = 0; f1._i < ALLOCS; f1._i++) { + uint32_t size_i = f1._i < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE; + + f1._res[f1._i] = (char *) f1._tub.Alloc(size_i); + EXPECT_TRUE(((void *)f1._res[f1._i]) != ((void *)&f1._tub)); + memset(f1._res[f1._i], 0x55, size_i); + EXPECT_TRUE(f1.inTub(f1._res[f1._i], f1._res[f1._i] + size_i)); + } + TEST_FLUSH(); + + for (f1._i = 0; f1._i < ALLOCS; f1._i++) { + uint32_t size_i = f1._i < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE; + EXPECT_TRUE(f1.inTub(f1._res[f1._i], f1._res[f1._i] + size_i)); + + for (f1._j = f1._i + 1; f1._j < ALLOCS; f1._j++) { + uint32_t size_j = f1._j < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE; + EXPECT_TRUE(!f1.overlap(f1._res[f1._i], f1._res[f1._i] + size_i, + f1._res[f1._j], f1._res[f1._j] + size_j)); + } + } + TEST_FLUSH(); + + f1._tub.Reset(); + f1._j = NOID; + + for (f1._i = 0; f1._i < ALLOCS; f1._i++) { + uint32_t size_i = f1._i < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE; + EXPECT_TRUE(!f1.inTub(f1._res[f1._i], f1._res[f1._i] + size_i)); + } + TEST_FLUSH(); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/fnet/src/tests/frt/method_pt/.gitignore b/fnet/src/tests/frt/method_pt/.gitignore new file mode 100644 index 00000000000..5bb3f455d1d --- /dev/null +++ b/fnet/src/tests/frt/method_pt/.gitignore @@ -0,0 +1,6 @@ +*.core +.depend +Makefile +core +method_pt_test +fnet_method_pt_test_app diff --git a/fnet/src/tests/frt/method_pt/CMakeLists.txt b/fnet/src/tests/frt/method_pt/CMakeLists.txt new file mode 100644 index 00000000000..d5a9566dbba --- /dev/null +++ b/fnet/src/tests/frt/method_pt/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(fnet_method_pt_test_app + SOURCES + method_pt.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_method_pt_test_app COMMAND fnet_method_pt_test_app) diff --git a/fnet/src/tests/frt/method_pt/DESC b/fnet/src/tests/frt/method_pt/DESC new file mode 100644 index 00000000000..81e7d2c6d1c --- /dev/null +++ b/fnet/src/tests/frt/method_pt/DESC @@ -0,0 +1,2 @@ +Ensure that the method pointer magic used by FRT works with the +current compiler. diff --git a/fnet/src/tests/frt/method_pt/FILES b/fnet/src/tests/frt/method_pt/FILES new file mode 100644 index 00000000000..9586cd113fb --- /dev/null +++ b/fnet/src/tests/frt/method_pt/FILES @@ -0,0 +1 @@ +method_pt.cpp diff --git a/fnet/src/tests/frt/method_pt/method_pt.cpp b/fnet/src/tests/frt/method_pt/method_pt.cpp new file mode 100644 index 00000000000..539be6846a9 --- /dev/null +++ b/fnet/src/tests/frt/method_pt/method_pt.cpp @@ -0,0 +1,395 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/fnet/frt/frt.h> + +class Test; +class SimpleHandler; + +class MediumHandler1; +class MediumHandler2; +class MediumHandler3; + +class ComplexHandler1; +class ComplexHandler2; +class ComplexHandler3; + +//------------------------------------------------------------- + +Test *_test; + +FRT_Supervisor *_supervisor; +FRT_Target *_target; +SimpleHandler *_simpleHandler; +MediumHandler1 *_mediumHandler1; +MediumHandler2 *_mediumHandler2; +MediumHandler3 *_mediumHandler3; +ComplexHandler1 *_complexHandler1; +ComplexHandler2 *_complexHandler2; +ComplexHandler3 *_complexHandler3; + +bool _mediumHandlerOK; +bool _complexHandlerOK; + +//------------------------------------------------------------- + +class MediumA +{ +public: + + /** + * Destructor. No cleanup needed for base class. + */ + virtual ~MediumA(void) { } + + virtual void foo() = 0; +}; + + +class MediumB +{ +public: + + /** + * Destructor. No cleanup needed for base class. + */ + virtual ~MediumB(void) { } + + virtual void bar() = 0; +}; + +//------------------------------------------------------------- + +class ComplexA +{ +private: + uint32_t _fill1; + uint32_t _fill2; + uint32_t _fill3; + +public: + + /** + * Destructor. No cleanup needed for base class. + */ + virtual ~ComplexA(void) { } + + ComplexA() : _fill1(1), _fill2(2), _fill3(3) {} + virtual void foo() {} +}; + + +class ComplexB +{ +private: + uint32_t _fill1; + uint32_t _fill2; + uint32_t _fill3; + +public: + + /** + * Destructor. No cleanup needed for base class. + */ + virtual ~ComplexB(void) { } + + ComplexB() : _fill1(1), _fill2(2), _fill3(3) {} + virtual void bar() {} +}; + +//------------------------------------------------------------- + +class SimpleHandler : public FRT_Invokable +{ +public: + void RPC_Method(FRT_RPCRequest *req); +}; + +//------------------------------------------------------------- + +class MediumHandler1 : public FRT_Invokable, + public MediumA, + public MediumB +{ +public: + virtual void foo() {} + virtual void bar() {} + void RPC_Method(FRT_RPCRequest *req); +}; + + +class MediumHandler2 : public MediumA, + public FRT_Invokable, + public MediumB +{ +public: + virtual void foo() {} + virtual void bar() {} + void RPC_Method(FRT_RPCRequest *req); +}; + + +class MediumHandler3 : public MediumA, + public MediumB, + public FRT_Invokable +{ +public: + virtual void foo() {} + virtual void bar() {} + void RPC_Method(FRT_RPCRequest *req); +}; + +//------------------------------------------------------------- + +class ComplexHandler1 : public FRT_Invokable, + public ComplexA, + public ComplexB +{ +public: + virtual void foo() {} + virtual void bar() {} + void RPC_Method(FRT_RPCRequest *req); +}; + + +class ComplexHandler2 : public ComplexA, + public FRT_Invokable, + public ComplexB +{ +public: + virtual void foo() {} + virtual void bar() {} + void RPC_Method(FRT_RPCRequest *req); +}; + + +class ComplexHandler3 : public ComplexA, + public ComplexB, + public FRT_Invokable +{ +public: + virtual void foo() {} + virtual void bar() {} + void RPC_Method(FRT_RPCRequest *req); +}; + +//------------------------------------------------------------- + +void initTest() { + _supervisor = new FRT_Supervisor(); + _simpleHandler = new SimpleHandler(); + _mediumHandler1 = new MediumHandler1(); + _mediumHandler2 = new MediumHandler2(); + _mediumHandler3 = new MediumHandler3(); + _complexHandler1 = new ComplexHandler1(); + _complexHandler2 = new ComplexHandler2(); + _complexHandler3 = new ComplexHandler3(); + + ASSERT_TRUE(_supervisor != NULL); + ASSERT_TRUE(_simpleHandler != NULL); + ASSERT_TRUE(_mediumHandler1 != NULL); + ASSERT_TRUE(_mediumHandler2 != NULL); + ASSERT_TRUE(_mediumHandler3 != NULL); + ASSERT_TRUE(_complexHandler1 != NULL); + ASSERT_TRUE(_complexHandler2 != NULL); + ASSERT_TRUE(_complexHandler3 != NULL); + + ASSERT_TRUE(_supervisor->Listen(0)); + std::string spec = vespalib::make_string("tcp/localhost:%d", + _supervisor->GetListenPort()); + _target = _supervisor->GetTarget(spec.c_str()); + ASSERT_TRUE(_target != NULL); + + bool startOK = _supervisor->Start(); + ASSERT_TRUE(startOK); + + FRT_ReflectionBuilder rb(_supervisor); + + //------------------------------------------------------------------- + + rb.DefineMethod("simpleMethod", "", "", true, + FRT_METHOD(SimpleHandler::RPC_Method), + _simpleHandler); + + //------------------------------------------------------------------- + + rb.DefineMethod("mediumMethod1", "", "", true, + FRT_METHOD(MediumHandler1::RPC_Method), + _mediumHandler1); + + rb.DefineMethod("mediumMethod2", "", "", true, + FRT_METHOD(MediumHandler2::RPC_Method), + _mediumHandler2); + + rb.DefineMethod("mediumMethod3", "", "", true, + FRT_METHOD(MediumHandler3::RPC_Method), + _mediumHandler3); + + //------------------------------------------------------------------- + + rb.DefineMethod("complexMethod1", "", "", true, + FRT_METHOD(ComplexHandler1::RPC_Method), + _complexHandler1); + + rb.DefineMethod("complexMethod2", "", "", true, + FRT_METHOD(ComplexHandler2::RPC_Method), + _complexHandler2); + + rb.DefineMethod("complexMethod3", "", "", true, + FRT_METHOD(ComplexHandler3::RPC_Method), + _complexHandler3); + + //------------------------------------------------------------------- + + _mediumHandlerOK = true; + _complexHandlerOK = true; +} + + +void finiTest() { + _supervisor->ShutDown(true); + delete _complexHandler1; + delete _complexHandler2; + delete _complexHandler3; + delete _mediumHandler1; + delete _mediumHandler2; + delete _mediumHandler3; + delete _simpleHandler; + _target->SubRef(); + delete _supervisor; +} + + +TEST("method pt") { + FRT_RPCRequest *req = _supervisor->AllocRPCRequest(); + req->SetMethodName("simpleMethod"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + //-------------------------------- MEDIUM + + req->SubRef(); + req = _supervisor->AllocRPCRequest(); + req->SetMethodName("mediumMethod1"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + req->SubRef(); + req = _supervisor->AllocRPCRequest(); + req->SetMethodName("mediumMethod2"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + req->SubRef(); + req = _supervisor->AllocRPCRequest(); + req->SetMethodName("mediumMethod3"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + //-------------------------------- COMPLEX + + req->SubRef(); + req = _supervisor->AllocRPCRequest(); + req->SetMethodName("complexMethod1"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + req->SubRef(); + req = _supervisor->AllocRPCRequest(); + req->SetMethodName("complexMethod2"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + req->SubRef(); + req = _supervisor->AllocRPCRequest(); + req->SetMethodName("complexMethod3"); + _target->InvokeSync(req, 60.0); + EXPECT_TRUE(!req->IsError()); + + if (_mediumHandlerOK) { + fprintf(stderr, "Interface inheritance OK for method handlers\n"); + } else { + fprintf(stderr, "Interface inheritance NOT ok for method handlers\n"); + } + + if (_complexHandlerOK) { + fprintf(stderr, "Object inheritance OK for method handlers\n"); + } else { + fprintf(stderr, "Object inheritance NOT ok for method handlers\n"); + } + + req->SubRef(); +} + +//------------------------------------------------------------- + +void +SimpleHandler::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + EXPECT_TRUE(this == _simpleHandler); +} + +//------------------------------------------------------------- + +void +MediumHandler1::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + _mediumHandlerOK = (_mediumHandlerOK && + this == _mediumHandler1); +} + + +void +MediumHandler2::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + _mediumHandlerOK = (_mediumHandlerOK && + this == _mediumHandler2); +} + + +void +MediumHandler3::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + _mediumHandlerOK = (_mediumHandlerOK && + this == _mediumHandler3); +} + +//------------------------------------------------------------- + +void +ComplexHandler1::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + _complexHandlerOK = (_complexHandlerOK && + this == _complexHandler1); +} + + +void +ComplexHandler2::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + _complexHandlerOK = (_complexHandlerOK && + this == _complexHandler2); +} + + +void +ComplexHandler3::RPC_Method(FRT_RPCRequest *req) +{ + (void) req; + _complexHandlerOK = (_complexHandlerOK && + this == _complexHandler3); +} + +//------------------------------------------------------------- + +TEST_MAIN() { + initTest(); + TEST_RUN_ALL(); + finiTest(); +} diff --git a/fnet/src/tests/frt/parallel_rpc/.gitignore b/fnet/src/tests/frt/parallel_rpc/.gitignore new file mode 100644 index 00000000000..7b4b7428e52 --- /dev/null +++ b/fnet/src/tests/frt/parallel_rpc/.gitignore @@ -0,0 +1 @@ +fnet_parallel_rpc_test_app diff --git a/fnet/src/tests/frt/parallel_rpc/CMakeLists.txt b/fnet/src/tests/frt/parallel_rpc/CMakeLists.txt new file mode 100644 index 00000000000..00a0c12e413 --- /dev/null +++ b/fnet/src/tests/frt/parallel_rpc/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(fnet_parallel_rpc_test_app + SOURCES + parallel_rpc_test.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_parallel_rpc_test_app COMMAND fnet_parallel_rpc_test_app) diff --git a/fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp b/fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp new file mode 100644 index 00000000000..723f519cd37 --- /dev/null +++ b/fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp @@ -0,0 +1,129 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/fnet/frt/frt.h> +#include <vespa/vespalib/util/benchmark_timer.h> +#include <thread> + +using vespalib::BenchmarkTimer; + +struct Rpc : FRT_Invokable { + FastOS_ThreadPool thread_pool; + FNET_Transport transport; + FRT_Supervisor orb; + Rpc(size_t num_threads) + : thread_pool(128 * 1024), transport(num_threads), orb(&transport, &thread_pool) {} + void start() { + ASSERT_TRUE(transport.Start(&thread_pool)); + } + uint32_t listen() { + ASSERT_TRUE(orb.Listen(0)); + return orb.GetListenPort(); + } + FRT_Target *connect(uint32_t port) { + return orb.GetTarget(port); + } + ~Rpc() { + transport.ShutDown(true); + thread_pool.Close(); + } +}; + +struct Server : Rpc { + uint32_t port; + Server(size_t num_threads) : Rpc(num_threads), port(listen()) { + init_rpc(); + start(); + } + void init_rpc() { + FRT_ReflectionBuilder rb(&orb); + rb.DefineMethod("inc", "l", "l", true, FRT_METHOD(Server::rpc_inc), this); + rb.MethodDesc("increment a 64-bit integer"); + rb.ParamDesc("in", "an integer (64 bit)"); + rb.ReturnDesc("out", "in + 1 (64 bit)"); + } + void rpc_inc(FRT_RPCRequest *req) { + FRT_Values ¶ms = *req->GetParams(); + FRT_Values &ret = *req->GetReturn(); + ret.AddInt64(params[0]._intval64 + 1); + } +}; + +struct Client : Rpc { + uint32_t port; + Client(size_t num_threads, const Server &server) : Rpc(num_threads), port(server.port) { + start(); + } + FRT_Target *connect() { return Rpc::connect(port); } +}; + +struct Result { + std::vector<double> req_per_sec; + Result(size_t num_threads) : req_per_sec(num_threads, 0.0) {} + double throughput() const { + double sum = 0.0; + for (double sample: req_per_sec) { + sum += sample; + } + return sum; + } + double latency_ms() const { + double avg_req_per_sec = throughput() / req_per_sec.size(); + double avg_sec_per_req = 1.0 / avg_req_per_sec; + return avg_sec_per_req * 1000.0; + } + void print() const { + fprintf(stderr, "total throughput: %f req/s\n", throughput()); + fprintf(stderr, "average latency : %f ms\n", latency_ms()); + } +}; + +void perform_test(size_t thread_id, Client &client, Result &result) { + uint64_t seq = 0; + FRT_Target *target = client.connect(); + FRT_RPCRequest *req = client.orb.AllocRPCRequest(); + auto invoke = [&seq, target, &client, &req](){ + req = client.orb.AllocRPCRequest(req); + req->SetMethodName("inc"); + req->GetParams()->AddInt64(seq); + target->InvokeSync(req, 60.0); + ASSERT_TRUE(req->CheckReturnTypes("l")); + uint64_t ret = req->GetReturn()->GetValue(0)._intval64; + EXPECT_EQUAL(ret, seq + 1); + seq = ret; + }; + size_t loop_cnt = 128; + BenchmarkTimer::benchmark(invoke, invoke, 1.0); + BenchmarkTimer timer(3.0); + while (timer.has_budget()) { + timer.before(); + for (size_t i = 0; i < loop_cnt; ++i) { + invoke(); + } + timer.after(); + } + double t = timer.min_time(); + BenchmarkTimer::benchmark(invoke, invoke, 1.0); + EXPECT_GREATER_EQUAL(seq, loop_cnt); + result.req_per_sec[thread_id] = double(loop_cnt) / t; + req->SubRef(); + target->SubRef(); + TEST_BARRIER(); + if (thread_id == 0) { + result.print(); + } +} + +TEST_MT_FFF("parallel rpc with 1/1 transport threads and 128 user threads", + 128, Server(1), Client(1, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); } + +TEST_MT_FFF("parallel rpc with 1/8 transport threads and 128 user threads", + 128, Server(8), Client(1, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); } + +TEST_MT_FFF("parallel rpc with 8/1 transport threads and 128 user threads", + 128, Server(1), Client(8, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); } + +TEST_MT_FFF("parallel rpc with 8/8 transport threads and 128 user threads", + 128, Server(8), Client(8, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); } + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/fnet/src/tests/frt/rpc/.gitignore b/fnet/src/tests/frt/rpc/.gitignore new file mode 100644 index 00000000000..be31ed66868 --- /dev/null +++ b/fnet/src/tests/frt/rpc/.gitignore @@ -0,0 +1,12 @@ +*.core +.depend +Makefile +core +detach_return_invoke_test +invoke_test +session_test +sharedblob_test +fnet_detach_return_invoke_test_app +fnet_invoke_test_app +fnet_session_test_app +fnet_sharedblob_test_app diff --git a/fnet/src/tests/frt/rpc/CMakeLists.txt b/fnet/src/tests/frt/rpc/CMakeLists.txt new file mode 100644 index 00000000000..806a78ec6b7 --- /dev/null +++ b/fnet/src/tests/frt/rpc/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(fnet_invoke_test_app + SOURCES + invoke.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_invoke_test_app COMMAND fnet_invoke_test_app) +vespa_add_executable(fnet_detach_return_invoke_test_app + SOURCES + detach_return_invoke.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_detach_return_invoke_test_app COMMAND fnet_detach_return_invoke_test_app) +vespa_add_executable(fnet_session_test_app + SOURCES + session.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_session_test_app COMMAND fnet_session_test_app) +vespa_add_executable(fnet_sharedblob_test_app + SOURCES + sharedblob.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_sharedblob_test_app COMMAND fnet_sharedblob_test_app) diff --git a/fnet/src/tests/frt/rpc/DESC b/fnet/src/tests/frt/rpc/DESC new file mode 100644 index 00000000000..017c68b41f8 --- /dev/null +++ b/fnet/src/tests/frt/rpc/DESC @@ -0,0 +1 @@ +Various tests related to rpc invocation. diff --git a/fnet/src/tests/frt/rpc/FILES b/fnet/src/tests/frt/rpc/FILES new file mode 100644 index 00000000000..e038169da5d --- /dev/null +++ b/fnet/src/tests/frt/rpc/FILES @@ -0,0 +1,3 @@ +invoke.cpp +session.cpp +sharedblob.cpp diff --git a/fnet/src/tests/frt/rpc/detach_return_invoke.cpp b/fnet/src/tests/frt/rpc/detach_return_invoke.cpp new file mode 100644 index 00000000000..b689671372b --- /dev/null +++ b/fnet/src/tests/frt/rpc/detach_return_invoke.cpp @@ -0,0 +1,69 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/fnet/frt/frt.h> +#include <vespa/vespalib/util/stringfmt.h> + +struct Receptor : public FRT_IRequestWait +{ + FRT_RPCRequest *req; + + Receptor() : req(0) {} + void RequestDone(FRT_RPCRequest *r) { + req = r; + } +}; + +struct Server : public FRT_Invokable +{ + FRT_Supervisor &orb; + Receptor &receptor; + + Server(FRT_Supervisor &s, Receptor &r) : orb(s), receptor(r) { + FRT_ReflectionBuilder rb(&s); + rb.DefineMethod("hook", "", "", true, + FRT_METHOD(Server::rpc_hook), this); + } + + void rpc_hook(FRT_RPCRequest *req) { + FNET_Connection *conn = req->GetConnection(); + conn->AddRef(); // need to keep it alive + req->Detach(); + req->Return(); // will free request channel + FRT_RPCRequest *r = orb.AllocRPCRequest(); + r->SetMethodName("frt.rpc.ping"); + // might re-use request channel before it is unlinked from hashmap + orb.InvokeAsync(orb.GetTransport(), conn, r, 5.0, &receptor); + conn->SubRef(); // invocation will now keep the connection alive as needed + } +}; + +TEST("detach return invoke") { + Receptor receptor; + FRT_Supervisor orb; + Server server(orb, receptor); + ASSERT_TRUE(orb.Listen(0)); + ASSERT_TRUE(orb.Start()); + std::string spec = vespalib::make_string("tcp/localhost:%d", orb.GetListenPort()); + FRT_Target *target = orb.Get2WayTarget(spec.c_str()); + FRT_RPCRequest *req = orb.AllocRPCRequest(); + + req->SetMethodName("hook"); + target->InvokeSync(req, 5.0); + EXPECT_TRUE(!req->IsError()); + for (uint32_t i = 0; i < 1000; ++i) { + if (receptor.req != 0) { + break; + } + FastOS_Thread::Sleep(10); + } + req->SubRef(); + target->SubRef(); + orb.ShutDown(true); + if (receptor.req != 0) { + EXPECT_TRUE(!receptor.req->IsError()); + receptor.req->SubRef(); + } + EXPECT_TRUE(receptor.req != 0); +}; + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/fnet/src/tests/frt/rpc/invoke.cpp b/fnet/src/tests/frt/rpc/invoke.cpp new file mode 100644 index 00000000000..7983d2eb9d8 --- /dev/null +++ b/fnet/src/tests/frt/rpc/invoke.cpp @@ -0,0 +1,938 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/fnet/frt/frt.h> + +//------------------------------------------------------------- + +FNET_Mutex _delayedReturnCntLock; +uint32_t _delayedReturnCnt = 0; + +uint32_t _phase_simple_cnt = 0; +uint32_t _phase_void_cnt = 0; +uint32_t _phase_speed_cnt = 0; +uint32_t _phase_advanced_cnt = 0; +uint32_t _phase_error_cnt = 0; +uint32_t _phase_timeout_cnt = 0; +uint32_t _phase_abort_cnt = 0; +uint32_t _phase_echo_cnt = 0; + +//------------------------------------------------------------- + +struct LockedReqWait : public FRT_IRequestWait +{ + FNET_Cond _cond; // cond used to signal req done + bool _done; // flag indicating req done + + FNET_Mutex _lockLock; // lock protecting virtual lock + bool _lock; // virtual lock + bool _wasLocked; // was 'locked' when req done + + LockedReqWait() : _cond(), _done(false), _lockLock(), _lock(false), _wasLocked(false) {} + + void lock() { + _lockLock.Lock(); + _lock = true; + _lockLock.Unlock(); + } + + void unlock() { + _lockLock.Lock(); + _lock = false; + _lockLock.Unlock(); + } + + bool isLocked() { + _lockLock.Lock(); + bool ret = _lock; + _lockLock.Unlock(); + return ret; + } + + virtual void RequestDone(FRT_RPCRequest *) + { + _wasLocked = isLocked(); + _cond.Lock(); + _done = true; + _cond.Signal(); + _cond.Unlock(); + } + + void waitReq() + { + _cond.Lock(); + while(!_done) { + _cond.Wait(); + } + _cond.Unlock(); + } +}; + +//------------------------------------------------------------- + +class DelayedReturn : public FNET_Task +{ +private: + FRT_RPCRequest *_req; + + DelayedReturn(const DelayedReturn &); + DelayedReturn &operator=(const DelayedReturn &); + +public: + DelayedReturn(FNET_Scheduler *sched, + FRT_RPCRequest *req, + double delay) + : FNET_Task(sched), + _req(req) + { + _delayedReturnCntLock.Lock(); + _delayedReturnCnt++; + _delayedReturnCntLock.Unlock(); + Schedule(delay); + } + + void PerformTask() + { + _req->Return(); + _delayedReturnCntLock.Lock(); + _delayedReturnCnt--; + _delayedReturnCntLock.Unlock(); + } +}; + +//------------------------------------------------------------- + +class EchoTest : public FRT_Invokable +{ +private: + FRT_MemoryTub *_echo_tub; + FRT_Values *_echo_args; + + EchoTest(const EchoTest &); + EchoTest &operator=(const EchoTest &); + +public: + EchoTest() : _echo_tub(NULL), _echo_args(NULL) {} + ~EchoTest() + { + delete _echo_args; + delete _echo_tub; + } + + void Init(FRT_Supervisor *supervisor) + { + _echo_tub = new FRT_MemoryTub(); + _echo_args = new FRT_Values(_echo_tub); + assert(_echo_tub != NULL && _echo_args != NULL); + + FRT_ReflectionBuilder rb(supervisor); + rb.DefineMethod("echo", "*", "*", true, + FRT_METHOD(EchoTest::RPC_Echo), this); + + FRT_Values *args = _echo_args; + args->EnsureFree(16); + + args->AddInt8(8); + uint8_t *pt_int8 = args->AddInt8Array(3); + pt_int8[0] = 1; + pt_int8[1] = 2; + pt_int8[2] = 3; + + args->AddInt16(16); + uint16_t *pt_int16 = args->AddInt16Array(3); + pt_int16[0] = 2; + pt_int16[1] = 4; + pt_int16[2] = 6; + + args->AddInt32(32); + uint32_t *pt_int32 = args->AddInt32Array(3); + pt_int32[0] = 4; + pt_int32[1] = 8; + pt_int32[2] = 12; + + args->AddInt64(64); + uint64_t *pt_int64 = args->AddInt64Array(3); + pt_int64[0] = 8; + pt_int64[1] = 16; + pt_int64[2] = 24; + + args->AddFloat(32.5); + float *pt_float = args->AddFloatArray(3); + pt_float[0] = 0.25; + pt_float[1] = 0.5; + pt_float[2] = 0.75; + + args->AddDouble(64.5); + double *pt_double = args->AddDoubleArray(3); + pt_double[0] = 0.1; + pt_double[1] = 0.2; + pt_double[2] = 0.3; + + args->AddString("string"); + FRT_StringValue *pt_string = args->AddStringArray(3); + args->SetString(&pt_string[0], "str1"); + args->SetString(&pt_string[1], "str2"); + args->SetString(&pt_string[2], "str3"); + + args->AddData("data", 4); + FRT_DataValue *pt_data = args->AddDataArray(3); + args->SetData(&pt_data[0], "dat1", 4); + args->SetData(&pt_data[1], "dat2", 4); + args->SetData(&pt_data[2], "dat3", 4); + } + + bool PrepareEchoReq(FRT_RPCRequest *req) + { + FNET_DataBuffer buf; + + req->SetMethodName("echo"); + _echo_args->EncodeCopy(&buf); + req->GetParams()->DecodeCopy(&buf, buf.GetDataLen()); + return (req->GetParams()->Equals(_echo_args) && + _echo_args->Equals(req->GetParams())); + } + + void RPC_Echo(FRT_RPCRequest *req) + { + FNET_DataBuffer buf; + + req->GetParams()->EncodeCopy(&buf); + req->GetReturn()->DecodeCopy(&buf, buf.GetDataLen()); + if (!req->GetReturn()->Equals(_echo_args) || + !req->GetReturn()->Equals(req->GetParams())) + { + req->SetError(10000, "Streaming error"); + } + } +}; + +//------------------------------------------------------------- + +class TestRPC : public FRT_Invokable +{ +private: + FRT_Supervisor *_supervisor; + FNET_Scheduler *_scheduler; + uint32_t _intValue; + + TestRPC(const TestRPC &); + TestRPC &operator=(const TestRPC &); + +public: + TestRPC(FRT_Supervisor *supervisor, // server supervisor + FNET_Scheduler *scheduler) // client scheduler + : _supervisor(supervisor), + _scheduler(scheduler), + _intValue(0) + { + FRT_ReflectionBuilder rb(supervisor); + + rb.DefineMethod("inc", "i", "i", true, + FRT_METHOD(TestRPC::RPC_Inc), this); + rb.DefineMethod("setValue", "i", "", true, + FRT_METHOD(TestRPC::RPC_SetValue), this); + rb.DefineMethod("incValue", "", "", true, + FRT_METHOD(TestRPC::RPC_IncValue), this); + rb.DefineMethod("getValue", "", "i", true, + FRT_METHOD(TestRPC::RPC_GetValue), this); + rb.DefineMethod("testFast", "iiibb", "i", true, + FRT_METHOD(TestRPC::RPC_Test), this); + rb.DefineMethod("testSlow", "iiibb", "i", false, + FRT_METHOD(TestRPC::RPC_Test), this); + } + + void RPC_Test(FRT_RPCRequest *req) + { + FRT_Values ¶m = *req->GetParams(); + uint32_t value = param[0]._intval32; + uint32_t delay = param[1]._intval32; + uint32_t error = param[2]._intval32; + uint8_t extra = param[3]._intval8; + uint8_t async = param[4]._intval8; + + req->GetReturn()->AddInt32(value); + if (extra != 0) { + req->GetReturn()->AddInt32(value); + } + if (error != 0) { + req->SetError(error); + } + if (async != 0) { + req->Detach(); + if (delay == 0) { + req->Return(); + } else { + new (req->GetMemoryTub()) DelayedReturn(_scheduler, + req, + ((double)delay) / 1000.0); + } + } else { + + if (delay > 0) { + + const char *suffix = "testFast"; + uint32_t suffix_len = strlen(suffix); + uint32_t name_len = req->GetMethodNameLen(); + bool remote = req->GetContext()._value.VOIDP != NULL; + bool instant = name_len > suffix_len && + strcmp(req->GetMethodName() + name_len - suffix_len, suffix) == 0; + + if (remote && instant) { + + // block, but don't cripple server scheduler... + // (NB: in 'real life', instant methods should never block) + + FastOS_Time *now = _supervisor->GetTransport()->GetTimeSampler(); + FNET_Scheduler *scheduler = _supervisor->GetScheduler(); + assert(scheduler->GetTimeSampler() == now); + + while (delay > 0) { + if (delay > 20) { + FastOS_Thread::Sleep(20); + delay -= 20; + } else { + FastOS_Thread::Sleep(delay); + delay = 0; + } + now->SetNow(); + scheduler->CheckTasks(); + } + + } else { + + FastOS_Thread::Sleep(delay); + } + } + } + } + + void RPC_Inc(FRT_RPCRequest *req) + { + req->GetReturn()->AddInt32(req->GetParams()->GetValue(0)._intval32 + 1); + } + + void RPC_SetValue(FRT_RPCRequest *req) + { + _intValue = req->GetParams()->GetValue(0)._intval32; + } + + void RPC_IncValue(FRT_RPCRequest *req) + { + (void) req; + _intValue++; + } + + void RPC_GetValue(FRT_RPCRequest *req) + { + req->GetReturn()->AddInt32(_intValue); + } +}; + +//------------------------------------------------------------- + +enum { + OK_RET = 0, + BOGUS_RET = 1 +}; + +enum { + PHASE_NULL = 0, + PHASE_SETUP, + PHASE_SIMPLE, + PHASE_VOID, + PHASE_SPEED, + PHASE_ADVANCED, + PHASE_ERROR, + PHASE_TIMEOUT, + PHASE_ABORT, + PHASE_ECHO, + PHASE_SHUTDOWN, + PHASE_ZZZ +}; + +const char phase_names[PHASE_ZZZ][32] = +{ + "NULL", + "SETUP", + "SIMPLE", + "VOID", + "SPEED", + "ADVANCED", + "ERROR", + "TIMEOUT", + "ABORT", + "ECHO", + "SHUTDOWN" +}; + +enum { + TIMING_NULL = 0, + TIMING_INSTANT, + TIMING_NON_INSTANT, + TIMING_ZZZ +}; + +const char timing_names[TIMING_ZZZ][32] = +{ + "NULL", + "INSTANT", + "NON-INSTANT" +}; + +enum { + HANDLING_NULL = 0, + HANDLING_SYNC, + HANDLING_ASYNC, + HANDLING_ZZZ +}; + +const char handling_names[HANDLING_ZZZ][32] = +{ + "NULL", + "SYNC", + "ASYNC" +}; + +//------------------------------------------------------------- + +struct State { + FRT_Supervisor _client; + FRT_Supervisor _server; + TestRPC _rpc; + EchoTest _echo; + std::string _peerSpec; + uint32_t _testPhase; + uint32_t _timing; + uint32_t _handling; + double _timeout; + FRT_Target *_target; + FRT_RPCRequest *_req; + + State() + : _client(), + _server(), + _rpc(&_server, _client.GetScheduler()), + _echo(), + _peerSpec(), + _testPhase(PHASE_NULL), + _timing(TIMING_NULL), + _handling(HANDLING_NULL), + _timeout(5.0), + _target(NULL), + _req(NULL) + { + _client.GetTransport()->SetTCPNoDelay(true); + _server.GetTransport()->SetTCPNoDelay(true); + _echo.Init(&_server); + } + + void SetTimeout(double timeout) + { + _timeout = timeout; + } + + void NewReq() + { + if (_req != NULL) { + _req->SubRef(); + } + _req = new FRT_RPCRequest(); + } + + void FreeReq() + { + if (_req != NULL) { + _req->SubRef(); + } + _req = NULL; + } + + void LostReq() + { + _req = NULL; + } + + void PrepareTestMethod() + { + NewReq(); + bool instant = (_timing == TIMING_INSTANT); + if (_timing != TIMING_INSTANT && + _timing != TIMING_NON_INSTANT) + { + ASSERT_TRUE(false); // consult your dealer... + } + if (instant) { + _req->SetMethodName("testFast"); + } else { + _req->SetMethodName("testSlow"); + } + } + + void SetTestParams(uint32_t value, uint32_t delay, + uint32_t error = FRTE_NO_ERROR, + uint8_t extra = 0) + { + _req->GetParams()->AddInt32(value); + _req->GetParams()->AddInt32(delay); + _req->GetParams()->AddInt32(error); + _req->GetParams()->AddInt8(extra); + bool async = (_handling == HANDLING_ASYNC); + if (_handling != HANDLING_SYNC && + _handling != HANDLING_ASYNC) + { + ASSERT_TRUE(false); // consult your dealer... + } + _req->GetParams()->AddInt8((async) ? 1 : 0); + } + + void InvokeSync(); + void InvokeVoid(); + void InvokeAsync(FRT_IRequestWait *w); + void InvokeTest(uint32_t value, + uint32_t delay = 0, + uint32_t error = FRTE_NO_ERROR, + uint8_t extra = 0); + void InvokeTestAndAbort(uint32_t value, + uint32_t delay = 0, + uint32_t error = FRTE_NO_ERROR, + uint8_t extra = 0); + bool WaitForDelayedReturnCount(uint32_t wantedCount, double timeout); + +private: + State(const State &); + State &operator=(const State &); +}; + + +void +State::InvokeSync() +{ + _target->InvokeSync(_req, _timeout); +} + + +void +State::InvokeVoid() +{ + _target->InvokeVoid(_req); +} + + +void +State::InvokeAsync(FRT_IRequestWait *w) +{ + _target->InvokeAsync(_req, _timeout, w); +} + + +void +State::InvokeTest(uint32_t value, uint32_t delay, + uint32_t error, uint8_t extra) +{ + PrepareTestMethod(); + SetTestParams(value, delay, error, extra); + InvokeSync(); +} + + +void +State::InvokeTestAndAbort(uint32_t value, uint32_t delay, + uint32_t error, uint8_t extra) +{ + PrepareTestMethod(); + SetTestParams(value, delay, error, extra); + FRT_SingleReqWait w; + InvokeAsync(&w); + _req->Abort(); + w.WaitReq(); +} + +bool +State::WaitForDelayedReturnCount(uint32_t wantedCount, double timeout) +{ + FastOS_Time timer; + timer.SetNow(); + for (;;) { + _delayedReturnCntLock.Lock(); + uint32_t delayedReturnCnt = _delayedReturnCnt; + _delayedReturnCntLock.Unlock(); + if (delayedReturnCnt == wantedCount) { + return true; + } + if ((timer.MilliSecsToNow() / 1000.0) > timeout) { + return false; + } + FastOS_Thread::Sleep(10); + } +} + +//------------------------------------------------------------- + +bool CheckTypes(FRT_RPCRequest *req, const char *spec) { + return FRT_Values::CheckTypes(spec, req->GetReturnSpec()); +} + +FRT_Value &Get(FRT_RPCRequest *req, uint32_t idx) { + return req->GetReturn()->GetValue(idx); +} + +//------------------------------------------------------------- + +void TestSetup(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_SETUP); + + bool listenOK = _state->_server.Listen("tcp/0"); + + char spec[64]; + sprintf(spec, "tcp/localhost:%d", _state->_server.GetListenPort()); + _state->_peerSpec = spec; + + bool serverStartOK = _state->_server.Start(); + bool clientStartOK = _state->_client.Start(); + + ASSERT_TRUE(listenOK); + ASSERT_TRUE(serverStartOK); + ASSERT_TRUE(clientStartOK); + + _state->_target = _state->_client.GetTarget(_state->_peerSpec.c_str()); + _state->NewReq(); + _state->_req->SetMethodName("frt.rpc.ping"); + _state->_target->InvokeSync(_state->_req, 5.0); + ASSERT_TRUE(!_state->_req->IsError()); +} + + +void TestSimple(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_SIMPLE); + _phase_simple_cnt++; + _state->NewReq(); + _state->_req->SetMethodName("inc"); + _state->_req->GetParams()->AddInt32(502); + _state->InvokeSync(); + EXPECT_TRUE(!_state->_req->IsError() && + CheckTypes(_state->_req, "i") && + Get(_state->_req, 0)._intval32 == 503); +} + + +void TestVoid(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_VOID); + _phase_void_cnt++; + + _state->NewReq(); + _state->_req->SetMethodName("setValue"); + _state->_req->GetParams()->AddInt32(40); + _state->InvokeSync(); + EXPECT_TRUE(!_state->_req->IsError() && + CheckTypes(_state->_req, "")); + + _state->NewReq(); + _state->_req->SetMethodName("incValue"); + _state->InvokeVoid(); + _state->LostReq(); + + _state->NewReq(); + _state->_req->SetMethodName("incValue"); + _state->InvokeVoid(); + _state->LostReq(); + + _state->NewReq(); + _state->_req->SetMethodName("getValue"); + _state->InvokeSync(); + EXPECT_TRUE(!_state->_req->IsError() && + CheckTypes(_state->_req, "i") && + Get(_state->_req, 0)._intval32 == 42); +} + + +void TestSpeed(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_SPEED); + _phase_speed_cnt++; + + FastOS_Time start; + FastOS_Time stop; + uint32_t val = 0; + uint32_t cnt = 0; + + _state->NewReq(); + FRT_RPCRequest *req = _state->_req; + FRT_Target *target = _state->_target; + + // calibrate cnt to be used + start.SetNow(); + for (cnt = 0; cnt < 1000000; cnt++) { + req->SetMethodName("inc"); + req->GetParams()->AddInt32(0); + target->InvokeSync(req, 5.0); + if (req->IsError()) { + break; + } + req->Reset(); // ok if no error + if (start.MilliSecsToNow() > 20.0) { + break; + } + } + cnt = (cnt == 0) ? 1 : cnt * 10; + + fprintf(stderr, "checking invocation latency... (cnt = %d)\n", cnt); + + _state->NewReq(); + req = _state->_req; + + // actual benchmark + start.SetNow(); + for (uint32_t i = 0; i < cnt; i++) { + req->SetMethodName("inc"); + req->GetParams()->AddInt32(val); + target->InvokeSync(req, 60.0); + if (req->IsError()) { + fprintf(stderr, "... rpc error(%d): %s\n", + req->GetErrorCode(), + req->GetErrorMessage()); + break; + } + val = req->GetReturn()->GetValue(0)._intval32; + req->Reset(); // ok if no error + } + stop.SetNow(); + stop -= start; + double latency = stop.MilliSecs() / (double) cnt; + + EXPECT_EQUAL(val, cnt); + fprintf(stderr, "latency of invocation: %1.3f ms\n", latency); +} + + +void TestAdvanced(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_ADVANCED); + _phase_advanced_cnt++; + + // Test invocation + //---------------- + _state->InvokeTest(42); + EXPECT_TRUE(!_state->_req->IsError() && + CheckTypes(_state->_req, "i") && + Get(_state->_req, 0)._intval32 == 42); + + // Abort has no effect after request is done + //------------------------------------------ + _state->_req->Abort(); + EXPECT_TRUE(!_state->_req->IsError() && + CheckTypes(_state->_req, "i") && + Get(_state->_req, 0)._intval32 == 42); + + // Test invocation with delay + //--------------------------- + _state->InvokeTest(58, 100); + EXPECT_TRUE(!_state->_req->IsError() && + CheckTypes(_state->_req, "i") && + Get(_state->_req, 0)._intval32 == 58); +} + + +void TestError(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_ERROR); + _phase_error_cnt++; + + // bad target -> sync error -> avoid deadlock + //------------------------------------------- + if (_state->_handling == HANDLING_ASYNC) + { + // stash away valid target + FRT_Target *stateTarget = _state->_target; // backup of valid target + + _state->_target = _state->_client.GetTarget("bogus address"); + _state->NewReq(); + _state->_req->SetMethodName("frt.rpc.ping"); + LockedReqWait lw; + lw.lock(); + _state->InvokeAsync(&lw); + lw.unlock(); + lw.waitReq(); + EXPECT_TRUE(!lw._wasLocked); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_CONNECTION); + + // restore valid target + _state->_target->SubRef(); + _state->_target = stateTarget; + } + + // no such method + //--------------- + if (_state->_timing == TIMING_INSTANT && + _state->_handling == HANDLING_SYNC) + { + _state->NewReq(); + _state->_req->SetMethodName("bogus"); + _state->InvokeSync(); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_NO_SUCH_METHOD); + } + + // wrong params + //------------- + if (_state->_handling == HANDLING_SYNC) { + + _state->PrepareTestMethod(); + _state->InvokeSync(); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_PARAMS); + + _state->PrepareTestMethod(); + _state->_req->GetParams()->AddInt32(42); + _state->_req->GetParams()->AddInt32(0); + _state->_req->GetParams()->AddInt8(0); + _state->_req->GetParams()->AddInt8(0); + _state->_req->GetParams()->AddInt8(0); + _state->InvokeSync(); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_PARAMS); + + _state->PrepareTestMethod(); + _state->_req->GetParams()->AddInt32(42); + _state->_req->GetParams()->AddInt32(0); + _state->_req->GetParams()->AddInt32(0); + _state->_req->GetParams()->AddInt8(0); + _state->_req->GetParams()->AddInt8(0); + _state->_req->GetParams()->AddInt8(0); + _state->InvokeSync(); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_PARAMS); + } + + // wrong return + //------------- + _state->InvokeTest(42, 0, 0, BOGUS_RET); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_RETURN); + + // method failed + //-------------- + _state->InvokeTest(42, 0, 5000, BOGUS_RET); + EXPECT_TRUE(_state->_req->GetErrorCode() == 5000); +} + + +void TestTimeout(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_TIMEOUT); + _phase_timeout_cnt++; + + _state->SetTimeout(0.1); + + // Test timeout + //------------- + _state->InvokeTest(123, 5000); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_TIMEOUT); + FastOS_Thread::Sleep(5500); // settle + + _state->SetTimeout(5.0); +} + + +void TestAbort(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_ABORT); + _phase_abort_cnt++; + + // Test abort + //----------- + _state->InvokeTestAndAbort(456, 1000); + EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_ABORT); + FastOS_Thread::Sleep(1500); // settle +} + + +void TestEcho(State *_state) { + ASSERT_TRUE(_state->_testPhase == PHASE_ECHO); + _phase_echo_cnt++; + + // Test echo + //---------- + _state->NewReq(); + EXPECT_TRUE(_state->_echo.PrepareEchoReq(_state->_req)); + _state->InvokeSync(); + EXPECT_TRUE(!_state->_req->IsError()); + EXPECT_TRUE(_state->_req->GetReturn()->Equals(_state->_req->GetParams())); +} + + +TEST_F("invoke test", State()) { + State *_state = &f1; + + _state->_testPhase = PHASE_SETUP; + TestSetup(_state); + + for (_state->_testPhase = PHASE_SIMPLE; + _state->_testPhase < PHASE_SHUTDOWN; + _state->_testPhase++) { + + { + for (_state->_timing = TIMING_INSTANT; + _state->_timing < TIMING_ZZZ; + _state->_timing++) { + + for (_state->_handling = HANDLING_SYNC; + _state->_handling < HANDLING_ZZZ; + _state->_handling++) { + + switch (_state->_testPhase) { + case PHASE_SIMPLE: + if (_state->_timing == TIMING_INSTANT && + _state->_handling == HANDLING_SYNC) + { + TestSimple(_state); + } + break; + case PHASE_VOID: + if (_state->_timing == TIMING_INSTANT && + _state->_handling == HANDLING_SYNC) + { + TestVoid(_state); + } + break; + case PHASE_SPEED: + if (_state->_timing == TIMING_INSTANT && + _state->_handling == HANDLING_SYNC) + { + TestSpeed(_state); + } + break; + case PHASE_ADVANCED: + TestAdvanced(_state); + break; + case PHASE_ERROR: + TestError(_state); + break; + case PHASE_TIMEOUT: + TestTimeout(_state); + break; + case PHASE_ABORT: + TestAbort(_state); + break; + case PHASE_ECHO: + if (_state->_timing == TIMING_INSTANT && + _state->_handling == HANDLING_SYNC) + { + TestEcho(_state); + } + break; + default: + ASSERT_TRUE(false); // consult your dealer... + } + } + } + } + } + _state->_testPhase = PHASE_SHUTDOWN; + _state->_timing = TIMING_NULL; + _state->_handling = HANDLING_NULL; + EXPECT_TRUE(_state->WaitForDelayedReturnCount(0, 120.0)); + _state->FreeReq(); + _state->_client.ShutDown(true); + _state->_server.ShutDown(true); + _state->_target->SubRef(); + _state->_target = NULL; + EXPECT_TRUE(_delayedReturnCnt == 0); + EXPECT_TRUE(_phase_simple_cnt == 1); + EXPECT_TRUE(_phase_void_cnt == 1); + EXPECT_TRUE(_phase_speed_cnt == 1); + EXPECT_TRUE(_phase_advanced_cnt == 4); + EXPECT_TRUE(_phase_error_cnt == 4); + EXPECT_TRUE(_phase_abort_cnt == 4); + EXPECT_TRUE(_phase_echo_cnt == 1); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/fnet/src/tests/frt/rpc/session.cpp b/fnet/src/tests/frt/rpc/session.cpp new file mode 100644 index 00000000000..c39fe8cba05 --- /dev/null +++ b/fnet/src/tests/frt/rpc/session.cpp @@ -0,0 +1,124 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/fnet/frt/frt.h> + + +class Session +{ +private: + static FNET_Mutex _lock; + static int _cnt; + int _val; + +public: + Session() : _val(0) + { + _lock.Lock(); + ++_cnt; + _lock.Unlock(); + } + + ~Session() + { + _lock.Lock(); + --_cnt; + _lock.Unlock(); + } + + void SetValue(int val) { _val = val; } + int GetValue() const { return _val; } + static int GetCnt() { return _cnt; } +}; + +FNET_Mutex Session::_lock; +int Session::_cnt(0); + + +struct RPC : public FRT_Invokable +{ + bool bogusFini; + + RPC() : bogusFini(false) {} + + void InitSession(FRT_RPCRequest *req) + { + Session *session = new Session(); + req->GetConnection()->SetContext(FNET_Context((void *) session)); + } + + void FiniSession(FRT_RPCRequest *req) + { + Session *session = + (Session *)req->GetConnection()->GetContext()._value.VOIDP; + bogusFini |= (session == NULL); + delete session; + } + + void GetValue(FRT_RPCRequest *req) + { + Session *session = + (Session *)req->GetConnection()->GetContext()._value.VOIDP; + req->GetReturn()->AddInt32(session->GetValue()); + } + + void SetValue(FRT_RPCRequest *req) + { + Session *session = + (Session *)req->GetConnection()->GetContext()._value.VOIDP; + session->SetValue(req->GetParams()->GetValue(0)._intval32); + } + + void Init(FRT_Supervisor *s) + { + FRT_ReflectionBuilder rb(s); + rb.DefineMethod("getValue", "", "i", true, + FRT_METHOD(RPC::GetValue), this); + rb.DefineMethod("setValue", "i", "", true, + FRT_METHOD(RPC::SetValue), this); + s->SetSessionInitHook(FRT_METHOD(RPC::InitSession), this); + s->SetSessionFiniHook(FRT_METHOD(RPC::FiniSession), this); + } +}; + +TEST("session") { + RPC rpc; + FRT_Supervisor orb; + char spec[64]; + rpc.Init(&orb); + ASSERT_TRUE(orb.Listen("tcp/0")); + sprintf(spec, "tcp/localhost:%d", orb.GetListenPort()); + ASSERT_TRUE(orb.Start()); + + FRT_Target *target = orb.GetTarget(spec); + FRT_RPCRequest *req = orb.AllocRPCRequest(); + + req->SetMethodName("getValue"); + target->InvokeSync(req, 5.0); + ASSERT_TRUE(!req->IsError() && + strcmp(req->GetReturnSpec(), "i") == 0 && + req->GetReturn()->GetValue(0)._intval32 == 0); + + req = orb.AllocRPCRequest(req); + req->SetMethodName("setValue"); + req->GetParams()->AddInt32(42); + target->InvokeSync(req, 5.0); + ASSERT_TRUE(!req->IsError() && + strcmp(req->GetReturnSpec(), "") == 0); + + req = orb.AllocRPCRequest(req); + req->SetMethodName("getValue"); + target->InvokeSync(req, 5.0); + ASSERT_TRUE(!req->IsError() && + strcmp(req->GetReturnSpec(), "i") == 0 && + req->GetReturn()->GetValue(0)._intval32 == 42); + + EXPECT_TRUE(Session::GetCnt() == 1); + + req->SubRef(); + target->SubRef(); + orb.ShutDown(true); + EXPECT_TRUE(Session::GetCnt() == 0); + EXPECT_TRUE(!rpc.bogusFini); +}; + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/fnet/src/tests/frt/rpc/sharedblob.cpp b/fnet/src/tests/frt/rpc/sharedblob.cpp new file mode 100644 index 00000000000..bb115f1c65f --- /dev/null +++ b/fnet/src/tests/frt/rpc/sharedblob.cpp @@ -0,0 +1,256 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/fnet/frt/frt.h> +#include <vector> + +struct MyBlob : FRT_ISharedBlob +{ + int refcnt; + MyBlob() : refcnt(1) {} + virtual uint32_t getLen() { return (strlen("blob_test") + 1); } + virtual const char *getData() { return "blob_test"; } + virtual void addRef() { ++refcnt; } + virtual void subRef() { --refcnt; } +}; + +struct Data +{ + enum { + SMALL = (FRT_MemoryTub::ALLOC_LIMIT / 2), + LARGE = (FRT_MemoryTub::ALLOC_LIMIT * 2) + }; + + char *buf; + uint32_t len; + + Data(const char *pt, uint32_t l) : buf(new char[l]), len(l) { + memcpy(buf, pt, len); + } + Data(uint32_t l, char c) : buf(new char[l]), len(l) { + memset(buf, c, len); + } + Data(const Data &rhs) : buf(new char[rhs.len]), len(rhs.len) { + memcpy(buf, rhs.buf, len); + } + Data &operator=(const Data &rhs) { + if (this != &rhs) { + delete [] buf; + buf = new char[rhs.len]; + len = rhs.len; + memcpy(buf, rhs.buf, len); + } + return *this; + } + bool check(uint32_t l, char c) { + if (l != len) { + fprintf(stderr, "blob length was %u, expected %u\n", len, l); + return false; + } + for (uint32_t i = 0; i < l; ++i) { + if (buf[i] != c) { + fprintf(stderr, "byte at offset %u was %c, expected %c\n", i, buf[i], c); + return false; + } + } + return true; + } + ~Data() { + delete [] buf; + } +}; + +struct DataSet +{ + std::vector<Data> blobs; + + void sample(FRT_Values &v) { + blobs.push_back(Data(v.GetNumValues(), 'V')); + for (uint32_t i = 0; i < v.GetNumValues(); ++i) { + if (v.GetType(i) == FRT_VALUE_DATA) { + blobs.push_back(Data(1, 'x')); + blobs.push_back(Data(v[i]._data._buf, v[i]._data._len)); + } else if (v.GetType(i) == FRT_VALUE_DATA_ARRAY) { + blobs.push_back(Data(v[i]._data_array._len, 'X')); + for (uint32_t j = 0; j < v[i]._data_array._len; ++j) { + blobs.push_back(Data(v[i]._data_array._pt[j]._buf, + v[i]._data_array._pt[j]._len)); + } + } + } + } +}; + +struct ServerSampler : public FRT_Invokable +{ + DataSet &dataSet; + FRT_RPCRequest *clientReq; + FRT_RPCRequest *serverReq; + + ServerSampler(DataSet &ds, FRT_RPCRequest *cr) : dataSet(ds), clientReq(cr), serverReq(0) {} + + void RPC_test(FRT_RPCRequest *req) + { + if (clientReq != 0) { + dataSet.sample(*clientReq->GetParams()); // client params after drop + } + + // store away parameters + FNET_DataBuffer buf; + buf.EnsureFree(req->GetParams()->GetLength()); + req->GetParams()->EncodeCopy(&buf); + + dataSet.sample(*req->GetParams()); // server params before drop + req->DiscardBlobs(); + dataSet.sample(*req->GetParams()); // server params after drop + + // restore parameters into return values + req->GetReturn()->DecodeCopy(&buf, buf.GetDataLen()); + + dataSet.sample(*req->GetReturn()); // server return before drop + + // keep request to sample return after drop + req->AddRef(); + serverReq = req; + } +}; + +TEST("testExplicitShared") { + FRT_Supervisor orb; + MyBlob blob; + + FRT_RPCRequest *req = orb.AllocRPCRequest(); + EXPECT_TRUE(blob.refcnt == 1); + + req->GetParams()->AddSharedData(&blob); + req->GetParams()->AddInt32(42); + req->GetParams()->AddSharedData(&blob); + req->GetParams()->AddInt32(84); + req->GetParams()->AddSharedData(&blob); + + EXPECT_TRUE(blob.refcnt == 4); + EXPECT_TRUE(strcmp(req->GetParamSpec(), "xixix") == 0); + EXPECT_TRUE(req->GetParams()->GetValue(0)._data._len == blob.getLen()); + EXPECT_TRUE(req->GetParams()->GetValue(0)._data._buf == blob.getData()); + EXPECT_TRUE(req->GetParams()->GetValue(1)._intval32 == 42); + EXPECT_TRUE(req->GetParams()->GetValue(2)._data._len == blob.getLen()); + EXPECT_TRUE(req->GetParams()->GetValue(2)._data._buf == blob.getData()); + EXPECT_TRUE(req->GetParams()->GetValue(3)._intval32 == 84); + EXPECT_TRUE(req->GetParams()->GetValue(4)._data._len == blob.getLen()); + EXPECT_TRUE(req->GetParams()->GetValue(4)._data._buf == blob.getData()); + + req->CreateRequestPacket(true)->Free(); // fake request send. + + EXPECT_TRUE(blob.refcnt == 1); + EXPECT_TRUE(strcmp(req->GetParamSpec(), "xixix") == 0); + EXPECT_TRUE(req->GetParams()->GetValue(0)._data._len == 0); + EXPECT_TRUE(req->GetParams()->GetValue(0)._data._buf == NULL); + EXPECT_TRUE(req->GetParams()->GetValue(1)._intval32 == 42); + EXPECT_TRUE(req->GetParams()->GetValue(2)._data._len == 0); + EXPECT_TRUE(req->GetParams()->GetValue(2)._data._buf == NULL); + EXPECT_TRUE(req->GetParams()->GetValue(3)._intval32 == 84); + EXPECT_TRUE(req->GetParams()->GetValue(4)._data._len == 0); + EXPECT_TRUE(req->GetParams()->GetValue(4)._data._buf == NULL); + + req = orb.AllocRPCRequest(req); + + req->GetParams()->AddSharedData(&blob); + req->GetParams()->AddInt32(42); + req->GetParams()->AddSharedData(&blob); + req->GetParams()->AddInt32(84); + req->GetParams()->AddSharedData(&blob); + + EXPECT_TRUE(blob.refcnt == 4); + req->SubRef(); + EXPECT_TRUE(blob.refcnt == 1); +} + +TEST("testImplicitShared") { + DataSet dataSet; + FRT_Supervisor orb; + FRT_RPCRequest *req = orb.AllocRPCRequest(); + ServerSampler serverSampler(dataSet, req); + { + FRT_ReflectionBuilder rb(&orb); + rb.DefineMethod("test", "*", "*", true, + FRT_METHOD(ServerSampler::RPC_test), &serverSampler); + } + orb.Listen(0); + int port = orb.GetListenPort(); + ASSERT_TRUE(port != 0); + orb.Start(); + + char tmp[64]; + snprintf(tmp, sizeof(tmp), "tcp/localhost:%d", port); + FRT_Target *target = orb.GetTarget(tmp); + req->SetMethodName("test"); + { + Data data(Data::SMALL, 'a'); + req->GetParams()->AddData(data.buf, data.len); + } + { + Data data(Data::LARGE, 'b'); + req->GetParams()->AddData(data.buf, data.len); + } + { + char *data = req->GetParams()->AddData(Data::LARGE); + memset(data, 'c', Data::LARGE); + } + { + Data data1(Data::SMALL, 'd'); + Data data2(Data::LARGE, 'e'); + FRT_DataValue *arr = req->GetParams()->AddDataArray(2); + req->GetParams()->SetData(&arr[0], data1.buf, data1.len); + req->GetParams()->SetData(&arr[1], data2.buf, data2.len); + } + + dataSet.sample(*req->GetParams()); // client params before drop + + target->InvokeSync(req, 30.0); + + if (serverSampler.serverReq != 0) { + dataSet.sample(*serverSampler.serverReq->GetReturn()); // server return after drop + } + dataSet.sample(*req->GetReturn()); // client return before drop + + req->DiscardBlobs(); + + dataSet.sample(*req->GetReturn()); // client return after drop + + // verify blob samples + EXPECT_EQUAL(dataSet.blobs.size(), 80u); + + for (int i = 0; i < 80; i += 20) { + // before discard (client params, server params, server return, client return) + EXPECT_TRUE(dataSet.blobs[i + 0].check(4, 'V')); + EXPECT_TRUE(dataSet.blobs[i + 1].check(1, 'x')); + EXPECT_TRUE(dataSet.blobs[i + 2].check(Data::SMALL, 'a')); + EXPECT_TRUE(dataSet.blobs[i + 3].check(1, 'x')); + EXPECT_TRUE(dataSet.blobs[i + 4].check(Data::LARGE, 'b')); + EXPECT_TRUE(dataSet.blobs[i + 5].check(1, 'x')); + EXPECT_TRUE(dataSet.blobs[i + 6].check(Data::LARGE, 'c')); + EXPECT_TRUE(dataSet.blobs[i + 7].check(2, 'X')); + EXPECT_TRUE(dataSet.blobs[i + 8].check(Data::SMALL, 'd')); + EXPECT_TRUE(dataSet.blobs[i + 9].check(Data::LARGE, 'e')); + + // after discard (client params, server params, server return, client return) + EXPECT_TRUE(dataSet.blobs[i + 10].check(4, 'V')); + EXPECT_TRUE(dataSet.blobs[i + 11].check(1, 'x')); + EXPECT_TRUE(dataSet.blobs[i + 12].check(Data::SMALL, 'a')); + EXPECT_TRUE(dataSet.blobs[i + 13].check(1, 'x')); + EXPECT_TRUE(dataSet.blobs[i + 14].check(0, 0)); + EXPECT_TRUE(dataSet.blobs[i + 15].check(1, 'x')); + EXPECT_TRUE(dataSet.blobs[i + 16].check(0, 0)); + EXPECT_TRUE(dataSet.blobs[i + 17].check(2, 'X')); + EXPECT_TRUE(dataSet.blobs[i + 18].check(Data::SMALL, 'd')); + EXPECT_TRUE(dataSet.blobs[i + 19].check(0, 0)); + } + + if (serverSampler.serverReq != 0) { + serverSampler.serverReq->SubRef(); + } + req->SubRef(); + target->SubRef(); + orb.ShutDown(true); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/fnet/src/tests/frt/values/.gitignore b/fnet/src/tests/frt/values/.gitignore new file mode 100644 index 00000000000..509dbc76bff --- /dev/null +++ b/fnet/src/tests/frt/values/.gitignore @@ -0,0 +1 @@ +fnet_values_test_app diff --git a/fnet/src/tests/frt/values/CMakeLists.txt b/fnet/src/tests/frt/values/CMakeLists.txt new file mode 100644 index 00000000000..f1a851a09b1 --- /dev/null +++ b/fnet/src/tests/frt/values/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(fnet_values_test_app + SOURCES + values_test.cpp + DEPENDS + fnet +) +vespa_add_test(NAME fnet_values_test_app COMMAND fnet_values_test_app) diff --git a/fnet/src/tests/frt/values/FILES b/fnet/src/tests/frt/values/FILES new file mode 100644 index 00000000000..8450fbfdf7e --- /dev/null +++ b/fnet/src/tests/frt/values/FILES @@ -0,0 +1 @@ +values_test.cpp diff --git a/fnet/src/tests/frt/values/values_test.cpp b/fnet/src/tests/frt/values/values_test.cpp new file mode 100644 index 00000000000..01c43d8207d --- /dev/null +++ b/fnet/src/tests/frt/values/values_test.cpp @@ -0,0 +1,207 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/fnet/frt/frt.h> + +uint8_t int8_arr[3] = { 1, 2, 3 }; +uint16_t int16_arr[3] = { 2, 4, 6 }; +uint32_t int32_arr[3] = { 4, 8, 12 }; +uint64_t int64_arr[3] = { 8, 16, 24 }; +float float_arr[3] = { 0.5, 1.0, 1.5 }; +double double_arr[3] = { 0.25, 0.50, 0.75 }; + +template <typename T> +void arr_cpy(T *dst, const T* src, size_t len) { + for (size_t i = 0; i < len; ++i) { + dst[i] = src[i]; + } +} + +void fillValues(FRT_Values &values) { + { + values.AddInt8(int8_arr[0]); + arr_cpy(values.AddInt8Array(3), int8_arr, 3); + values.AddInt8Array(int8_arr, 3); + values.AddInt8ArrayRef(int8_arr, 3); + } + { + values.AddInt16(int16_arr[0]); + arr_cpy(values.AddInt16Array(3), int16_arr, 3); + values.AddInt16Array(int16_arr, 3); + values.AddInt16ArrayRef(int16_arr, 3); + } + { + values.AddInt32(int32_arr[0]); + arr_cpy(values.AddInt32Array(3), int32_arr, 3); + values.AddInt32Array(int32_arr, 3); + values.AddInt32ArrayRef(int32_arr, 3); + } + { + values.AddInt64(int64_arr[0]); + arr_cpy(values.AddInt64Array(3), int64_arr, 3); + values.AddInt64Array(int64_arr, 3); + values.AddInt64ArrayRef(int64_arr, 3); + } + { + values.AddFloat(float_arr[0]); + arr_cpy(values.AddFloatArray(3), float_arr, 3); + values.AddFloatArray(float_arr, 3); + values.AddFloatArrayRef(float_arr, 3); + } + { + values.AddDouble(double_arr[0]); + arr_cpy(values.AddDoubleArray(3), double_arr, 3); + values.AddDoubleArray(double_arr, 3); + values.AddDoubleArrayRef(double_arr, 3); + } + { + values.AddString("foo"); + values.AddString("bar", 3); + strcpy(values.AddString(3), "baz"); + FRT_StringValue *str_arr = values.AddStringArray(3); + values.SetString(str_arr, "foo"); + values.SetString(str_arr + 1, "bar"); + values.SetString(str_arr + 2, "baz", 3); + } + { + values.AddData("foo", 3); + strncpy(values.AddData(3), "bar", 3); + FRT_DataValue *data_arr = values.AddDataArray(3); + values.SetData(data_arr, "foo", 3); + values.SetData(data_arr + 1, "bar", 3); + values.SetData(data_arr + 2, "baz", 3); + } +} + +void checkValues(FRT_Values &values) { + ASSERT_EQUAL(31u, values.GetNumValues()); + ASSERT_EQUAL(std::string("bBBBhHHHiIIIlLLLfFFFdDDDsssSxxX"), values.GetTypeString()); + size_t idx = 0; + EXPECT_EQUAL(int8_arr[0], values[idx++]._intval8); + for (size_t i = 0; i < 3; ++i, ++idx) { + ASSERT_EQUAL(3u, values[idx]._int8_array._len); + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQUAL(int8_arr[j], values[idx]._int8_array._pt[j]); + } + } + EXPECT_EQUAL(int16_arr[0], values[idx++]._intval16); + for (size_t i = 0; i < 3; ++i, ++idx) { + ASSERT_EQUAL(3u, values[idx]._int16_array._len); + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQUAL(int16_arr[j], values[idx]._int16_array._pt[j]); + } + } + EXPECT_EQUAL(int32_arr[0], values[idx++]._intval32); + for (size_t i = 0; i < 3; ++i, ++idx) { + ASSERT_EQUAL(3u, values[idx]._int32_array._len); + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQUAL(int32_arr[j], values[idx]._int32_array._pt[j]); + } + } + EXPECT_EQUAL(int64_arr[0], values[idx++]._intval64); + for (size_t i = 0; i < 3; ++i, ++idx) { + ASSERT_EQUAL(3u, values[idx]._int64_array._len); + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQUAL(int64_arr[j], values[idx]._int64_array._pt[j]); + } + } + EXPECT_EQUAL(float_arr[0], values[idx++]._float); + for (size_t i = 0; i < 3; ++i, ++idx) { + ASSERT_EQUAL(3u, values[idx]._float_array._len); + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQUAL(float_arr[j], values[idx]._float_array._pt[j]); + } + } + EXPECT_EQUAL(double_arr[0], values[idx++]._double); + for (size_t i = 0; i < 3; ++i, ++idx) { + ASSERT_EQUAL(3u, values[idx]._double_array._len); + for (size_t j = 0; j < 3; ++j) { + EXPECT_EQUAL(double_arr[j], values[idx]._double_array._pt[j]); + } + } + EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._string._str, + values[idx]._string._len)); + ++idx; + EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._string._str, + values[idx]._string._len)); + ++idx; + EXPECT_EQUAL(std::string("baz"), std::string(values[idx]._string._str, + values[idx]._string._len)); + ++idx; + ASSERT_EQUAL(3u, values[idx]._string_array._len); + EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._string_array._pt[0]._str, + values[idx]._string_array._pt[0]._len)); + EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._string_array._pt[1]._str, + values[idx]._string_array._pt[1]._len)); + EXPECT_EQUAL(std::string("baz"), std::string(values[idx]._string_array._pt[2]._str, + values[idx]._string_array._pt[2]._len)); + ++idx; + EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._data._buf, + values[idx]._data._len)); + ++idx; + EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._data._buf, + values[idx]._data._len)); + ++idx; + ASSERT_EQUAL(3u, values[idx]._data_array._len); + EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._data_array._pt[0]._buf, + values[idx]._data_array._pt[0]._len)); + EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._data_array._pt[1]._buf, + values[idx]._data_array._pt[1]._len)); + EXPECT_EQUAL(std::string("baz"), std::string(values[idx]._data_array._pt[2]._buf, + values[idx]._data_array._pt[2]._len)); + ++idx; + EXPECT_EQUAL(31u, idx); +} + +void checkValues(FRT_Values &v1, FRT_Values &v2) { + checkValues(v1); + checkValues(v2); + EXPECT_TRUE(v1.Equals(&v2)); + EXPECT_TRUE(v2.Equals(&v1)); +} + +TEST_FF("set and get", FRT_MemoryTub(), FRT_Values(&f1)) { + fillValues(f2); + checkValues(f2); +} + +TEST_FFFF("encode/decode big endian", FRT_MemoryTub(), FRT_Values(&f1), + FNET_DataBuffer(), FRT_Values(&f1)) +{ + fillValues(f2); + f2.EncodeBig(&f3); + EXPECT_EQUAL(f2.GetLength(), f3.GetDataLen()); + EXPECT_TRUE(f4.DecodeBig(&f3, f3.GetDataLen())); + checkValues(f2, f4); +} + +TEST_FFFF("encode/decode host endian", FRT_MemoryTub(), FRT_Values(&f1), + FNET_DataBuffer(), FRT_Values(&f1)) +{ + fillValues(f2); + f2.EncodeCopy(&f3); + EXPECT_EQUAL(f2.GetLength(), f3.GetDataLen()); + EXPECT_TRUE(f4.DecodeCopy(&f3, f3.GetDataLen())); + checkValues(f2, f4); +} + +TEST_FFFF("decode little if host is little", FRT_MemoryTub(), FRT_Values(&f1), + FNET_DataBuffer(), FRT_Values(&f1)) +{ + if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE) { + fprintf(stderr, "little endian detected...\n"); + fillValues(f2); + f2.EncodeCopy(&f3); + EXPECT_EQUAL(f2.GetLength(), f3.GetDataLen()); + EXPECT_TRUE(f4.DecodeLittle(&f3, f3.GetDataLen())); + checkValues(f2, f4); + } else { + fprintf(stderr, "host is not little endian, coverage will suffer...\n"); + } +} + +TEST_FF("print values", FRT_MemoryTub(), FRT_Values(&f1)) { + fillValues(f2); + f2.Print(); +} + +TEST_MAIN() { TEST_RUN_ALL(); } |