aboutsummaryrefslogtreecommitdiffstats
path: root/fnet/src/tests
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
Publish
Diffstat (limited to 'fnet/src/tests')
-rw-r--r--fnet/src/tests/.gitignore3
-rw-r--r--fnet/src/tests/connect_thread/.gitignore1
-rw-r--r--fnet/src/tests/connect_thread/CMakeLists.txt8
-rw-r--r--fnet/src/tests/connect_thread/connect_thread_test.cpp26
-rw-r--r--fnet/src/tests/connection_spread/.gitignore1
-rw-r--r--fnet/src/tests/connection_spread/CMakeLists.txt8
-rw-r--r--fnet/src/tests/connection_spread/connection_spread_test.cpp83
-rw-r--r--fnet/src/tests/databuffer/.gitignore4
-rw-r--r--fnet/src/tests/databuffer/CMakeLists.txt8
-rw-r--r--fnet/src/tests/databuffer/DESC2
-rw-r--r--fnet/src/tests/databuffer/FILES1
-rw-r--r--fnet/src/tests/databuffer/databuffer.cpp201
-rw-r--r--fnet/src/tests/examples/.gitignore1
-rw-r--r--fnet/src/tests/examples/CMakeLists.txt8
-rw-r--r--fnet/src/tests/examples/FILES1
-rw-r--r--fnet/src/tests/examples/examples_test.cpp246
-rw-r--r--fnet/src/tests/fdselector/.gitignore4
-rw-r--r--fnet/src/tests/fdselector/CMakeLists.txt8
-rw-r--r--fnet/src/tests/fdselector/DESC1
-rw-r--r--fnet/src/tests/fdselector/FILES1
-rw-r--r--fnet/src/tests/fdselector/fdselector.cpp220
-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
-rw-r--r--fnet/src/tests/info/.gitignore4
-rw-r--r--fnet/src/tests/info/CMakeLists.txt8
-rw-r--r--fnet/src/tests/info/DESC1
-rw-r--r--fnet/src/tests/info/FILES1
-rw-r--r--fnet/src/tests/info/info.cpp89
-rw-r--r--fnet/src/tests/locking/.gitignore8
-rw-r--r--fnet/src/tests/locking/CMakeLists.txt23
-rw-r--r--fnet/src/tests/locking/DESC1
-rw-r--r--fnet/src/tests/locking/FILES2
-rw-r--r--fnet/src/tests/locking/castspeed.cpp219
-rw-r--r--fnet/src/tests/locking/drainpackets.cpp134
-rw-r--r--fnet/src/tests/locking/dummy.cpp9
-rw-r--r--fnet/src/tests/locking/dummy.h17
-rw-r--r--fnet/src/tests/locking/lockspeed.cpp177
-rw-r--r--fnet/src/tests/printstuff/.gitignore1
-rw-r--r--fnet/src/tests/printstuff/CMakeLists.txt8
-rw-r--r--fnet/src/tests/printstuff/FILES1
-rw-r--r--fnet/src/tests/printstuff/printstuff_test.cpp45
-rw-r--r--fnet/src/tests/regress/databuffer/.gitignore0
-rw-r--r--fnet/src/tests/regress/fdselector/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/memorytub/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/method_pt/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/rpc/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/values/.gitignore0
-rw-r--r--fnet/src/tests/regress/info/.gitignore0
-rw-r--r--fnet/src/tests/regress/locking/.gitignore0
-rw-r--r--fnet/src/tests/regress/scheduling/.gitignore0
-rw-r--r--fnet/src/tests/regress/spiral/.gitignore0
-rw-r--r--fnet/src/tests/regress/sync_execute/.gitignore0
-rw-r--r--fnet/src/tests/regress/thread_id/.gitignore0
-rw-r--r--fnet/src/tests/regress/time/.gitignore0
-rw-r--r--fnet/src/tests/scheduling/.gitignore6
-rw-r--r--fnet/src/tests/scheduling/CMakeLists.txt15
-rw-r--r--fnet/src/tests/scheduling/DESC1
-rw-r--r--fnet/src/tests/scheduling/FILES1
-rw-r--r--fnet/src/tests/scheduling/schedule.cpp153
-rw-r--r--fnet/src/tests/scheduling/sloweventloop.cpp65
-rw-r--r--fnet/src/tests/sync_execute/.gitignore4
-rw-r--r--fnet/src/tests/sync_execute/CMakeLists.txt8
-rw-r--r--fnet/src/tests/sync_execute/DESC1
-rw-r--r--fnet/src/tests/sync_execute/FILES1
-rw-r--r--fnet/src/tests/sync_execute/sync_execute.cpp39
-rw-r--r--fnet/src/tests/thread_selection/.gitignore1
-rw-r--r--fnet/src/tests/thread_selection/CMakeLists.txt8
-rw-r--r--fnet/src/tests/thread_selection/thread_selection_test.cpp89
-rw-r--r--fnet/src/tests/time/.gitignore4
-rw-r--r--fnet/src/tests/time/CMakeLists.txt8
-rw-r--r--fnet/src/tests/time/DESC1
-rw-r--r--fnet/src/tests/time/FILES1
-rw-r--r--fnet/src/tests/time/timespeed.cpp68
96 files changed, 4397 insertions, 0 deletions
diff --git a/fnet/src/tests/.gitignore b/fnet/src/tests/.gitignore
new file mode 100644
index 00000000000..a3e9c375723
--- /dev/null
+++ b/fnet/src/tests/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+*_test
diff --git a/fnet/src/tests/connect_thread/.gitignore b/fnet/src/tests/connect_thread/.gitignore
new file mode 100644
index 00000000000..66bba07002d
--- /dev/null
+++ b/fnet/src/tests/connect_thread/.gitignore
@@ -0,0 +1 @@
+fnet_connect_thread_test_app
diff --git a/fnet/src/tests/connect_thread/CMakeLists.txt b/fnet/src/tests/connect_thread/CMakeLists.txt
new file mode 100644
index 00000000000..535ac5e3561
--- /dev/null
+++ b/fnet/src/tests/connect_thread/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_connect_thread_test_app
+ SOURCES
+ connect_thread_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_connect_thread_test_app COMMAND fnet_connect_thread_test_app)
diff --git a/fnet/src/tests/connect_thread/connect_thread_test.cpp b/fnet/src/tests/connect_thread/connect_thread_test.cpp
new file mode 100644
index 00000000000..f8492d147a6
--- /dev/null
+++ b/fnet/src/tests/connect_thread/connect_thread_test.cpp
@@ -0,0 +1,26 @@
+// 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/connect_thread.h>
+
+struct MyConn : public fnet::ExtConnectable {
+ bool connected = false;
+ void ext_connect() override { connected = true; }
+};
+
+TEST("require that connect thread will connect stuff") {
+ std::vector<MyConn> conns(5);
+ {
+ fnet::ConnectThread thread;
+ thread.connect_later(&conns[0]);
+ thread.connect_later(&conns[2]);
+ thread.connect_later(&conns[4]);
+ }
+ EXPECT_TRUE(conns[0].connected);
+ EXPECT_TRUE(!conns[1].connected);
+ EXPECT_TRUE(conns[2].connected);
+ EXPECT_TRUE(!conns[3].connected);
+ EXPECT_TRUE(conns[4].connected);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/connection_spread/.gitignore b/fnet/src/tests/connection_spread/.gitignore
new file mode 100644
index 00000000000..34408abbf7c
--- /dev/null
+++ b/fnet/src/tests/connection_spread/.gitignore
@@ -0,0 +1 @@
+fnet_connection_spread_test_app
diff --git a/fnet/src/tests/connection_spread/CMakeLists.txt b/fnet/src/tests/connection_spread/CMakeLists.txt
new file mode 100644
index 00000000000..9ed541b62e3
--- /dev/null
+++ b/fnet/src/tests/connection_spread/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_connection_spread_test_app
+ SOURCES
+ connection_spread_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_connection_spread_test_app COMMAND fnet_connection_spread_test_app)
diff --git a/fnet/src/tests/connection_spread/connection_spread_test.cpp b/fnet/src/tests/connection_spread/connection_spread_test.cpp
new file mode 100644
index 00000000000..a10278daf32
--- /dev/null
+++ b/fnet/src/tests/connection_spread/connection_spread_test.cpp
@@ -0,0 +1,83 @@
+// 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/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <thread>
+#include <chrono>
+#include <set>
+
+using namespace std::literals;
+
+struct DummyAdapter : FNET_IServerAdapter {
+ bool InitAdminChannel(FNET_Channel *) override { return false; }
+ bool InitChannel(FNET_Channel *, uint32_t) override { return false; }
+};
+
+struct DummyStreamer : FNET_IPacketStreamer {
+ bool GetPacketInfo(FNET_DataBuffer *, uint32_t *, uint32_t *, uint32_t *, bool *) override { return false; }
+ FNET_Packet *Decode(FNET_DataBuffer *, uint32_t, uint32_t, FNET_Context) override { return nullptr; }
+ void Encode(FNET_Packet *, uint32_t, FNET_DataBuffer *) override {}
+};
+
+struct Fixture {
+ DummyStreamer streamer;
+ DummyAdapter adapter;
+ FastOS_ThreadPool thread_pool;
+ FNET_Transport client;
+ FNET_Transport server;
+ Fixture() : streamer(), adapter(), thread_pool(128 * 1024), client(8), server(8)
+ {
+ ASSERT_TRUE(client.Start(&thread_pool));
+ ASSERT_TRUE(server.Start(&thread_pool));
+ }
+ void wait_for_components(size_t client_cnt, size_t server_cnt) {
+ bool ok = false;
+ for (size_t i = 0; !ok && (i < 10000); ++i) {
+ std::this_thread::sleep_for(3ms);
+ ok = ((client.GetNumIOComponents() == client_cnt) &&
+ (server.GetNumIOComponents() == server_cnt));
+ }
+ EXPECT_EQUAL(client.GetNumIOComponents(), client_cnt);
+ EXPECT_EQUAL(server.GetNumIOComponents(), server_cnt);
+ }
+ ~Fixture() {
+ server.ShutDown(true);
+ client.ShutDown(true);
+ thread_pool.Close();
+ }
+};
+
+void check_threads(FNET_Transport &transport, size_t num_threads, const vespalib::string &tag) {
+ std::set<FNET_TransportThread *> threads;
+ while (threads.size() < num_threads) {
+ threads.insert(transport.select_thread(nullptr, 0));
+ }
+ for (auto thread: threads) {
+ uint32_t cnt = thread->GetNumIOComponents();
+ fprintf(stderr, "-- %s thread: %u io components\n", tag.c_str(), cnt);
+ EXPECT_GREATER(cnt, 1u);
+ }
+}
+
+TEST_F("require that connections are spread among transport threads", Fixture)
+{
+ FNET_Connector *listener = f1.server.Listen("tcp/0", &f1.streamer, &f1.adapter);
+ ASSERT_TRUE(listener);
+ uint32_t port = listener->GetPortNumber();
+ vespalib::string spec = vespalib::make_string("tcp/localhost:%u", port);
+ std::vector<FNET_Connection *> connections;
+ for (size_t i = 0; i < 256; ++i) {
+ std::this_thread::sleep_for(1ms);
+ connections.push_back(f1.client.Connect(spec.c_str(), &f1.streamer));
+ ASSERT_TRUE(connections.back());
+ }
+ f1.wait_for_components(256, 257);
+ check_threads(f1.client, 8, "client");
+ check_threads(f1.server, 8, "server");
+ listener->SubRef();
+ for (FNET_Connection *conn: connections) {
+ conn->SubRef();
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/databuffer/.gitignore b/fnet/src/tests/databuffer/.gitignore
new file mode 100644
index 00000000000..b9c5810fe42
--- /dev/null
+++ b/fnet/src/tests/databuffer/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+databuffer_test
+fnet_databuffer_test_app
diff --git a/fnet/src/tests/databuffer/CMakeLists.txt b/fnet/src/tests/databuffer/CMakeLists.txt
new file mode 100644
index 00000000000..47122e11d96
--- /dev/null
+++ b/fnet/src/tests/databuffer/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_databuffer_test_app
+ SOURCES
+ databuffer.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_databuffer_test_app COMMAND fnet_databuffer_test_app)
diff --git a/fnet/src/tests/databuffer/DESC b/fnet/src/tests/databuffer/DESC
new file mode 100644
index 00000000000..d947b059899
--- /dev/null
+++ b/fnet/src/tests/databuffer/DESC
@@ -0,0 +1,2 @@
+Benchmark some databuffer operations and check for consistent
+encoding/decoding.
diff --git a/fnet/src/tests/databuffer/FILES b/fnet/src/tests/databuffer/FILES
new file mode 100644
index 00000000000..c0265d2742f
--- /dev/null
+++ b/fnet/src/tests/databuffer/FILES
@@ -0,0 +1 @@
+databuffer.cpp
diff --git a/fnet/src/tests/databuffer/databuffer.cpp b/fnet/src/tests/databuffer/databuffer.cpp
new file mode 100644
index 00000000000..7aa75a95e65
--- /dev/null
+++ b/fnet/src/tests/databuffer/databuffer.cpp
@@ -0,0 +1,201 @@
+// 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/databuffer.h>
+
+TEST("test resetIfEmpty") {
+ FNET_DataBuffer buf(64);
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_TRUE(buf.GetData() == buf.GetFree());
+ buf.WriteInt32(11111111);
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_FALSE(buf.GetData() == buf.GetFree());
+ buf.resetIfEmpty();
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_FALSE(buf.GetData() == buf.GetFree());
+ EXPECT_EQUAL(11111111u, buf.ReadInt32());
+ buf.resetIfEmpty();
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_TRUE(buf.GetData() == buf.GetFree());
+}
+
+TEST("testResize") {
+ FNET_DataBuffer buf(64);
+ uint32_t initialSize = buf.GetBufSize();
+ buf.WriteInt32(11111111);
+ buf.WriteInt32(22222222);
+ buf.WriteInt32(33333333);
+ buf.WriteInt32(44444444);
+ buf.WriteInt32(55555555);
+ EXPECT_TRUE(buf.ReadInt32() == 11111111);
+ buf.EnsureFree(initialSize);
+ EXPECT_TRUE(buf.GetBufSize() > initialSize);
+ EXPECT_TRUE(buf.ReadInt32() == 22222222);
+ EXPECT_TRUE(!buf.Shrink(buf.GetBufSize()));
+ EXPECT_TRUE(!buf.Shrink(buf.GetBufSize() + 16));
+ EXPECT_TRUE(!buf.Shrink(2 * 4));
+ EXPECT_TRUE(buf.Shrink(3 * 4));
+ EXPECT_TRUE(buf.GetBufSize() == 3 * 4);
+ EXPECT_TRUE(buf.ReadInt32() == 33333333);
+ buf.WriteInt32(66666666);
+ buf.EnsureFree(16);
+ EXPECT_TRUE(buf.GetDataLen() == 3 * 4);
+ EXPECT_TRUE(buf.GetBufSize() >= 16 + 3 * 4);
+ EXPECT_TRUE(buf.ReadInt32() == 44444444);
+ EXPECT_TRUE(buf.ReadInt32() == 55555555);
+ EXPECT_TRUE(buf.ReadInt32() == 66666666);
+ EXPECT_TRUE(buf.Shrink(0));
+ EXPECT_TRUE(buf.GetBufSize() == 0);
+ buf.WriteInt32(42);
+ EXPECT_TRUE(buf.GetBufSize() >= 4);
+ EXPECT_TRUE(buf.ReadInt32() == 42);
+ EXPECT_TRUE(buf.GetDataLen() == 0);
+}
+
+TEST("testSpeed") {
+ FNET_DataBuffer buf0(20000);
+ FNET_DataBuffer buf1(20000);
+ FNET_DataBuffer buf2(20000);
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ int i;
+ int k;
+
+ // fill buf0 with random data
+ for (i = 0; i < 16000; i++) {
+ buf0.WriteInt8((uint8_t)rand());
+ }
+
+ // copy buf0 into buf1
+ for (i = 0; i < 16000; i++) {
+ buf1.WriteInt8(buf0.ReadInt8());
+ }
+
+ // undo read from buf0
+ buf0.DeadToData(buf0.GetDeadLen());
+
+ // test encode/decode speed
+ start.SetNow();
+
+ for (i = 0; i < 5000; i++) {
+ buf2.Clear();
+ for (k = 0; k < 500; k++) {
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt32(buf1.ReadInt32());
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt16(buf1.ReadInt16());
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt32(buf1.ReadInt32());
+ buf2.WriteInt16(buf1.ReadInt16());
+ buf2.WriteInt32(buf1.ReadInt32());
+ buf2.WriteInt64(buf1.ReadInt64());
+ buf2.WriteInt32(buf1.ReadInt32());
+ }
+ buf1.Clear();
+ for (k = 0; k < 500; k++) {
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt16(buf2.ReadInt16());
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt32(buf2.ReadInt32());
+ buf1.WriteInt32(buf2.ReadInt32());
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt64(buf2.ReadInt64());
+ buf1.WriteInt32(buf2.ReadInt32());
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt16(buf2.ReadInt16());
+ buf1.WriteInt32(buf2.ReadInt32());
+ }
+ }
+ buf2.DeadToData(buf2.GetDeadLen());
+ stop.SetNow();
+
+ stop -= start;
+ fprintf(stderr, "encode/decode time (~160MB): %1.2f\n", stop.MilliSecs());
+
+ EXPECT_TRUE(buf0.Equals(&buf1) && buf0.Equals(&buf2));
+
+ // test encode[fast]/decode speed
+ start.SetNow();
+
+ for (i = 0; i < 5000; i++) {
+ buf2.Clear();
+ for (k = 0; k < 500; k++) {
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt16Fast(buf1.ReadInt16());
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ buf2.WriteInt16Fast(buf1.ReadInt16());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ buf2.WriteInt64Fast(buf1.ReadInt64());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ }
+ buf1.Clear();
+ for (k = 0; k < 500; k++) {
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt16Fast(buf2.ReadInt16());
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt64Fast(buf2.ReadInt64());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt16Fast(buf2.ReadInt16());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ }
+ }
+ buf2.DeadToData(buf2.GetDeadLen());
+ stop.SetNow();
+
+ stop -= start;
+ fprintf(stderr, "encode[fast]/decode time (~160MB): %1.2f\n", stop.MilliSecs());
+
+ EXPECT_TRUE(buf0.Equals(&buf1) && buf0.Equals(&buf2));
+
+ // init source table for table streaming test
+ uint32_t table[4000];
+ for (i = 0; i < 4000; i++) {
+ table[i] = i;
+ }
+
+ // test byte-swap table encoding speed
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+ buf1.Clear();
+ for (k = 0; k < 4000; k += 8) {
+ buf1.WriteInt32Fast(table[k]);
+ buf1.WriteInt32Fast(table[k + 1]);
+ buf1.WriteInt32Fast(table[k + 2]);
+ buf1.WriteInt32Fast(table[k + 3]);
+ buf1.WriteInt32Fast(table[k + 4]);
+ buf1.WriteInt32Fast(table[k + 5]);
+ buf1.WriteInt32Fast(table[k + 6]);
+ buf1.WriteInt32Fast(table[k + 7]);
+ }
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "byte-swap array encoding[fast] (~160 MB): %1.2f ms\n",
+ stop.MilliSecs());
+
+ // test direct-copy table encoding speed
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+ buf2.Clear();
+ buf2.EnsureFree(16000);
+ memcpy(buf2.GetFree(), table, 16000);
+ buf2.FreeToData(16000);
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "direct-copy array encoding (~160 MB): %1.2f ms\n",
+ stop.MilliSecs());
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/examples/.gitignore b/fnet/src/tests/examples/.gitignore
new file mode 100644
index 00000000000..a3c2ba8686c
--- /dev/null
+++ b/fnet/src/tests/examples/.gitignore
@@ -0,0 +1 @@
+fnet_examples_test_app
diff --git a/fnet/src/tests/examples/CMakeLists.txt b/fnet/src/tests/examples/CMakeLists.txt
new file mode 100644
index 00000000000..79f9047f626
--- /dev/null
+++ b/fnet/src/tests/examples/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_examples_test_app
+ SOURCES
+ examples_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_examples_test_app NO_VALGRIND COMMAND fnet_examples_test_app)
diff --git a/fnet/src/tests/examples/FILES b/fnet/src/tests/examples/FILES
new file mode 100644
index 00000000000..58573211940
--- /dev/null
+++ b/fnet/src/tests/examples/FILES
@@ -0,0 +1 @@
+examples_test.cpp
diff --git a/fnet/src/tests/examples/examples_test.cpp b/fnet/src/tests/examples/examples_test.cpp
new file mode 100644
index 00000000000..5debd969a60
--- /dev/null
+++ b/fnet/src/tests/examples/examples_test.cpp
@@ -0,0 +1,246 @@
+// 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/slaveproc.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/thread.h>
+
+// reserved in vespa/factory/doc/port-ranges.txt
+static const int PORT0 = 18570;
+static const int PORT1 = 18571;
+static const int PORT2 = 18572;
+static const int PORT3 = 18573;
+static const int PORT4 = 18574;
+static const int PORT5 = 18575;
+static const int PORT6 = 18576;
+static const int PORT7 = 18577;
+static const int PORT8 = 18578;
+static const int PORT9 = 18579;
+
+using vespalib::SlaveProc;
+
+bool runProc(SlaveProc &proc, bool &done) {
+ char buf[4096];
+ proc.close(); // close stdin
+ while (proc.running() && !done) {
+ if (!proc.eof()) {
+ uint32_t res = proc.read(buf, sizeof(buf), 10);
+ std::string tmp(buf, res);
+ fprintf(stderr, "%s", tmp.c_str());
+ }
+ vespalib::Thread::sleep(10);
+ }
+ if (done && proc.running()) {
+ kill(proc.getPid(), SIGTERM);
+ return proc.wait(60000);
+ }
+ return !proc.failed();
+}
+
+bool runProc(const std::string &cmd) {
+ bool ok = false;
+ for (size_t retry = 0; !ok && retry < 60; ++retry) {
+ if (retry > 0) {
+ fprintf(stderr, "retrying command in 500ms...\n");
+ vespalib::Thread::sleep(500);
+ }
+ bool done = false;
+ SlaveProc proc(cmd.c_str());
+ ok = runProc(proc, done);
+ }
+ return ok;
+}
+
+TEST("usage") {
+ bool done = false;
+ {
+ SlaveProc proc("../../examples/proxy/fnet_proxy_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/ping/fnet_pingserver_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/ping/fnet_pingclient_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_client_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_server_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_echo_client_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/rpc_info");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/rpc_invoke");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_callback_server_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_callback_client_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/rpc_proxy");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+}
+
+TEST("timeout") {
+ std::string out;
+ EXPECT_TRUE(SlaveProc::run("../../examples/timeout/fnet_timeout_app", out));
+ fprintf(stderr, "%s\n", out.c_str());
+}
+
+TEST_MT_F("ping", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/ping/fnet_pingserver_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("ping times out", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("ping with proxy", 3, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/ping/fnet_pingserver_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else if (thread_id == 1) {
+ SlaveProc proc(vespalib::make_string("../../examples/proxy/fnet_proxy_app tcp/%d tcp/localhost:%d",
+ PORT1, PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d",
+ PORT1).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc client server", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_client_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc echo client", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_echo_client_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc info", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_info tcp/localhost:%d",
+ PORT0).c_str()));
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_info tcp/localhost:%d verbose",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc invoke", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_invoke tcp/localhost:%d frt.rpc.echo "
+ "b:1 h:2 i:4 l:8 f:0.5 d:0.25 s:foo",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc callback client server", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc callback client server with proxy", 3, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else if (thread_id == 1) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/rpc_proxy tcp/%d tcp/localhost:%d",
+ PORT1, PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d",
+ PORT1).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MAIN_WITH_PROCESS_PROXY() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/fdselector/.gitignore b/fnet/src/tests/fdselector/.gitignore
new file mode 100644
index 00000000000..68a9dea5652
--- /dev/null
+++ b/fnet/src/tests/fdselector/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+fdselector_test
+fnet_fdselector_test_app
diff --git a/fnet/src/tests/fdselector/CMakeLists.txt b/fnet/src/tests/fdselector/CMakeLists.txt
new file mode 100644
index 00000000000..149d3642983
--- /dev/null
+++ b/fnet/src/tests/fdselector/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_fdselector_test_app
+ SOURCES
+ fdselector.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_fdselector_test_app COMMAND fnet_fdselector_test_app)
diff --git a/fnet/src/tests/fdselector/DESC b/fnet/src/tests/fdselector/DESC
new file mode 100644
index 00000000000..050e2a746d1
--- /dev/null
+++ b/fnet/src/tests/fdselector/DESC
@@ -0,0 +1 @@
+Test selecting on external file descriptors in the FNET event loop.
diff --git a/fnet/src/tests/fdselector/FILES b/fnet/src/tests/fdselector/FILES
new file mode 100644
index 00000000000..d03840ead52
--- /dev/null
+++ b/fnet/src/tests/fdselector/FILES
@@ -0,0 +1 @@
+fdselector.cpp
diff --git a/fnet/src/tests/fdselector/fdselector.cpp b/fnet/src/tests/fdselector/fdselector.cpp
new file mode 100644
index 00000000000..5af2716c0d4
--- /dev/null
+++ b/fnet/src/tests/fdselector/fdselector.cpp
@@ -0,0 +1,220 @@
+// 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/fnet.h>
+
+
+struct Handler : public FNET_IFDSelectorHandler
+{
+ int readEventCnt[2];
+ int writeEventCnt[2];
+
+ Handler()
+ : readEventCnt(),
+ writeEventCnt()
+ {
+ reset();
+ }
+ void readEvent(FNET_FDSelector *src)
+ {
+ readEventCnt[src->getContext()._value.INT]++;
+ }
+ void writeEvent(FNET_FDSelector *src)
+ {
+ writeEventCnt[src->getContext()._value.INT]++;
+ }
+ bool empty()
+ {
+ return ( readEventCnt[0] == 0
+ && readEventCnt[1] == 0
+ && writeEventCnt[0] == 0
+ && writeEventCnt[1] == 0);
+ }
+ void reset()
+ {
+ readEventCnt[0] = 0;
+ readEventCnt[1] = 0;
+ writeEventCnt[0] = 0;
+ writeEventCnt[1] = 0;
+ }
+};
+
+
+struct State
+{
+ int pipefd[2];
+ FNET_Transport transport;
+ Handler handler;
+
+ void eventLoop(int cnt)
+ {
+ for (int i = 0; i < cnt; ++i) {
+ transport.EventLoopIteration();
+ }
+ }
+ bool checkEmpty()
+ {
+ eventLoop(1);
+ return handler.empty();
+ }
+ void shutDown()
+ {
+ transport.ShutDown(false);
+ for (;;) {
+ if (!transport.EventLoopIteration()) {
+ return;
+ }
+ }
+ }
+ State() : pipefd(), transport(), handler() {
+ pipefd[0] = -1;
+ pipefd[1] = -1;
+ ASSERT_TRUE(pipe(pipefd) == 0);
+ ASSERT_TRUE(transport.InitEventLoop());
+ ASSERT_TRUE(handler.empty());
+ }
+ ~State() {
+ shutDown();
+ }
+};
+
+
+struct Selector : public FNET_FDSelector
+{
+ static FNET_Mutex mutex;
+ static int ctorCnt;
+ static int dtorCnt;
+
+ Selector(State &state, uint32_t idx)
+ : FNET_FDSelector(&state.transport, state.pipefd[idx],
+ &state.handler, FNET_Context(idx))
+ {
+ mutex.Lock();
+ ctorCnt++;
+ mutex.Unlock();
+ }
+ ~Selector()
+ {
+ mutex.Lock();
+ dtorCnt++;
+ mutex.Unlock();
+ }
+};
+
+FNET_Mutex Selector::mutex;
+int Selector::ctorCnt = 0;
+int Selector::dtorCnt = 0;
+
+
+TEST_F("testEmptySelection", State()) {
+ State &state = f1;
+ Selector *sel_0 = new Selector(state, 0);
+ Selector *sel_1 = new Selector(state, 1);
+
+ state.eventLoop(5);
+ EXPECT_TRUE(state.handler.empty());
+
+ sel_0->dispose();
+ sel_1->dispose();
+}
+
+
+TEST_F("testWriteEvent", State()) {
+ State &state = f1;
+ Selector *sel = new Selector(state, 1);
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+
+TEST_F("testReadEvent", State()) {
+ State &state = f1;
+ char buf[16];
+ char buf2[16];
+ strcpy(buf, "test");
+ strcpy(buf2, "bogus");
+
+ Selector *sel = new Selector(state, 0);
+
+ sel->updateReadSelection(true);
+ EXPECT_TRUE(state.checkEmpty());
+ EXPECT_TRUE(state.checkEmpty());
+ EXPECT_TRUE(state.checkEmpty());
+
+ int res = write(state.pipefd[1], buf, 5);
+ EXPECT_TRUE(res == 5);
+
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.readEventCnt[0] > 7);
+ state.handler.readEventCnt[0] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ res = read(state.pipefd[0], buf2, 10);
+ EXPECT_TRUE(res == 5);
+ EXPECT_TRUE(strcmp(buf, buf2) == 0);
+
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.readEventCnt[0] < 4);
+ state.handler.readEventCnt[0] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+
+TEST_F("testDispose", State()) {
+ State &state = f1;
+ Selector *sel = new Selector(state, 1);
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+
+TEST_F("testToggleEvent", State()) {
+ State &state = f1;
+ Selector *sel = new Selector(state, 1);
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->updateWriteSelection(false);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] < 4);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+TEST_MAIN() {
+ ASSERT_TRUE(Selector::ctorCnt == 0);
+ ASSERT_TRUE(Selector::dtorCnt == 0);
+ TEST_RUN_ALL();
+ EXPECT_TRUE(Selector::ctorCnt > 0);
+ EXPECT_TRUE(Selector::dtorCnt > 0);
+ EXPECT_TRUE(Selector::ctorCnt == Selector::dtorCnt);
+}
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(); }
diff --git a/fnet/src/tests/info/.gitignore b/fnet/src/tests/info/.gitignore
new file mode 100644
index 00000000000..bd20557beb5
--- /dev/null
+++ b/fnet/src/tests/info/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+info_test
+fnet_info_test_app
diff --git a/fnet/src/tests/info/CMakeLists.txt b/fnet/src/tests/info/CMakeLists.txt
new file mode 100644
index 00000000000..fb6069e5f8a
--- /dev/null
+++ b/fnet/src/tests/info/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_info_test_app
+ SOURCES
+ info.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_info_test_app COMMAND fnet_info_test_app)
diff --git a/fnet/src/tests/info/DESC b/fnet/src/tests/info/DESC
new file mode 100644
index 00000000000..4e7cd423af6
--- /dev/null
+++ b/fnet/src/tests/info/DESC
@@ -0,0 +1 @@
+Dummy test used to print out some general info about FNET.
diff --git a/fnet/src/tests/info/FILES b/fnet/src/tests/info/FILES
new file mode 100644
index 00000000000..62e5403ecb5
--- /dev/null
+++ b/fnet/src/tests/info/FILES
@@ -0,0 +1 @@
+info.cpp
diff --git a/fnet/src/tests/info/info.cpp b/fnet/src/tests/info/info.cpp
new file mode 100644
index 00000000000..284be22db63
--- /dev/null
+++ b/fnet/src/tests/info/info.cpp
@@ -0,0 +1,89 @@
+// 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 <mutex>
+#include <condition_variable>
+
+struct RPC : public FRT_Invokable
+{
+ void GetInfo(FRT_RPCRequest *req)
+ {
+ req->GetReturn()->AddString("fastos X current");
+ req->GetReturn()->AddString(FNET_Info::GetFNETVersion());
+ const char *endian_str = "UNKNOWN";
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE)
+ endian_str = "LITTLE";
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_BIG)
+ endian_str = "BIG";
+ req->GetReturn()->AddString(endian_str);
+ req->GetReturn()->AddInt32(FD_SETSIZE);
+ req->GetReturn()->AddInt32(sizeof(FRT_RPCRequest));
+ }
+
+ void Init(FRT_Supervisor *s)
+ {
+ FRT_ReflectionBuilder rb(s);
+ //-------------------------------------------------------------------
+ rb.DefineMethod("getInfo", "", "sssii", true,
+ FRT_METHOD(RPC::GetInfo), this);
+ // FastOS version
+ // FNET version
+ // endian
+ // FD_SETSIZE
+ // req object size
+ //-------------------------------------------------------------------
+ }
+};
+
+TEST("info") {
+ 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 *local_info = orb.AllocRPCRequest();
+ FRT_RPCRequest *remote_info = orb.AllocRPCRequest();
+
+ rpc.GetInfo(local_info);
+ remote_info->SetMethodName("getInfo");
+ target->InvokeSync(remote_info, 10.0);
+ EXPECT_FALSE(remote_info->IsError());
+
+ FRT_Values &l = *local_info->GetReturn();
+ // FRT_Values &r = *remote_info->GetReturn();
+
+ fprintf(stderr, "FastOS Version: %s\n", l[0]._string._str);
+ fprintf(stderr, "FNET Version: %s\n", l[1]._string._str);
+ fprintf(stderr, "Endian: %s\n", l[2]._string._str);
+ fprintf(stderr, "FD_SETSIZE: %d\n", l[3]._intval32);
+ fprintf(stderr, "sizeof(FRT_RPCRequest): %d\n", l[4]._intval32);
+
+ target->SubRef();
+ local_info->SubRef();
+ remote_info->SubRef();
+ orb.ShutDown(true);
+};
+
+TEST("size of important objects")
+{
+ EXPECT_EQUAL(184u, sizeof(FNET_IOComponent));
+ EXPECT_EQUAL(32u, sizeof(FNET_Channel));
+ EXPECT_EQUAL(40u, sizeof(FNET_PacketQueue_NoLock));
+ EXPECT_EQUAL(512u, sizeof(FNET_Connection));
+ EXPECT_EQUAL(96u, sizeof(FNET_Cond));
+ EXPECT_EQUAL(48u, sizeof(FNET_DataBuffer));
+ EXPECT_EQUAL(24u, sizeof(FastOS_Time));
+ EXPECT_EQUAL(8u, sizeof(FNET_Context));
+ EXPECT_EQUAL(8u, sizeof(fastos::TimeStamp));
+ EXPECT_EQUAL(48u, sizeof(FastOS_Mutex));
+ EXPECT_EQUAL(40u, sizeof(pthread_mutex_t));
+ EXPECT_EQUAL(48u, sizeof(pthread_cond_t));
+ EXPECT_EQUAL(40u, sizeof(std::mutex));
+ EXPECT_EQUAL(48u, sizeof(std::condition_variable));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/locking/.gitignore b/fnet/src/tests/locking/.gitignore
new file mode 100644
index 00000000000..dc9c395ba5b
--- /dev/null
+++ b/fnet/src/tests/locking/.gitignore
@@ -0,0 +1,8 @@
+.depend
+Makefile
+castspeed_test
+drainpackets_test
+lockspeed_test
+fnet_castspeed_test_app
+fnet_drainpackets_test_app
+fnet_lockspeed_test_app
diff --git a/fnet/src/tests/locking/CMakeLists.txt b/fnet/src/tests/locking/CMakeLists.txt
new file mode 100644
index 00000000000..7a0187717e6
--- /dev/null
+++ b/fnet/src/tests/locking/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_drainpackets_test_app
+ SOURCES
+ drainpackets.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_drainpackets_test_app NO_VALGRIND COMMAND fnet_drainpackets_test_app)
+vespa_add_executable(fnet_lockspeed_test_app
+ SOURCES
+ lockspeed.cpp
+ dummy.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_lockspeed_test_app NO_VALGRIND COMMAND fnet_lockspeed_test_app)
+vespa_add_executable(fnet_castspeed_test_app
+ SOURCES
+ castspeed.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_castspeed_test_app NO_VALGRIND COMMAND fnet_castspeed_test_app)
diff --git a/fnet/src/tests/locking/DESC b/fnet/src/tests/locking/DESC
new file mode 100644
index 00000000000..86c035e5a09
--- /dev/null
+++ b/fnet/src/tests/locking/DESC
@@ -0,0 +1 @@
+Benchmark locking and some queue locking strategies.
diff --git a/fnet/src/tests/locking/FILES b/fnet/src/tests/locking/FILES
new file mode 100644
index 00000000000..dfa689e7256
--- /dev/null
+++ b/fnet/src/tests/locking/FILES
@@ -0,0 +1,2 @@
+lockspeed.cpp
+drainpackets.cpp
diff --git a/fnet/src/tests/locking/castspeed.cpp b/fnet/src/tests/locking/castspeed.cpp
new file mode 100644
index 00000000000..874769a7579
--- /dev/null
+++ b/fnet/src/tests/locking/castspeed.cpp
@@ -0,0 +1,219 @@
+// 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/fnet.h>
+
+class B;
+
+static int taken = 0;
+extern void takeB(B* foo) __attribute__((noinline));
+
+class A
+{
+public:
+ virtual B* asB() { return 0; }
+ virtual ~A() {}
+};
+
+class C: public A
+{
+public:
+ B *otherB;
+ virtual B* asB() { return otherB; }
+ C() : otherB(NULL) {}
+};
+
+class B: public C
+{
+public:
+ virtual B* asB() { return this; }
+};
+
+
+class CastTest
+{
+ A* myB;
+ B* realB;
+public:
+ B* DummyCast() {
+ return realB;
+ }
+ B* DynamicCast() {
+ return dynamic_cast<B*>(myB);
+ }
+ B* TypesafeCast() {
+ return myB->asB();
+ }
+ B* UnsafeCast() {
+ return reinterpret_cast<B*>(myB);
+ }
+ B* StaticCast() {
+ return static_cast<B*>(myB);
+ }
+
+ CastTest() {
+ myB = realB = new B;
+ }
+
+ ~CastTest() {
+ delete myB;
+ }
+};
+
+#define LOOPCNT 30000000
+
+TEST("cast speed") {
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ CastTest casttest;
+
+ double actualTime;
+ uint32_t i;
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d dummy cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d dynamic cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d typesafe cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d static cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d reinterpret_cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d dummy cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+}
+
+void takeB(B* foo)
+{
+ if (foo != 0) {
+ taken++;
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/locking/drainpackets.cpp b/fnet/src/tests/locking/drainpackets.cpp
new file mode 100644
index 00000000000..066923f0c70
--- /dev/null
+++ b/fnet/src/tests/locking/drainpackets.cpp
@@ -0,0 +1,134 @@
+// 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/fnet.h>
+
+
+class MyPacket : public FNET_Packet
+{
+public:
+ uint32_t GetPCODE() { return 0; }
+ uint32_t GetLength() { return 0; }
+ void Encode(FNET_DataBuffer *) {}
+ bool Decode(FNET_DataBuffer *, uint32_t)
+ { return true; }
+};
+
+
+TEST("drain packets") {
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ FNET_Mutex lock;
+
+ FNET_PacketQueue q1(512);
+ FNET_PacketQueue q2(512);
+ FNET_PacketQueue q3(512);
+
+ int i;
+
+ // create dummy packets
+
+ for (i = 0; i < 500; i++) {
+ q1.QueuePacket_NoLock(new MyPacket(), FNET_Context());
+ }
+
+ // drain packets directly with single lock interval
+
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ lock.Lock();
+
+ while (!q1.IsEmpty_NoLock()) {
+ packet = q1.DequeuePacket_NoLock(&context);
+ q3.QueuePacket_NoLock(packet, context);
+ }
+
+ lock.Unlock();
+
+ //------------------------
+
+ lock.Lock();
+
+ while (!q3.IsEmpty_NoLock()) {
+ packet = q3.DequeuePacket_NoLock(&context);
+ q1.QueuePacket_NoLock(packet, context);
+ }
+
+ lock.Unlock();
+ }
+
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "direct, single lock interval (10M packets): %1.2f ms\n",
+ stop.MilliSecs());
+
+ // flush packets, then move without lock
+
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ lock.Lock();
+ q1.FlushPackets_NoLock(&q2);
+ lock.Unlock();
+
+ while (!q2.IsEmpty_NoLock()) {
+ packet = q2.DequeuePacket_NoLock(&context);
+ q3.QueuePacket_NoLock(packet, context);
+ }
+
+ //------------------------
+
+ lock.Lock();
+ q3.FlushPackets_NoLock(&q2);
+ lock.Unlock();
+
+ while (!q2.IsEmpty_NoLock()) {
+ packet = q2.DequeuePacket_NoLock(&context);
+ q1.QueuePacket_NoLock(packet, context);
+ }
+ }
+
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "indirect (10M packets): %1.2f ms\n", stop.MilliSecs());
+
+ // drain packets directly with multiple lock intervals
+
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ while ((packet = q1.DequeuePacket(0, &context)) != NULL) {
+ q3.QueuePacket_NoLock(packet, context);
+ }
+
+ //------------------------
+
+ while ((packet = q3.DequeuePacket(0, &context)) != NULL) {
+ q1.QueuePacket_NoLock(packet, context);
+ }
+ }
+
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "direct, multiple lock intervals (10M packets): %1.2f ms\n",
+ stop.MilliSecs());
+
+ EXPECT_TRUE(q1.GetPacketCnt_NoLock() == 500 &&
+ q2.GetPacketCnt_NoLock() == 0 &&
+ q3.GetPacketCnt_NoLock() == 0);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/locking/dummy.cpp b/fnet/src/tests/locking/dummy.cpp
new file mode 100644
index 00000000000..bab18ef3db9
--- /dev/null
+++ b/fnet/src/tests/locking/dummy.cpp
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "dummy.h"
+
+DummyObj::DummyObj() {}
+DummyObj::~DummyObj() {}
+
+void DummyLock::Lock() {}
+void DummyLock::Unlock() {}
diff --git a/fnet/src/tests/locking/dummy.h b/fnet/src/tests/locking/dummy.h
new file mode 100644
index 00000000000..9a3578b43e5
--- /dev/null
+++ b/fnet/src/tests/locking/dummy.h
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+class DummyObj
+{
+public:
+ DummyObj();
+ ~DummyObj();
+};
+
+class DummyLock
+{
+public:
+ void Lock();
+ void Unlock();
+};
+
diff --git a/fnet/src/tests/locking/lockspeed.cpp b/fnet/src/tests/locking/lockspeed.cpp
new file mode 100644
index 00000000000..bccedc61b8e
--- /dev/null
+++ b/fnet/src/tests/locking/lockspeed.cpp
@@ -0,0 +1,177 @@
+// 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/fnet.h>
+#include "dummy.h"
+
+TEST("lock speed") {
+ FastOS_Time start;
+ FastOS_Time stop;
+ DummyLock dummy;
+ FNET_Mutex lock;
+ double dummyTime;
+ double actualTime;
+ double overhead;
+ uint32_t i;
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ }
+ stop.SetNow();
+ stop -= start;
+ dummyTime = stop.MilliSecs();
+
+ fprintf(stderr,
+ "10M dummy lock/unlock: %f ms (%1.2f/ms)\n",
+ dummyTime, 10000000.0 / dummyTime);
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+
+ fprintf(stderr,
+ "10M actual lock/unlock: %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ overhead = (actualTime - dummyTime) / 10000.0;
+
+ fprintf(stderr,
+ "approx overhead per lock/unlock: %f microseconds\n",
+ overhead);
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ FNET_Mutex lock0;
+ FNET_Mutex lock1;
+ FNET_Mutex lock2;
+ FNET_Mutex lock3;
+ FNET_Mutex lock4;
+ FNET_Mutex lock5;
+ FNET_Mutex lock6;
+ FNET_Mutex lock7;
+ FNET_Mutex lock8;
+ FNET_Mutex lock9;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M mutex create/destroy %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ FNET_Cond cond0;
+ FNET_Cond cond1;
+ FNET_Cond cond2;
+ FNET_Cond cond3;
+ FNET_Cond cond4;
+ FNET_Cond cond5;
+ FNET_Cond cond6;
+ FNET_Cond cond7;
+ FNET_Cond cond8;
+ FNET_Cond cond9;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M cond create/destroy %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ DummyObj dummy0;
+ DummyObj dummy1;
+ DummyObj dummy2;
+ DummyObj dummy3;
+ DummyObj dummy4;
+ DummyObj dummy5;
+ DummyObj dummy6;
+ DummyObj dummy7;
+ DummyObj dummy8;
+ DummyObj dummy9;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M dummy create/destroy %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ DummyObj *dummy0 = new DummyObj();
+ DummyObj *dummy1 = new DummyObj();
+ DummyObj *dummy2 = new DummyObj();
+ DummyObj *dummy3 = new DummyObj();
+ DummyObj *dummy4 = new DummyObj();
+ DummyObj *dummy5 = new DummyObj();
+ DummyObj *dummy6 = new DummyObj();
+ DummyObj *dummy7 = new DummyObj();
+ DummyObj *dummy8 = new DummyObj();
+ DummyObj *dummy9 = new DummyObj();
+ delete dummy9;
+ delete dummy8;
+ delete dummy7;
+ delete dummy6;
+ delete dummy5;
+ delete dummy4;
+ delete dummy3;
+ delete dummy2;
+ delete dummy1;
+ delete dummy0;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M dummy new/delete %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/printstuff/.gitignore b/fnet/src/tests/printstuff/.gitignore
new file mode 100644
index 00000000000..bfb8f2d1754
--- /dev/null
+++ b/fnet/src/tests/printstuff/.gitignore
@@ -0,0 +1 @@
+fnet_printstuff_test_app
diff --git a/fnet/src/tests/printstuff/CMakeLists.txt b/fnet/src/tests/printstuff/CMakeLists.txt
new file mode 100644
index 00000000000..7180d2866f0
--- /dev/null
+++ b/fnet/src/tests/printstuff/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_printstuff_test_app
+ SOURCES
+ printstuff_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_printstuff_test_app COMMAND fnet_printstuff_test_app)
diff --git a/fnet/src/tests/printstuff/FILES b/fnet/src/tests/printstuff/FILES
new file mode 100644
index 00000000000..95a889d6494
--- /dev/null
+++ b/fnet/src/tests/printstuff/FILES
@@ -0,0 +1 @@
+printstuff_test.cpp
diff --git a/fnet/src/tests/printstuff/printstuff_test.cpp b/fnet/src/tests/printstuff/printstuff_test.cpp
new file mode 100644
index 00000000000..3778cef9c8c
--- /dev/null
+++ b/fnet/src/tests/printstuff/printstuff_test.cpp
@@ -0,0 +1,45 @@
+// 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>
+
+void printError(uint32_t ecode) {
+ fprintf(stderr, "error(%u): %s: %s\n",
+ ecode, FRT_GetErrorCodeName(ecode), FRT_GetDefaultErrorMessage(ecode));
+}
+
+TEST("frt error code names and default messages") {
+ printError(0);
+ printError(99);
+ for (uint32_t i = 100; i < 112; ++i) {
+ printError(i);
+ }
+ printError(198);
+ printError(199);
+ printError(200);
+ printError(70000);
+}
+
+TEST("rpc packets in a queue") {
+ FRT_RPCRequest *req = new FRT_RPCRequest();
+ {
+ req->SetMethodName("foo");
+ FNET_PacketQueue_NoLock q1(1, FNET_IPacketHandler::FNET_KEEP_CHANNEL);
+ q1.QueuePacket_NoLock(new (req->GetMemoryTub()) FRT_RPCRequestPacket(req, 0, false), FNET_Context());
+ q1.QueuePacket_NoLock(new (req->GetMemoryTub()) FRT_RPCReplyPacket(req, 0, false), FNET_Context());
+ q1.QueuePacket_NoLock(new (req->GetMemoryTub()) FRT_RPCErrorPacket(req, 0, false), FNET_Context());
+ q1.Print();
+ FNET_PacketQueue q2(2, FNET_IPacketHandler::FNET_KEEP_CHANNEL);
+ q2.QueuePacket(new (req->GetMemoryTub()) FRT_RPCRequestPacket(req, 0, false), FNET_Context());
+ q2.QueuePacket(new (req->GetMemoryTub()) FRT_RPCReplyPacket(req, 0, false), FNET_Context());
+ q2.QueuePacket(new (req->GetMemoryTub()) FRT_RPCErrorPacket(req, 0, false), FNET_Context());
+ q2.Print();
+ }
+ req->SubRef();
+}
+
+TEST("info") {
+ FNET_Info::PrintInfo();
+ FNET_Info::LogInfo();
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/regress/databuffer/.gitignore b/fnet/src/tests/regress/databuffer/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/databuffer/.gitignore
diff --git a/fnet/src/tests/regress/fdselector/.gitignore b/fnet/src/tests/regress/fdselector/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/fdselector/.gitignore
diff --git a/fnet/src/tests/regress/frt/memorytub/.gitignore b/fnet/src/tests/regress/frt/memorytub/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/memorytub/.gitignore
diff --git a/fnet/src/tests/regress/frt/method_pt/.gitignore b/fnet/src/tests/regress/frt/method_pt/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/method_pt/.gitignore
diff --git a/fnet/src/tests/regress/frt/rpc/.gitignore b/fnet/src/tests/regress/frt/rpc/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/rpc/.gitignore
diff --git a/fnet/src/tests/regress/frt/values/.gitignore b/fnet/src/tests/regress/frt/values/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/values/.gitignore
diff --git a/fnet/src/tests/regress/info/.gitignore b/fnet/src/tests/regress/info/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/info/.gitignore
diff --git a/fnet/src/tests/regress/locking/.gitignore b/fnet/src/tests/regress/locking/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/locking/.gitignore
diff --git a/fnet/src/tests/regress/scheduling/.gitignore b/fnet/src/tests/regress/scheduling/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/scheduling/.gitignore
diff --git a/fnet/src/tests/regress/spiral/.gitignore b/fnet/src/tests/regress/spiral/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/spiral/.gitignore
diff --git a/fnet/src/tests/regress/sync_execute/.gitignore b/fnet/src/tests/regress/sync_execute/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/sync_execute/.gitignore
diff --git a/fnet/src/tests/regress/thread_id/.gitignore b/fnet/src/tests/regress/thread_id/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/thread_id/.gitignore
diff --git a/fnet/src/tests/regress/time/.gitignore b/fnet/src/tests/regress/time/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/time/.gitignore
diff --git a/fnet/src/tests/scheduling/.gitignore b/fnet/src/tests/scheduling/.gitignore
new file mode 100644
index 00000000000..b6dccb18324
--- /dev/null
+++ b/fnet/src/tests/scheduling/.gitignore
@@ -0,0 +1,6 @@
+.depend
+Makefile
+schedule_test
+sloweventloop_test
+fnet_schedule_test_app
+fnet_sloweventloop_test_app
diff --git a/fnet/src/tests/scheduling/CMakeLists.txt b/fnet/src/tests/scheduling/CMakeLists.txt
new file mode 100644
index 00000000000..244211a7cb6
--- /dev/null
+++ b/fnet/src/tests/scheduling/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_schedule_test_app
+ SOURCES
+ schedule.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_schedule_test_app COMMAND fnet_schedule_test_app)
+vespa_add_executable(fnet_sloweventloop_test_app
+ SOURCES
+ sloweventloop.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_sloweventloop_test_app COMMAND fnet_sloweventloop_test_app)
diff --git a/fnet/src/tests/scheduling/DESC b/fnet/src/tests/scheduling/DESC
new file mode 100644
index 00000000000..6e4ca2972b9
--- /dev/null
+++ b/fnet/src/tests/scheduling/DESC
@@ -0,0 +1 @@
+Scheduler test.
diff --git a/fnet/src/tests/scheduling/FILES b/fnet/src/tests/scheduling/FILES
new file mode 100644
index 00000000000..bd5dc7f2572
--- /dev/null
+++ b/fnet/src/tests/scheduling/FILES
@@ -0,0 +1 @@
+schedule.cpp
diff --git a/fnet/src/tests/scheduling/schedule.cpp b/fnet/src/tests/scheduling/schedule.cpp
new file mode 100644
index 00000000000..0625b83e121
--- /dev/null
+++ b/fnet/src/tests/scheduling/schedule.cpp
@@ -0,0 +1,153 @@
+// 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/fnet.h>
+
+FastOS_Time _time;
+FNET_Scheduler *_scheduler;
+
+
+class MyTask : public FNET_Task
+{
+public:
+ FastOS_Time _time;
+ int _target;
+ bool _done;
+
+ MyTask(int target)
+ : FNET_Task(::_scheduler),
+ _time(),
+ _target(target),
+ _done(false) {}
+
+ int GetTarget() const { return _target; }
+
+ bool Check() const
+ {
+ int a = _target;
+ int b = (int)_time.MilliSecs();
+
+ if (!_done)
+ return false;
+
+ if (b < a)
+ return false;
+
+ if ((b - a) > (2 * FNET_Scheduler::SLOT_TICK))
+ return false;
+
+ return true;
+ }
+
+ void PerformTask()
+ {
+ _time = ::_time;
+ _done = true;
+ }
+};
+
+
+class RealTimeTask : public FNET_Task
+{
+public:
+ uint32_t _cnt;
+
+ RealTimeTask() : FNET_Task(::_scheduler), _cnt(0)
+ {
+ }
+
+ uint32_t GetCnt() { return _cnt; }
+
+ void PerformTask()
+ {
+ _cnt++;
+ ScheduleNow(); // re-schedule as fast as possible
+ }
+};
+
+
+TEST("schedule") {
+ _time.SetMilliSecs(0);
+ _scheduler = new FNET_Scheduler(&_time, &_time);
+
+ RealTimeTask rt_task1;
+ RealTimeTask rt_task2;
+ RealTimeTask rt_task3;
+ rt_task1.ScheduleNow();
+ rt_task2.ScheduleNow();
+ rt_task3.ScheduleNow();
+
+ uint32_t taskCnt = 1000000;
+ MyTask **tasks = new MyTask*[taskCnt];
+ assert(tasks != NULL);
+ for (uint32_t i = 0; i < taskCnt; i++) {
+ tasks[i] = new MyTask(rand() & 131071);
+ assert(tasks[i] != NULL);
+ }
+
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ start.SetNow();
+ for (uint32_t j = 0; j < taskCnt; j++) {
+ tasks[j]->Schedule(tasks[j]->GetTarget() / 1000.0);
+ }
+ stop.SetNow();
+ stop -= start;
+ double scheduleTime = stop.MilliSecs() / (double)taskCnt;
+ fprintf(stderr, "scheduling cost: %1.2f microseconds\n", scheduleTime * 1000.0);
+
+ start.SetNow();
+ uint32_t tickCnt = 0;
+ while (_time.MilliSecs() < 135000.0) {
+ _time.AddMilliSecs(FNET_Scheduler::SLOT_TICK);
+ _scheduler->CheckTasks();
+ tickCnt++;
+ }
+ stop.SetNow();
+ stop -= start;
+ double runTime = stop.MilliSecs();
+ fprintf(stderr, "3 RT tasks + %d one-shot tasks over 135s\n", taskCnt);
+ fprintf(stderr, "%1.2f seconds actual run time\n", runTime / 1000.0);
+ fprintf(stderr, "%1.2f tasks per simulated second\n", (double)taskCnt / (double)135);
+ fprintf(stderr, "%d ticks\n", tickCnt);
+ fprintf(stderr, "%1.2f %% simulated CPU usage\n", 100 * (runTime / 135000.0));
+ fprintf(stderr, "%1.2f microseconds per performed task\n",
+ 1000.0 * (runTime / (taskCnt + tickCnt * 3.0)));
+
+ for (uint32_t k = 0; k < taskCnt; k++) {
+ EXPECT_TRUE(tasks[k]->Check());
+ }
+ EXPECT_TRUE(rt_task1.GetCnt() == tickCnt);
+ EXPECT_TRUE(rt_task2.GetCnt() == tickCnt);
+ EXPECT_TRUE(rt_task3.GetCnt() == tickCnt);
+
+ for (uint32_t l = 0; l < taskCnt; l++) {
+ delete tasks[l];
+ }
+ rt_task1.Kill();
+ rt_task2.Kill();
+ rt_task3.Kill();
+ delete [] tasks;
+ delete _scheduler;
+
+ { // trigger warning from scheduler destructor
+
+ FNET_Scheduler *s = new FNET_Scheduler();
+
+ FNET_Task t1(s);
+ FNET_Task t2(s);
+ FNET_Task t3(s);
+ FNET_Task t4(s);
+ FNET_Task t5(s);
+
+ t1.ScheduleNow();
+ t2.Schedule(5.0);
+ t3.Schedule(5.0);
+ t4.Schedule(10.0);
+ t5.Schedule(15.0);
+
+ delete s;
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/scheduling/sloweventloop.cpp b/fnet/src/tests/scheduling/sloweventloop.cpp
new file mode 100644
index 00000000000..3e27bfef131
--- /dev/null
+++ b/fnet/src/tests/scheduling/sloweventloop.cpp
@@ -0,0 +1,65 @@
+// 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/fnet.h>
+
+
+class MyTask : public FNET_Task
+{
+public:
+ bool _done;
+
+ MyTask(FNET_Scheduler &scheduler)
+ : FNET_Task(&scheduler),
+ _done(false) {}
+
+ bool done() const { return _done; }
+ void PerformTask() { _done = true; }
+};
+
+
+TEST("slow event loop") {
+ FastOS_Time t;
+ t.SetMilliSecs(0);
+
+ FNET_Scheduler scheduler(&t, &t);
+ MyTask task(scheduler);
+ MyTask task2(scheduler);
+
+ scheduler.CheckTasks();
+ t.AddMilliSecs(10000);
+ task.Schedule(5.0);
+
+ uint32_t cnt = 0;
+ for (;;) {
+ scheduler.CheckTasks();
+ if (task.done()) {
+ break;
+ }
+ ++cnt;
+ t.AddMilliSecs(1);
+ }
+
+ if (!EXPECT_TRUE(cnt > 4700 && cnt < 4800)) {
+ fprintf(stderr, "cnt=%d\n", cnt);
+ }
+
+ scheduler.CheckTasks();
+ t.AddMilliSecs(10000);
+ task2.Schedule(5.0);
+
+ uint32_t cnt2 = 0;
+ for(;;) {
+ scheduler.CheckTasks();
+ if (task2.done()) {
+ break;
+ }
+ ++cnt2;
+ t.AddMilliSecs(10000);
+ }
+
+ if (!EXPECT_TRUE(cnt2 > 15 && cnt2 < 25)) {
+ fprintf(stderr, "cnt2=%d\n", cnt2);
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/sync_execute/.gitignore b/fnet/src/tests/sync_execute/.gitignore
new file mode 100644
index 00000000000..90d51f31d97
--- /dev/null
+++ b/fnet/src/tests/sync_execute/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+sync_execute_test
+fnet_sync_execute_test_app
diff --git a/fnet/src/tests/sync_execute/CMakeLists.txt b/fnet/src/tests/sync_execute/CMakeLists.txt
new file mode 100644
index 00000000000..820cdb1af86
--- /dev/null
+++ b/fnet/src/tests/sync_execute/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_sync_execute_test_app
+ SOURCES
+ sync_execute.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_sync_execute_test_app COMMAND fnet_sync_execute_test_app)
diff --git a/fnet/src/tests/sync_execute/DESC b/fnet/src/tests/sync_execute/DESC
new file mode 100644
index 00000000000..4f1b88599a8
--- /dev/null
+++ b/fnet/src/tests/sync_execute/DESC
@@ -0,0 +1 @@
+Test the sync and execute methods on the transport object.
diff --git a/fnet/src/tests/sync_execute/FILES b/fnet/src/tests/sync_execute/FILES
new file mode 100644
index 00000000000..2e03de91167
--- /dev/null
+++ b/fnet/src/tests/sync_execute/FILES
@@ -0,0 +1 @@
+sync_execute.cpp
diff --git a/fnet/src/tests/sync_execute/sync_execute.cpp b/fnet/src/tests/sync_execute/sync_execute.cpp
new file mode 100644
index 00000000000..e24d63effd5
--- /dev/null
+++ b/fnet/src/tests/sync_execute/sync_execute.cpp
@@ -0,0 +1,39 @@
+// 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/fnet.h>
+#include <vespa/vespalib/util/sync.h>
+
+struct DoIt : public FNET_IExecutable {
+ vespalib::Gate gate;
+ virtual void execute() {
+ gate.countDown();
+ }
+};
+
+TEST("sync execute") {
+ DoIt exe1;
+ DoIt exe2;
+ DoIt exe3;
+ DoIt exe4;
+ FastOS_ThreadPool pool(128 * 1024 * 1024);
+ FNET_Transport transport;
+ ASSERT_TRUE(transport.execute(&exe1));
+ ASSERT_TRUE(transport.Start(&pool));
+ exe1.gate.await();
+ ASSERT_TRUE(transport.execute(&exe2));
+ transport.sync();
+ ASSERT_TRUE(exe2.gate.getCount() == 0u);
+ ASSERT_TRUE(transport.execute(&exe3));
+ transport.ShutDown(false);
+ ASSERT_TRUE(!transport.execute(&exe4));
+ transport.sync();
+ transport.WaitFinished();
+ transport.sync();
+ pool.Close();
+ ASSERT_TRUE(exe1.gate.getCount() == 0u);
+ ASSERT_TRUE(exe2.gate.getCount() == 0u);
+ ASSERT_TRUE(exe3.gate.getCount() == 0u);
+ ASSERT_TRUE(exe4.gate.getCount() == 1u);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/thread_selection/.gitignore b/fnet/src/tests/thread_selection/.gitignore
new file mode 100644
index 00000000000..e0d5b3c18f7
--- /dev/null
+++ b/fnet/src/tests/thread_selection/.gitignore
@@ -0,0 +1 @@
+fnet_thread_selection_test_app
diff --git a/fnet/src/tests/thread_selection/CMakeLists.txt b/fnet/src/tests/thread_selection/CMakeLists.txt
new file mode 100644
index 00000000000..183781f3b21
--- /dev/null
+++ b/fnet/src/tests/thread_selection/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_thread_selection_test_app
+ SOURCES
+ thread_selection_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_thread_selection_test_app COMMAND fnet_thread_selection_test_app)
diff --git a/fnet/src/tests/thread_selection/thread_selection_test.cpp b/fnet/src/tests/thread_selection/thread_selection_test.cpp
new file mode 100644
index 00000000000..5e5f0891ba4
--- /dev/null
+++ b/fnet/src/tests/thread_selection/thread_selection_test.cpp
@@ -0,0 +1,89 @@
+// 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/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <thread>
+#include <chrono>
+#include <mutex>
+#include <map>
+
+struct Fixture {
+ std::mutex lock;
+ FNET_Transport transport;
+ std::map<FNET_TransportThread *, size_t> counts;
+ Fixture(size_t num_threads) : transport(num_threads) {}
+ void count_selected_thread(const void *key, size_t key_len) {
+ std::lock_guard<std::mutex> guard(lock);
+ FNET_TransportThread *thread = transport.select_thread(key, key_len);
+ ++counts[thread];
+ }
+ std::vector<size_t> get_counts() {
+ std::vector<size_t> result;
+ for (const auto &entry: counts) {
+ result.push_back(entry.second);
+ }
+ return result;
+ }
+ void dump_counts() {
+ std::vector<size_t> list = get_counts();
+ fprintf(stderr, "thread selection counts: [");
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (i > 0) {
+ fprintf(stderr, ", ");
+ }
+ fprintf(stderr, "%zu", list[i]);
+ }
+ fprintf(stderr, "]\n");
+ }
+};
+
+TEST_F("require that selection is time sensistive", Fixture(8))
+{
+ using namespace std::literals;
+ vespalib::string key("my random key");
+ for (size_t i = 0; i < 256; ++i) {
+ f1.count_selected_thread(key.data(), key.size());
+ std::this_thread::sleep_for(10ms);
+ }
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+}
+
+TEST_F("require that selection is key sensistive", Fixture(8))
+{
+ for (size_t i = 0; i < 256; ++i) {
+ vespalib::string key = vespalib::make_string("my random key %zu", i);
+ f1.count_selected_thread(key.data(), key.size());
+ }
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+}
+
+TEST_MT_F("require that selection is thread sensitive", 256, Fixture(8))
+{
+ f1.count_selected_thread(nullptr, 0);
+ TEST_BARRIER();
+ if (thread_id == 0) {
+ std::vector<size_t> counts = f1.get_counts();
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+ }
+}
+
+void recursive_select(Fixture &f, size_t n) {
+ char dummy[32];
+ if (n > 0) {
+ recursive_select(f, n - 1);
+ f.count_selected_thread(nullptr, 0);
+ (void) dummy;
+ }
+}
+
+TEST_F("require that selection is stack location sensistive", Fixture(8))
+{
+ recursive_select(f, 256);
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/time/.gitignore b/fnet/src/tests/time/.gitignore
new file mode 100644
index 00000000000..fddb94a8e8a
--- /dev/null
+++ b/fnet/src/tests/time/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+timespeed_test
+fnet_timespeed_test_app
diff --git a/fnet/src/tests/time/CMakeLists.txt b/fnet/src/tests/time/CMakeLists.txt
new file mode 100644
index 00000000000..5f620af2b53
--- /dev/null
+++ b/fnet/src/tests/time/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_timespeed_test_app
+ SOURCES
+ timespeed.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_timespeed_test_app NO_VALGRIND COMMAND fnet_timespeed_test_app)
diff --git a/fnet/src/tests/time/DESC b/fnet/src/tests/time/DESC
new file mode 100644
index 00000000000..27214fa9bcc
--- /dev/null
+++ b/fnet/src/tests/time/DESC
@@ -0,0 +1 @@
+Check how fast we can determine what time it is.
diff --git a/fnet/src/tests/time/FILES b/fnet/src/tests/time/FILES
new file mode 100644
index 00000000000..bf3708f36fa
--- /dev/null
+++ b/fnet/src/tests/time/FILES
@@ -0,0 +1 @@
+timespeed.cpp
diff --git a/fnet/src/tests/time/timespeed.cpp b/fnet/src/tests/time/timespeed.cpp
new file mode 100644
index 00000000000..cdbaaa27781
--- /dev/null
+++ b/fnet/src/tests/time/timespeed.cpp
@@ -0,0 +1,68 @@
+// 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/fnet.h>
+
+
+class DummyTime
+{
+public:
+ void SetNow() {}
+};
+
+
+TEST("time speed") {
+ FastOS_Time start;
+ FastOS_Time stop;
+ DummyTime dummy;
+ FastOS_Time now;
+ double dummyTime;
+ double actualTime;
+ double overhead;
+ uint32_t i;
+
+ start.SetNow();
+ for (i = 0; i < 100000; i++) {
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ }
+ stop.SetNow();
+ stop -= start;
+ dummyTime = stop.MilliSecs();
+
+ fprintf(stderr, "1M dummy SetNow: %f ms (%1.2f/ms)\n",
+ dummyTime, 1000000.0 / dummyTime);
+
+ start.SetNow();
+ for (i = 0; i < 100000; i++) {
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+
+ fprintf(stderr, "1M actual SetNow: %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 1000000.0 / stop.MilliSecs());
+
+ overhead = (actualTime - dummyTime) / 1000.0;
+
+ fprintf(stderr, "approx overhead per SetNow: %f microseconds\n", overhead);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }