summaryrefslogtreecommitdiffstats
path: root/fnet/src/tests/frt
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /fnet/src/tests/frt
Publish
Diffstat (limited to 'fnet/src/tests/frt')
-rw-r--r--fnet/src/tests/frt/memorytub/.gitignore6
-rw-r--r--fnet/src/tests/frt/memorytub/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/memorytub/DESC1
-rw-r--r--fnet/src/tests/frt/memorytub/FILES1
-rw-r--r--fnet/src/tests/frt/memorytub/memorytub.cpp124
-rw-r--r--fnet/src/tests/frt/method_pt/.gitignore6
-rw-r--r--fnet/src/tests/frt/method_pt/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/method_pt/DESC2
-rw-r--r--fnet/src/tests/frt/method_pt/FILES1
-rw-r--r--fnet/src/tests/frt/method_pt/method_pt.cpp395
-rw-r--r--fnet/src/tests/frt/parallel_rpc/.gitignore1
-rw-r--r--fnet/src/tests/frt/parallel_rpc/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp129
-rw-r--r--fnet/src/tests/frt/rpc/.gitignore12
-rw-r--r--fnet/src/tests/frt/rpc/CMakeLists.txt29
-rw-r--r--fnet/src/tests/frt/rpc/DESC1
-rw-r--r--fnet/src/tests/frt/rpc/FILES3
-rw-r--r--fnet/src/tests/frt/rpc/detach_return_invoke.cpp69
-rw-r--r--fnet/src/tests/frt/rpc/invoke.cpp938
-rw-r--r--fnet/src/tests/frt/rpc/session.cpp124
-rw-r--r--fnet/src/tests/frt/rpc/sharedblob.cpp256
-rw-r--r--fnet/src/tests/frt/values/.gitignore1
-rw-r--r--fnet/src/tests/frt/values/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/values/FILES1
-rw-r--r--fnet/src/tests/frt/values/values_test.cpp207
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 &params = *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 &param = *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(); }